diff --git a/src/components/Button/index.js b/src/components/Button/index.js index c16860344837..fb48ede28e16 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -306,6 +306,7 @@ class Button extends Component { ]} nativeID={this.props.nativeID} accessibilityLabel={this.props.accessibilityLabel} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} hoverDimmingValue={1} > {this.renderContent()} diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index e57f00e1849c..f1de4a04e78d 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -136,6 +136,9 @@ function BaseSelectionList({ }; }, [canSelectMultiple, sections]); + // Disable `Enter` hotkey if the active element is a button or checkbox + const shouldDisableHotkeys = activeElement && [CONST.ACCESSIBILITY_ROLE.BUTTON, CONST.ACCESSIBILITY_ROLE.CHECKBOX].includes(activeElement.role); + // If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member const [focusedIndex, setFocusedIndex] = useState(() => _.findIndex(flattenedSections.allOptions, (option) => option.keyForList === initiallyFocusedOptionKey)); @@ -168,23 +171,35 @@ function BaseSelectionList({ listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight}); }; - const selectRow = (item, index) => { + /** + * Logic to run when a row is selected, either with click/press or keyboard hotkeys. + * + * @param {Object} item - the list item + * @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) => { // 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) { - // If the list has only 1 section (e.g. Workspace Members list), we always focus the next available item - const nextAvailableIndex = _.findIndex(flattenedSections.allOptions, (option, i) => i > index && !option.isDisabled); - setFocusedIndex(nextAvailableIndex); - } else { - // If the list has multiple sections (e.g. Workspace Invite list), we focus the first one after all the selected (selected items are always at the top) + if (sections.length > 1) { + // If the list has only 1 section (e.g. Workspace Members list), we do nothing. + // If the list has multiple sections (e.g. Workspace Invite list), and `shouldUnfocusRow` is false, + // we focus the first one after all the selected (selected items are always at the top). const selectedOptionsCount = item.isSelected ? flattenedSections.selectedOptions.length - 1 : flattenedSections.selectedOptions.length + 1; - setFocusedIndex(selectedOptionsCount); + + if (!shouldUnfocusRow) { + setFocusedIndex(selectedOptionsCount); + } if (!item.isSelected) { // If we're selecting an item, scroll to it's position at the top, so we can see it scrollToIndex(Math.max(selectedOptionsCount - 1, 0), true); } } + + if (shouldUnfocusRow) { + // Unfocus all rows when selecting row with click/press + setFocusedIndex(-1); + } } onSelectRow(item); @@ -197,7 +212,7 @@ function BaseSelectionList({ return; } - selectRow(focusedOption, focusedIndex); + selectRow(focusedOption); }; /** @@ -254,7 +269,7 @@ function BaseSelectionList({ selectRow(item, index)} + onSelectRow={() => selectRow(item, true)} onDismissError={onDismissError} showTooltip={showTooltip} /> @@ -266,7 +281,7 @@ function BaseSelectionList({ item={item} isFocused={isItemFocused} isDisabled={isDisabled} - onSelectRow={() => selectRow(item, index)} + onSelectRow={() => selectRow(item, true)} /> ); }; @@ -290,7 +305,7 @@ function BaseSelectionList({ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, { captureOnInputs: true, shouldBubble: () => !flattenedSections.allOptions[focusedIndex], - isActive: !activeElement && isFocused, + isActive: !shouldDisableHotkeys && isFocused, }); /** Calls confirm action when pressing CTRL (CMD) + Enter */ diff --git a/src/components/SelectionList/RadioListItem.js b/src/components/SelectionList/RadioListItem.js index df022992e24a..530af66d91d3 100644 --- a/src/components/SelectionList/RadioListItem.js +++ b/src/components/SelectionList/RadioListItem.js @@ -18,7 +18,6 @@ function RadioListItem({item, isFocused = false, isDisabled = false, onSelectRow accessibilityRole="button" hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} > diff --git a/src/components/SelectionList/UserListItem.js b/src/components/SelectionList/UserListItem.js index 014e0cf879a5..98241c91deb1 100644 --- a/src/components/SelectionList/UserListItem.js +++ b/src/components/SelectionList/UserListItem.js @@ -62,7 +62,6 @@ function UserListItem({item, isFocused = false, showTooltip, onSelectRow, onDism accessibilityState={{checked: item.isSelected}} hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} > diff --git a/src/hooks/useActiveElement/index.js b/src/hooks/useActiveElement/index.js index c973eb8eda18..0db0ed604067 100644 --- a/src/hooks/useActiveElement/index.js +++ b/src/hooks/useActiveElement/index.js @@ -1,5 +1,11 @@ import {useEffect, useState} from 'react'; +/** + * Listens for the focusin and focusout events and sets the DOM activeElement to the state. + * On native, we just return null. + * + * @return {Element} the active element in the DOM + */ export default function useActiveElement() { const [active, setActive] = useState(document.activeElement); diff --git a/src/hooks/useActiveElement/index.native.js b/src/hooks/useActiveElement/index.native.js index 2f658d48ca9a..afdfe8a047e4 100644 --- a/src/hooks/useActiveElement/index.native.js +++ b/src/hooks/useActiveElement/index.native.js @@ -1,3 +1,8 @@ +/** + * Native doesn't have the DOM, so we just return null. + * + * @return {null} + */ export default function useActiveElement() { return null; }