diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index 98166cabd944..cc2e1e1e872b 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -26,12 +26,16 @@ const propTypes = { /** Function to call when selection in text input is changed */ onSelectionChange: PropTypes.func, + + /** Function to call to handle key presses in the text input */ + onKeyPress: PropTypes.func, }; const defaultProps = { forwardedRef: undefined, selection: undefined, onSelectionChange: () => {}, + onKeyPress: () => {}, }; function AmountTextInput(props) { @@ -51,6 +55,7 @@ function AmountTextInput(props) { selection={props.selection} onSelectionChange={props.onSelectionChange} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + onKeyPress={props.onKeyPress} /> ); } diff --git a/src/components/TextInputWithCurrencySymbol.js b/src/components/TextInputWithCurrencySymbol.js deleted file mode 100644 index 72ae373539f0..000000000000 --- a/src/components/TextInputWithCurrencySymbol.js +++ /dev/null @@ -1,119 +0,0 @@ -import React, {useState, useEffect} from 'react'; -import PropTypes from 'prop-types'; -import AmountTextInput from './AmountTextInput'; -import CurrencySymbolButton from './CurrencySymbolButton'; -import * as CurrencyUtils from '../libs/CurrencyUtils'; -import useLocalize from '../hooks/useLocalize'; -import * as MoneyRequestUtils from '../libs/MoneyRequestUtils'; -import refPropTypes from './refPropTypes'; - -const propTypes = { - /** A ref to forward to amount text input */ - forwardedRef: refPropTypes, - - /** Formatted amount in local currency */ - formattedAmount: PropTypes.string.isRequired, - - /** Function to call when amount in text input is changed */ - onChangeAmount: PropTypes.func, - - /** Function to call when currency button is pressed */ - onCurrencyButtonPress: PropTypes.func, - - /** Placeholder value for amount text input */ - placeholder: PropTypes.string.isRequired, - - /** Currency code of user's selected currency */ - selectedCurrencyCode: PropTypes.string.isRequired, - - /** Selection Object */ - selection: PropTypes.shape({ - start: PropTypes.number, - end: PropTypes.number, - }), - - /** Function to call when selection in text input is changed */ - onSelectionChange: PropTypes.func, -}; - -const defaultProps = { - forwardedRef: undefined, - onChangeAmount: () => {}, - onCurrencyButtonPress: () => {}, - selection: undefined, - onSelectionChange: () => {}, -}; - -function TextInputWithCurrencySymbol(props) { - const {fromLocaleDigit} = useLocalize(); - const currencySymbol = CurrencyUtils.getLocalizedCurrencySymbol(props.selectedCurrencyCode); - const isCurrencySymbolLTR = CurrencyUtils.isCurrencySymbolLTR(props.selectedCurrencyCode); - - const [skipNextSelectionChange, setSkipNextSelectionChange] = useState(false); - - useEffect(() => { - setSkipNextSelectionChange(true); - }, [props.formattedAmount]); - - const currencySymbolButton = ( - - ); - - /** - * Set a new amount value properly formatted - * - * @param {String} text - Changed text from user input - */ - const setFormattedAmount = (text) => { - const newAmount = MoneyRequestUtils.addLeadingZero(MoneyRequestUtils.replaceAllDigits(text, fromLocaleDigit)); - props.onChangeAmount(newAmount); - }; - - const amountTextInput = ( - { - if (skipNextSelectionChange) { - setSkipNextSelectionChange(false); - return; - } - props.onSelectionChange(e); - }} - /> - ); - - if (isCurrencySymbolLTR) { - return ( - <> - {currencySymbolButton} - {amountTextInput} - - ); - } - - return ( - <> - {amountTextInput} - {currencySymbolButton} - - ); -} - -TextInputWithCurrencySymbol.propTypes = propTypes; -TextInputWithCurrencySymbol.defaultProps = defaultProps; -TextInputWithCurrencySymbol.displayName = 'TextInputWithCurrencySymbol'; - -export default React.forwardRef((props, ref) => ( - -)); diff --git a/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.js b/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.js new file mode 100644 index 000000000000..6dd1aacb0b09 --- /dev/null +++ b/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.js @@ -0,0 +1,72 @@ +import React from 'react'; +import AmountTextInput from '../AmountTextInput'; +import CurrencySymbolButton from '../CurrencySymbolButton'; +import * as CurrencyUtils from '../../libs/CurrencyUtils'; +import useLocalize from '../../hooks/useLocalize'; +import * as MoneyRequestUtils from '../../libs/MoneyRequestUtils'; +import * as textInputWithCurrencySymbolPropTypes from './textInputWithCurrencySymbolPropTypes'; + +function BaseTextInputWithCurrencySymbol(props) { + const {fromLocaleDigit} = useLocalize(); + const currencySymbol = CurrencyUtils.getLocalizedCurrencySymbol(props.selectedCurrencyCode); + const isCurrencySymbolLTR = CurrencyUtils.isCurrencySymbolLTR(props.selectedCurrencyCode); + + const currencySymbolButton = ( + + ); + + /** + * Set a new amount value properly formatted + * + * @param {String} text - Changed text from user input + */ + const setFormattedAmount = (text) => { + const newAmount = MoneyRequestUtils.addLeadingZero(MoneyRequestUtils.replaceAllDigits(text, fromLocaleDigit)); + props.onChangeAmount(newAmount); + }; + + const amountTextInput = ( + { + props.onSelectionChange(e); + }} + onKeyPress={props.onKeyPress} + /> + ); + + if (isCurrencySymbolLTR) { + return ( + <> + {currencySymbolButton} + {amountTextInput} + + ); + } + + return ( + <> + {amountTextInput} + {currencySymbolButton} + + ); +} + +BaseTextInputWithCurrencySymbol.propTypes = textInputWithCurrencySymbolPropTypes.propTypes; +BaseTextInputWithCurrencySymbol.defaultProps = textInputWithCurrencySymbolPropTypes.defaultProps; +BaseTextInputWithCurrencySymbol.displayName = 'BaseTextInputWithCurrencySymbol'; + +export default React.forwardRef((props, ref) => ( + +)); diff --git a/src/components/TextInputWithCurrencySymbol/index.android.js b/src/components/TextInputWithCurrencySymbol/index.android.js new file mode 100644 index 000000000000..7fb65257e707 --- /dev/null +++ b/src/components/TextInputWithCurrencySymbol/index.android.js @@ -0,0 +1,37 @@ +import React, {useState, useEffect} from 'react'; +import BaseTextInputWithCurrencySymbol from './BaseTextInputWithCurrencySymbol'; +import * as textInputWithCurrencySymbolPropTypes from './textInputWithCurrencySymbolPropTypes'; + +function TextInputWithCurrencySymbol(props) { + const [skipNextSelectionChange, setSkipNextSelectionChange] = useState(false); + + useEffect(() => { + setSkipNextSelectionChange(true); + }, [props.formattedAmount]); + + return ( + { + if (skipNextSelectionChange) { + setSkipNextSelectionChange(false); + return; + } + props.onSelectionChange(e); + }} + /> + ); +} + +TextInputWithCurrencySymbol.propTypes = textInputWithCurrencySymbolPropTypes.propTypes; +TextInputWithCurrencySymbol.defaultProps = textInputWithCurrencySymbolPropTypes.defaultProps; +TextInputWithCurrencySymbol.displayName = 'TextInputWithCurrencySymbol'; + +export default React.forwardRef((props, ref) => ( + +)); diff --git a/src/components/TextInputWithCurrencySymbol/index.js b/src/components/TextInputWithCurrencySymbol/index.js new file mode 100644 index 000000000000..bd4c881c55ba --- /dev/null +++ b/src/components/TextInputWithCurrencySymbol/index.js @@ -0,0 +1,24 @@ +import React from 'react'; +import BaseTextInputWithCurrencySymbol from './BaseTextInputWithCurrencySymbol'; +import * as textInputWithCurrencySymbolPropTypes from './textInputWithCurrencySymbolPropTypes'; + +function TextInputWithCurrencySymbol(props) { + return ( + + ); +} + +TextInputWithCurrencySymbol.propTypes = textInputWithCurrencySymbolPropTypes.propTypes; +TextInputWithCurrencySymbol.defaultProps = textInputWithCurrencySymbolPropTypes.defaultProps; +TextInputWithCurrencySymbol.displayName = 'TextInputWithCurrencySymbol'; + +export default React.forwardRef((props, ref) => ( + +)); diff --git a/src/components/TextInputWithCurrencySymbol/textInputWithCurrencySymbolPropTypes.js b/src/components/TextInputWithCurrencySymbol/textInputWithCurrencySymbolPropTypes.js new file mode 100644 index 000000000000..4e891029e71d --- /dev/null +++ b/src/components/TextInputWithCurrencySymbol/textInputWithCurrencySymbolPropTypes.js @@ -0,0 +1,45 @@ +import PropTypes from 'prop-types'; +import refPropTypes from '../refPropTypes'; + +const propTypes = { + /** A ref to forward to amount text input */ + forwardedRef: refPropTypes, + + /** Formatted amount in local currency */ + formattedAmount: PropTypes.string.isRequired, + + /** Function to call when amount in text input is changed */ + onChangeAmount: PropTypes.func, + + /** Function to call when currency button is pressed */ + onCurrencyButtonPress: PropTypes.func, + + /** Placeholder value for amount text input */ + placeholder: PropTypes.string.isRequired, + + /** Currency code of user's selected currency */ + selectedCurrencyCode: PropTypes.string.isRequired, + + /** Selection Object */ + selection: PropTypes.shape({ + start: PropTypes.number, + end: PropTypes.number, + }), + + /** Function to call when selection in text input is changed */ + onSelectionChange: PropTypes.func, + + /** Function to call to handle key presses in the text input */ + onKeyPress: PropTypes.func, +}; + +const defaultProps = { + forwardedRef: undefined, + onChangeAmount: () => {}, + onCurrencyButtonPress: () => {}, + selection: undefined, + onSelectionChange: () => {}, + onKeyPress: () => {}, +}; + +export {propTypes, defaultProps}; diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js index 4c2c76452c55..1d7444397c0e 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.js @@ -13,6 +13,8 @@ import TextInputWithCurrencySymbol from '../../../components/TextInputWithCurren import useLocalize from '../../../hooks/useLocalize'; import CONST from '../../../CONST'; import refPropTypes from '../../../components/refPropTypes'; +import getOperatingSystem from '../../../libs/getOperatingSystem'; +import * as Browser from '../../../libs/Browser'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; const propTypes = { @@ -75,6 +77,8 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu end: selectedAmountAsString.length, }); + const forwardDeletePressedRef = useRef(false); + /** * Event occurs when a user presses a mouse button over an DOM element. * @@ -125,7 +129,8 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu } setCurrentAmount((prevAmount) => { const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); - setSelection((prevSelection) => getNewSelection(prevSelection, prevAmount.length, strippedAmount.length)); + const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current; + setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); return strippedAmount; }); }; @@ -175,6 +180,22 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu onSubmitButtonPress(currentAmount); }, [onSubmitButtonPress, currentAmount]); + /** + * Input handler to check for a forward-delete key (or keyboard shortcut) press. + */ + const textInputKeyPress = ({nativeEvent}) => { + const key = nativeEvent.key.toLowerCase(); + if (Browser.isMobileSafari() && key === CONST.PLATFORM_SPECIFIC_KEYS.CTRL.DEFAULT) { + // Optimistically anticipate forward-delete on iOS Safari (in cases where the Mac Accessiblity keyboard is being + // used for input). If the Control-D shortcut doesn't get sent, the ref will still be reset on the next key press. + forwardDeletePressedRef.current = true; + return; + } + // Control-D on Mac is a keyboard shortcut for forward-delete. See https://support.apple.com/en-us/HT201236 for Mac keyboard shortcuts. + // Also check for the keyboard shortcut on iOS in cases where a hardware keyboard may be connected to the device. + forwardDeletePressedRef.current = key === 'delete' || (_.contains([CONST.OS.MAC_OS, CONST.OS.IOS], getOperatingSystem()) && nativeEvent.ctrlKey && key === 'd'); + }; + const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); const buttonText = isEditing ? translate('common.save') : translate('common.next'); @@ -207,6 +228,7 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu } setSelection(e.nativeEvent.selection); }} + onKeyPress={textInputKeyPress} />