Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standardize arrow navigation in OptionsLists #7702

Merged
merged 132 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
132 commits
Select commit Hold shift + click to select a range
e47da89
Remove unnecessary arrow function
roryabraham Feb 10, 2022
ad207d8
Fix arrow key navigation for new chat and new group pages
roryabraham Feb 10, 2022
a8788b9
Fix arrow key navigation on IOUParticipantsPage
roryabraham Feb 10, 2022
3ea199f
Clean up SettlementButton onPress
roryabraham Feb 10, 2022
203d9ef
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Feb 10, 2022
770ae54
Get naive working implementation in IOUConfirmationList
roryabraham Feb 12, 2022
46a4a20
Implement flattenSections in O(n) instead of O(n^2)
roryabraham Feb 12, 2022
40dd812
Ditch the deprecated KeyboardEvent.keyCode
roryabraham Feb 12, 2022
2f1f1b8
Use KeyboardShortcuts lib instead of onKeyPress for OptionsSelector
roryabraham Feb 12, 2022
91c813e
Move keyboard event shim to platformSetup
roryabraham Feb 14, 2022
e4ae1a5
Deprecate disableArrowKeysActions
roryabraham Feb 14, 2022
c89e442
Create withArrowKeyListTraversal HOC
roryabraham Feb 14, 2022
851a8f0
Pass focusedIndex to wrapped component
roryabraham Feb 14, 2022
d2d5a50
Convert HOC to regular component
roryabraham Feb 14, 2022
86a4d48
Implement ArrowKeyFocusManager in OptionsSelector
roryabraham Feb 14, 2022
2d7ed4e
Implement integerPropType and allow key events to bubble
roryabraham Feb 15, 2022
5225c33
Implement ArrowKeyFocusManager in IOUConfirmationList
roryabraham Feb 15, 2022
a3c4730
Fix off-by-one error in NewGroupPage and don't show selected options …
roryabraham Feb 15, 2022
8e9aef4
Optimize allOptions computation in OptionsSelector
roryabraham Feb 15, 2022
1f6a301
Fix JS style
roryabraham Feb 15, 2022
410b0f8
Cleanup and optimize IOUConfirmationList
roryabraham Feb 15, 2022
b12e61e
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Feb 15, 2022
d0882bb
Get rid of unnecessary arrow function in render
roryabraham Feb 15, 2022
65de321
Make enter key create new group if search bar is focused
roryabraham Feb 15, 2022
6e9efea
Don't scroll to top if we can't select multiple options
roryabraham Feb 15, 2022
b738d4e
Make CMD+Enter create a group
roryabraham Feb 16, 2022
c731eff
Fix KeyboardEvent unsubscribe
roryabraham Feb 16, 2022
6933b0e
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Feb 18, 2022
437575a
Fix indexOffset on IOUParticipantsRequest and IOUParticipantsSplit
roryabraham Feb 18, 2022
c28e99c
Move some translations to common
roryabraham Feb 19, 2022
2a845f1
Rename events to eventHandlers in KeyboardShortcut lib
roryabraham Feb 19, 2022
2624935
Update KeyboardShortcut lib to map callbacks by key+modifier combo
roryabraham Feb 19, 2022
81cb74b
(draft state) move group selection confirm button into OptionsSelector
roryabraham Feb 19, 2022
4b7fc98
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Feb 23, 2022
5a8eba7
Move allOptions to state
roryabraham Feb 24, 2022
318a161
Fix shift key modifier
roryabraham Feb 24, 2022
c9d7797
Make CMD+Enter work for New Chat page
roryabraham Feb 24, 2022
bac85e5
Make CMD+Enter work for IOUConfirmationList
roryabraham Feb 24, 2022
5f9b731
Make CMD+Enter work for WorkspaceInvitePage
roryabraham Feb 24, 2022
72cd111
Fix JS style
roryabraham Feb 24, 2022
7ced82a
Move count to the end of maxParticipantsReached UI string
roryabraham Feb 24, 2022
e0a106d
Get rid of NumberUtils library
roryabraham Feb 24, 2022
42dbb04
Rename maybeToggleIndex to maybeToggleParticipant
roryabraham Feb 25, 2022
12742f8
Make enter key confirm IOU if there's only one participant
roryabraham Feb 25, 2022
da17bce
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Mar 2, 2022
12efe8f
Fix scrolling to first item in second section
roryabraham Mar 2, 2022
a2d4917
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Mar 2, 2022
a7d246e
Fix bad merge
roryabraham Mar 2, 2022
19afbfa
Fix PropTypes comments
roryabraham Mar 2, 2022
33f1f69
Use reject instead of filter
roryabraham Mar 2, 2022
93e7c5a
Improve comment in KeyboardShortcut module
roryabraham Mar 2, 2022
d0db2f9
Remove unused variable
roryabraham Mar 2, 2022
297d675
Remove unnecessary fragment
roryabraham Mar 2, 2022
ba2a9ac
Fix bad merge (again)
roryabraham Mar 2, 2022
8fa9378
Another key priority comment
roryabraham Mar 2, 2022
944d657
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Mar 25, 2022
9643c4e
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Apr 6, 2022
dd1ed1a
Remove unused state field
roryabraham Apr 6, 2022
86b7b73
Remove hack to adjust item index
roryabraham Apr 6, 2022
0d15439
Fix JS style
roryabraham Apr 6, 2022
61d757f
Get rid of deprecated getShortcutModifiers
roryabraham Apr 7, 2022
3b70fb0
Require shortcut modifiers to be an array
roryabraham Apr 7, 2022
4e13c02
Disable deselection of IOU participants if split initiated in global …
roryabraham Apr 7, 2022
fa8d3fc
Rename canDeselectParticipants to canModifyParticipants
roryabraham Apr 7, 2022
874265d
Do not allow splits with yourself
roryabraham Apr 7, 2022
07ba516
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Apr 11, 2022
301f3ae
Add keyboard shortcut for CTRL_ENTER
roryabraham Apr 11, 2022
8ec2a5e
Adjust focus when list items change
roryabraham Apr 11, 2022
1c7cdaa
Fix JS crash
roryabraham Apr 13, 2022
01ae6e0
Fix auto-focus jank
roryabraham Apr 13, 2022
baea43e
auto-scroll to first unselected option
roryabraham Apr 13, 2022
908b18e
Get rid of PropTypeUtils
roryabraham Apr 14, 2022
57d9f75
Remove unused getFocusedIndex
roryabraham Apr 14, 2022
51faacf
Cleanup setState style
roryabraham Apr 14, 2022
8ef6923
simplify shouldEnterKeyEventBubble
roryabraham Apr 14, 2022
4c06345
Get rid of unrelated SettlementButton changes
roryabraham Apr 14, 2022
21545f7
Remove redundant comment
roryabraham Apr 14, 2022
90941d6
Use reduce instead of groupBy
roryabraham Apr 14, 2022
0a7f4a3
Simplify callbacks in NewChatPage
roryabraham Apr 15, 2022
132c824
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Apr 15, 2022
2ad0ee3
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Apr 18, 2022
a3845b4
Make ArrowKeyFocusManager fully managed
roryabraham Apr 18, 2022
d002b0a
Simplify rendering children in ArrowKeyFocusManager
roryabraham Apr 18, 2022
d63a5ba
Fix JS style
roryabraham Apr 18, 2022
2dfae82
Extract enter key config from ArrowKeyFocusManager
roryabraham Apr 18, 2022
27e1900
Save draft state
roryabraham Apr 20, 2022
65b3e7a
Merge branch 'main' into Rory-CombineIOUConfirmationListAndOptionsSel…
roryabraham May 5, 2022
64672b1
Fix lastActorDetails JS crash
roryabraham May 5, 2022
2643e27
Fix IOUSelectionList footer style
roryabraham May 5, 2022
41b15c4
Add disableArrowKeyActions back for unmodifiable participants lists
roryabraham May 5, 2022
f3177cc
DRY up enter keyboard shortcut
roryabraham May 5, 2022
e28b109
Remove unused updateFocusedIndex member function
roryabraham May 5, 2022
03813cf
Remove unused allOptions and scrollToIndex
roryabraham May 5, 2022
211e1d2
DRY up CTRLEnter shortcut
roryabraham May 5, 2022
adf1241
Remove unnecessary componentWillUnmount
roryabraham May 5, 2022
0f42e59
DRY up text input delayed focus
roryabraham May 5, 2022
ba6802b
Simplify diff
roryabraham May 5, 2022
82216ad
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham May 6, 2022
7c82064
Move flattenSections out of OptionsListUtils and into OptionsSelector…
roryabraham May 6, 2022
6796e1b
Update focusManager code'
roryabraham May 6, 2022
7063fc2
Put allOptions back in state, clean up componentDidUpdate
roryabraham May 6, 2022
55d73b6
Fix shouldShowOptions
roryabraham May 6, 2022
48746c8
Fix text input style when under OptionsList
roryabraham May 6, 2022
ebb94d7
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham May 26, 2022
64692f7
Make onFocusedIndexChanged required
roryabraham May 26, 2022
3c978bd
Fix bad merge
roryabraham May 26, 2022
138ac7a
Focus the next best result when an option is selected
roryabraham May 26, 2022
527bad4
Fix missing button on 1:1 request confirmation page
roryabraham May 26, 2022
896649e
DRY up and simplify IOUCurrencySelection
roryabraham May 26, 2022
c53ada3
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham May 31, 2022
8c5e6de
Dont execute callback until after determininig shouldBubble
roryabraham May 31, 2022
a658b0b
Implement getItemLayout to fix navigation on long lists
roryabraham Jun 1, 2022
993c910
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Jun 1, 2022
773602e
Address low-hanging fruit
roryabraham Jun 2, 2022
10d8d5e
Get rid of unnecessary changes to state in IOUConfirmationList
roryabraham Jun 2, 2022
b01d1ac
Add back local isLoading variable
roryabraham Jun 2, 2022
71d3e43
Fix getItemLayout
roryabraham Jun 3, 2022
5f149ef
Add an implementation note about headers and footers
roryabraham Jun 3, 2022
337b365
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Jun 3, 2022
4336e8c
Improve brittle comment
roryabraham Jun 3, 2022
f53ecc0
Merge branch main into Rory-SearchResultsArrowNavigation
roryabraham Jun 10, 2022
fa322c6
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Jun 11, 2022
1a3c15c
Revert unneeded change in OptionsList
roryabraham Jun 11, 2022
0a187fd
Fix newFocusedIndex
roryabraham Jun 11, 2022
1510dcd
Merge branch 'main' into Rory-SearchResultsArrowNavigation
roryabraham Jun 20, 2022
066b0a5
Implement https://github.com/Expensify/App/pull/9404 in new location
roryabraham Jun 20, 2022
93bc317
Clean up getItemLayout JSDoc
roryabraham Jun 23, 2022
1ddb135
Rename variables and improve comments in getItemLayout`
roryabraham Jun 23, 2022
f1bfcf8
Use memoization and build flat data array
roryabraham Jun 24, 2022
4bd7bfd
Remove unnecessary onScrollToIndexFailed
roryabraham Jun 24, 2022
9996b46
Fix deletion crash
roryabraham Jun 24, 2022
c9cf004
Get rid of restructuring alias
roryabraham Jun 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"react-web-config": "^1.0.0",
"rn-fetch-blob": "^0.12.0",
"save": "^2.4.0",
"shim-keyboard-event-key": "^1.0.3",
"smoothscroll-polyfill": "^0.4.4",
"underscore": "^1.13.1",
"urbanairship-react-native": "^11.0.2"
Expand Down
16 changes: 13 additions & 3 deletions src/CONST/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,21 @@ const CONST = {
shortcutKey: 'C',
modifiers: ['CTRL'],
},
ARROW_UP: {
descriptionKey: null,
shortcutKey: 'ArrowUp',
modifiers: [],
},
ARROW_DOWN: {
descriptionKey: null,
shortcutKey: 'ArrowDown',
modifiers: [],
},
},
KEYBOARD_SHORTCUT_KEY_DISPLAY_NAME: {
CONTROL: 'Ctrl',
META: 'Cmd',
SHIFT: 'Shift',
CONTROL: 'CTRL',
META: 'CMD',
SHIFT: 'SHIFT',
},
CURRENCY: {
USD: 'USD',
Expand Down
139 changes: 139 additions & 0 deletions src/components/ArrowKeyFocusManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import _ from 'underscore';
import {Component} from 'react';
import PropTypes from 'prop-types';
import * as PropTypeUtils from '../libs/PropTypeUtils';
import CONST from '../CONST';
import KeyboardShortcut from '../libs/KeyboardShortcut';

const propTypes = {
/** Children to render. */
children: PropTypes.oneOfType([
PropTypes.func,
PropTypes.node,
]).isRequired,

/** The initial focused index. */
initialFocusedIndex: PropTypeUtils.integerPropType,

/** The number of items in the list that this component is managing focus for. */
listLength: PropTypeUtils.wholeNumberPropType,

/** A callback executed when the focused input changes. */
onFocusedIndexChanged: PropTypes.func,

/** A callback executed when the enter key is pressed. */
onEnterKeyPressed: PropTypes.func,

/** Should the enter key event bubble? */
shouldEnterKeyEventBubble: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
};

const defaultProps = {
initialFocusedIndex: 0,
listLength: 0,
onFocusedIndexChanged: () => {},
onEnterKeyPressed: undefined,
shouldEnterKeyEventBubble: false,
};

class ArrowKeyFocusManager extends Component {
constructor(props) {
super(props);
this.state = {
focusedIndex: props.initialFocusedIndex,
};
this.shouldEnterKeyEventBubble = this.shouldEnterKeyEventBubble.bind(this);
}

componentDidMount() {
const enterConfig = CONST.KEYBOARD_SHORTCUTS.ENTER;
const arrowUpConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_UP;
const arrowDownConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_DOWN;

if (this.props.onEnterKeyPressed) {
this.unsubscribeEnterKey = KeyboardShortcut.subscribe(enterConfig.shortcutKey, () => {
this.props.onEnterKeyPressed(this.state.focusedIndex);
}, enterConfig.descriptionKey, enterConfig.modifiers, true, this.shouldEnterKeyEventBubble);
}

this.unsubscribeArrowUpKey = KeyboardShortcut.subscribe(arrowUpConfig.shortcutKey, () => {
if (this.props.listLength <= 1) {
return;
}

this.setState(
(prevState) => {
let newFocusedIndex = prevState.focusedIndex - 1;

// Wrap around to the bottom of the list
if (newFocusedIndex < 0) {
newFocusedIndex = this.props.listLength - 1;
}

return {focusedIndex: newFocusedIndex};
},
() => this.props.onFocusedIndexChanged(this.state.focusedIndex),
);
}, arrowUpConfig.descriptionKey, arrowUpConfig.modifiers, true);

this.unsubscribeArrowDownKey = KeyboardShortcut.subscribe(arrowDownConfig.shortcutKey, () => {
if (this.props.listLength <= 1) {
return;
}

this.setState(
(prevState) => {
let newFocusedIndex = prevState.focusedIndex + 1;

// Wrap around to the top of the list
if (newFocusedIndex > this.props.listLength - 1) {
newFocusedIndex = 0;
}

return {focusedIndex: newFocusedIndex};
},
() => this.props.onFocusedIndexChanged(this.state.focusedIndex),
);
}, arrowDownConfig.descriptionKey, arrowDownConfig.modifiers, true);
}

componentWillUnmount() {
if (this.unsubscribeEnterKey) {
this.unsubscribeEnterKey();
}

if (this.unsubscribeArrowUpKey) {
this.unsubscribeArrowUpKey();
}

if (this.unsubscribeArrowDownKey) {
this.unsubscribeArrowDownKey();
}
}

/**
* @returns {Boolean}
*/
shouldEnterKeyEventBubble() {
if (_.isFunction(this.props.shouldEnterKeyEventBubble)) {
const shouldBubble = this.props.shouldEnterKeyEventBubble(this.state.focusedIndex);
if (!_.isBoolean(shouldBubble)) {
throw new Error('shouldEnterKeyEventBubble prop did not return a boolean');
}
return shouldBubble;
}

return this.props.shouldEnterKeyEventBubble;
}

render() {
return _.isFunction(this.props.children)
? this.props.children({focusedIndex: this.state.focusedIndex})
: this.props.children;
}
}

ArrowKeyFocusManager.propTypes = propTypes;
ArrowKeyFocusManager.defaultProps = defaultProps;

export default ArrowKeyFocusManager;
6 changes: 5 additions & 1 deletion src/components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const propTypes = {
/** Call the onPress function when Enter key is pressed */
pressOnEnter: PropTypes.bool,

/** The priority to assign the enter key event listener */
enterKeyEventListenerPriority: PropTypes.number,

/** Additional styles to add after local styles. Applied to Pressable portion of button */
style: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.object),
Expand Down Expand Up @@ -88,6 +91,7 @@ const defaultProps = {
onPressIn: () => {},
onPressOut: () => {},
pressOnEnter: false,
enterKeyEventListenerPriority: 0,
style: [],
innerStyles: [],
textStyles: [],
Expand Down Expand Up @@ -119,7 +123,7 @@ class Button extends Component {
return;
}
this.props.onPress();
}, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true);
}, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true, false, this.props.enterKeyEventListenerPriority);
}

componentWillUnmount() {
Expand Down
Loading