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

fix stale focusedIndex when maxIndex changed in ArrowKeyFocusManager #18955

Merged
merged 2 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
100 changes: 54 additions & 46 deletions src/components/ArrowKeyFocusManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,55 +33,16 @@ class ArrowKeyFocusManager extends Component {
const arrowUpConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_UP;
const arrowDownConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_DOWN;

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

const currentFocusedIndex = this.props.focusedIndex > 0 ? this.props.focusedIndex - 1 : this.props.maxIndex;
let newFocusedIndex = currentFocusedIndex;

while (this.props.disabledIndexes.includes(newFocusedIndex)) {
newFocusedIndex = newFocusedIndex > 0 ? newFocusedIndex - 1 : this.props.maxIndex;
if (newFocusedIndex === currentFocusedIndex) {
// all indexes are disabled
return; // no-op
}
}

this.props.onFocusedIndexChanged(newFocusedIndex);
},
arrowUpConfig.descriptionKey,
arrowUpConfig.modifiers,
true,
false,
0,
true,
[this.props.shouldExcludeTextAreaNodes && 'TEXTAREA'],
);
this.onArrowUpKey = this.onArrowUpKey.bind(this);
this.onArrowDownKey = this.onArrowDownKey.bind(this);

this.unsubscribeArrowUpKey = KeyboardShortcut.subscribe(arrowUpConfig.shortcutKey, this.onArrowUpKey, arrowUpConfig.descriptionKey, arrowUpConfig.modifiers, true, false, 0, true, [
this.props.shouldExcludeTextAreaNodes && 'TEXTAREA',
]);

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

const currentFocusedIndex = this.props.focusedIndex < this.props.maxIndex ? this.props.focusedIndex + 1 : 0;
let newFocusedIndex = currentFocusedIndex;

while (this.props.disabledIndexes.includes(newFocusedIndex)) {
newFocusedIndex = newFocusedIndex < this.props.maxIndex ? newFocusedIndex + 1 : 0;
if (newFocusedIndex === currentFocusedIndex) {
// all indexes are disabled
return; // no-op
}
}

this.props.onFocusedIndexChanged(newFocusedIndex);
},
this.onArrowDownKey,
arrowDownConfig.descriptionKey,
arrowDownConfig.modifiers,
true,
Expand All @@ -92,6 +53,15 @@ class ArrowKeyFocusManager extends Component {
);
}

componentDidUpdate(prevProps) {
if (prevProps.maxIndex === this.props.maxIndex) {
return;
}
if (this.props.focusedIndex > this.props.maxIndex) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 Coming from #20152
This has caused a small regression, where selecting max number of participants in a list would focus the first participant
(when we select max number of participants, we hide other options from the list, which updates maxIndex, making focusedIndex greater than maxIndex and calling this.onArrowDownKey())
We've added a shouldResetIndexOnEndReached prop to disable this behavior on Invite And NewGroup pages

this.onArrowDownKey();
}
}

componentWillUnmount() {
if (this.unsubscribeArrowUpKey) {
this.unsubscribeArrowUpKey();
Expand All @@ -102,6 +72,44 @@ class ArrowKeyFocusManager extends Component {
}
}

onArrowUpKey() {
if (this.props.maxIndex < 0) {
return;
}

const currentFocusedIndex = this.props.focusedIndex > 0 ? this.props.focusedIndex - 1 : this.props.maxIndex;
let newFocusedIndex = currentFocusedIndex;

while (this.props.disabledIndexes.includes(newFocusedIndex)) {
newFocusedIndex = newFocusedIndex > 0 ? newFocusedIndex - 1 : this.props.maxIndex;
if (newFocusedIndex === currentFocusedIndex) {
// all indexes are disabled
return; // no-op
}
}

this.props.onFocusedIndexChanged(newFocusedIndex);
}

onArrowDownKey() {
if (this.props.maxIndex < 0) {
return;
}

const currentFocusedIndex = this.props.focusedIndex < this.props.maxIndex ? this.props.focusedIndex + 1 : 0;
let newFocusedIndex = currentFocusedIndex;

while (this.props.disabledIndexes.includes(newFocusedIndex)) {
newFocusedIndex = newFocusedIndex < this.props.maxIndex ? newFocusedIndex + 1 : 0;
if (newFocusedIndex === currentFocusedIndex) {
// all indexes are disabled
return; // no-op
}
}

this.props.onFocusedIndexChanged(newFocusedIndex);
}

render() {
return this.props.children;
}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ const defaultProps = {
const getMaxArrowIndex = (numRows, isAutoSuggestionPickerLarge) => {
// EmojiRowCount is number of emoji suggestions. For small screen we can fit 3 items and for large we show up to 5 items
const emojiRowCount = isAutoSuggestionPickerLarge
? Math.max(numRows, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_ITEMS)
: Math.max(numRows, CONST.AUTO_COMPLETE_SUGGESTER.MIN_AMOUNT_OF_ITEMS);
? Math.min(numRows, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_ITEMS)
: Math.min(numRows, CONST.AUTO_COMPLETE_SUGGESTER.MIN_AMOUNT_OF_ITEMS);

// -1 because we start at 0
return emojiRowCount - 1;
Expand Down