diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index fe58f3c0a6d8..6717c1736f65 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -24,8 +24,7 @@ const custom = require('../config/webpack/webpack.common')({ module.exports = ({config}) => { config.resolve.alias = { 'react-native-config': 'react-web-config', - 'react-native$': '@expensify/react-native-web', - 'react-native-web': '@expensify/react-native-web', + 'react-native$': 'react-native-web', '@react-native-community/netinfo': path.resolve(__dirname, '../__mocks__/@react-native-community/netinfo.js'), '@react-navigation/native': path.resolve(__dirname, '../__mocks__/@react-navigation/native'), diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js index d12f602260e1..b66b4f67a3b6 100644 --- a/config/webpack/webpack.common.js +++ b/config/webpack/webpack.common.js @@ -13,7 +13,7 @@ const includeModules = [ 'react-native-animatable', 'react-native-reanimated', 'react-native-picker-select', - '@expensify/react-native-web', + 'react-native-web', 'react-native-webview', '@react-native-picker', 'react-native-modal', @@ -185,8 +185,7 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ resolve: { alias: { 'react-native-config': 'react-web-config', - 'react-native$': '@expensify/react-native-web', - 'react-native-web': '@expensify/react-native-web', + 'react-native$': 'react-native-web', // Module alias for web & desktop // https://webpack.js.org/configuration/resolve/#resolvealias diff --git a/contributingGuides/FORMS.md b/contributingGuides/FORMS.md index 1fb67483daca..ffec5f20254c 100644 --- a/contributingGuides/FORMS.md +++ b/contributingGuides/FORMS.md @@ -53,17 +53,27 @@ The phone number can be formatted in different ways. ### Native Keyboards -We should always set people up for success on native platforms by enabling the best keyboard for the type of input we’re asking them to provide. See [keyboardType](https://reactnative.dev/docs/0.64/textinput#keyboardtype) in the React Native documentation. +We should always set people up for success on native platforms by enabling the best keyboard for the type of input we’re asking them to provide. See [inputMode](https://reactnative.dev/docs/textinput#inputmode) in the React Native documentation. -We have a couple of keyboard types [defined](https://github.com/Expensify/App/blob/572caa9e7cf32a2d64fe0e93d171bb05a1dfb217/src/CONST.js#L357-L360) and should be used like so: +We have a list of input modes [defined](https://github.com/Expensify/App/blob/9418b870515102631ea2156b5ea253ee05a98ff1/src/CONST.js#L765-L774) and should be used like so: ```jsx ``` +We also have [keyboardType](https://github.com/Expensify/App/blob/9418b870515102631ea2156b5ea253ee05a98ff1/src/CONST.js#L760-L763) and should be used for specific use cases when there is no `inputMode` equivalent of the value exist. and should be used like so: + +```jsx + +``` + + ### Autofill Behavior Forms should autofill information whenever possible i.e. they should work with browsers and password managers auto complete features. diff --git a/package-lock.json b/package-lock.json index b9eb50615924..b4513f4359ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "license": "MIT", "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-web": "0.18.15", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-getcanonicallocales": "^2.2.0", "@formatjs/intl-listformat": "^7.2.2", @@ -114,6 +113,7 @@ "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", "react-native-vision-camera": "^2.16.2", + "react-native-web": "^0.19.9", "react-native-web-linear-gradient": "^1.1.2", "react-native-webview": "^11.17.2", "react-pdf": "^6.2.2", @@ -3684,25 +3684,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@expensify/react-native-web": { - "version": "0.18.15", - "resolved": "https://registry.npmjs.org/@expensify/react-native-web/-/react-native-web-0.18.15.tgz", - "integrity": "sha512-xE3WdGKY4SRLfIrimUlgP78ZsDaWy3g+KIO8mpxTm9zCXeX/sgEYs6QvhFghgEhhp7Y1bLH9LWTKiZy9LZM8EA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.6", - "create-react-class": "^15.7.0", - "fbjs": "^3.0.4", - "inline-style-prefixer": "^6.0.1", - "normalize-css-color": "^1.0.2", - "postcss-value-parser": "^4.2.0", - "styleq": "^0.1.2" - }, - "peerDependencies": { - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" - } - }, "node_modules/@expo/config-plugins": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-4.1.5.tgz", @@ -26262,16 +26243,6 @@ "sha.js": "^2.4.8" } }, - "node_modules/create-react-class": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", - "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - }, "node_modules/cross-fetch": { "version": "3.1.5", "license": "MIT", @@ -41728,12 +41699,6 @@ "url": "https://github.com/sponsors/antelle" } }, - "node_modules/normalize-css-color": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/normalize-css-color/-/normalize-css-color-1.0.2.tgz", - "integrity": "sha512-jPJ/V7Cp1UytdidsPqviKEElFQJs22hUUgK5BOPHTwOonNCk7/2qOxhhqzEajmFrWJowADFfOFh1V+aWkRfy+w==", - "license": "BSD-3-Clause" - }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -45106,7 +45071,6 @@ "version": "0.19.9", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.9.tgz", "integrity": "sha512-m69arZbS6FV+BNSKE6R/NQwUX+CzxCkYM7AJlSLlS8dz3BDzlaxG8Bzqtzv/r3r1YFowhnZLBXVKIwovKDw49g==", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-color": "^2.1.0", @@ -45133,8 +45097,7 @@ "node_modules/react-native-web/node_modules/memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "peer": true + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" }, "node_modules/react-native-webview": { "version": "11.23.0", @@ -55659,20 +55622,6 @@ } } }, - "@expensify/react-native-web": { - "version": "0.18.15", - "resolved": "https://registry.npmjs.org/@expensify/react-native-web/-/react-native-web-0.18.15.tgz", - "integrity": "sha512-xE3WdGKY4SRLfIrimUlgP78ZsDaWy3g+KIO8mpxTm9zCXeX/sgEYs6QvhFghgEhhp7Y1bLH9LWTKiZy9LZM8EA==", - "requires": { - "@babel/runtime": "^7.18.6", - "create-react-class": "^15.7.0", - "fbjs": "^3.0.4", - "inline-style-prefixer": "^6.0.1", - "normalize-css-color": "^1.0.2", - "postcss-value-parser": "^4.2.0", - "styleq": "^0.1.2" - } - }, "@expo/config-plugins": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-4.1.5.tgz", @@ -72107,15 +72056,6 @@ "sha.js": "^2.4.8" } }, - "create-react-class": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", - "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - }, "cross-fetch": { "version": "3.1.5", "requires": { @@ -83165,11 +83105,6 @@ "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==" }, - "normalize-css-color": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/normalize-css-color/-/normalize-css-color-1.0.2.tgz", - "integrity": "sha512-jPJ/V7Cp1UytdidsPqviKEElFQJs22hUUgK5BOPHTwOonNCk7/2qOxhhqzEajmFrWJowADFfOFh1V+aWkRfy+w==" - }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -85603,7 +85538,6 @@ "version": "0.19.9", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.9.tgz", "integrity": "sha512-m69arZbS6FV+BNSKE6R/NQwUX+CzxCkYM7AJlSLlS8dz3BDzlaxG8Bzqtzv/r3r1YFowhnZLBXVKIwovKDw49g==", - "peer": true, "requires": { "@babel/runtime": "^7.18.6", "@react-native/normalize-color": "^2.1.0", @@ -85618,8 +85552,7 @@ "memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "peer": true + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" } } }, diff --git a/package.json b/package.json index f10ec0d81ee9..add29281e08b 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ }, "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-web": "0.18.15", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-getcanonicallocales": "^2.2.0", "@formatjs/intl-listformat": "^7.2.2", @@ -163,6 +162,7 @@ "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", "react-native-vision-camera": "^2.16.2", + "react-native-web": "^0.19.9", "react-native-web-linear-gradient": "^1.1.2", "react-native-webview": "^11.17.2", "react-pdf": "^6.2.2", diff --git a/patches/eslint-plugin-react-native-a11y+3.3.0.patch b/patches/eslint-plugin-react-native-a11y+3.3.0.patch new file mode 100644 index 000000000000..fe5998118afa --- /dev/null +++ b/patches/eslint-plugin-react-native-a11y+3.3.0.patch @@ -0,0 +1,59 @@ +diff --git a/node_modules/eslint-plugin-react-native-a11y/__tests__/src/rules/has-valid-accessibility-descriptors-test.js b/node_modules/eslint-plugin-react-native-a11y/__tests__/src/rules/has-valid-accessibility-descriptors-test.js +index 9ecf8b1..fef94dd 100644 +--- a/node_modules/eslint-plugin-react-native-a11y/__tests__/src/rules/has-valid-accessibility-descriptors-test.js ++++ b/node_modules/eslint-plugin-react-native-a11y/__tests__/src/rules/has-valid-accessibility-descriptors-test.js +@@ -20,7 +20,7 @@ const ruleTester = new RuleTester(); + + const expectedError = { + message: +- 'Missing a11y props. Expected one of: accessibilityRole OR BOTH accessibilityLabel + accessibilityHint OR BOTH accessibilityActions + onAccessibilityAction', ++ 'Missing a11y props. Expected one of: accessibilityRole OR role OR BOTH accessibilityLabel + accessibilityHint OR BOTH accessibilityActions + onAccessibilityAction', + type: 'JSXOpeningElement', + }; + +@@ -29,6 +29,11 @@ ruleTester.run('has-valid-accessibility-descriptors', rule, { + { + code: ';', + }, ++ { ++ code: ` ++ Back ++ `, ++ }, + { + code: ` + Back +diff --git a/node_modules/eslint-plugin-react-native-a11y/lib/rules/has-valid-accessibility-descriptors.js b/node_modules/eslint-plugin-react-native-a11y/lib/rules/has-valid-accessibility-descriptors.js +index 99deb91..555ebd9 100644 +--- a/node_modules/eslint-plugin-react-native-a11y/lib/rules/has-valid-accessibility-descriptors.js ++++ b/node_modules/eslint-plugin-react-native-a11y/lib/rules/has-valid-accessibility-descriptors.js +@@ -16,7 +16,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de + // ---------------------------------------------------------------------------- + // Rule Definition + // ---------------------------------------------------------------------------- +-var errorMessage = 'Missing a11y props. Expected one of: accessibilityRole OR BOTH accessibilityLabel + accessibilityHint OR BOTH accessibilityActions + onAccessibilityAction'; ++var errorMessage = 'Missing a11y props. Expected one of: accessibilityRole OR role OR BOTH accessibilityLabel + accessibilityHint OR BOTH accessibilityActions + onAccessibilityAction'; + var schema = (0, _schemas.generateObjSchema)(); + + var hasSpreadProps = function hasSpreadProps(attributes) { +@@ -35,7 +35,7 @@ module.exports = { + return { + JSXOpeningElement: function JSXOpeningElement(node) { + if ((0, _isTouchable.default)(node, context) || (0, _jsxAstUtils.elementType)(node) === 'TextInput') { +- if (!(0, _jsxAstUtils.hasAnyProp)(node.attributes, ['accessibilityRole', 'accessibilityLabel', 'accessibilityActions', 'accessible']) && !hasSpreadProps(node.attributes)) { ++ if (!(0, _jsxAstUtils.hasAnyProp)(node.attributes, ['role', 'accessibilityRole', 'accessibilityLabel', 'accessibilityActions', 'accessible']) && !hasSpreadProps(node.attributes)) { + context.report({ + node, + message: errorMessage, +diff --git a/node_modules/eslint-plugin-react-native-a11y/lib/rules/has-valid-accessibility-role.js b/node_modules/eslint-plugin-react-native-a11y/lib/rules/has-valid-accessibility-role.js +index fe74702..fa6bdaa 100644 +--- a/node_modules/eslint-plugin-react-native-a11y/lib/rules/has-valid-accessibility-role.js ++++ b/node_modules/eslint-plugin-react-native-a11y/lib/rules/has-valid-accessibility-role.js +@@ -13,5 +13,5 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de + // Rule Definition + // ---------------------------------------------------------------------------- + var errorMessage = 'accessibilityRole must be one of defined values'; +-var validValues = ['togglebutton', 'adjustable', 'alert', 'button', 'checkbox', 'combobox', 'header', 'image', 'imagebutton', 'keyboardkey', 'link', 'menu', 'menubar', 'menuitem', 'none', 'progressbar', 'radio', 'radiogroup', 'scrollbar', 'search', 'spinbutton', 'summary', 'switch', 'tab', 'tabbar', 'tablist', 'text', 'timer', 'list', 'toolbar']; ++var validValues = ['img', 'img button', 'img link', 'togglebutton', 'adjustable', 'alert', 'button', 'checkbox', 'combobox', 'header', 'image', 'imagebutton', 'keyboardkey', 'link', 'menu', 'menubar', 'menuitem', 'none', 'progressbar', 'radio', 'radiogroup', 'scrollbar', 'search', 'spinbutton', 'summary', 'switch', 'tab', 'tabbar', 'tablist', 'text', 'timer', 'list', 'toolbar']; + module.exports = (0, _validProp.default)('accessibilityRole', validValues, errorMessage); +\ No newline at end of file diff --git a/patches/react-native-modal+13.0.1.patch b/patches/react-native-modal+13.0.1.patch index 049a7a09d16a..5bfb2cc5f0b0 100644 --- a/patches/react-native-modal+13.0.1.patch +++ b/patches/react-native-modal+13.0.1.patch @@ -11,7 +11,7 @@ index b63bcfc..bd6419e 100644 buildPanResponder: () => void; getAccDistancePerDirection: (gestureState: PanResponderGestureState) => number; diff --git a/node_modules/react-native-modal/dist/modal.js b/node_modules/react-native-modal/dist/modal.js -index 80f4e75..a88a2ca 100644 +index 80f4e75..3ba8b8c 100644 --- a/node_modules/react-native-modal/dist/modal.js +++ b/node_modules/react-native-modal/dist/modal.js @@ -75,6 +75,13 @@ export class ReactNativeModal extends React.Component { @@ -28,23 +28,27 @@ index 80f4e75..a88a2ca 100644 this.shouldPropagateSwipe = (evt, gestureState) => { return typeof this.props.propagateSwipe === 'function' ? this.props.propagateSwipe(evt, gestureState) -@@ -454,9 +461,15 @@ export class ReactNativeModal extends React.Component { +@@ -453,10 +460,18 @@ export class ReactNativeModal extends React.Component { + if (this.state.isVisible) { this.open(); } - BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPress); + if (Platform.OS === 'web') { + document?.body?.addEventListener?.('keyup', this.handleEscape, true); ++ return; + } + BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPress); } componentWillUnmount() { - BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPress); +- BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPress); + if (Platform.OS === 'web') { + document?.body?.removeEventListener?.('keyup', this.handleEscape, true); ++ } else { ++ BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPress); + } if (this.didUpdateDimensionsEmitter) { this.didUpdateDimensionsEmitter.remove(); } -@@ -525,7 +538,7 @@ export class ReactNativeModal extends React.Component { +@@ -525,7 +540,7 @@ export class ReactNativeModal extends React.Component { } return (React.createElement(Modal, Object.assign({ transparent: true, animationType: 'none', visible: this.state.isVisible, onRequestClose: onBackButtonPress }, otherProps), this.makeBackdrop(), diff --git a/patches/react-native-web+0.19.9+001+initial.patch b/patches/react-native-web+0.19.9+001+initial.patch new file mode 100644 index 000000000000..d88ef83d4bcd --- /dev/null +++ b/patches/react-native-web+0.19.9+001+initial.patch @@ -0,0 +1,286 @@ +diff --git a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js +index c879838..288316c 100644 +--- a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js ++++ b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js +@@ -117,6 +117,14 @@ function findLastWhere(arr, predicate) { + * + */ + class VirtualizedList extends StateSafePureComponent { ++ pushOrUnshift(input, item) { ++ if (this.props.inverted) { ++ input.unshift(item); ++ } else { ++ input.push(item); ++ } ++ } ++ + // scrollToEnd may be janky without getItemLayout prop + scrollToEnd(params) { + var animated = params ? params.animated : true; +@@ -350,6 +358,7 @@ class VirtualizedList extends StateSafePureComponent { + }; + this._defaultRenderScrollComponent = props => { + var onRefresh = props.onRefresh; ++ var inversionStyle = this.props.inverted ? this.props.horizontal ? styles.rowReverse : styles.columnReverse : null; + if (this._isNestedWithSameOrientation()) { + // $FlowFixMe[prop-missing] - Typing ReactNativeComponent revealed errors + return /*#__PURE__*/React.createElement(View, props); +@@ -367,13 +376,16 @@ class VirtualizedList extends StateSafePureComponent { + refreshing: props.refreshing, + onRefresh: onRefresh, + progressViewOffset: props.progressViewOffset +- }) : props.refreshControl ++ }) : props.refreshControl, ++ contentContainerStyle: [inversionStyle, this.props.contentContainerStyle] + })) + ); + } else { + // $FlowFixMe[prop-missing] Invalid prop usage + // $FlowFixMe[incompatible-use] +- return /*#__PURE__*/React.createElement(ScrollView, props); ++ return /*#__PURE__*/React.createElement(ScrollView, _extends({}, props, { ++ contentContainerStyle: [inversionStyle, this.props.contentContainerStyle] ++ })); + } + }; + this._onCellLayout = (e, cellKey, index) => { +@@ -683,7 +695,7 @@ class VirtualizedList extends StateSafePureComponent { + onViewableItemsChanged = _this$props3.onViewableItemsChanged, + viewabilityConfig = _this$props3.viewabilityConfig; + if (onViewableItemsChanged) { +- this._viewabilityTuples.push({ ++ this.pushOrUnshift(this._viewabilityTuples, { + viewabilityHelper: new ViewabilityHelper(viewabilityConfig), + onViewableItemsChanged: onViewableItemsChanged + }); +@@ -937,10 +949,10 @@ class VirtualizedList extends StateSafePureComponent { + var key = _this._keyExtractor(item, ii, _this.props); + _this._indicesToKeys.set(ii, key); + if (stickyIndicesFromProps.has(ii + stickyOffset)) { +- stickyHeaderIndices.push(cells.length); ++ _this.pushOrUnshift(stickyHeaderIndices, cells.length); + } + var shouldListenForLayout = getItemLayout == null || debug || _this._fillRateHelper.enabled(); +- cells.push( /*#__PURE__*/React.createElement(CellRenderer, _extends({ ++ _this.pushOrUnshift(cells, /*#__PURE__*/React.createElement(CellRenderer, _extends({ + CellRendererComponent: CellRendererComponent, + ItemSeparatorComponent: ii < end ? ItemSeparatorComponent : undefined, + ListItemComponent: ListItemComponent, +@@ -1012,14 +1024,14 @@ class VirtualizedList extends StateSafePureComponent { + // 1. Add cell for ListHeaderComponent + if (ListHeaderComponent) { + if (stickyIndicesFromProps.has(0)) { +- stickyHeaderIndices.push(0); ++ this.pushOrUnshift(stickyHeaderIndices, 0); + } + var _element = /*#__PURE__*/React.isValidElement(ListHeaderComponent) ? ListHeaderComponent : + /*#__PURE__*/ + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] + React.createElement(ListHeaderComponent, null); +- cells.push( /*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { ++ this.pushOrUnshift(cells, /*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { + cellKey: this._getCellKey() + '-header', + key: "$header" + }, /*#__PURE__*/React.createElement(View, { +@@ -1038,7 +1050,7 @@ class VirtualizedList extends StateSafePureComponent { + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] + React.createElement(ListEmptyComponent, null); +- cells.push( /*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { ++ this.pushOrUnshift(cells, /*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { + cellKey: this._getCellKey() + '-empty', + key: "$empty" + }, /*#__PURE__*/React.cloneElement(_element2, { +@@ -1077,7 +1089,7 @@ class VirtualizedList extends StateSafePureComponent { + var firstMetrics = this.__getFrameMetricsApprox(section.first, this.props); + var lastMetrics = this.__getFrameMetricsApprox(last, this.props); + var spacerSize = lastMetrics.offset + lastMetrics.length - firstMetrics.offset; +- cells.push( /*#__PURE__*/React.createElement(View, { ++ this.pushOrUnshift(cells, /*#__PURE__*/React.createElement(View, { + key: "$spacer-" + section.first, + style: { + [spacerKey]: spacerSize +@@ -1100,7 +1112,7 @@ class VirtualizedList extends StateSafePureComponent { + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] + React.createElement(ListFooterComponent, null); +- cells.push( /*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { ++ this.pushOrUnshift(cells, /*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { + cellKey: this._getFooterCellKey(), + key: "$footer" + }, /*#__PURE__*/React.createElement(View, { +@@ -1266,7 +1278,7 @@ class VirtualizedList extends StateSafePureComponent { + * suppresses an error found when Flow v0.68 was deployed. To see the + * error delete this comment and run Flow. */ + if (frame.inLayout) { +- framesInLayout.push(frame); ++ this.pushOrUnshift(framesInLayout, frame); + } + } + var windowTop = this.__getFrameMetricsApprox(this.state.cellsAroundViewport.first, this.props).offset; +@@ -1452,6 +1464,12 @@ var styles = StyleSheet.create({ + left: 0, + borderColor: 'red', + borderWidth: 2 ++ }, ++ rowReverse: { ++ flexDirection: 'row-reverse' ++ }, ++ columnReverse: { ++ flexDirection: 'column-reverse' + } + }); + export default VirtualizedList; +\ No newline at end of file +diff --git a/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js b/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js +index c7d68bb..46b3fc9 100644 +--- a/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js ++++ b/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js +@@ -167,6 +167,14 @@ function findLastWhere( + class VirtualizedList extends StateSafePureComponent { + static contextType: typeof VirtualizedListContext = VirtualizedListContext; + ++ pushOrUnshift(input: Array, item: Item) { ++ if (this.props.inverted) { ++ input.unshift(item) ++ } else { ++ input.push(item) ++ } ++ } ++ + // scrollToEnd may be janky without getItemLayout prop + scrollToEnd(params?: ?{animated?: ?boolean, ...}) { + const animated = params ? params.animated : true; +@@ -438,7 +446,7 @@ class VirtualizedList extends StateSafePureComponent { + } else { + const {onViewableItemsChanged, viewabilityConfig} = this.props; + if (onViewableItemsChanged) { +- this._viewabilityTuples.push({ ++ this.pushOrUnshift(this._viewabilityTuples, { + viewabilityHelper: new ViewabilityHelper(viewabilityConfig), + onViewableItemsChanged: onViewableItemsChanged, + }); +@@ -814,13 +822,13 @@ class VirtualizedList extends StateSafePureComponent { + + this._indicesToKeys.set(ii, key); + if (stickyIndicesFromProps.has(ii + stickyOffset)) { +- stickyHeaderIndices.push(cells.length); ++ this.pushOrUnshift(stickyHeaderIndices, (cells.length)); + } + + const shouldListenForLayout = + getItemLayout == null || debug || this._fillRateHelper.enabled(); + +- cells.push( ++ this.pushOrUnshift(cells, + { + // 1. Add cell for ListHeaderComponent + if (ListHeaderComponent) { + if (stickyIndicesFromProps.has(0)) { +- stickyHeaderIndices.push(0); ++ this.pushOrUnshift(stickyHeaderIndices, 0); + } + const element = React.isValidElement(ListHeaderComponent) ? ( + ListHeaderComponent +@@ -932,7 +940,7 @@ class VirtualizedList extends StateSafePureComponent { + // $FlowFixMe[incompatible-type-arg] + + ); +- cells.push( ++ this.pushOrUnshift(cells, + +@@ -963,7 +971,7 @@ class VirtualizedList extends StateSafePureComponent { + // $FlowFixMe[incompatible-type-arg] + + )): any); +- cells.push( ++ this.pushOrUnshift(cells, + +@@ -1017,7 +1025,7 @@ class VirtualizedList extends StateSafePureComponent { + const lastMetrics = this.__getFrameMetricsApprox(last, this.props); + const spacerSize = + lastMetrics.offset + lastMetrics.length - firstMetrics.offset; +- cells.push( ++ this.pushOrUnshift(cells, + { + // $FlowFixMe[incompatible-type-arg] + + ); +- cells.push( ++ this.pushOrUnshift(cells, + +@@ -1246,6 +1254,12 @@ class VirtualizedList extends StateSafePureComponent { + * LTI update could not be added via codemod */ + _defaultRenderScrollComponent = props => { + const onRefresh = props.onRefresh; ++ const inversionStyle = this.props.inverted ++ ? this.props.horizontal ++ ? styles.rowReverse ++ : styles.columnReverse ++ : null; ++ + if (this._isNestedWithSameOrientation()) { + // $FlowFixMe[prop-missing] - Typing ReactNativeComponent revealed errors + return ; +@@ -1273,12 +1287,24 @@ class VirtualizedList extends StateSafePureComponent { + props.refreshControl + ) + } ++ contentContainerStyle={[ ++ inversionStyle, ++ this.props.contentContainerStyle, ++ ]} + /> + ); + } else { + // $FlowFixMe[prop-missing] Invalid prop usage + // $FlowFixMe[incompatible-use] +- return ; ++ return ( ++ ++ ); + } + }; + +@@ -1432,7 +1458,7 @@ class VirtualizedList extends StateSafePureComponent { + * suppresses an error found when Flow v0.68 was deployed. To see the + * error delete this comment and run Flow. */ + if (frame.inLayout) { +- framesInLayout.push(frame); ++ this.pushOrUnshift(framesInLayout, frame); + } + } + const windowTop = this.__getFrameMetricsApprox( +@@ -2044,6 +2070,12 @@ const styles = StyleSheet.create({ + borderColor: 'red', + borderWidth: 2, + }, ++ rowReverse: { ++ flexDirection: 'row-reverse', ++ }, ++ columnReverse: { ++ flexDirection: 'column-reverse', ++ }, + }); + + export default VirtualizedList; +\ No newline at end of file diff --git a/patches/react-native-web+0.19.9+002+fix-mvcp.patch b/patches/react-native-web+0.19.9+002+fix-mvcp.patch new file mode 100644 index 000000000000..afd681bba3b0 --- /dev/null +++ b/patches/react-native-web+0.19.9+002+fix-mvcp.patch @@ -0,0 +1,687 @@ +diff --git a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js +index a6fe142..faeb323 100644 +--- a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js ++++ b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js +@@ -293,7 +293,7 @@ class VirtualizedList extends StateSafePureComponent { + // $FlowFixMe[missing-local-annot] + + constructor(_props) { +- var _this$props$updateCel; ++ var _this$props$updateCel, _this$props$maintainV, _this$props$maintainV2; + super(_props); + this._getScrollMetrics = () => { + return this._scrollMetrics; +@@ -532,6 +532,11 @@ class VirtualizedList extends StateSafePureComponent { + visibleLength, + zoomScale + }; ++ if (this.state.pendingScrollUpdateCount > 0) { ++ this.setState(state => ({ ++ pendingScrollUpdateCount: state.pendingScrollUpdateCount - 1 ++ })); ++ } + this._updateViewableItems(this.props, this.state.cellsAroundViewport); + if (!this.props) { + return; +@@ -581,7 +586,7 @@ class VirtualizedList extends StateSafePureComponent { + this._updateCellsToRender = () => { + this._updateViewableItems(this.props, this.state.cellsAroundViewport); + this.setState((state, props) => { +- var cellsAroundViewport = this._adjustCellsAroundViewport(props, state.cellsAroundViewport); ++ var cellsAroundViewport = this._adjustCellsAroundViewport(props, state.cellsAroundViewport, state.pendingScrollUpdateCount); + var renderMask = VirtualizedList._createRenderMask(props, cellsAroundViewport, this._getNonViewportRenderRegions(props)); + if (cellsAroundViewport.first === state.cellsAroundViewport.first && cellsAroundViewport.last === state.cellsAroundViewport.last && renderMask.equals(state.renderMask)) { + return null; +@@ -601,7 +606,7 @@ class VirtualizedList extends StateSafePureComponent { + return { + index, + item, +- key: this._keyExtractor(item, index, props), ++ key: VirtualizedList._keyExtractor(item, index, props), + isViewable + }; + }; +@@ -633,12 +638,10 @@ class VirtualizedList extends StateSafePureComponent { + }; + this._getFrameMetrics = (index, props) => { + var data = props.data, +- getItem = props.getItem, + getItemCount = props.getItemCount, + getItemLayout = props.getItemLayout; + invariant(index >= 0 && index < getItemCount(data), 'Tried to get frame for out of range index ' + index); +- var item = getItem(data, index); +- var frame = this._frames[this._keyExtractor(item, index, props)]; ++ var frame = this._frames[VirtualizedList._getItemKey(props, index)]; + if (!frame || frame.index !== index) { + if (getItemLayout) { + /* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment +@@ -662,7 +665,7 @@ class VirtualizedList extends StateSafePureComponent { + + // The last cell we rendered may be at a new index. Bail if we don't know + // where it is. +- if (focusedCellIndex >= itemCount || this._keyExtractor(props.getItem(props.data, focusedCellIndex), focusedCellIndex, props) !== this._lastFocusedCellKey) { ++ if (focusedCellIndex >= itemCount || VirtualizedList._getItemKey(props, focusedCellIndex) !== this._lastFocusedCellKey) { + return []; + } + var first = focusedCellIndex; +@@ -702,9 +705,15 @@ class VirtualizedList extends StateSafePureComponent { + } + } + var initialRenderRegion = VirtualizedList._initialRenderRegion(_props); ++ var minIndexForVisible = (_this$props$maintainV = (_this$props$maintainV2 = this.props.maintainVisibleContentPosition) == null ? void 0 : _this$props$maintainV2.minIndexForVisible) !== null && _this$props$maintainV !== void 0 ? _this$props$maintainV : 0; + this.state = { + cellsAroundViewport: initialRenderRegion, +- renderMask: VirtualizedList._createRenderMask(_props, initialRenderRegion) ++ renderMask: VirtualizedList._createRenderMask(_props, initialRenderRegion), ++ firstVisibleItemKey: this.props.getItemCount(this.props.data) > minIndexForVisible ? VirtualizedList._getItemKey(this.props, minIndexForVisible) : null, ++ // When we have a non-zero initialScrollIndex, we will receive a ++ // scroll event later so this will prevent the window from updating ++ // until we get a valid offset. ++ pendingScrollUpdateCount: this.props.initialScrollIndex != null && this.props.initialScrollIndex > 0 ? 1 : 0 + }; + + // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. +@@ -715,7 +724,7 @@ class VirtualizedList extends StateSafePureComponent { + var clientLength = this.props.horizontal ? ev.target.clientWidth : ev.target.clientHeight; + var isEventTargetScrollable = scrollLength > clientLength; + var delta = this.props.horizontal ? ev.deltaX || ev.wheelDeltaX : ev.deltaY || ev.wheelDeltaY; +- var leftoverDelta = delta; ++ var leftoverDelta = delta * 0.5; + if (isEventTargetScrollable) { + leftoverDelta = delta < 0 ? Math.min(delta + scrollOffset, 0) : Math.max(delta - (scrollLength - clientLength - scrollOffset), 0); + } +@@ -760,6 +769,26 @@ class VirtualizedList extends StateSafePureComponent { + } + } + } ++ static _findItemIndexWithKey(props, key, hint) { ++ var itemCount = props.getItemCount(props.data); ++ if (hint != null && hint >= 0 && hint < itemCount) { ++ var curKey = VirtualizedList._getItemKey(props, hint); ++ if (curKey === key) { ++ return hint; ++ } ++ } ++ for (var ii = 0; ii < itemCount; ii++) { ++ var _curKey = VirtualizedList._getItemKey(props, ii); ++ if (_curKey === key) { ++ return ii; ++ } ++ } ++ return null; ++ } ++ static _getItemKey(props, index) { ++ var item = props.getItem(props.data, index); ++ return VirtualizedList._keyExtractor(item, index, props); ++ } + static _createRenderMask(props, cellsAroundViewport, additionalRegions) { + var itemCount = props.getItemCount(props.data); + invariant(cellsAroundViewport.first >= 0 && cellsAroundViewport.last >= cellsAroundViewport.first - 1 && cellsAroundViewport.last < itemCount, "Invalid cells around viewport \"[" + cellsAroundViewport.first + ", " + cellsAroundViewport.last + "]\" was passed to VirtualizedList._createRenderMask"); +@@ -808,7 +837,7 @@ class VirtualizedList extends StateSafePureComponent { + } + } + } +- _adjustCellsAroundViewport(props, cellsAroundViewport) { ++ _adjustCellsAroundViewport(props, cellsAroundViewport, pendingScrollUpdateCount) { + var data = props.data, + getItemCount = props.getItemCount; + var onEndReachedThreshold = onEndReachedThresholdOrDefault(props.onEndReachedThreshold); +@@ -831,17 +860,9 @@ class VirtualizedList extends StateSafePureComponent { + last: Math.min(cellsAroundViewport.last + renderAhead, getItemCount(data) - 1) + }; + } else { +- // If we have a non-zero initialScrollIndex and run this before we've scrolled, +- // we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex. +- // So let's wait until we've scrolled the view to the right place. And until then, +- // we will trust the initialScrollIndex suggestion. +- +- // Thus, we want to recalculate the windowed render limits if any of the following hold: +- // - initialScrollIndex is undefined or is 0 +- // - initialScrollIndex > 0 AND scrolling is complete +- // - initialScrollIndex > 0 AND the end of the list is visible (this handles the case +- // where the list is shorter than the visible area) +- if (props.initialScrollIndex && !this._scrollMetrics.offset && Math.abs(distanceFromEnd) >= Number.EPSILON) { ++ // If we have a pending scroll update, we should not adjust the render window as it ++ // might override the correct window. ++ if (pendingScrollUpdateCount > 0) { + return cellsAroundViewport.last >= getItemCount(data) ? VirtualizedList._constrainToItemCount(cellsAroundViewport, props) : cellsAroundViewport; + } + newCellsAroundViewport = computeWindowedRenderLimits(props, maxToRenderPerBatchOrDefault(props.maxToRenderPerBatch), windowSizeOrDefault(props.windowSize), cellsAroundViewport, this.__getFrameMetricsApprox, this._scrollMetrics); +@@ -914,16 +935,36 @@ class VirtualizedList extends StateSafePureComponent { + } + } + static getDerivedStateFromProps(newProps, prevState) { ++ var _newProps$maintainVis, _newProps$maintainVis2; + // first and last could be stale (e.g. if a new, shorter items props is passed in), so we make + // sure we're rendering a reasonable range here. + var itemCount = newProps.getItemCount(newProps.data); + if (itemCount === prevState.renderMask.numCells()) { + return prevState; + } +- var constrainedCells = VirtualizedList._constrainToItemCount(prevState.cellsAroundViewport, newProps); ++ var maintainVisibleContentPositionAdjustment = null; ++ var prevFirstVisibleItemKey = prevState.firstVisibleItemKey; ++ var minIndexForVisible = (_newProps$maintainVis = (_newProps$maintainVis2 = newProps.maintainVisibleContentPosition) == null ? void 0 : _newProps$maintainVis2.minIndexForVisible) !== null && _newProps$maintainVis !== void 0 ? _newProps$maintainVis : 0; ++ var newFirstVisibleItemKey = newProps.getItemCount(newProps.data) > minIndexForVisible ? VirtualizedList._getItemKey(newProps, minIndexForVisible) : null; ++ if (newProps.maintainVisibleContentPosition != null && prevFirstVisibleItemKey != null && newFirstVisibleItemKey != null) { ++ if (newFirstVisibleItemKey !== prevFirstVisibleItemKey) { ++ // Fast path if items were added at the start of the list. ++ var hint = itemCount - prevState.renderMask.numCells() + minIndexForVisible; ++ var firstVisibleItemIndex = VirtualizedList._findItemIndexWithKey(newProps, prevFirstVisibleItemKey, hint); ++ maintainVisibleContentPositionAdjustment = firstVisibleItemIndex != null ? firstVisibleItemIndex - minIndexForVisible : null; ++ } else { ++ maintainVisibleContentPositionAdjustment = null; ++ } ++ } ++ var constrainedCells = VirtualizedList._constrainToItemCount(maintainVisibleContentPositionAdjustment != null ? { ++ first: prevState.cellsAroundViewport.first + maintainVisibleContentPositionAdjustment, ++ last: prevState.cellsAroundViewport.last + maintainVisibleContentPositionAdjustment ++ } : prevState.cellsAroundViewport, newProps); + return { + cellsAroundViewport: constrainedCells, +- renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells) ++ renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells), ++ firstVisibleItemKey: newFirstVisibleItemKey, ++ pendingScrollUpdateCount: maintainVisibleContentPositionAdjustment != null ? prevState.pendingScrollUpdateCount + 1 : prevState.pendingScrollUpdateCount + }; + } + _pushCells(cells, stickyHeaderIndices, stickyIndicesFromProps, first, last, inversionStyle) { +@@ -946,7 +987,7 @@ class VirtualizedList extends StateSafePureComponent { + last = Math.min(end, last); + var _loop = function _loop() { + var item = getItem(data, ii); +- var key = _this._keyExtractor(item, ii, _this.props); ++ var key = VirtualizedList._keyExtractor(item, ii, _this.props); + _this._indicesToKeys.set(ii, key); + if (stickyIndicesFromProps.has(ii + stickyOffset)) { + _this.pushOrUnshift(stickyHeaderIndices, cells.length); +@@ -981,20 +1022,23 @@ class VirtualizedList extends StateSafePureComponent { + } + static _constrainToItemCount(cells, props) { + var itemCount = props.getItemCount(props.data); +- var last = Math.min(itemCount - 1, cells.last); ++ var lastPossibleCellIndex = itemCount - 1; ++ ++ // Constraining `last` may significantly shrink the window. Adjust `first` ++ // to expand the window if the new `last` results in a new window smaller ++ // than the number of cells rendered per batch. + var maxToRenderPerBatch = maxToRenderPerBatchOrDefault(props.maxToRenderPerBatch); ++ var maxFirst = Math.max(0, lastPossibleCellIndex - maxToRenderPerBatch); + return { +- first: clamp(0, itemCount - 1 - maxToRenderPerBatch, cells.first), +- last ++ first: clamp(0, cells.first, maxFirst), ++ last: Math.min(lastPossibleCellIndex, cells.last) + }; + } + _isNestedWithSameOrientation() { + var nestedContext = this.context; + return !!(nestedContext && !!nestedContext.horizontal === horizontalOrDefault(this.props.horizontal)); + } +- _keyExtractor(item, index, props +- // $FlowFixMe[missing-local-annot] +- ) { ++ static _keyExtractor(item, index, props) { + if (props.keyExtractor != null) { + return props.keyExtractor(item, index); + } +@@ -1034,7 +1078,12 @@ class VirtualizedList extends StateSafePureComponent { + this.pushOrUnshift(cells, /*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { + cellKey: this._getCellKey() + '-header', + key: "$header" +- }, /*#__PURE__*/React.createElement(View, { ++ }, /*#__PURE__*/React.createElement(View ++ // We expect that header component will be a single native view so make it ++ // not collapsable to avoid this view being flattened and make this assumption ++ // no longer true. ++ , { ++ collapsable: false, + onLayout: this._onLayoutHeader, + style: [inversionStyle, this.props.ListHeaderComponentStyle] + }, +@@ -1136,7 +1185,11 @@ class VirtualizedList extends StateSafePureComponent { + // TODO: Android support + invertStickyHeaders: this.props.invertStickyHeaders !== undefined ? this.props.invertStickyHeaders : this.props.inverted, + stickyHeaderIndices, +- style: inversionStyle ? [inversionStyle, this.props.style] : this.props.style ++ style: inversionStyle ? [inversionStyle, this.props.style] : this.props.style, ++ maintainVisibleContentPosition: this.props.maintainVisibleContentPosition != null ? _objectSpread(_objectSpread({}, this.props.maintainVisibleContentPosition), {}, { ++ // Adjust index to account for ListHeaderComponent. ++ minIndexForVisible: this.props.maintainVisibleContentPosition.minIndexForVisible + (this.props.ListHeaderComponent ? 1 : 0) ++ }) : undefined + }); + this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1; + var innerRet = /*#__PURE__*/React.createElement(VirtualizedListContextProvider, { +@@ -1319,8 +1372,12 @@ class VirtualizedList extends StateSafePureComponent { + onStartReached = _this$props8.onStartReached, + onStartReachedThreshold = _this$props8.onStartReachedThreshold, + onEndReached = _this$props8.onEndReached, +- onEndReachedThreshold = _this$props8.onEndReachedThreshold, +- initialScrollIndex = _this$props8.initialScrollIndex; ++ onEndReachedThreshold = _this$props8.onEndReachedThreshold; ++ // If we have any pending scroll updates it means that the scroll metrics ++ // are out of date and we should not call any of the edge reached callbacks. ++ if (this.state.pendingScrollUpdateCount > 0) { ++ return; ++ } + var _this$_scrollMetrics2 = this._scrollMetrics, + contentLength = _this$_scrollMetrics2.contentLength, + visibleLength = _this$_scrollMetrics2.visibleLength, +@@ -1360,16 +1417,10 @@ class VirtualizedList extends StateSafePureComponent { + // and call onStartReached only once for a given content length, + // and only if onEndReached is not being executed + else if (onStartReached != null && this.state.cellsAroundViewport.first === 0 && isWithinStartThreshold && this._scrollMetrics.contentLength !== this._sentStartForContentLength) { +- // On initial mount when using initialScrollIndex the offset will be 0 initially +- // and will trigger an unexpected onStartReached. To avoid this we can use +- // timestamp to differentiate between the initial scroll metrics and when we actually +- // received the first scroll event. +- if (!initialScrollIndex || this._scrollMetrics.timestamp !== 0) { +- this._sentStartForContentLength = this._scrollMetrics.contentLength; +- onStartReached({ +- distanceFromStart +- }); +- } ++ this._sentStartForContentLength = this._scrollMetrics.contentLength; ++ onStartReached({ ++ distanceFromStart ++ }); + } + + // If the user scrolls away from the start or end and back again, +@@ -1424,6 +1475,11 @@ class VirtualizedList extends StateSafePureComponent { + } + } + _updateViewableItems(props, cellsAroundViewport) { ++ // If we have any pending scroll updates it means that the scroll metrics ++ // are out of date and we should not call any of the visibility callbacks. ++ if (this.state.pendingScrollUpdateCount > 0) { ++ return; ++ } + this._viewabilityTuples.forEach(tuple => { + tuple.viewabilityHelper.onUpdate(props, this._scrollMetrics.offset, this._scrollMetrics.visibleLength, this._getFrameMetrics, this._createViewToken, tuple.onViewableItemsChanged, cellsAroundViewport); + }); +diff --git a/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js b/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js +index d896fb1..f303b31 100644 +--- a/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js ++++ b/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js +@@ -75,6 +75,10 @@ type ViewabilityHelperCallbackTuple = { + type State = { + renderMask: CellRenderMask, + cellsAroundViewport: {first: number, last: number}, ++ // Used to track items added at the start of the list for maintainVisibleContentPosition. ++ firstVisibleItemKey: ?string, ++ // When > 0 the scroll position available in JS is considered stale and should not be used. ++ pendingScrollUpdateCount: number, + }; + + /** +@@ -455,9 +459,24 @@ class VirtualizedList extends StateSafePureComponent { + + const initialRenderRegion = VirtualizedList._initialRenderRegion(props); + ++ const minIndexForVisible = ++ this.props.maintainVisibleContentPosition?.minIndexForVisible ?? 0; ++ + this.state = { + cellsAroundViewport: initialRenderRegion, + renderMask: VirtualizedList._createRenderMask(props, initialRenderRegion), ++ firstVisibleItemKey: ++ this.props.getItemCount(this.props.data) > minIndexForVisible ++ ? VirtualizedList._getItemKey(this.props, minIndexForVisible) ++ : null, ++ // When we have a non-zero initialScrollIndex, we will receive a ++ // scroll event later so this will prevent the window from updating ++ // until we get a valid offset. ++ pendingScrollUpdateCount: ++ this.props.initialScrollIndex != null && ++ this.props.initialScrollIndex > 0 ++ ? 1 ++ : 0, + }; + + // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. +@@ -470,7 +489,7 @@ class VirtualizedList extends StateSafePureComponent { + const delta = this.props.horizontal + ? ev.deltaX || ev.wheelDeltaX + : ev.deltaY || ev.wheelDeltaY; +- let leftoverDelta = delta; ++ let leftoverDelta = delta * 5; + if (isEventTargetScrollable) { + leftoverDelta = delta < 0 + ? Math.min(delta + scrollOffset, 0) +@@ -542,6 +561,40 @@ class VirtualizedList extends StateSafePureComponent { + } + } + ++ static _findItemIndexWithKey( ++ props: Props, ++ key: string, ++ hint: ?number, ++ ): ?number { ++ const itemCount = props.getItemCount(props.data); ++ if (hint != null && hint >= 0 && hint < itemCount) { ++ const curKey = VirtualizedList._getItemKey(props, hint); ++ if (curKey === key) { ++ return hint; ++ } ++ } ++ for (let ii = 0; ii < itemCount; ii++) { ++ const curKey = VirtualizedList._getItemKey(props, ii); ++ if (curKey === key) { ++ return ii; ++ } ++ } ++ return null; ++ } ++ ++ static _getItemKey( ++ props: { ++ data: Props['data'], ++ getItem: Props['getItem'], ++ keyExtractor: Props['keyExtractor'], ++ ... ++ }, ++ index: number, ++ ): string { ++ const item = props.getItem(props.data, index); ++ return VirtualizedList._keyExtractor(item, index, props); ++ } ++ + static _createRenderMask( + props: Props, + cellsAroundViewport: {first: number, last: number}, +@@ -625,6 +678,7 @@ class VirtualizedList extends StateSafePureComponent { + _adjustCellsAroundViewport( + props: Props, + cellsAroundViewport: {first: number, last: number}, ++ pendingScrollUpdateCount: number, + ): {first: number, last: number} { + const {data, getItemCount} = props; + const onEndReachedThreshold = onEndReachedThresholdOrDefault( +@@ -656,21 +710,9 @@ class VirtualizedList extends StateSafePureComponent { + ), + }; + } else { +- // If we have a non-zero initialScrollIndex and run this before we've scrolled, +- // we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex. +- // So let's wait until we've scrolled the view to the right place. And until then, +- // we will trust the initialScrollIndex suggestion. +- +- // Thus, we want to recalculate the windowed render limits if any of the following hold: +- // - initialScrollIndex is undefined or is 0 +- // - initialScrollIndex > 0 AND scrolling is complete +- // - initialScrollIndex > 0 AND the end of the list is visible (this handles the case +- // where the list is shorter than the visible area) +- if ( +- props.initialScrollIndex && +- !this._scrollMetrics.offset && +- Math.abs(distanceFromEnd) >= Number.EPSILON +- ) { ++ // If we have a pending scroll update, we should not adjust the render window as it ++ // might override the correct window. ++ if (pendingScrollUpdateCount > 0) { + return cellsAroundViewport.last >= getItemCount(data) + ? VirtualizedList._constrainToItemCount(cellsAroundViewport, props) + : cellsAroundViewport; +@@ -779,14 +821,59 @@ class VirtualizedList extends StateSafePureComponent { + return prevState; + } + ++ let maintainVisibleContentPositionAdjustment: ?number = null; ++ const prevFirstVisibleItemKey = prevState.firstVisibleItemKey; ++ const minIndexForVisible = ++ newProps.maintainVisibleContentPosition?.minIndexForVisible ?? 0; ++ const newFirstVisibleItemKey = ++ newProps.getItemCount(newProps.data) > minIndexForVisible ++ ? VirtualizedList._getItemKey(newProps, minIndexForVisible) ++ : null; ++ if ( ++ newProps.maintainVisibleContentPosition != null && ++ prevFirstVisibleItemKey != null && ++ newFirstVisibleItemKey != null ++ ) { ++ if (newFirstVisibleItemKey !== prevFirstVisibleItemKey) { ++ // Fast path if items were added at the start of the list. ++ const hint = ++ itemCount - prevState.renderMask.numCells() + minIndexForVisible; ++ const firstVisibleItemIndex = VirtualizedList._findItemIndexWithKey( ++ newProps, ++ prevFirstVisibleItemKey, ++ hint, ++ ); ++ maintainVisibleContentPositionAdjustment = ++ firstVisibleItemIndex != null ++ ? firstVisibleItemIndex - minIndexForVisible ++ : null; ++ } else { ++ maintainVisibleContentPositionAdjustment = null; ++ } ++ } ++ + const constrainedCells = VirtualizedList._constrainToItemCount( +- prevState.cellsAroundViewport, ++ maintainVisibleContentPositionAdjustment != null ++ ? { ++ first: ++ prevState.cellsAroundViewport.first + ++ maintainVisibleContentPositionAdjustment, ++ last: ++ prevState.cellsAroundViewport.last + ++ maintainVisibleContentPositionAdjustment, ++ } ++ : prevState.cellsAroundViewport, + newProps, + ); + + return { + cellsAroundViewport: constrainedCells, + renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells), ++ firstVisibleItemKey: newFirstVisibleItemKey, ++ pendingScrollUpdateCount: ++ maintainVisibleContentPositionAdjustment != null ++ ? prevState.pendingScrollUpdateCount + 1 ++ : prevState.pendingScrollUpdateCount, + }; + } + +@@ -818,11 +905,11 @@ class VirtualizedList extends StateSafePureComponent { + + for (let ii = first; ii <= last; ii++) { + const item = getItem(data, ii); +- const key = this._keyExtractor(item, ii, this.props); ++ const key = VirtualizedList._keyExtractor(item, ii, this.props); + + this._indicesToKeys.set(ii, key); + if (stickyIndicesFromProps.has(ii + stickyOffset)) { +- this.pushOrUnshift(stickyHeaderIndices, (cells.length)); ++ this.pushOrUnshift(stickyHeaderIndices, cells.length); + } + + const shouldListenForLayout = +@@ -861,15 +948,19 @@ class VirtualizedList extends StateSafePureComponent { + props: Props, + ): {first: number, last: number} { + const itemCount = props.getItemCount(props.data); +- const last = Math.min(itemCount - 1, cells.last); ++ const lastPossibleCellIndex = itemCount - 1; + ++ // Constraining `last` may significantly shrink the window. Adjust `first` ++ // to expand the window if the new `last` results in a new window smaller ++ // than the number of cells rendered per batch. + const maxToRenderPerBatch = maxToRenderPerBatchOrDefault( + props.maxToRenderPerBatch, + ); ++ const maxFirst = Math.max(0, lastPossibleCellIndex - maxToRenderPerBatch); + + return { +- first: clamp(0, itemCount - 1 - maxToRenderPerBatch, cells.first), +- last, ++ first: clamp(0, cells.first, maxFirst), ++ last: Math.min(lastPossibleCellIndex, cells.last), + }; + } + +@@ -891,15 +982,14 @@ class VirtualizedList extends StateSafePureComponent { + _getSpacerKey = (isVertical: boolean): string => + isVertical ? 'height' : 'width'; + +- _keyExtractor( ++ static _keyExtractor( + item: Item, + index: number, + props: { + keyExtractor?: ?(item: Item, index: number) => string, + ... + }, +- // $FlowFixMe[missing-local-annot] +- ) { ++ ): string { + if (props.keyExtractor != null) { + return props.keyExtractor(item, index); + } +@@ -945,6 +1035,10 @@ class VirtualizedList extends StateSafePureComponent { + cellKey={this._getCellKey() + '-header'} + key="$header"> + { + style: inversionStyle + ? [inversionStyle, this.props.style] + : this.props.style, ++ maintainVisibleContentPosition: ++ this.props.maintainVisibleContentPosition != null ++ ? { ++ ...this.props.maintainVisibleContentPosition, ++ // Adjust index to account for ListHeaderComponent. ++ minIndexForVisible: ++ this.props.maintainVisibleContentPosition.minIndexForVisible + ++ (this.props.ListHeaderComponent ? 1 : 0), ++ } ++ : undefined, + }; + + this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1; +@@ -1255,11 +1359,10 @@ class VirtualizedList extends StateSafePureComponent { + _defaultRenderScrollComponent = props => { + const onRefresh = props.onRefresh; + const inversionStyle = this.props.inverted +- ? this.props.horizontal +- ? styles.rowReverse +- : styles.columnReverse +- : null; +- ++ ? this.props.horizontal ++ ? styles.rowReverse ++ : styles.columnReverse ++ : null; + if (this._isNestedWithSameOrientation()) { + // $FlowFixMe[prop-missing] - Typing ReactNativeComponent revealed errors + return ; +@@ -1542,8 +1645,12 @@ class VirtualizedList extends StateSafePureComponent { + onStartReachedThreshold, + onEndReached, + onEndReachedThreshold, +- initialScrollIndex, + } = this.props; ++ // If we have any pending scroll updates it means that the scroll metrics ++ // are out of date and we should not call any of the edge reached callbacks. ++ if (this.state.pendingScrollUpdateCount > 0) { ++ return; ++ } + const {contentLength, visibleLength, offset} = this._scrollMetrics; + let distanceFromStart = offset; + let distanceFromEnd = contentLength - visibleLength - offset; +@@ -1595,14 +1702,8 @@ class VirtualizedList extends StateSafePureComponent { + isWithinStartThreshold && + this._scrollMetrics.contentLength !== this._sentStartForContentLength + ) { +- // On initial mount when using initialScrollIndex the offset will be 0 initially +- // and will trigger an unexpected onStartReached. To avoid this we can use +- // timestamp to differentiate between the initial scroll metrics and when we actually +- // received the first scroll event. +- if (!initialScrollIndex || this._scrollMetrics.timestamp !== 0) { +- this._sentStartForContentLength = this._scrollMetrics.contentLength; +- onStartReached({distanceFromStart}); +- } ++ this._sentStartForContentLength = this._scrollMetrics.contentLength; ++ onStartReached({distanceFromStart}); + } + + // If the user scrolls away from the start or end and back again, +@@ -1729,6 +1830,11 @@ class VirtualizedList extends StateSafePureComponent { + visibleLength, + zoomScale, + }; ++ if (this.state.pendingScrollUpdateCount > 0) { ++ this.setState(state => ({ ++ pendingScrollUpdateCount: state.pendingScrollUpdateCount - 1, ++ })); ++ } + this._updateViewableItems(this.props, this.state.cellsAroundViewport); + if (!this.props) { + return; +@@ -1844,6 +1950,7 @@ class VirtualizedList extends StateSafePureComponent { + const cellsAroundViewport = this._adjustCellsAroundViewport( + props, + state.cellsAroundViewport, ++ state.pendingScrollUpdateCount, + ); + const renderMask = VirtualizedList._createRenderMask( + props, +@@ -1874,7 +1981,7 @@ class VirtualizedList extends StateSafePureComponent { + return { + index, + item, +- key: this._keyExtractor(item, index, props), ++ key: VirtualizedList._keyExtractor(item, index, props), + isViewable, + }; + }; +@@ -1935,13 +2042,12 @@ class VirtualizedList extends StateSafePureComponent { + inLayout?: boolean, + ... + } => { +- const {data, getItem, getItemCount, getItemLayout} = props; ++ const {data, getItemCount, getItemLayout} = props; + invariant( + index >= 0 && index < getItemCount(data), + 'Tried to get frame for out of range index ' + index, + ); +- const item = getItem(data, index); +- const frame = this._frames[this._keyExtractor(item, index, props)]; ++ const frame = this._frames[VirtualizedList._getItemKey(props, index)]; + if (!frame || frame.index !== index) { + if (getItemLayout) { + /* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment +@@ -1976,11 +2082,8 @@ class VirtualizedList extends StateSafePureComponent { + // where it is. + if ( + focusedCellIndex >= itemCount || +- this._keyExtractor( +- props.getItem(props.data, focusedCellIndex), +- focusedCellIndex, +- props, +- ) !== this._lastFocusedCellKey ++ VirtualizedList._getItemKey(props, focusedCellIndex) !== ++ this._lastFocusedCellKey + ) { + return []; + } +@@ -2021,6 +2124,11 @@ class VirtualizedList extends StateSafePureComponent { + props: FrameMetricProps, + cellsAroundViewport: {first: number, last: number}, + ) { ++ // If we have any pending scroll updates it means that the scroll metrics ++ // are out of date and we should not call any of the visibility callbacks. ++ if (this.state.pendingScrollUpdateCount > 0) { ++ return; ++ } + this._viewabilityTuples.forEach(tuple => { + tuple.viewabilityHelper.onUpdate( + props, diff --git a/src/CONST.ts b/src/CONST.ts index de902931ffd8..a840699f1c46 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -869,14 +869,19 @@ const CONST = { RECOVERY_CODE_LENGTH: 8, KEYBOARD_TYPE: { - PHONE_PAD: 'phone-pad', - NUMBER_PAD: 'number-pad', - DECIMAL_PAD: 'decimal-pad', VISIBLE_PASSWORD: 'visible-password', - EMAIL_ADDRESS: 'email-address', ASCII_CAPABLE: 'ascii-capable', + }, + + INPUT_MODE: { + NONE: 'none', + TEXT: 'text', + DECIMAL: 'decimal', + NUMERIC: 'numeric', + TEL: 'tel', + SEARCH: 'search', + EMAIL: 'email', URL: 'url', - DEFAULT: 'default', }, YOUR_LOCATION_TEXT: 'Your Location', @@ -2698,13 +2703,13 @@ const CONST = { BUTTON: 'button', LINK: 'link', MENUITEM: 'menuitem', - TEXT: 'text', + TEXT: 'presentation', RADIO: 'radio', - IMAGEBUTTON: 'imagebutton', + IMAGEBUTTON: 'img button', CHECKBOX: 'checkbox', SWITCH: 'switch', - ADJUSTABLE: 'adjustable', - IMAGE: 'image', + ADJUSTABLE: 'slider', + IMAGE: 'img', }, TRANSLATION_KEYS: { ATTACHMENT: 'common.attachment', diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index 5899e68bedb3..43fd5e6a1b98 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -50,11 +50,11 @@ function AmountTextInput(props) { ref={props.forwardedRef} value={props.formattedAmount} placeholder={props.placeholder} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + inputMode={CONST.INPUT_MODE.NUMERIC} blurOnSubmit={false} selection={props.selection} onSelectionChange={props.onSelectionChange} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} onKeyPress={props.onKeyPress} /> ); diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js index fd6c3d358a33..1e2d18bc4691 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js @@ -58,7 +58,7 @@ function BaseAnchorForAttachmentsOnly(props) { onPressOut={props.onPressOut} onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} accessibilityLabel={fileName} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > (linkRef = el)} style={StyleSheet.flatten([style, defaultTextStyle])} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.LINK} + role={CONST.ACCESSIBILITY_ROLE.LINK} hrefAttrs={{ rel, target: isEmail || !linkProps.href ? '_self' : target, diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 53a8606c927f..38f70057be61 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -61,8 +61,7 @@ function CarouselItem({item, isFocused, onPress}) { onPress={() => setIsHidden(!isHidden)} > {isHidden ? translate('moderation.revealMessage') : translate('moderation.hideMessage')} @@ -81,7 +80,7 @@ function CarouselItem({item, isFocused, onPress}) { {children} diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js index 307dbe8e9ddb..d88eb81506ca 100755 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js @@ -27,7 +27,7 @@ function AttachmentViewImage({source, file, isAuthTokenRequired, loadComplete, o onPress={onPress} disabled={loadComplete} style={[styles.flex1, styles.flexRow, styles.alignSelfStretch]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={file.name || translate('attachmentView.unknownFilename')} > {children} diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js index cb1190fa1fdd..8b29d8d5ba3d 100755 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js @@ -35,7 +35,7 @@ function AttachmentViewImage({source, file, isAuthTokenRequired, isFocused, isUs onPress={onPress} disabled={loadComplete} style={[styles.flex1, styles.flexRow, styles.alignSelfStretch]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={file.name || translate('attachmentView.unknownFilename')} > {children} diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 4b8ddd45aa95..5e414486cc70 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -88,10 +88,7 @@ function Avatar(props) { const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon || Expensicons.FallbackAvatar; return ( - + {_.isFunction(props.source) || (imageError && _.isFunction(fallbackAvatar)) ? ( runOnUI(sliderOnPress)(e.nativeEvent.locationX)} accessibilityLabel="slider" - accessibilityRole={CONST.ACCESSIBILITY_ROLE.ADJUSTABLE} + role={CONST.ACCESSIBILITY_ROLE.ADJUSTABLE} > showActorDetails(props.report, props.shouldEnableDetailPageNavigation)} accessibilityLabel={title} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {shouldShowSubscriptAvatar ? ( ReportUtils.navigateToDetailsPage(props.report)} style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]} accessibilityLabel={title} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {headerView} diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 871d967e23dc..87bd382e806b 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -264,7 +264,7 @@ class AvatarWithImagePicker extends React.Component { this.setState((prev) => ({isMenuVisible: !prev.isMenuVisible}))} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={this.props.translate('avatarWithImagePicker.editImage')} disabled={this.state.isAvatarCropModalOpen} ref={this.anchorRef} diff --git a/src/components/Badge.js b/src/components/Badge.js index 0a6b72201655..49b330ae37b2 100644 --- a/src/components/Badge.js +++ b/src/components/Badge.js @@ -59,8 +59,9 @@ function Badge(props) { diff --git a/src/components/BigNumberPad.js b/src/components/BigNumberPad.js index ecbde3a5afe6..451b8fc3e0bf 100644 --- a/src/components/BigNumberPad.js +++ b/src/components/BigNumberPad.js @@ -16,14 +16,14 @@ const propTypes = { longPressHandlerStateChanged: PropTypes.func, /** Used to locate this view from native classes. */ - nativeID: PropTypes.string, + id: PropTypes.string, ...withLocalizePropTypes, }; const defaultProps = { longPressHandlerStateChanged: () => {}, - nativeID: 'numPadView', + id: 'numPadView', }; const padNumbers = [ @@ -59,7 +59,7 @@ function BigNumberPad(props) { return ( {_.map(padNumbers, (row, rowIndex) => ( {this.renderContent()} diff --git a/src/components/Checkbox.js b/src/components/Checkbox.js index 51b9212133a4..5734ad2fed26 100644 --- a/src/components/Checkbox.js +++ b/src/components/Checkbox.js @@ -93,8 +93,8 @@ function Checkbox(props) { ref={props.forwardedRef} style={[StyleUtils.getCheckboxPressableStyle(props.containerBorderRadius + 2), props.style]} // to align outline on focus, border-radius of pressable should be 2px more than Checkbox onKeyDown={handleSpaceKey} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.CHECKBOX} - accessibilityState={{checked: props.isChecked}} + role={CONST.ACCESSIBILITY_ROLE.CHECKBOX} + ariaChecked={props.isChecked} accessibilityLabel={props.accessibilityLabel} pressDimmingValue={1} > diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 4bffadecb733..86dba1d2a932 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -108,7 +108,7 @@ function CheckboxWithLabel(props) { accessibilityLabel={props.accessibilityLabel || props.label} /> {this.props.title} diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index aca2a9d06f7a..08d3e45e671f 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -4,6 +4,7 @@ import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; +import styles from '@styles/styles'; import themeColors from '@styles/themes/default'; const propTypes = { @@ -103,7 +104,7 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC return maxLines; }, [isComposerFullSize, maxLines]); - const styles = useMemo(() => { + const composerStyles = useMemo(() => { StyleSheet.flatten(props.style); }, [props.style]); @@ -114,16 +115,15 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC ref={setTextInputRef} onContentSizeChange={(e) => ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} rejectResponderTermination={false} - textAlignVertical="center" // Setting a really high number here fixes an issue with the `maxNumberOfLines` prop on TextInput, where on Android the text input would collapse to only one line, // when it should actually expand to the container (https://github.com/Expensify/App/issues/11694#issuecomment-1560520670) // @Szymon20000 is working on fixing this (android-only) issue in the in the upstream PR (https://github.com/facebook/react-native/pulls?q=is%3Apr+is%3Aopen+maxNumberOfLines) // TODO: remove this comment once upstream PR is merged and available in a future release maxNumberOfLines={maxNumberOfLines} - style={styles} + style={[composerStyles, styles.verticalAlignMiddle]} /* eslint-disable-next-line react/jsx-props-no-spreading */ {...props} - editable={!isDisabled} + readOnly={isDisabled} /> ); } diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index e5dab3756594..a1b8c1a4ffe6 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -4,6 +4,7 @@ import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; +import styles from '@styles/styles'; import themeColors from '@styles/themes/default'; const propTypes = { @@ -103,7 +104,7 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC return maxLines; }, [isComposerFullSize, maxLines]); - const styles = useMemo(() => { + const composerStyles = useMemo(() => { StyleSheet.flatten(props.style); }, [props.style]); @@ -118,13 +119,12 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC ref={setTextInputRef} onContentSizeChange={(e) => ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} rejectResponderTermination={false} - textAlignVertical="center" smartInsertDelete={false} maxNumberOfLines={maxNumberOfLines} - style={styles} + style={[composerStyles, styles.verticalAlignMiddle]} /* eslint-disable-next-line react/jsx-props-no-spreading */ {...propsToPass} - editable={!isDisabled} + readOnly={isDisabled} /> ); } diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index 924705e0fd39..d5d905f7d639 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -467,7 +467,7 @@ function Composer({ /* eslint-disable-next-line react/jsx-props-no-spreading */ {...props} onSelectionChange={addCursorPositionToSelectionChange} - numberOfLines={numberOfLines} + rows={numberOfLines} disabled={isDisabled} onKeyPress={handleKeyPress} onFocus={(e) => { diff --git a/src/components/CurrencySymbolButton.js b/src/components/CurrencySymbolButton.js index 695cb2bc10c8..ca7816a9f117 100644 --- a/src/components/CurrencySymbolButton.js +++ b/src/components/CurrencySymbolButton.js @@ -22,7 +22,7 @@ function CurrencySymbolButton({onCurrencyButtonPress, currencySymbol}) { {currencySymbol} diff --git a/src/components/DatePicker/datepickerPropTypes.js b/src/components/DatePicker/datepickerPropTypes.js index 26424f2d8283..02d11806b8af 100644 --- a/src/components/DatePicker/datepickerPropTypes.js +++ b/src/components/DatePicker/datepickerPropTypes.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import {defaultProps as defaultFieldPropTypes, propTypes as fieldPropTypes} from '@components/TextInput/baseTextInputPropTypes'; +import {defaultProps as defaultFieldPropTypes, propTypes as fieldPropTypes} from '@components/TextInput/BaseTextInput/baseTextInputPropTypes'; import CONST from '@src/CONST'; const propTypes = { diff --git a/src/components/DatePicker/index.android.js b/src/components/DatePicker/index.android.js index 3b2a6ec3e650..93a9f521ef2c 100644 --- a/src/components/DatePicker/index.android.js +++ b/src/components/DatePicker/index.android.js @@ -46,7 +46,7 @@ function DatePicker({value, defaultValue, label, placeholder, errorText, contain setIsHighlighted(false)} style={({pressed}) => [StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), styles.categoryShortcutButton, isHighlighted && styles.emojiItemHighlighted]} accessibilityLabel={`emojiPicker.headers.${props.code}`} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > { outerStyle={StyleUtils.getOuterModalStyle(windowHeight, props.viewportOffsetTop)} innerContainerStyle={styles.popoverInnerContainer} avoidKeyboard + shouldUseTargetLocation > {({hovered, pressed}) => ( diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js index 8a5a66444fda..63a6c33a437f 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js @@ -38,6 +38,7 @@ function EmojiPickerButtonDropdown(props) { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, shiftVertical: 4, + shouldUseTargetLocation: true, }); }; @@ -48,9 +49,9 @@ function EmojiPickerButtonDropdown(props) { style={styles.emojiPickerButtonDropdown} disabled={props.isDisabled} onPress={onPress} - nativeID="emojiDropdownButton" + id="emojiDropdownButton" accessibilityLabel="statusEmoji" - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {({hovered, pressed}) => ( diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 7dc53e958849..0ee12579733d 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -471,15 +471,18 @@ function EmojiPickerMenu(props) { const overflowLimit = Math.floor(height / CONST.EMOJI_PICKER_ITEM_HEIGHT) * 8; return ( {translate('common.noResultsFound')}} + ListEmptyComponent={() => {translate('common.noResultsFound')}} /> 0} /> diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index 90f7f966172f..451e2e939a09 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -100,7 +100,7 @@ class EmojiPickerMenuItem extends PureComponent { styles.emojiItem, ]} accessibilityLabel={this.props.emoji} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {this.props.emoji} diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js index 099adf620af7..6ebaa3391992 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js @@ -77,7 +77,7 @@ class EmojiPickerMenuItem extends PureComponent { styles.emojiItem, ]} accessibilityLabel={this.props.emoji} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {this.props.emoji} diff --git a/src/components/EmojiPicker/EmojiSkinToneList.js b/src/components/EmojiPicker/EmojiSkinToneList.js index edb8bf49e77f..29c39c335b14 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.js +++ b/src/components/EmojiPicker/EmojiSkinToneList.js @@ -48,7 +48,7 @@ function EmojiSkinToneList(props) { onPress={toggleIsSkinToneListVisible} style={[styles.flexRow, styles.alignSelfCenter, styles.justifyContentStart, styles.alignItemsCenter]} accessibilityLabel={props.translate('emojiPicker.skinTonePickerLabel')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {currentSkinTone.code} diff --git a/src/components/FloatingActionButton.js b/src/components/FloatingActionButton.js index 22f88cc53f59..d8a5a0256e62 100644 --- a/src/components/FloatingActionButton.js +++ b/src/components/FloatingActionButton.js @@ -90,7 +90,7 @@ class FloatingActionButton extends PureComponent { } }} accessibilityLabel={this.props.accessibilityLabel} - accessibilityRole={this.props.accessibilityRole} + role={this.props.role} pressDimmingValue={1} onPress={(e) => { // Drop focus to avoid blue focus ring. diff --git a/src/components/FormElement.js b/src/components/FormElement.js index cc9423a6147f..d929ddb5f2e4 100644 --- a/src/components/FormElement.js +++ b/src/components/FormElement.js @@ -4,7 +4,7 @@ import * as ComponentUtils from '@libs/ComponentUtils'; const FormElement = forwardRef((props, ref) => ( diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index f6d37f661252..8461f714373b 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -76,7 +76,7 @@ function ImageRenderer(props) { ReportUtils.isArchivedRoom(report), ) } - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('accessibilityHints.viewAttachment')} > { onPressIn={props.onPressIn} onPressOut={props.onPressOut} onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} accessibilityLabel={props.translate('accessibilityHints.prestyledText')} > diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index 67e8790560dc..6a8f630d1e78 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -76,7 +76,7 @@ function HeaderWithBackButton({ onBackButtonPress(); }} style={[styles.touchableButtonImage]} - accessibilityRole="button" + role="button" accessibilityLabel={translate('common.back')} > Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID))))} style={[styles.touchableButtonImage]} - accessibilityRole="button" + role="button" accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')} > ( // eslint-disable-next-line react/jsx-props-no-spreading {...props} ref={ref} + contentContainerStyle={styles.justifyContentEnd} CellRendererComponent={CellRendererComponent} /** * To achieve absolute positioning and handle overflows for list items, the property must be disabled diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index ebd00d4706d8..0c5383054d04 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -179,7 +179,7 @@ function LHNOptionsList({ stickySectionHeadersEnabled={false} renderItem={renderItem} getItemLayout={getItemLayout} - initialNumToRender={5} + initialNumToRender={20} maxToRenderPerBatch={5} windowSize={5} /> diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index 1a5bcf5ffb60..685b8763781d 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -205,7 +205,7 @@ function OptionRowLHN(props) { props.isFocused ? styles.sidebarLinkActive : null, (hovered || isContextMenuActive) && !props.isFocused ? props.hoverStyle : null, ]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} needsOffscreenAlphaCompositing={props.optionItem.icons.length >= 2} > diff --git a/src/components/LocationErrorMessage/BaseLocationErrorMessage.js b/src/components/LocationErrorMessage/BaseLocationErrorMessage.js index b5114acaa36b..5880d3475650 100644 --- a/src/components/LocationErrorMessage/BaseLocationErrorMessage.js +++ b/src/components/LocationErrorMessage/BaseLocationErrorMessage.js @@ -62,7 +62,7 @@ function BaseLocationErrorMessage({onClose, onAllowLocationLinkPress, locationEr onPress={onClose} onMouseDown={(e) => e.preventDefault()} style={[styles.touchableButtonImage]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('common.close')} > diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 8119248c760d..978b101a6cce 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -319,7 +319,6 @@ function MagicCodeInput(props) { value={input} hideFocusedState autoComplete={index === 0 ? props.autoComplete : 'off'} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} onChangeText={(value) => { // Do not run when the event comes from an input that is // not currently being responsible for the input, this is @@ -337,7 +336,7 @@ function MagicCodeInput(props) { selectionColor="transparent" textInputContainerStyles={[styles.borderNone]} inputStyle={[styles.inputTransparent]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} /> diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 08535f1724fb..103d063f9024 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -184,7 +184,7 @@ const MenuItem = React.forwardRef((props, ref) => { ]} disabled={props.disabled} ref={ref} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.MENUITEM} + role={CONST.ACCESSIBILITY_ROLE.MENUITEM} accessibilityLabel={props.title ? props.title.toString() : ''} > {({pressed}) => ( diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js index 6ed3b16c676d..bf1fdc8ee7de 100644 --- a/src/components/Modal/BaseModal.js +++ b/src/components/Modal/BaseModal.js @@ -183,6 +183,7 @@ function BaseModal({ onModalShow={handleShowModal} propagateSwipe={propagateSwipe} onModalHide={hideModal} + onModalWillShow={() => ComposerFocusManager.resetReadyToFocus()} onDismiss={handleDismissModal} onSwipeComplete={onClose} swipeDirection={swipeDirection} diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index 85b6f7995693..209540189b69 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -227,8 +227,7 @@ function MultipleAvatars(props) { ]} > {`+${avatars.length - props.maxAvatarsInRow}`} @@ -293,8 +292,7 @@ function MultipleAvatars(props) { {`+${props.icons.length - 1}`} diff --git a/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js b/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js index d784f439dfee..f040a99450f1 100644 --- a/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js +++ b/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js @@ -76,7 +76,7 @@ function YearPickerModal(props) { textInputValue={searchText} textInputMaxLength={4} onChangeText={(text) => setSearchText(text.replace(CONST.REGEX.NON_NUMERIC, '').trim())} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + inputMode={CONST.INPUT_MODE.NUMERIC} headerMessage={headerMessage} sections={sections} onSelectRow={(option) => props.onYearChange(option.value)} diff --git a/src/components/NewDatePicker/CalendarPicker/index.js b/src/components/NewDatePicker/CalendarPicker/index.js index 4b17766feb17..58ab42a9b56a 100644 --- a/src/components/NewDatePicker/CalendarPicker/index.js +++ b/src/components/NewDatePicker/CalendarPicker/index.js @@ -213,7 +213,7 @@ class CalendarPicker extends React.PureComponent { onPress={() => this.onDayPressed(day)} style={styles.calendarDayRoot} accessibilityLabel={day ? day.toString() : undefined} - focusable={Boolean(day)} + tabIndex={day ? 0 : -1} accessible={Boolean(day)} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} > diff --git a/src/components/NewDatePicker/index.js b/src/components/NewDatePicker/index.js index f03b4e2cb796..351e5034cfb4 100644 --- a/src/components/NewDatePicker/index.js +++ b/src/components/NewDatePicker/index.js @@ -6,7 +6,7 @@ import {View} from 'react-native'; import InputWrapper from '@components/Form/InputWrapper'; import * as Expensicons from '@components/Icon/Expensicons'; import TextInput from '@components/TextInput'; -import {propTypes as baseTextInputPropTypes, defaultProps as defaultBaseTextInputPropTypes} from '@components/TextInput/baseTextInputPropTypes'; +import {propTypes as baseTextInputPropTypes, defaultProps as defaultBaseTextInputPropTypes} from '@components/TextInput/BaseTextInput/baseTextInputPropTypes'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import styles from '@styles/styles'; import CONST from '@src/CONST'; @@ -75,7 +75,7 @@ function NewDatePicker({containerStyles, defaultValue, disabled, errorText, inpu icon={Expensicons.Calendar} label={label} accessibilityLabel={label} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} value={value || selectedDate || ''} placeholder={placeholder || translate('common.dateFormat')} errorText={errorText} @@ -83,7 +83,7 @@ function NewDatePicker({containerStyles, defaultValue, disabled, errorText, inpu textInputContainerStyles={[styles.borderColorFocus]} inputStyle={[styles.pointerEventsNone]} disabled={disabled} - editable={false} + readOnly /> diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index c2f272663c20..1a60cc0280b6 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -188,7 +188,7 @@ function OptionRow(props) { props.isSelected && props.highlightSelected && styles.optionRowSelected, ]} accessibilityLabel={props.option.text} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} hoverDimmingValue={1} hoverStyle={props.hoverStyle} needsOffscreenAlphaCompositing={lodashGet(props.option, 'icons.length', 0) >= 2} @@ -263,7 +263,7 @@ function OptionRow(props) { props.onSelectedStatePressed(props.option)} disabled={isDisabled} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.CHECKBOX} + role={CONST.ACCESSIBILITY_ROLE.CHECKBOX} accessibilityLabel={CONST.ACCESSIBILITY_ROLE.CHECKBOX} > diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index fb312125efc0..8c480c27f20f 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -378,7 +378,7 @@ class BaseOptionsSelector extends Component { value={this.props.value} label={this.props.textInputLabel} accessibilityLabel={this.props.textInputLabel} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} onChangeText={this.updateSearchValue} errorText={this.state.errorMessage} onSubmitEditing={this.selectFocusedOption} diff --git a/src/components/OptionsSelector/optionsSelectorPropTypes.js b/src/components/OptionsSelector/optionsSelectorPropTypes.js index 37c15b6e3ae2..94aab8fac5f6 100644 --- a/src/components/OptionsSelector/optionsSelectorPropTypes.js +++ b/src/components/OptionsSelector/optionsSelectorPropTypes.js @@ -40,8 +40,8 @@ const propTypes = { /** Label to display for the text input */ textInputLabel: PropTypes.string, - /** Optional keyboard type for the input */ - keyboardType: PropTypes.string, + /** Optional input mode precedence over keyboardType */ + inputMode: PropTypes.string, /** Optional placeholder text for the selector */ placeholderText: PropTypes.string, @@ -144,7 +144,7 @@ const defaultProps = { onSelectRow: undefined, textInputLabel: '', placeholderText: '', - keyboardType: 'default', + inputMode: CONST.INPUT_MODE.TEXT, selectedOptions: [], headerMessage: '', canSelectMultipleOptions: false, diff --git a/src/components/PDFView/PDFPasswordForm.js b/src/components/PDFView/PDFPasswordForm.js index 2105bc13cb00..e495057dec46 100644 --- a/src/components/PDFView/PDFPasswordForm.js +++ b/src/components/PDFView/PDFPasswordForm.js @@ -122,7 +122,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat ref={textInputRef} label={translate('common.password')} accessibilityLabel={translate('common.password')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} /** * This is a workaround to bypass Safari's autofill odd behaviour. * This tricks the browser not to fill the username somewhere else and still fill the password correctly. @@ -131,7 +131,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat autoCorrect={false} textContentType="password" onChangeText={updatePassword} - returnKeyType="go" + enterKeyHint="done" onSubmitEditing={submitPassword} errorText={errorText} onFocus={() => onPasswordFieldFocused(true)} diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index af153d69d166..fd01176d9fb2 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -274,7 +274,7 @@ class PDFView extends Component { return ( {this.renderPDFView()} diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 7c6514c1e035..c8636b2dc50f 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -180,7 +180,7 @@ class PDFView extends Component { {this.renderPDFView()} diff --git a/src/components/ParentNavigationSubtitle.js b/src/components/ParentNavigationSubtitle.js index b29794e62856..5b4825392719 100644 --- a/src/components/ParentNavigationSubtitle.js +++ b/src/components/ParentNavigationSubtitle.js @@ -41,7 +41,7 @@ function ParentNavigationSubtitle(props) { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(props.parentReportID)); }} accessibilityLabel={translate('threads.parentNavigationSummary', {rootReportName, workspaceName})} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.LINK} + role={CONST.ACCESSIBILITY_ROLE.LINK} style={[...props.pressableStyles]} > - {props.label && ( - - {props.label} - - )} + {props.label && {props.label}} Report.togglePinnedState(props.report.reportID, props.report.isPinned))} style={[styles.touchableButtonImage]} - accessibilityState={{checked: props.report.isPinned}} + ariaChecked={props.report.isPinned} accessibilityLabel={props.report.isPinned ? props.translate('common.unPin') : props.translate('common.pin')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > { let horizontalConstraint; switch (props.anchorAlignment.horizontal) { @@ -103,13 +111,18 @@ function PopoverWithMeasuredContent(props) { break; case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT: default: - horizontalConstraint = {left: props.anchorPosition.horizontal}; + horizontalConstraint = {left: clickedTargetLocation.horizontal}; } let verticalConstraint; + const anchorLocationVertical = clickedTargetLocation.vertical; + switch (props.anchorAlignment.vertical) { case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM: - verticalConstraint = {top: props.anchorPosition.vertical - popoverHeight}; + if (!anchorLocationVertical) { + break; + } + verticalConstraint = {top: anchorLocationVertical - popoverHeight}; break; case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.CENTER: verticalConstraint = { @@ -125,7 +138,7 @@ function PopoverWithMeasuredContent(props) { ...horizontalConstraint, ...verticalConstraint, }; - }, [props.anchorPosition, props.anchorAlignment, popoverWidth, popoverHeight]); + }, [props.anchorPosition, props.anchorAlignment, clickedTargetLocation.vertical, clickedTargetLocation.horizontal, popoverWidth, popoverHeight]); const horizontalShift = computeHorizontalShift(adjustedAnchorPosition.left, popoverWidth, windowWidth); const verticalShift = computeVerticalShift(adjustedAnchorPosition.top, popoverHeight, windowHeight); diff --git a/src/components/Pressable/PressableWithDelayToggle.tsx b/src/components/Pressable/PressableWithDelayToggle.tsx index 316adc25076d..c402710d71bd 100644 --- a/src/components/Pressable/PressableWithDelayToggle.tsx +++ b/src/components/Pressable/PressableWithDelayToggle.tsx @@ -108,7 +108,7 @@ function PressableWithDelayToggle( shouldRender > props.onPress()} style={[styles.flexRow, styles.flexWrap, styles.flexShrink1, styles.alignItemsCenter]} diff --git a/src/components/Reactions/AddReactionBubble.js b/src/components/Reactions/AddReactionBubble.js index 4e12ace9cc6c..653236f35831 100644 --- a/src/components/Reactions/AddReactionBubble.js +++ b/src/components/Reactions/AddReactionBubble.js @@ -98,7 +98,7 @@ function AddReactionBubble(props) { e.preventDefault(); }} accessibilityLabel={props.translate('emojiReactions.addReactionTooltip')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} // disable dimming pressDimmingValue={1} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} diff --git a/src/components/Reactions/EmojiReactionBubble.js b/src/components/Reactions/EmojiReactionBubble.js index a61923e49860..822b15711c50 100644 --- a/src/components/Reactions/EmojiReactionBubble.js +++ b/src/components/Reactions/EmojiReactionBubble.js @@ -82,7 +82,7 @@ function EmojiReactionBubble(props) { // Prevent text input blur when emoji reaction is left clicked e.preventDefault(); }} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={props.emojiCodes.join('')} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} > diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index ff4b94443940..d5f9f7ed06b8 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -84,7 +84,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report.reportID, imageSource); Navigation.navigate(route); }} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('accessibilityHints.viewAttachment')} > {receiptImageComponent} diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 00a4526b382f..e735210178ef 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -192,7 +192,7 @@ function ReportPreview(props) { onPressOut={() => ControlSelection.unblock()} onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)} style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} - accessibilityRole="button" + role="button" accessibilityLabel={props.translate('iou.viewDetails')} > diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index af5b1e25f2a9..b31de0d22f4c 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -105,7 +105,7 @@ function TaskPreview(props) { onPressOut={() => ControlSelection.unblock()} onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)} style={[styles.flexRow, styles.justifyContentBetween]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={props.translate('task.task')} > diff --git a/src/components/ReportHeaderSkeletonView.js b/src/components/ReportHeaderSkeletonView.js index 6d2a8e343e3b..1b183fa9604c 100644 --- a/src/components/ReportHeaderSkeletonView.js +++ b/src/components/ReportHeaderSkeletonView.js @@ -32,7 +32,7 @@ function ReportHeaderSkeletonView(props) { {}} style={[styles.LHNToggle]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={props.translate('common.back')} > diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.js index 6f191f82ba35..13d07bb0278a 100644 --- a/src/components/RoomHeaderAvatars.js +++ b/src/components/RoomHeaderAvatars.js @@ -39,7 +39,7 @@ function RoomHeaderAvatars(props) { + {_.map(iconsToDisplay, (icon, index) => ( onSelectRow(item)} disabled={isDisabled} accessibilityLabel={item.text} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 2a7947733a9e..e3ba0dbd7c2f 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -40,7 +40,7 @@ function BaseSelectionList({ textInputPlaceholder = '', textInputValue = '', textInputMaxLength, - keyboardType = CONST.KEYBOARD_TYPE.DEFAULT, + inputMode = CONST.INPUT_MODE.TEXT, onChangeText, initiallyFocusedOptionKey = '', onScroll, @@ -389,12 +389,12 @@ function BaseSelectionList({ }} label={textInputLabel} accessibilityLabel={textInputLabel} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} value={textInputValue} placeholder={textInputPlaceholder} maxLength={textInputMaxLength} onChangeText={onChangeText} - keyboardType={keyboardType} + inputMode={inputMode} selectTextOnFocus spellCheck={false} onSubmitEditing={selectFocusedOption} @@ -417,7 +417,7 @@ function BaseSelectionList({ style={[styles.peopleRow, styles.userSelectNone, styles.ph5, styles.pb3]} onPress={selectAllRow} accessibilityLabel={translate('workspace.people.selectAll')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role="button" accessibilityState={{checked: flattenedSections.allSelected}} disabled={flattenedSections.allOptions.length === flattenedSections.disabledOptionsIndexes.length} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index c3bae89eaba2..5b95f7dd0cbf 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -138,8 +138,8 @@ const propTypes = { /** Callback to fire when the text input changes */ onChangeText: PropTypes.func, - /** Keyboard type for the text input */ - keyboardType: PropTypes.string, + /** Input mode for the text input */ + inputMode: PropTypes.string, /** Item `keyForList` to focus initially */ initiallyFocusedOptionKey: PropTypes.string, diff --git a/src/components/SignInButtons/GoogleSignIn/index.website.js b/src/components/SignInButtons/GoogleSignIn/index.website.js index 7f3a3019c318..d65af124bfe8 100644 --- a/src/components/SignInButtons/GoogleSignIn/index.website.js +++ b/src/components/SignInButtons/GoogleSignIn/index.website.js @@ -5,6 +5,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import styles from '@styles/styles'; import * as Session from '@userActions/Session'; import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; const propTypes = { /** Whether we're rendering in the Desktop Flow, if so show a different button. */ @@ -74,7 +75,7 @@ function GoogleSignIn({translate, isDesktopFlow}) {
@@ -82,7 +83,7 @@ function GoogleSignIn({translate, isDesktopFlow}) {
diff --git a/src/components/SignInButtons/IconButton.js b/src/components/SignInButtons/IconButton.js index 0d18779ea9ba..ce932b875542 100644 --- a/src/components/SignInButtons/IconButton.js +++ b/src/components/SignInButtons/IconButton.js @@ -37,7 +37,7 @@ function IconButton({onPress, translate, provider}) { onSelectOption(option)} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityState={{checked: selectedOptionKey === option.key}} aria-checked={selectedOptionKey === option.key} accessibilityLabel={option.label} diff --git a/src/components/Switch.js b/src/components/Switch.js index 755cd60f2597..c5adbbef61da 100644 --- a/src/components/Switch.js +++ b/src/components/Switch.js @@ -38,9 +38,8 @@ function Switch(props) { style={[styles.switchTrack, !props.isOn && styles.switchInactive]} onPress={() => props.onToggle(!props.isOn)} onLongPress={() => props.onToggle(!props.isOn)} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.SWITCH} - accessibilityState={{checked: props.isOn}} - aria-checked={props.isOn} + role={CONST.ACCESSIBILITY_ROLE.SWITCH} + ariaChecked={props.isOn} accessibilityLabel={props.accessibilityLabel} // disable hover dim for switch hoverDimmingValue={1} diff --git a/src/components/TextInput/baseTextInputPropTypes.js b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js similarity index 100% rename from src/components/TextInput/baseTextInputPropTypes.js rename to src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput/index.js similarity index 92% rename from src/components/TextInput/BaseTextInput.js rename to src/components/TextInput/BaseTextInput/index.js index c9b1944b5b81..e643c6ff6b4f 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput/index.js @@ -10,9 +10,10 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import RNTextInput from '@components/RNTextInput'; import SwipeInterceptPanResponder from '@components/SwipeInterceptPanResponder'; import Text from '@components/Text'; +import * as styleConst from '@components/TextInput/styleConst'; +import TextInputLabel from '@components/TextInput/TextInputLabel'; import withLocalize from '@components/withLocalize'; import * as Browser from '@libs/Browser'; -import getSecureEntryKeyboardType from '@libs/getSecureEntryKeyboardType'; import isInputAutoFilled from '@libs/isInputAutoFilled'; import useNativeDriver from '@libs/useNativeDriver'; import styles from '@styles/styles'; @@ -21,8 +22,6 @@ import themeColors from '@styles/themes/default'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import * as baseTextInputPropTypes from './baseTextInputPropTypes'; -import * as styleConst from './styleConst'; -import TextInputLabel from './TextInputLabel'; function BaseTextInput(props) { const initialValue = props.value || props.defaultValue || ''; @@ -214,7 +213,7 @@ function BaseTextInput(props) { // eslint-disable-next-line react/forbid-foreign-prop-types const inputProps = _.omit(props, _.keys(baseTextInputPropTypes.propTypes)); const hasLabel = Boolean(props.label.length); - const isEditable = _.isUndefined(props.editable) ? !props.disabled : props.editable; + const isReadOnly = _.isUndefined(props.readOnly) ? props.disabled : props.readOnly; const inputHelpText = props.errorText || props.hint; const placeholder = props.prefixCharacter || isFocused || !hasLabel || (hasLabel && props.forceActiveLabel) ? props.placeholder : null; const maxHeight = StyleSheet.flatten(props.containerStyles).maxHeight; @@ -231,7 +230,7 @@ function BaseTextInput(props) { /* To prevent text jumping caused by virtual DOM calculations on Safari and mobile Chrome, make sure to include the `lineHeight`. Reference: https://github.com/Expensify/App/issues/26735 - + For other platforms, explicitly remove `lineHeight` from single-line inputs to prevent long text from disappearing once it exceeds the input space. See https://github.com/Expensify/App/issues/13802 */ @@ -256,7 +255,7 @@ function BaseTextInput(props) { > {/* Adding this background to the label only for multiline text input, to prevent text overlapping with label when scrolling */} - {isMultiline && ( - - )} + {isMultiline && } ) : null} - + {Boolean(props.prefixCharacter) && ( {props.prefixCharacter} @@ -346,6 +336,7 @@ function BaseTextInput(props) { props.autoGrowHeight && StyleUtils.getAutoGrowHeightInputStyle(textInputHeight, maxHeight), // Add disabled color theme when field is not editable. props.disabled && styles.textInputDisabled, + styles.pointerEventsAuto, ]} multiline={isMultiline} maxLength={props.maxLength} @@ -355,10 +346,10 @@ function BaseTextInput(props) { secureTextEntry={passwordHidden} onPressOut={props.onPress} showSoftInputOnFocus={!props.disableKeyboard} - keyboardType={getSecureEntryKeyboardType(props.keyboardType, props.secureTextEntry, passwordHidden)} + inputMode={props.inputMode} value={props.value} selection={props.selection} - editable={isEditable} + readOnly={isReadOnly} defaultValue={props.defaultValue} // FormSubmit Enter key handler does not have access to direct props. // `dataset.submitOnEnter` is used to indicate that pressing Enter on this input should call the submit callback. @@ -385,7 +376,7 @@ function BaseTextInput(props) { )} {!props.secureTextEntry && Boolean(props.icon) && ( - + {/* - Text input component doesn't support auto grow by default. - We're using a hidden text input to achieve that. - This text view is used to calculate width or height of the input value given textStyle in this component. - This Text component is intentionally positioned out of the screen. - */} + Text input component doesn't support auto grow by default. + We're using a hidden text input to achieve that. + This text view is used to calculate width or height of the input value given textStyle in this component. + This Text component is intentionally positioned out of the screen. + */} {(props.autoGrow || props.autoGrowHeight) && ( // Add +2 to width on Safari browsers so that text is not cut off due to the cursor or when changing the value // https://github.com/Expensify/App/issues/8158 diff --git a/src/components/TextInput/BaseTextInput/index.native.js b/src/components/TextInput/BaseTextInput/index.native.js new file mode 100644 index 000000000000..5c3e19a2d94c --- /dev/null +++ b/src/components/TextInput/BaseTextInput/index.native.js @@ -0,0 +1,401 @@ +import Str from 'expensify-common/lib/str'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; +import {ActivityIndicator, Animated, StyleSheet, View} from 'react-native'; +import _ from 'underscore'; +import Checkbox from '@components/Checkbox'; +import FormHelpMessage from '@components/FormHelpMessage'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import RNTextInput from '@components/RNTextInput'; +import SwipeInterceptPanResponder from '@components/SwipeInterceptPanResponder'; +import Text from '@components/Text'; +import * as styleConst from '@components/TextInput/styleConst'; +import TextInputLabel from '@components/TextInput/TextInputLabel'; +import withLocalize from '@components/withLocalize'; +import getSecureEntryKeyboardType from '@libs/getSecureEntryKeyboardType'; +import isInputAutoFilled from '@libs/isInputAutoFilled'; +import useNativeDriver from '@libs/useNativeDriver'; +import styles from '@styles/styles'; +import * as StyleUtils from '@styles/StyleUtils'; +import themeColors from '@styles/themes/default'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import * as baseTextInputPropTypes from './baseTextInputPropTypes'; + +function BaseTextInput(props) { + const initialValue = props.value || props.defaultValue || ''; + const initialActiveLabel = props.forceActiveLabel || initialValue.length > 0 || Boolean(props.prefixCharacter); + + const [isFocused, setIsFocused] = useState(false); + const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry); + const [textInputWidth, setTextInputWidth] = useState(0); + const [textInputHeight, setTextInputHeight] = useState(0); + const [height, setHeight] = useState(variables.componentSizeLarge); + const [width, setWidth] = useState(); + const labelScale = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE)).current; + const labelTranslateY = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)).current; + + const input = useRef(null); + const isLabelActive = useRef(initialActiveLabel); + + // AutoFocus which only works on mount: + useEffect(() => { + // We are manually managing focus to prevent this issue: https://github.com/Expensify/App/issues/4514 + if (!props.autoFocus || !input.current) { + return; + } + + if (props.shouldDelayFocus) { + const focusTimeout = setTimeout(() => input.current.focus(), CONST.ANIMATED_TRANSITION); + return () => clearTimeout(focusTimeout); + } + input.current.focus(); + // We only want this to run on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const animateLabel = useCallback( + (translateY, scale) => { + Animated.parallel([ + Animated.spring(labelTranslateY, { + toValue: translateY, + duration: styleConst.LABEL_ANIMATION_DURATION, + useNativeDriver, + }), + Animated.spring(labelScale, { + toValue: scale, + duration: styleConst.LABEL_ANIMATION_DURATION, + useNativeDriver, + }), + ]).start(); + }, + [labelScale, labelTranslateY], + ); + + const activateLabel = useCallback(() => { + const value = props.value || ''; + + if (value.length < 0 || isLabelActive.current) { + return; + } + + animateLabel(styleConst.ACTIVE_LABEL_TRANSLATE_Y, styleConst.ACTIVE_LABEL_SCALE); + isLabelActive.current = true; + }, [animateLabel, props.value]); + + const deactivateLabel = useCallback(() => { + const value = props.value || ''; + + if (props.forceActiveLabel || value.length !== 0 || props.prefixCharacter) { + return; + } + + animateLabel(styleConst.INACTIVE_LABEL_TRANSLATE_Y, styleConst.INACTIVE_LABEL_SCALE); + isLabelActive.current = false; + }, [animateLabel, props.forceActiveLabel, props.prefixCharacter, props.value]); + + const onFocus = (event) => { + if (props.onFocus) { + props.onFocus(event); + } + setIsFocused(true); + }; + + const onBlur = (event) => { + if (props.onBlur) { + props.onBlur(event); + } + setIsFocused(false); + }; + + const onPress = (event) => { + if (props.disabled) { + return; + } + + if (props.onPress) { + props.onPress(event); + } + + if (!event.isDefaultPrevented()) { + input.current.focus(); + } + }; + + const onLayout = useCallback( + (event) => { + if (!props.autoGrowHeight && props.multiline) { + return; + } + + const layout = event.nativeEvent.layout; + + setWidth((prevWidth) => (props.autoGrowHeight ? layout.width : prevWidth)); + setHeight((prevHeight) => (!props.multiline ? layout.height : prevHeight)); + }, + [props.autoGrowHeight, props.multiline], + ); + + // The ref is needed when the component is uncontrolled and we don't have a value prop + const hasValueRef = useRef(initialValue.length > 0); + const inputValue = props.value || ''; + const hasValue = inputValue.length > 0 || hasValueRef.current; + + // Activate or deactivate the label when either focus changes, or for controlled + // components when the value prop changes: + useEffect(() => { + if ( + hasValue || + isFocused || + // If the text has been supplied by Chrome autofill, the value state is not synced with the value + // as Chrome doesn't trigger a change event. When there is autofill text, keep the label activated. + isInputAutoFilled(input.current) + ) { + activateLabel(); + } else { + deactivateLabel(); + } + }, [activateLabel, deactivateLabel, hasValue, isFocused]); + + // When the value prop gets cleared externally, we need to keep the ref in sync: + useEffect(() => { + // Return early when component uncontrolled, or we still have a value + if (props.value === undefined || !_.isEmpty(props.value)) { + return; + } + hasValueRef.current = false; + }, [props.value]); + + /** + * Set Value & activateLabel + * + * @param {String} value + * @memberof BaseTextInput + */ + const setValue = (value) => { + if (props.onInputChange) { + props.onInputChange(value); + } + + Str.result(props.onChangeText, value); + + if (value && value.length > 0) { + hasValueRef.current = true; + // When the componment is uncontrolled, we need to manually activate the label: + if (props.value === undefined) { + activateLabel(); + } + } else { + hasValueRef.current = false; + } + }; + + const togglePasswordVisibility = useCallback(() => { + setPasswordHidden((prevPasswordHidden) => !prevPasswordHidden); + }, []); + + // When adding a new prefix character, adjust this method to add expected character width. + // This is because character width isn't known before it's rendered to the screen, and once it's rendered, + // it's too late to calculate it's width because the change in padding would cause a visible jump. + // Some characters are wider than the others when rendered, e.g. '@' vs '#'. Chosen font-family and font-size + // also have an impact on the width of the character, but as long as there's only one font-family and one font-size, + // this method will produce reliable results. + const getCharacterPadding = (prefix) => { + switch (prefix) { + case CONST.POLICY.ROOM_PREFIX: + return 10; + default: + throw new Error(`Prefix ${prefix} has no padding assigned.`); + } + }; + + // eslint-disable-next-line react/forbid-foreign-prop-types + const inputProps = _.omit(props, _.keys(baseTextInputPropTypes.propTypes)); + const hasLabel = Boolean(props.label.length); + const isReadOnly = _.isUndefined(props.readOnly) ? props.disabled : props.readOnly; + const inputHelpText = props.errorText || props.hint; + const placeholder = props.prefixCharacter || isFocused || !hasLabel || (hasLabel && props.forceActiveLabel) ? props.placeholder : null; + const maxHeight = StyleSheet.flatten(props.containerStyles).maxHeight; + const textInputContainerStyles = StyleSheet.flatten([ + styles.textInputContainer, + ...props.textInputContainerStyles, + props.autoGrow && StyleUtils.getWidthStyle(textInputWidth), + !props.hideFocusedState && isFocused && styles.borderColorFocus, + (props.hasError || props.errorText) && styles.borderColorDanger, + props.autoGrowHeight && {scrollPaddingTop: 2 * maxHeight}, + ]); + const isMultiline = props.multiline || props.autoGrowHeight; + + return ( + <> + + + + {hasLabel ? ( + <> + {/* Adding this background to the label only for multiline text input, + to prevent text overlapping with label when scrolling */} + {isMultiline && } + + + ) : null} + + {Boolean(props.prefixCharacter) && ( + + + {props.prefixCharacter} + + + )} + { + if (typeof props.innerRef === 'function') { + props.innerRef(ref); + } else if (props.innerRef && _.has(props.innerRef, 'current')) { + // eslint-disable-next-line no-param-reassign + props.innerRef.current = ref; + } + input.current = ref; + }} + // eslint-disable-next-line + {...inputProps} + autoCorrect={props.secureTextEntry ? false : props.autoCorrect} + placeholder={placeholder} + placeholderTextColor={themeColors.placeholderText} + underlineColorAndroid="transparent" + style={[ + styles.flex1, + styles.w100, + props.inputStyle, + (!hasLabel || isMultiline) && styles.pv0, + props.prefixCharacter && StyleUtils.getPaddingLeft(getCharacterPadding(props.prefixCharacter) + styles.pl1.paddingLeft), + props.secureTextEntry && styles.secureInput, + + !isMultiline && {height, lineHeight: undefined}, + + // Stop scrollbar flashing when breaking lines with autoGrowHeight enabled. + props.autoGrowHeight && StyleUtils.getAutoGrowHeightInputStyle(textInputHeight, maxHeight), + // Add disabled color theme when field is not editable. + props.disabled && styles.textInputDisabled, + styles.pointerEventsAuto, + ]} + multiline={isMultiline} + maxLength={props.maxLength} + onFocus={onFocus} + onBlur={onBlur} + onChangeText={setValue} + secureTextEntry={passwordHidden} + onPressOut={props.onPress} + showSoftInputOnFocus={!props.disableKeyboard} + keyboardType={getSecureEntryKeyboardType(props.keyboardType, props.secureTextEntry, passwordHidden)} + inputMode={!props.disableKeyboard ? props.inputMode : CONST.INPUT_MODE.NONE} + value={props.value} + selection={props.selection} + readOnly={isReadOnly} + defaultValue={props.defaultValue} + // FormSubmit Enter key handler does not have access to direct props. + // `dataset.submitOnEnter` is used to indicate that pressing Enter on this input should call the submit callback. + dataSet={{submitOnEnter: isMultiline && props.submitOnEnter}} + /> + {props.isLoading && ( + + )} + {Boolean(props.secureTextEntry) && ( + e.preventDefault()} + accessibilityLabel={props.translate('common.visible')} + > + + + )} + {!props.secureTextEntry && Boolean(props.icon) && ( + + + + )} + + + + {!_.isEmpty(inputHelpText) && ( + + )} + + {/* + Text input component doesn't support auto grow by default. + We're using a hidden text input to achieve that. + This text view is used to calculate width or height of the input value given textStyle in this component. + This Text component is intentionally positioned out of the screen. + */} + {(props.autoGrow || props.autoGrowHeight) && ( + // Add +2 to width on Safari browsers so that text is not cut off due to the cursor or when changing the value + // https://github.com/Expensify/App/issues/8158 + // https://github.com/Expensify/App/issues/26628 + { + setTextInputWidth(e.nativeEvent.layout.width); + setTextInputHeight(e.nativeEvent.layout.height); + }} + > + {/* \u200B added to solve the issue of not expanding the text input enough when the value ends with '\n' (https://github.com/Expensify/App/issues/21271) */} + {props.value ? `${props.value}${props.value.endsWith('\n') ? '\u200B' : ''}` : props.placeholder} + + )} + + ); +} + +BaseTextInput.displayName = 'BaseTextInput'; +BaseTextInput.propTypes = baseTextInputPropTypes.propTypes; +BaseTextInput.defaultProps = baseTextInputPropTypes.defaultProps; + +export default withLocalize(BaseTextInput); diff --git a/src/components/TextInput/TextInputLabel/index.js b/src/components/TextInput/TextInputLabel/index.js index f777eff039bd..b49635b91d96 100644 --- a/src/components/TextInput/TextInputLabel/index.js +++ b/src/components/TextInput/TextInputLabel/index.js @@ -18,9 +18,8 @@ function TextInputLabel({for: inputId, label, labelTranslateY, labelScale}) { return ( {label} diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js index 5f6164d3bc9a..044399ec6e11 100644 --- a/src/components/TextInput/index.js +++ b/src/components/TextInput/index.js @@ -5,7 +5,7 @@ import DomUtils from '@libs/DomUtils'; import Visibility from '@libs/Visibility'; import styles from '@styles/styles'; import BaseTextInput from './BaseTextInput'; -import * as baseTextInputPropTypes from './baseTextInputPropTypes'; +import * as baseTextInputPropTypes from './BaseTextInput/baseTextInputPropTypes'; import * as styleConst from './styleConst'; function TextInput(props) { diff --git a/src/components/TextInput/index.native.js b/src/components/TextInput/index.native.js index d28824a9977b..a4d0c76337ab 100644 --- a/src/components/TextInput/index.native.js +++ b/src/components/TextInput/index.native.js @@ -2,7 +2,7 @@ import React, {forwardRef, useEffect} from 'react'; import {AppState, Keyboard} from 'react-native'; import styles from '@styles/styles'; import BaseTextInput from './BaseTextInput'; -import * as baseTextInputPropTypes from './baseTextInputPropTypes'; +import * as baseTextInputPropTypes from './BaseTextInput/baseTextInputPropTypes'; const TextInput = forwardRef((props, ref) => { useEffect(() => { diff --git a/src/components/TextLink.js b/src/components/TextLink.js index 79f3d43a7743..9292ac51e78f 100644 --- a/src/components/TextLink.js +++ b/src/components/TextLink.js @@ -66,7 +66,7 @@ function TextLink(props) { return ( diff --git a/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js b/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js index ceb10de0f909..589866eecc67 100755 --- a/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js +++ b/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js @@ -101,7 +101,7 @@ function BaseVideoChatButtonAndMenu(props) { })} style={styles.touchableButtonImage} accessibilityLabel={props.translate('videoChatButtonAndMenu.tooltip')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > = (...params: T) => void | Promise; + +/** + * This hook was specifically written for native issue + * more information: https://github.com/Expensify/App/pull/24614 https://github.com/Expensify/App/pull/24173 + * on web we don't need this mechanism so we just call the action directly. + */ +export default function useSingleExecution() { + const singleExecution = useCallback( + (action: Action) => + (...params: T) => { + action(...params); + }, + [], + ); + + return {isExecuting: false, singleExecution}; +} diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay.js b/src/libs/Navigation/AppNavigator/Navigators/Overlay.js index a36f98076d22..c030b91cf930 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay.js +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay.js @@ -27,13 +27,13 @@ function Overlay(props) { style={[styles.draggableTopBar]} onPress={props.onPress} accessibilityLabel={translate('common.close')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} /> diff --git a/src/libs/Navigation/FreezeWrapper.js b/src/libs/Navigation/FreezeWrapper.js index 592d869dc0de..16a353ebddea 100644 --- a/src/libs/Navigation/FreezeWrapper.js +++ b/src/libs/Navigation/FreezeWrapper.js @@ -3,6 +3,7 @@ import lodashFindIndex from 'lodash/findIndex'; import PropTypes from 'prop-types'; import React, {useEffect, useRef, useState} from 'react'; import {Freeze} from 'react-freeze'; +import {InteractionManager} from 'react-native'; const propTypes = { /** Prop to disable freeze */ @@ -35,7 +36,7 @@ function FreezeWrapper(props) { // we don't want to freeze the screen if it's the previous screen because the freeze placeholder // would be visible at the beginning of the back animation then if (navigation.getState().index - screenIndexRef.current > 1) { - setIsScreenBlurred(true); + InteractionManager.runAfterInteractions(() => setIsScreenBlurred(true)); } else { setIsScreenBlurred(false); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 1de15c1184cb..aba77d1f5e2f 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2006,7 +2006,7 @@ function openReportFromDeepLink(url, isAuthenticated) { }); return; } - Navigation.navigate(route, CONST.NAVIGATION.TYPE.PUSH); + Navigation.navigate(route, CONST.NAVIGATION.ACTION_TYPE.PUSH); }); }); } diff --git a/src/libs/focusComposerWithDelay.ts b/src/libs/focusComposerWithDelay.ts index 94eb168328aa..19f1050d24bd 100644 --- a/src/libs/focusComposerWithDelay.ts +++ b/src/libs/focusComposerWithDelay.ts @@ -1,4 +1,4 @@ -import {InteractionManager, TextInput} from 'react-native'; +import {TextInput} from 'react-native'; import * as EmojiPickerAction from './actions/EmojiPickerAction'; import ComposerFocusManager from './ComposerFocusManager'; @@ -14,21 +14,19 @@ function focusComposerWithDelay(textInput: TextInput | null): FocusComposerWithD return (shouldDelay = false) => { // There could be other animations running while we trigger manual focus. // This prevents focus from making those animations janky. - InteractionManager.runAfterInteractions(() => { - if (!textInput || EmojiPickerAction.isEmojiPickerVisible()) { - return; - } + if (!textInput || EmojiPickerAction.isEmojiPickerVisible()) { + return; + } - if (!shouldDelay) { - textInput.focus(); + if (!shouldDelay) { + textInput.focus(); + return; + } + ComposerFocusManager.isReadyToFocus().then(() => { + if (!textInput) { return; } - ComposerFocusManager.isReadyToFocus().then(() => { - if (!textInput) { - return; - } - textInput.focus(); - }); + textInput.focus(); }); }; } diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index 7f01256cc024..5dcdc41afc6d 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -126,10 +126,7 @@ function DetailsPage(props) { - + {details ? ( @@ -144,7 +141,7 @@ function DetailsPage(props) { style={[styles.noOutline]} onPress={show} accessibilityLabel={props.translate('common.details')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} > { if (!el) { return; @@ -75,8 +75,7 @@ function EditRequestDescriptionPage({defaultDescription, onSubmit}) { updateMultilineInputRange(descriptionInputRef.current); }} autoGrowHeight - containerStyles={[styles.autoGrowHeightMultilineInput]} - textAlignVertical="top" + containerStyles={[styles.autoGrowHeightMultilineInput, styles.verticalAlignTop]} submitOnEnter={!Browser.isMobile()} /> diff --git a/src/pages/EditRequestMerchantPage.js b/src/pages/EditRequestMerchantPage.js index e64fd121b4ff..af9b5c9a539e 100644 --- a/src/pages/EditRequestMerchantPage.js +++ b/src/pages/EditRequestMerchantPage.js @@ -56,7 +56,7 @@ function EditRequestMerchantPage({defaultMerchant, onSubmit}) { defaultValue={defaultMerchant} label={translate('common.merchant')} accessibilityLabel={translate('common.merchant')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} ref={(e) => (merchantInputRef.current = e)} /> diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index f1813062d0d7..e58d45b5f1c4 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -190,7 +190,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP containerStyles={[styles.mt4]} label={translate(fieldNameTranslationKeys.legalFirstName)} accessibilityLabel={translate(fieldNameTranslationKeys.legalFirstName)} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} defaultValue={PersonalDetails.extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).firstName} shouldSaveDraft /> @@ -199,7 +199,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP containerStyles={[styles.mt4]} label={translate(fieldNameTranslationKeys.legalLastName)} accessibilityLabel={translate(fieldNameTranslationKeys.legalLastName)} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} defaultValue={PersonalDetails.extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).lastName} shouldSaveDraft /> @@ -217,10 +217,10 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.js b/src/pages/PrivateNotes/PrivateNotesEditPage.js index e3c2c7170ceb..f38dabee9183 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.js +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.js @@ -144,7 +144,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report}) { > Navigation.goBack(navigateBackTo)} /> - + {hasMinimumDetails && ( @@ -172,7 +169,7 @@ function ProfilePage(props) { style={[styles.noOutline]} onPress={show} accessibilityLabel={props.translate('common.profile')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} > props.onFieldChange({city: value})} @@ -138,8 +138,8 @@ function AddressForm(props) { shouldSaveDraft={props.shouldSaveDraft} label={props.translate('common.zip')} accessibilityLabel={props.translate('common.zip')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + role={CONST.ACCESSIBILITY_ROLE.TEXT} + inputMode={CONST.INPUT_MODE.NUMERIC} value={props.values.zipCode} defaultValue={props.defaultValues.zipCode} onChangeText={(value) => props.onFieldChange({zipCode: value})} diff --git a/src/pages/ReimbursementAccount/BankAccountManualStep.js b/src/pages/ReimbursementAccount/BankAccountManualStep.js index 13155d286a5e..1612238ed8d9 100644 --- a/src/pages/ReimbursementAccount/BankAccountManualStep.js +++ b/src/pages/ReimbursementAccount/BankAccountManualStep.js @@ -102,10 +102,10 @@ function BankAccountManualStep(props) { shouldDelayFocus={shouldDelayFocus} inputID="routingNumber" label={translate('bankAccount.routingNumber')} - accessibilityLabel={translate('bankAccount.routingNumber')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + aria-label={translate('bankAccount.routingNumber')} + role={CONST.ACCESSIBILITY_ROLE.TEXT} defaultValue={props.getDefaultStateForField('routingNumber', '')} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + inputMode={CONST.INPUT_MODE.NUMERIC} disabled={shouldDisableInputs} shouldSaveDraft shouldUseDefaultValue={shouldDisableInputs} @@ -114,16 +114,16 @@ function BankAccountManualStep(props) { inputID="accountNumber" containerStyles={[styles.mt4]} label={translate('bankAccount.accountNumber')} - accessibilityLabel={translate('bankAccount.accountNumber')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + aria-label={translate('bankAccount.accountNumber')} + role={CONST.ACCESSIBILITY_ROLE.TEXT} defaultValue={props.getDefaultStateForField('accountNumber', '')} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + inputMode={CONST.INPUT_MODE.NUMERIC} disabled={shouldDisableInputs} shouldSaveDraft shouldUseDefaultValue={shouldDisableInputs} /> ( diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 24cfbf5ae4c6..41f73d1ebf8e 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -163,7 +163,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul props.onFieldChange({firstName: value})} @@ -158,8 +158,8 @@ function IdentityForm(props) { inputID={props.inputKeys.lastName} shouldSaveDraft={props.shouldSaveDraft} label={`${props.translate('common.lastName')}`} - accessibilityLabel={props.translate('common.lastName')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + aria-label={props.translate('common.lastName')} + role={CONST.ACCESSIBILITY_ROLE.TEXT} value={props.values.lastName} defaultValue={props.defaultValues.lastName} onChangeText={(value) => props.onFieldChange({lastName: value})} @@ -183,10 +183,10 @@ function IdentityForm(props) { inputID={props.inputKeys.ssnLast4} shouldSaveDraft={props.shouldSaveDraft} label={`${props.translate('common.ssnLast4')}`} - accessibilityLabel={props.translate('common.ssnLast4')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + aria-label={props.translate('common.ssnLast4')} + role={CONST.ACCESSIBILITY_ROLE.TEXT} containerStyles={[styles.mt4]} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + inputMode={CONST.INPUT_MODE.NUMERIC} defaultValue={props.defaultValues.ssnLast4} onChangeText={(value) => props.onFieldChange({ssnLast4: value})} errorText={props.errors.ssnLast4 ? props.translate('bankAccount.error.ssnLast4') : ''} diff --git a/src/pages/ReimbursementAccount/ValidationStep.js b/src/pages/ReimbursementAccount/ValidationStep.js index 5a0149aa3ba4..343f98644766 100644 --- a/src/pages/ReimbursementAccount/ValidationStep.js +++ b/src/pages/ReimbursementAccount/ValidationStep.js @@ -157,24 +157,24 @@ function ValidationStep({reimbursementAccount, translate, onBackButtonPress, acc shouldSaveDraft containerStyles={[styles.mb1]} placeholder="1.52" - keyboardType="decimal-pad" - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + inputMode={CONST.INPUT_MODE.DECIMAL} + role={CONST.ACCESSIBILITY_ROLE.TEXT} /> {!requiresTwoFactorAuth && ( diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index ef28102cc144..de25fdc3a081 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -215,7 +215,7 @@ function ReportDetailsPage(props) { {isPolicyAdmin ? ( { Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(props.report.policyID)); diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index c2179c53126b..1ae6942c6412 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -106,10 +106,7 @@ function ReportParticipantsPage(props) { : 'common.details', )} /> - + {Boolean(participants.length) && ( { @@ -111,7 +111,7 @@ function ReportWelcomeMessagePage(props) { value={welcomeMessage} onChangeText={handleWelcomeMessageChange} autoCapitalize="none" - textAlignVertical="top" + inputStyle={[styles.verticalAlignTop]} containerStyles={[styles.autoGrowHeightMultilineInput]} /> diff --git a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js index 7b84c5bc94d1..16389d69053d 100644 --- a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js +++ b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js @@ -106,7 +106,7 @@ function IntroSchoolPrincipalPage(props) { name="firstName" label={translate('teachersUnitePage.principalFirstName')} accessibilityLabel={translate('teachersUnitePage.principalFirstName')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} autoCapitalize="words" /> @@ -118,7 +118,7 @@ function IntroSchoolPrincipalPage(props) { name="lastName" label={translate('teachersUnitePage.principalLastName')} accessibilityLabel={translate('teachersUnitePage.principalLastName')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} autoCapitalize="words" /> @@ -130,8 +130,8 @@ function IntroSchoolPrincipalPage(props) { name="partnerUserID" label={translate('teachersUnitePage.principalWorkEmail')} accessibilityLabel={translate('teachersUnitePage.principalWorkEmail')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - keyboardType={CONST.KEYBOARD_TYPE.EMAIL_ADDRESS} + role={CONST.ACCESSIBILITY_ROLE.TEXT} + inputMode={CONST.INPUT_MODE.EMAIL} autoCapitalize="none" /> diff --git a/src/pages/TeachersUnite/KnowATeacherPage.js b/src/pages/TeachersUnite/KnowATeacherPage.js index c9d429a14596..696a9ef8b704 100644 --- a/src/pages/TeachersUnite/KnowATeacherPage.js +++ b/src/pages/TeachersUnite/KnowATeacherPage.js @@ -116,7 +116,7 @@ function KnowATeacherPage(props) { name="fname" label={translate('common.firstName')} accessibilityLabel={translate('common.firstName')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} autoCapitalize="words" /> @@ -128,7 +128,7 @@ function KnowATeacherPage(props) { name="lname" label={translate('common.lastName')} accessibilityLabel={translate('common.lastName')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} autoCapitalize="words" /> @@ -140,8 +140,8 @@ function KnowATeacherPage(props) { name="partnerUserID" label={`${translate('common.email')}/${translate('common.phoneNumber')}`} accessibilityLabel={`${translate('common.email')}/${translate('common.phoneNumber')}`} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - keyboardType={CONST.KEYBOARD_TYPE.EMAIL_ADDRESS} + role={CONST.ACCESSIBILITY_ROLE.TEXT} + inputMode={CONST.INPUT_MODE.EMAIL} autoCapitalize="none" /> diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 36c48bd48fd2..27ee820de02f 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -184,7 +184,7 @@ function HeaderView(props) { style={[styles.LHNToggle]} accessibilityHint={props.translate('accessibilityHints.navigateToChatsList')} accessibilityLabel={props.translate('common.back')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {shouldShowSubscript ? ( {translate('newMessages')} diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index a31e718933ea..a8ecff7c8d82 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -208,7 +208,7 @@ function AttachmentPickerWithMenuItems({ onMouseDown={(e) => e.preventDefault()} style={styles.composerSizeButton} disabled={isBlockedFromConcierge || disabled} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('reportActionCompose.collapse')} > @@ -227,7 +227,7 @@ function AttachmentPickerWithMenuItems({ onMouseDown={(e) => e.preventDefault()} style={styles.composerSizeButton} disabled={isBlockedFromConcierge || disabled} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('reportActionCompose.expand')} > @@ -247,7 +247,7 @@ function AttachmentPickerWithMenuItems({ }} style={styles.composerSizeButton} disabled={isBlockedFromConcierge || disabled} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('reportActionCompose.addAction')} > diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 6c661992fe20..c61633618d9f 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -588,12 +588,11 @@ function ComposerWithSuggestions({ autoFocus={shouldAutoFocus} multiline ref={setTextInputRef} - textAlignVertical="top" placeholder={inputPlaceholder} placeholderTextColor={themeColors.placeholderText} onChangeText={(commentValue) => updateComment(commentValue, true)} onKeyPress={triggerHotkeyActions} - style={[styles.textInputCompose, isComposerFullSize ? styles.textInputFullCompose : styles.flex4]} + style={[styles.textInputCompose, isComposerFullSize ? styles.textInputFullCompose : styles.flex4, styles.verticalAlignTop]} maxLines={maxComposerLines} onFocus={onFocus} onBlur={onBlur} diff --git a/src/pages/home/report/ReportActionCompose/SendButton.js b/src/pages/home/report/ReportActionCompose/SendButton.js index 061251d13c01..41f35b0f8d3d 100644 --- a/src/pages/home/report/ReportActionCompose/SendButton.js +++ b/src/pages/home/report/ReportActionCompose/SendButton.js @@ -42,7 +42,7 @@ function SendButton({isDisabled: isDisabledProp, handleSendMessage}) { isDisabledProp || pressed || isDisabled ? undefined : styles.buttonSuccess, isDisabledProp ? styles.cursorDisabled : undefined, ]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('common.send')} > {({pressed}) => ( diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index c91a29a37eec..24e48eb3e7d0 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -442,8 +442,7 @@ function ReportActionItem(props) { onPress={() => updateHiddenState(!isHidden)} > {isHidden ? props.translate('moderation.revealMessage') : props.translate('moderation.hideMessage')} @@ -656,8 +655,8 @@ function ReportActionItem(props) { const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; return ( props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onSecondaryInteraction={showPopover} diff --git a/src/pages/home/report/ReportActionItemCreated.js b/src/pages/home/report/ReportActionItemCreated.js index 10ebb13302b2..a7772ad5e0fb 100644 --- a/src/pages/home/report/ReportActionItemCreated.js +++ b/src/pages/home/report/ReportActionItemCreated.js @@ -74,7 +74,7 @@ function ReportActionItemCreated(props) { onPress={() => ReportUtils.navigateToDetailsPage(props.report)} style={[styles.mh5, styles.mb3, styles.alignSelfStart]} accessibilityLabel={props.translate('common.details')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} disabled={shouldDisableDetailPage} > {convertToLTR(props.iouMessage || text)} {Boolean(props.fragment.isEdited) && ( <> diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 2acb624e28ef..db453ca38265 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -386,7 +386,7 @@ function ReportActionItemMessageEdit(props) { focus(true)} onEmojiSelected={addEmojiToTextBox} - nativeID={emojiButtonID} + id={emojiButtonID} emojiPickerID={props.action.reportActionID} /> @@ -454,7 +454,7 @@ function ReportActionItemMessageEdit(props) { style={[styles.chatItemSubmitButton, hasExceededMaxCommentLength ? {} : styles.buttonSuccess]} onPress={publishDraft} disabled={hasExceededMaxCommentLength} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('common.saveChanges')} hoverDimmingValue={1} pressDimmingValue={0.2} diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index e9e1ef39e417..955e024bd7a8 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -219,7 +219,7 @@ function ReportActionItemSingle(props) { onPress={showActorDetails} disabled={shouldDisableDetailPage} accessibilityLabel={actorHint} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {getAvatar()} @@ -233,7 +233,7 @@ function ReportActionItemSingle(props) { onPress={showActorDetails} disabled={shouldDisableDetailPage} accessibilityLabel={actorHint} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > {_.map(personArray, (fragment, index) => ( { Report.navigateToAndOpenChildReport(props.childReportID); }} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={`${props.numberOfReplies} ${replyText}`} onSecondaryInteraction={props.onSecondaryInteraction} > @@ -60,16 +60,14 @@ function ReportActionItemThread(props) { /> {`${numberOfRepliesText} ${replyText}`} {timeStamp} diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.js b/src/pages/home/sidebar/AvatarWithOptionalStatus.js index b456788224fb..300a898b9e90 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.js @@ -41,7 +41,7 @@ function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { diff --git a/src/pages/home/sidebar/GlobalNavigation/GlobalNavigationMenuItem.js b/src/pages/home/sidebar/GlobalNavigation/GlobalNavigationMenuItem.js index 93355fcfd530..5c28681a6cfa 100644 --- a/src/pages/home/sidebar/GlobalNavigation/GlobalNavigationMenuItem.js +++ b/src/pages/home/sidebar/GlobalNavigation/GlobalNavigationMenuItem.js @@ -35,7 +35,7 @@ const GlobalNavigationMenuItem = React.forwardRef(({icon, title, isFocused, onPr onPress={() => !isFocused && onPress()} style={styles.globalNavigationItemContainer} ref={ref} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.MENUITEM} + role={CONST.ACCESSIBILITY_ROLE.MENUITEM} accessibilityLabel={title} > {({pressed}) => ( diff --git a/src/pages/home/sidebar/PressableAvatarWithIndicator.js b/src/pages/home/sidebar/PressableAvatarWithIndicator.js index e798bece339f..58703e49dae4 100644 --- a/src/pages/home/sidebar/PressableAvatarWithIndicator.js +++ b/src/pages/home/sidebar/PressableAvatarWithIndicator.js @@ -45,7 +45,7 @@ function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDeta return ( diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 0e7d6aa38545..1f5a07194732 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -148,13 +148,13 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority >
{translate('globalNavigationOptions.chats')}} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} shouldShowEnvironmentBadge /> diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index cca91a45e36f..57f31a8c3e9f 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -238,7 +238,7 @@ function FloatingActionButtonAndPopover(props) { /> { diff --git a/src/pages/home/sidebar/SignInButton.js b/src/pages/home/sidebar/SignInButton.js index 9e8722e2e083..afa67bdc04cd 100644 --- a/src/pages/home/sidebar/SignInButton.js +++ b/src/pages/home/sidebar/SignInButton.js @@ -14,7 +14,7 @@ function SignInButton() { return ( diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js index 425aa313a468..3b52c2ae711c 100644 --- a/src/pages/iou/MoneyRequestDescriptionPage.js +++ b/src/pages/iou/MoneyRequestDescriptionPage.js @@ -131,7 +131,7 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { defaultValue={iou.comment} label={translate('moneyRequestConfirmationList.whatsItFor')} accessibilityLabel={translate('moneyRequestConfirmationList.whatsItFor')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} ref={(el) => { if (!el) { return; @@ -140,8 +140,7 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { updateMultilineInputRange(inputRef.current); }} autoGrowHeight - containerStyles={[styles.autoGrowHeightMultilineInput]} - textAlignVertical="top" + containerStyles={[styles.autoGrowHeightMultilineInput, styles.verticalAlignTop]} submitOnEnter={!Browser.isMobile()} /> diff --git a/src/pages/iou/MoneyRequestMerchantPage.js b/src/pages/iou/MoneyRequestMerchantPage.js index f072c0f78535..4a609f013d95 100644 --- a/src/pages/iou/MoneyRequestMerchantPage.js +++ b/src/pages/iou/MoneyRequestMerchantPage.js @@ -115,7 +115,7 @@ function MoneyRequestMerchantPage({iou, route}) { maxLength={CONST.MERCHANT_NAME_MAX_LENGTH} label={translate('common.merchant')} accessibilityLabel={translate('common.merchant')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} ref={inputCallbackRef} /> diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index b6a15d69f02c..d1dc3682e6c9 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -208,7 +208,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { {({openPicker}) => ( { openPicker({ onPicked: (file) => { @@ -227,7 +227,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { )} {({openPicker}) => ( { @@ -258,7 +258,7 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator, s )} - + {isScanning && ( } nativeIds + * @param {Array} ids */ - const onMouseDown = (event, nativeIds) => { + const onMouseDown = (event, ids) => { const relatedTargetId = lodashGet(event, 'nativeEvent.target.id'); - if (!_.contains(nativeIds, relatedTargetId)) { + if (!_.contains(ids, relatedTargetId)) { return; } event.preventDefault(); @@ -240,7 +240,7 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu return ( onMouseDown(event, [AMOUNT_VIEW_ID])} style={[styles.flex1, styles.flexRow, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter]} > @@ -279,11 +279,11 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu onMouseDown(event, [NUM_PAD_CONTAINER_VIEW_ID, NUM_PAD_VIEW_ID])} style={[styles.w100, styles.justifyContentEnd, styles.pageWrapper, styles.pt0]} - nativeID={NUM_PAD_CONTAINER_VIEW_ID} + id={NUM_PAD_CONTAINER_VIEW_ID} > {canUseTouchScreen ? ( diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 8e9618036861..b09117719a8c 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -112,10 +112,7 @@ function AboutPage(props) { height={80} width={80} /> - + v{Environment.isInternalTestBuild() ? `${pkg.version} PR:${CONST.PULL_REQUEST_NUMBER}${getFlavor()}` : `${pkg.version}${getFlavor()}`} {props.translate('initialSettingsPage.aboutPage.description')} diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 8fb752d2a574..207c006a31c2 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -344,7 +344,7 @@ function InitialSettingsPage(props) { disabled={isExecuting} onPress={singleExecution(openProfileSettings)} accessibilityLabel={translate('common.profile')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > (loginInputRef.current = el)} inputID="phoneOrEmail" autoCapitalize="none" - returnKeyType="go" + enterKeyHint="done" maxLength={CONST.LOGIN_CHARACTER_LIMIT} /> diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js index 26f66e3f0294..6fbbe770591b 100644 --- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js @@ -192,7 +192,7 @@ function BaseValidateCodeForm(props) { underlayColor={themeColors.componentBG} hoverDimmingValue={1} pressDimmingValue={0.2} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={props.translate('validateCodeForm.magicCodeNotReceived')} > {props.translate('validateCodeForm.magicCodeNotReceived')} diff --git a/src/pages/settings/Profile/CustomStatus/StatusSetPage.js b/src/pages/settings/Profile/CustomStatus/StatusSetPage.js index 1d26c0e6dec4..ffe2d06b304a 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusSetPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusSetPage.js @@ -66,7 +66,7 @@ function StatusSetPage({draftStatus, currentUserPersonalDetails}) { @@ -74,8 +74,8 @@ function StatusSetPage({draftStatus, currentUserPersonalDetails}) { InputComponent={TextInput} inputID={INPUT_IDS.STATUS_TEXT} label={translate('statusPage.message')} - accessibilityLabel={INPUT_IDS.STATUS_TEXT} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + aria-label={INPUT_IDS.STATUS_TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} defaultValue={defaultText} maxLength={CONST.STATUS_TEXT_MAX_LENGTH} autoFocus diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js index 379b5f225310..a7d87e08789f 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.js @@ -100,8 +100,8 @@ function DisplayNamePage(props) { inputID="firstName" name="fname" label={props.translate('common.firstName')} - accessibilityLabel={props.translate('common.firstName')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + aria-label={props.translate('common.firstName')} + role={CONST.ACCESSIBILITY_ROLE.TEXT} defaultValue={lodashGet(currentUserDetails, 'firstName', '')} maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} spellCheck={false} @@ -113,8 +113,8 @@ function DisplayNamePage(props) { inputID="lastName" name="lname" label={props.translate('common.lastName')} - accessibilityLabel={props.translate('common.lastName')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + aria-label={props.translate('common.lastName')} + role={CONST.ACCESSIBILITY_ROLE.TEXT} defaultValue={lodashGet(currentUserDetails, 'lastName', '')} maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} spellCheck={false} diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index d7c4b16270db..b86d646794bd 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -212,8 +212,8 @@ function AddressPage({privatePersonalDetails, route}) { @@ -120,11 +120,11 @@ function CloseAccountPage(props) { inputID="phoneOrEmail" autoCapitalize="none" label={props.translate('closeAccountPage.enterDefaultContact')} - accessibilityLabel={props.translate('closeAccountPage.enterDefaultContact')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + aria-label={props.translate('closeAccountPage.enterDefaultContact')} + role={CONST.ACCESSIBILITY_ROLE.TEXT} containerStyles={[styles.mt5]} autoCorrect={false} - keyboardType={Str.isValidEmail(userEmailOrPhone) ? CONST.KEYBOARD_TYPE.EMAIL_ADDRESS : CONST.KEYBOARD_TYPE.DEFAULT} + inputMode={Str.isValidEmail(userEmailOrPhone) ? CONST.INPUT_MODE.EMAIL : CONST.INPUT_MODE.TEXT} /> (nameOnCardRef.current = ref)} spellCheck={false} /> @@ -157,10 +157,10 @@ function DebitCardPage(props) { @@ -177,9 +177,9 @@ function DebitCardPage(props) { {translate('paymentMethodList.addFirstPaymentMethod')}; + const renderListEmptyComponent = () => {translate('paymentMethodList.addFirstPaymentMethod')}; const renderListFooterComponent = useCallback( () => ( diff --git a/src/pages/signin/ChangeExpensifyLoginLink.js b/src/pages/signin/ChangeExpensifyLoginLink.js index f5e526964333..d776dafc1aa6 100755 --- a/src/pages/signin/ChangeExpensifyLoginLink.js +++ b/src/pages/signin/ChangeExpensifyLoginLink.js @@ -37,7 +37,7 @@ function ChangeExpensifyLoginLink(props) { diff --git a/src/pages/signin/EmailDeliveryFailurePage.js b/src/pages/signin/EmailDeliveryFailurePage.js index a7b690a6151a..7bbfe7d52ec5 100644 --- a/src/pages/signin/EmailDeliveryFailurePage.js +++ b/src/pages/signin/EmailDeliveryFailurePage.js @@ -75,7 +75,7 @@ function EmailDeliveryFailurePage(props) { redirectToSignIn()} - accessibilityRole="button" + role="button" accessibilityLabel={translate('common.back')} // disable hover dim for switch hoverDimmingValue={1} diff --git a/src/pages/signin/LoginForm/BaseLoginForm.js b/src/pages/signin/LoginForm/BaseLoginForm.js index 2c96de33557e..b239ce6d3a86 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.js +++ b/src/pages/signin/LoginForm/BaseLoginForm.js @@ -221,18 +221,18 @@ function LoginForm(props) { ref={input} label={translate('loginForm.phoneOrEmail')} accessibilityLabel={translate('loginForm.phoneOrEmail')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} value={login} returnKeyType="go" autoCompleteType="username" textContentType="username" - nativeID="username" + id="username" name="username" onChangeText={onTextInput} onSubmitEditing={validateAndSubmitForm} autoCapitalize="none" autoCorrect={false} - keyboardType={CONST.KEYBOARD_TYPE.EMAIL_ADDRESS} + inputMode={CONST.INPUT_MODE.EMAIL} errorText={formErrorText} hasError={hasError} maxLength={CONST.LOGIN_CHARACTER_LIMIT} diff --git a/src/pages/signin/SignInPageLayout/BackgroundImage/index.js b/src/pages/signin/SignInPageLayout/BackgroundImage/index.js index 2dc95bd28215..b0022c32c565 100644 --- a/src/pages/signin/SignInPageLayout/BackgroundImage/index.js +++ b/src/pages/signin/SignInPageLayout/BackgroundImage/index.js @@ -18,13 +18,11 @@ const propTypes = { function BackgroundImage(props) { return props.isSmallScreen ? ( ) : ( diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 2d8f0e98e03c..351a32dfca48 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -341,7 +341,7 @@ function BaseValidateCodeForm(props) { hoverDimmingValue={1} pressDimmingValue={0.2} disabled={isValidateCodeFormSubmitting} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={props.isUsingRecoveryCode ? props.translate('recoveryCodeForm.use2fa') : props.translate('recoveryCodeForm.useRecoveryCode')} > {props.isUsingRecoveryCode ? props.translate('recoveryCodeForm.use2fa') : props.translate('recoveryCodeForm.useRecoveryCode')} @@ -376,7 +376,7 @@ function BaseValidateCodeForm(props) { disabled={shouldDisableResendValidateCode} hoverDimmingValue={1} pressDimmingValue={0.2} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={props.translate('validateCodeForm.magicCodeNotReceived')} > diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index 0d6f03006263..c25beba384ad 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -78,7 +78,7 @@ function NewTaskDescriptionPage(props) { inputID="taskDescription" label={props.translate('newTaskPage.descriptionOptional')} accessibilityLabel={props.translate('newTaskPage.descriptionOptional')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} ref={(el) => { inputCallbackRef(el); updateMultilineInputRange(el); @@ -86,7 +86,7 @@ function NewTaskDescriptionPage(props) { autoGrowHeight submitOnEnter={!Browser.isMobile()} containerStyles={[styles.autoGrowHeightMultilineInput]} - textAlignVertical="top" + inputStyle={[styles.verticalAlignTop]} /> diff --git a/src/pages/tasks/NewTaskDetailsPage.js b/src/pages/tasks/NewTaskDetailsPage.js index 0ab3771c28f2..87e0e7e430a9 100644 --- a/src/pages/tasks/NewTaskDetailsPage.js +++ b/src/pages/tasks/NewTaskDetailsPage.js @@ -99,7 +99,7 @@ function NewTaskDetailsPage(props) { setTaskDescription(value)} /> diff --git a/src/pages/tasks/NewTaskTitlePage.js b/src/pages/tasks/NewTaskTitlePage.js index e7be9a239e5d..c522ec35bcef 100644 --- a/src/pages/tasks/NewTaskTitlePage.js +++ b/src/pages/tasks/NewTaskTitlePage.js @@ -91,7 +91,7 @@ function NewTaskTitlePage(props) { diff --git a/src/pages/tasks/TaskTitlePage.js b/src/pages/tasks/TaskTitlePage.js index 375a23cc3012..b4dd1c7c9507 100644 --- a/src/pages/tasks/TaskTitlePage.js +++ b/src/pages/tasks/TaskTitlePage.js @@ -89,7 +89,7 @@ function TaskTitlePage(props) { openEditor(policy.id)))} accessibilityLabel={props.translate('workspace.common.settings')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > openEditor(policy.id)))} accessibilityLabel={props.translate('workspace.common.settings')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} > (this.welcomeMessageInputRef = el)} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} inputID="welcomeMessage" label={this.props.translate('workspace.inviteMessage.personalMessagePrompt')} accessibilityLabel={this.props.translate('workspace.inviteMessage.personalMessagePrompt')} autoCompleteType="off" autoCorrect={false} autoGrowHeight - textAlignVertical="top" + inputStyle={[styles.verticalAlignTop]} containerStyles={[styles.autoGrowHeightMultilineInput]} defaultValue={this.state.welcomeNote} value={this.state.welcomeNote} diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 271dc45026c7..956dc2a3e7eb 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -211,7 +211,7 @@ function WorkspaceNewRoomPage(props) { inputID="welcomeMessage" label={translate('welcomeMessagePage.welcomeMessageOptional')} accessibilityLabel={translate('welcomeMessagePage.welcomeMessageOptional')} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ACCESSIBILITY_ROLE.TEXT} autoGrowHeight maxLength={CONST.MAX_COMMENT_LENGTH} autoCapitalize="none" diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index 2ec17294b17a..b78e593e8c8a 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -142,7 +142,7 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { this.setState({rate: value})} diff --git a/src/setup/platformSetup/index.desktop.js b/src/setup/platformSetup/index.desktop.js index ab485b1855f1..fab7dc3f5b93 100644 --- a/src/setup/platformSetup/index.desktop.js +++ b/src/setup/platformSetup/index.desktop.js @@ -9,6 +9,7 @@ import ELECTRON_EVENTS from '../../../desktop/ELECTRON_EVENTS'; export default function () { AppRegistry.runApplication(Config.APP_NAME, { rootTag: document.getElementById('root'), + mode: 'legacy', }); // Send local notification when update is downloaded diff --git a/src/setup/platformSetup/index.website.js b/src/setup/platformSetup/index.website.js index d26268cd94bf..bdc64e769e09 100644 --- a/src/setup/platformSetup/index.website.js +++ b/src/setup/platformSetup/index.website.js @@ -56,6 +56,7 @@ const webUpdater = () => ({ export default function () { AppRegistry.runApplication(Config.APP_NAME, { rootTag: document.getElementById('root'), + mode: 'legacy', }); // When app loads, get current version (production only) diff --git a/src/stories/Composer.stories.js b/src/stories/Composer.stories.js index fc817ef2c86d..2db1011d1b3a 100644 --- a/src/stories/Composer.stories.js +++ b/src/stories/Composer.stories.js @@ -36,16 +36,15 @@ function Default(args) { // eslint-disable-next-line react/jsx-props-no-spreading {...args} multiline - textAlignVertical="top" onChangeText={setComment} onPasteFile={setPastedFile} - style={[styles.textInputCompose, styles.w100]} + style={[styles.textInputCompose, styles.w100, styles.verticalAlignTop]} /> Entered Comment (Drop Enabled) {comment} diff --git a/src/stories/Form.stories.js b/src/stories/Form.stories.js index 8bcbaf31b600..d385cf0613e6 100644 --- a/src/stories/Form.stories.js +++ b/src/stories/Form.stories.js @@ -46,7 +46,7 @@ function Template(args) { {}, onChangeText: () => {}, diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index e826142fc022..74ea0ed06c02 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -652,7 +652,7 @@ function getMiniReportActionContextMenuWrapperStyle(isReportActionItemGrouped: b ...positioning.r4, ...styles.cursorDefault, position: 'absolute', - zIndex: 1, + zIndex: 8, }; } @@ -1071,7 +1071,7 @@ function getEmojiReactionCounterTextStyle(hasUserReacted: boolean): TextStyle { */ function getDirectionStyle(direction: ValueOf): ViewStyle { if (direction === CONST.DIRECTION.LEFT) { - return {transform: [{rotate: '180deg'}]}; + return {transform: 'rotate(180deg)'}; } return {}; @@ -1097,7 +1097,7 @@ function getGoogleListViewStyle(shouldDisplayBorder: boolean): ViewStyle { } return { - transform: [{scale: 0}], + transform: 'scale(0)', }; } diff --git a/src/styles/getModalStyles.ts b/src/styles/getModalStyles.ts index 55f822693b3e..984bf018e42d 100644 --- a/src/styles/getModalStyles.ts +++ b/src/styles/getModalStyles.ts @@ -73,15 +73,7 @@ export default function getModalStyles( }, }; modalContainerStyle = { - // Shadow Styles - shadowColor: themeColors.shadow, - shadowOffset: { - width: 0, - height: 0, - }, - shadowOpacity: 0.1, - shadowRadius: 5, - + boxShadow: '0px 0px 5px 5px rgba(0, 0, 0, 0.1)', borderRadius: 12, overflow: 'hidden', width: variables.sideBarWidth, @@ -105,15 +97,7 @@ export default function getModalStyles( }, }; modalContainerStyle = { - // Shadow Styles - shadowColor: themeColors.shadow, - shadowOffset: { - width: 0, - height: 0, - }, - shadowOpacity: 0.1, - shadowRadius: 5, - + boxShadow: '0px 0px 5px 5px rgba(0, 0, 0, 0.1)', flex: 1, marginTop: isSmallScreenWidth ? 0 : 20, marginBottom: isSmallScreenWidth ? 0 : 20, @@ -140,15 +124,7 @@ export default function getModalStyles( }, }; modalContainerStyle = { - // Shadow Styles - shadowColor: themeColors.shadow, - shadowOffset: { - width: 0, - height: 0, - }, - shadowOpacity: 0.1, - shadowRadius: 5, - + boxShadow: '0px 0px 5px 5px rgba(0, 0, 0, 0.1)', flex: 1, marginTop: isSmallScreenWidth ? 0 : 20, marginBottom: isSmallScreenWidth ? 0 : 20, @@ -173,15 +149,7 @@ export default function getModalStyles( }, }; modalContainerStyle = { - // Shadow Styles - shadowColor: themeColors.shadow, - shadowOffset: { - width: 0, - height: 0, - }, - shadowOpacity: 0.1, - shadowRadius: 5, - + boxShadow: '0px 0px 5px 5px rgba(0, 0, 0, 0.1)', borderRadius: 12, borderWidth: 0, }; diff --git a/src/styles/pointerEventsBoxNone/index.native.ts b/src/styles/pointerEventsBoxNone/index.native.ts new file mode 100644 index 000000000000..05ad2c07db39 --- /dev/null +++ b/src/styles/pointerEventsBoxNone/index.native.ts @@ -0,0 +1,5 @@ +import PointerEventsBoxNone from './types'; + +const pointerEventsBoxNone: PointerEventsBoxNone = {}; + +export default pointerEventsBoxNone; diff --git a/src/styles/pointerEventsBoxNone/index.ts b/src/styles/pointerEventsBoxNone/index.ts new file mode 100644 index 000000000000..0e63e2deda09 --- /dev/null +++ b/src/styles/pointerEventsBoxNone/index.ts @@ -0,0 +1,7 @@ +import PointerEventsBoxNone from './types'; + +const pointerEventsNone: PointerEventsBoxNone = { + pointerEvents: 'box-none', +}; + +export default pointerEventsNone; diff --git a/src/styles/pointerEventsBoxNone/types.ts b/src/styles/pointerEventsBoxNone/types.ts new file mode 100644 index 000000000000..25e85812f4e0 --- /dev/null +++ b/src/styles/pointerEventsBoxNone/types.ts @@ -0,0 +1,5 @@ +import {ViewStyle} from 'react-native'; + +type PointerEventsBoxNone = Pick; + +export default PointerEventsBoxNone; diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 73f9aa823f40..cdfb049e2dce 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -17,6 +17,7 @@ import getPopOverVerticalOffset from './getPopOverVerticalOffset'; import optionAlternateTextPlatformStyles from './optionAlternateTextPlatformStyles'; import overflowXHidden from './overflowXHidden'; import pointerEventsAuto from './pointerEventsAuto'; +import pointerEventsBoxNone from './pointerEventsBoxNone'; import pointerEventsNone from './pointerEventsNone'; import defaultTheme from './themes/default'; import {ThemeColors} from './themes/types'; @@ -330,6 +331,14 @@ const styles = (theme: ThemeColors) => textDecorationLine: 'underline', }, + verticalAlignMiddle: { + verticalAlign: 'middle', + }, + + verticalAlignTop: { + verticalAlign: 'top', + }, + label: { fontSize: variables.fontSizeLabel, lineHeight: variables.lineHeightLarge, @@ -1050,7 +1059,7 @@ const styles = (theme: ThemeColors) => paddingRight: 12, paddingTop: 10, paddingBottom: 10, - textAlignVertical: 'center', + verticalAlign: 'middle', }, textInputPrefixWrapper: { @@ -1069,7 +1078,7 @@ const styles = (theme: ThemeColors) => color: theme.text, fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeNormal, - textAlignVertical: 'center', + verticalAlign: 'middle', }, pickerContainer: { @@ -1651,7 +1660,7 @@ const styles = (theme: ThemeColors) => chatContentScrollView: { flexGrow: 1, - justifyContent: 'flex-start', + justifyContent: 'flex-end', paddingBottom: 16, }, @@ -1795,13 +1804,13 @@ const styles = (theme: ThemeColors) => ...overflowXHidden, // On Android, multiline TextInput with height: 'auto' will show extra padding unless they are configured with - // paddingVertical: 0, alignSelf: 'center', and textAlignVertical: 'center' + // paddingVertical: 0, alignSelf: 'center', and verticalAlign: 'middle' paddingHorizontal: variables.avatarChatSpacing, paddingTop: 0, paddingBottom: 0, alignSelf: 'center', - textAlignVertical: 'center', + verticalAlign: 'middle', }, 0, ), @@ -1810,7 +1819,7 @@ const styles = (theme: ThemeColors) => alignSelf: 'stretch', flex: 1, maxHeight: '100%', - textAlignVertical: 'top', + verticalAlign: 'top', }, // composer padding should not be modified unless thoroughly tested against the cases in this PR: #12669 @@ -2141,6 +2150,8 @@ const styles = (theme: ThemeColors) => pointerEventsAuto, + pointerEventsBoxNone, + headerBar: { overflow: 'hidden', justifyContent: 'center', @@ -2477,7 +2488,7 @@ const styles = (theme: ThemeColors) => }, flipUpsideDown: { - transform: [{rotate: '180deg'}], + transform: `rotate(180deg)`, }, navigationScreenCardStyle: { @@ -2778,7 +2789,7 @@ const styles = (theme: ThemeColors) => alignItems: 'center', flexDirection: 'row', justifyContent: 'space-between', - shadowColor: theme.shadow, + boxShadow: `${theme.shadow}`, ...spacing.p5, }, @@ -2877,7 +2888,7 @@ const styles = (theme: ThemeColors) => }, text: { color: theme.textSupporting, - textAlignVertical: 'center', + verticalAlign: 'middle', fontSize: variables.fontSizeLabel, }, errorDot: { @@ -3218,11 +3229,11 @@ const styles = (theme: ThemeColors) => miniQuickEmojiReactionText: { fontSize: 15, lineHeight: 20, - textAlignVertical: 'center', + verticalAlign: 'middle', }, emojiReactionBubbleText: { - textAlignVertical: 'center', + verticalAlign: 'middle', }, reactionCounterText: { @@ -3420,7 +3431,6 @@ const styles = (theme: ThemeColors) => linkPreviewImage: { flex: 1, - resizeMode: 'contain', borderRadius: 8, marginTop: 8, }, @@ -3788,7 +3798,7 @@ const styles = (theme: ThemeColors) => }, rotate90: { - transform: [{rotate: '90deg'}], + transform: 'rotate(90deg)', }, emojiStatusLHN: {