From 0b2fbb3936bd387093c80dd7a29e65db442821b5 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 15 Sep 2023 14:21:40 +0700 Subject: [PATCH 01/14] fix: Web - Members page search bar does not get focused --- src/components/SelectionList/BaseSelectionList.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index e54d64fe56e..b9f606cfc2c 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -168,6 +168,7 @@ function BaseSelectionList({ }; const selectRow = (item, index) => { + textInputRef.current.focus(); // In single-selection lists we don't care about updating the focused index, because the list is closed after selecting an item if (canSelectMultiple) { if (sections.length === 1) { From c3f05b2bdd2a174d7f64670763d1d2cc1d3b1d5c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Sep 2023 22:05:18 +0700 Subject: [PATCH 02/14] merge main --- src/components/SelectionList/BaseSelectionList.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index acdbdcfc008..57431b124cb 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -178,10 +178,6 @@ function BaseSelectionList({ * @param {Boolean} shouldUnfocusRow - flag to decide if we should unfocus all rows. True when selecting a row with click or press (not keyboard) */ const selectRow = (item, shouldUnfocusRow = false) => { - if (shouldShowTextInput) { - focusTimeoutRef.current = setTimeout(() => textInputRef.current.focus(), CONST.ANIMATED_TRANSITION); - } - // In single-selection lists we don't care about updating the focused index, because the list is closed after selecting an item if (canSelectMultiple) { if (sections.length > 1) { @@ -205,15 +201,12 @@ function BaseSelectionList({ setFocusedIndex(-1); } } - - return () => { - if (!focusTimeoutRef.current) { - return; - } - clearTimeout(focusTimeoutRef.current); - }; onSelectRow(item); + + if (shouldShowTextInput && textInputRef.current) { + textInputRef.current.focus(); + } }; const selectFocusedOption = () => { From c4869c15ed0ae52d21a9ff175e693cecf13794b7 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Sep 2023 22:24:00 +0700 Subject: [PATCH 03/14] fix lint --- src/components/SelectionList/BaseSelectionList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 57431b124cb..78686f57c6e 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -205,7 +205,7 @@ function BaseSelectionList({ onSelectRow(item); if (shouldShowTextInput && textInputRef.current) { - textInputRef.current.focus(); + textInputRef.current.focus(); } }; From 6b8f1e79d6930710044e8e8790747b1ec60d0f55 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Sep 2023 22:30:14 +0700 Subject: [PATCH 04/14] refocus on select all --- src/components/SelectionList/BaseSelectionList.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 78686f57c6e..ff83f80c4a6 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -209,6 +209,13 @@ function BaseSelectionList({ } }; + const selectAllRow = () => { + onSelectAll(); + if (shouldShowTextInput && textInputRef.current) { + textInputRef.current.focus(); + } + }; + const selectFocusedOption = () => { const focusedOption = flattenedSections.allOptions[focusedIndex]; @@ -362,7 +369,7 @@ function BaseSelectionList({ {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( From 9b06dab28cef4573f2e42bc594e96a1f9cab4e77 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 17:06:32 +0700 Subject: [PATCH 05/14] refocus after removing user --- src/components/SelectionList/BaseSelectionList.js | 9 ++++++++- src/pages/workspace/WorkspaceMembersPage.js | 13 +++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index ff83f80c4a6..7aa4dbdecbe 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -52,6 +52,7 @@ function BaseSelectionList({ showLoadingPlaceholder = false, showConfirmButton = false, isKeyboardShown = false, + inputRef = null, }) { const {translate} = useLocalize(); const firstLayoutRef = useRef(true); @@ -342,7 +343,13 @@ function BaseSelectionList({ {shouldShowTextInput && ( { + if (inputRef) { + // eslint-disable-next-line no-param-reassign + inputRef.current = el; + } + textInputRef.current = el; + }} label={textInputLabel} accessibilityLabel={textInputLabel} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 9eff4eba47e..1c243dd7c43 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -1,7 +1,7 @@ -import React, {useCallback, useEffect, useState, useMemo} from 'react'; +import React, {useCallback, useEffect, useState, useMemo, useRef} from 'react'; import _ from 'underscore'; import lodashGet from 'lodash/get'; -import {View} from 'react-native'; +import {InteractionManager, View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import styles from '../../styles/styles'; @@ -77,6 +77,7 @@ function WorkspaceMembersPage(props) { const prevIsOffline = usePrevious(props.network.isOffline); const accountIDs = useMemo(() => _.keys(props.policyMembers), [props.policyMembers]); const prevAccountIDs = usePrevious(accountIDs); + const textInputRef = useRef(null); /** * Get members for the current workspace @@ -153,6 +154,13 @@ function WorkspaceMembersPage(props) { Policy.removeMembers(accountIDsToRemove, props.route.params.policyID); setSelectedEmployees([]); setRemoveMembersConfirmModalVisible(false); + + InteractionManager.runAfterInteractions(() => { + if (!textInputRef || !textInputRef.current) { + return; + } + textInputRef.current.focus(); + }); }; /** @@ -401,6 +409,7 @@ function WorkspaceMembersPage(props) { onDismissError={dismissError} showLoadingPlaceholder={!OptionsListUtils.isPersonalDetailsReady(props.personalDetails) || _.isEmpty(props.policyMembers)} showScrollIndicator + innputRef={textInputRef} /> From 57876ada49e9f02a285ceee85ade731fe2f18ca5 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 17:52:57 +0700 Subject: [PATCH 06/14] fix refocus after removing user --- src/pages/workspace/WorkspaceMembersPage.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 1c243dd7c43..149fa99c7b9 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -155,11 +155,19 @@ function WorkspaceMembersPage(props) { setSelectedEmployees([]); setRemoveMembersConfirmModalVisible(false); + // Refocus the text input after removing user + // We need to use bot InteractionManager.runAfterInteractions and setTimeout to make sure no animation block focus InteractionManager.runAfterInteractions(() => { if (!textInputRef || !textInputRef.current) { return; } - textInputRef.current.focus(); + const focusTimeout = setTimeout(() => { + textInputRef.current.focus(); + }, CONST.ANIMATED_TRANSITION); + + return () => { + clearTimeout(focusTimeout); + }; }); }; @@ -409,7 +417,7 @@ function WorkspaceMembersPage(props) { onDismissError={dismissError} showLoadingPlaceholder={!OptionsListUtils.isPersonalDetailsReady(props.personalDetails) || _.isEmpty(props.policyMembers)} showScrollIndicator - innputRef={textInputRef} + inputRef={textInputRef} /> From c7943a4859ac7dbcf08fd12df9b51e76c404d2dd Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 22:56:23 +0700 Subject: [PATCH 07/14] refocus whenever the modal is closed --- .../SelectionList/BaseSelectionList.js | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index ff83f80c4a6..8c913a97322 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -1,8 +1,9 @@ -import React, {useCallback, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {useFocusEffect, useIsFocused} from '@react-navigation/native'; +import {withOnyx} from 'react-native-onyx'; import SectionList from '../SectionList'; import Text from '../Text'; import styles from '../../styles/styles'; @@ -24,6 +25,8 @@ import useLocalize from '../../hooks/useLocalize'; import Log from '../../libs/Log'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import useActiveElement from '../../hooks/useActiveElement'; +import ONYXKEYS from '../../ONYXKEYS'; +import compose from '../../libs/compose'; const propTypes = { ...keyboardStatePropTypes, @@ -52,6 +55,7 @@ function BaseSelectionList({ showLoadingPlaceholder = false, showConfirmButton = false, isKeyboardShown = false, + modal, }) { const {translate} = useLocalize(); const firstLayoutRef = useRef(true); @@ -62,7 +66,6 @@ function BaseSelectionList({ const shouldShowSelectAll = Boolean(onSelectAll); const activeElement = useActiveElement(); const isFocused = useIsFocused(); - /** * Iterates through the sections and items inside each section, and builds 3 arrays along the way: * - `allOptions`: Contains all the items in the list, flattened, regardless of section @@ -326,6 +329,14 @@ function BaseSelectionList({ isActive: Boolean(onConfirm) && isFocused, }); + /**Refocus the text input when the modal is closed */ + useEffect(() => { + if (modal.isVisible || !textInputRef || !textInputRef.current || !shouldShowTextInput) { + return; + } + textInputRef.current.focus(); + }, [modal.isVisible]); + return ( Date: Thu, 28 Sep 2023 01:06:49 +0700 Subject: [PATCH 08/14] add prop type to test jest --- src/components/SelectionList/selectionListPropTypes.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index 0a3c1efdf6a..56c8784e3ac 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -156,6 +156,11 @@ const propTypes = { /** Whether to show the default confirm button */ showConfirmButton: PropTypes.bool, + + /** The modal state */ + modal: PropTypes.shape({ + isVisible: PropTypes.bool, + }), }; export {propTypes, radioListItemPropTypes, userListItemPropTypes}; From 344f5cd69a5e5718a98b29f0d03e7bc9d6eed503 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 29 Sep 2023 14:39:00 +0700 Subject: [PATCH 09/14] revert to test jest --- .../SelectionList/BaseSelectionList.js | 23 ++----------------- .../SelectionList/selectionListPropTypes.js | 5 ---- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 982b3ec1156..5d9c9f7ad58 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -1,9 +1,8 @@ -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {useFocusEffect, useIsFocused} from '@react-navigation/native'; -import {withOnyx} from 'react-native-onyx'; import SectionList from '../SectionList'; import Text from '../Text'; import styles from '../../styles/styles'; @@ -25,8 +24,6 @@ import useLocalize from '../../hooks/useLocalize'; import Log from '../../libs/Log'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import useActiveElement from '../../hooks/useActiveElement'; -import ONYXKEYS from '../../ONYXKEYS'; -import compose from '../../libs/compose'; const propTypes = { ...keyboardStatePropTypes, @@ -55,7 +52,6 @@ function BaseSelectionList({ showLoadingPlaceholder = false, showConfirmButton = false, isKeyboardShown = false, - modal, }) { const {translate} = useLocalize(); const firstLayoutRef = useRef(true); @@ -329,14 +325,6 @@ function BaseSelectionList({ isActive: Boolean(onConfirm) && isFocused, }); - /** Refocus the text input when the modal is closed */ - useEffect(() => { - if (modal.isVisible || !textInputRef || !textInputRef.current || !shouldShowTextInput) { - return; - } - textInputRef.current.focus(); - }, [modal.isVisible, shouldShowTextInput]); - return ( Date: Fri, 29 Sep 2023 15:48:44 +0700 Subject: [PATCH 10/14] refocus when confirm modal is closed --- src/components/SelectionList/BaseSelectionList.js | 9 ++++++++- .../SelectionList/selectionListPropTypes.js | 3 +++ src/pages/workspace/WorkspaceMembersPage.js | 14 ++++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 5d9c9f7ad58..b505566cd1b 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -52,6 +52,7 @@ function BaseSelectionList({ showLoadingPlaceholder = false, showConfirmButton = false, isKeyboardShown = false, + inputRef = null, }) { const {translate} = useLocalize(); const firstLayoutRef = useRef(true); @@ -341,7 +342,13 @@ function BaseSelectionList({ {shouldShowTextInput && ( { + if (inputRef) { + // eslint-disable-next-line no-param-reassign + inputRef.current = el; + } + textInputRef.current = el; + }} label={textInputLabel} accessibilityLabel={textInputLabel} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index 0a3c1efdf6a..22a3e6e0918 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -156,6 +156,9 @@ const propTypes = { /** Whether to show the default confirm button */ showConfirmButton: PropTypes.bool, + + /** A ref to forward to the TextInput */ + inputRef: PropTypes.oneOfType([PropTypes.object]), }; export {propTypes, radioListItemPropTypes, userListItemPropTypes}; diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 9eff4eba47e..fa3726ce552 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -1,7 +1,7 @@ -import React, {useCallback, useEffect, useState, useMemo} from 'react'; +import React, {useCallback, useEffect, useState, useMemo, useRef} from 'react'; import _ from 'underscore'; import lodashGet from 'lodash/get'; -import {View} from 'react-native'; +import {InteractionManager, View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import styles from '../../styles/styles'; @@ -77,6 +77,7 @@ function WorkspaceMembersPage(props) { const prevIsOffline = usePrevious(props.network.isOffline); const accountIDs = useMemo(() => _.keys(props.policyMembers), [props.policyMembers]); const prevAccountIDs = usePrevious(accountIDs); + const textInputRef = useRef(null); /** * Get members for the current workspace @@ -370,6 +371,14 @@ function WorkspaceMembersPage(props) { prompt={props.translate('workspace.people.removeMembersPrompt')} confirmText={props.translate('common.remove')} cancelText={props.translate('common.cancel')} + onModalHide={() => + InteractionManager.runAfterInteractions(() => { + if (!textInputRef.current) { + return; + } + textInputRef.current.focus(); + }) + } /> @@ -401,6 +410,7 @@ function WorkspaceMembersPage(props) { onDismissError={dismissError} showLoadingPlaceholder={!OptionsListUtils.isPersonalDetailsReady(props.personalDetails) || _.isEmpty(props.policyMembers)} showScrollIndicator + inputRef={textInputRef} /> From ce1424c7699d1db9972a43334b26757ebb783e5e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 29 Sep 2023 15:52:35 +0700 Subject: [PATCH 11/14] fix jest --- src/pages/workspace/WorkspaceMembersPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index fa3726ce552..92b21880a46 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -376,6 +376,7 @@ function WorkspaceMembersPage(props) { if (!textInputRef.current) { return; } + textInputRef.current.focus(); }) } From a9f05455f6ea73c0c2cdca52d0e48af1646fa530 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 29 Sep 2023 15:53:10 +0700 Subject: [PATCH 12/14] fix/26955 --- src/pages/workspace/WorkspaceMembersPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 92b21880a46..fa3726ce552 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -376,7 +376,6 @@ function WorkspaceMembersPage(props) { if (!textInputRef.current) { return; } - textInputRef.current.focus(); }) } From 4f9a761a1b46f0f3b4a80e4ff1b8f153b118bbb1 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 29 Sep 2023 20:29:17 +0700 Subject: [PATCH 13/14] don't refocus on select the row in mobile web --- src/components/SelectionList/BaseSelectionList.js | 5 +++-- src/components/SelectionList/selectionListPropTypes.js | 3 +++ src/pages/workspace/WorkspaceMembersPage.js | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index b505566cd1b..03ae1d9cf8c 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -51,6 +51,7 @@ function BaseSelectionList({ showScrollIndicator = false, showLoadingPlaceholder = false, showConfirmButton = false, + shouldFocusOnSelectRow = false, isKeyboardShown = false, inputRef = null, }) { @@ -204,14 +205,14 @@ function BaseSelectionList({ onSelectRow(item); - if (shouldShowTextInput && textInputRef.current) { + if (shouldShowTextInput && shouldFocusOnSelectRow && textInputRef.current) { textInputRef.current.focus(); } }; const selectAllRow = () => { onSelectAll(); - if (shouldShowTextInput && textInputRef.current) { + if (shouldShowTextInput && shouldFocusOnSelectRow && textInputRef.current) { textInputRef.current.focus(); } }; diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index 22a3e6e0918..f1fbbdf2244 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -157,6 +157,9 @@ const propTypes = { /** Whether to show the default confirm button */ showConfirmButton: PropTypes.bool, + /** Whether to focus the textinput after an option is selected */ + shouldFocusOnSelectRow: PropTypes.bool, + /** A ref to forward to the TextInput */ inputRef: PropTypes.oneOfType([PropTypes.object]), }; diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index fa3726ce552..5ab6027eb4f 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -31,6 +31,7 @@ import Log from '../../libs/Log'; import * as PersonalDetailsUtils from '../../libs/PersonalDetailsUtils'; import SelectionList from '../../components/SelectionList'; import Text from '../../components/Text'; +import * as Browser from '../../libs/Browser'; const propTypes = { /** All personal details asssociated with user */ @@ -410,6 +411,7 @@ function WorkspaceMembersPage(props) { onDismissError={dismissError} showLoadingPlaceholder={!OptionsListUtils.isPersonalDetailsReady(props.personalDetails) || _.isEmpty(props.policyMembers)} showScrollIndicator + shouldFocusOnSelectRow={!Browser.isMobile()} inputRef={textInputRef} /> From c5c8206d197d29f756e6ae5e14e88d28bc43c463 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 2 Oct 2023 20:22:42 +0700 Subject: [PATCH 14/14] fix lint --- src/components/SelectionList/selectionListPropTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index d7b62c6716d..0d7a60e78a1 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -172,7 +172,7 @@ const propTypes = { /** A ref to forward to the TextInput */ inputRef: PropTypes.oneOfType([PropTypes.object]), - + /** Custom content to display in the footer */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), };