Skip to content

Commit

Permalink
Merge pull request #3871 from parasharrajat/emoji-picker
Browse files Browse the repository at this point in the history
fix: picker an arrow navigation
  • Loading branch information
roryabraham authored Jul 9, 2021
2 parents 0be2f95 + fbb073e commit 0287c8c
Showing 1 changed file with 47 additions and 30 deletions.
77 changes: 47 additions & 30 deletions src/pages/home/report/EmojiPickerMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ class EmojiPickerMenu extends Component {
this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300);
this.highlightAdjacentEmoji = this.highlightAdjacentEmoji.bind(this);
this.scrollToHighlightedIndex = this.scrollToHighlightedIndex.bind(this);
this.toggleArrowKeysOnSearchInput = this.toggleArrowKeysOnSearchInput.bind(this);
this.setupEventHandlers = this.setupEventHandlers.bind(this);
this.cleanupEventHandlers = this.cleanupEventHandlers.bind(this);
this.renderItem = this.renderItem.bind(this);
Expand Down Expand Up @@ -102,20 +101,31 @@ class EmojiPickerMenu extends Component {
if (document) {
this.keyDownHandler = (keyBoardEvent) => {
if (keyBoardEvent.key.startsWith('Arrow')) {
// Depending on the position of the highlighted emoji after moving and rendering,
// toggle which arrow keys can affect the cursor position in the search input.
this.toggleArrowKeysOnSearchInput(keyBoardEvent);

// Move the highlight when arrow keys are pressed
this.highlightAdjacentEmoji(keyBoardEvent.key);
return;
}

// Select the currently highlighted emoji if enter is pressed
if (keyBoardEvent.key === 'Enter' && this.state.highlightedIndex !== -1) {
this.props.onEmojiSelected(this.state.filteredEmojis[this.state.highlightedIndex].code);
return;
}

// We allow typing in the search box if any key is pressed apart from Arrow keys.
if (this.searchInput && !this.searchInput.isFocused()) {
this.setState({selectTextOnFocus: false});
this.searchInput.value = '';
this.searchInput.focus();

// Re-enable selection on the searchInput
this.setState({selectTextOnFocus: true});
}
};
document.addEventListener('keydown', this.keyDownHandler);

// Keyboard events are not bubbling on TextInput in RN-Web, Bubbling was needed for this event to trigger
// event handler attached to document root. To fix this, trigger event handler in Capture phase.
document.addEventListener('keydown', this.keyDownHandler, true);

// Re-enable pointer events and hovering over EmojiPickerItems when the mouse moves
this.mouseMoveHandler = () => {
Expand All @@ -132,7 +142,7 @@ class EmojiPickerMenu extends Component {
*/
cleanupEventHandlers() {
if (document) {
document.removeEventListener('keydown', this.keyDownHandler);
document.removeEventListener('keydown', this.keyDownHandler, true);
document.removeEventListener('mousemove', this.mouseMoveHandler);
}
}
Expand All @@ -144,6 +154,20 @@ class EmojiPickerMenu extends Component {
highlightAdjacentEmoji(arrowKey) {
const firstNonHeaderIndex = this.state.filteredEmojis.length === this.emojis.length ? this.numColumns : 0;

// Arrow Down enable arrow navigation when search is focused
if (this.searchInput && this.searchInput.isFocused() && this.state.filteredEmojis.length) {
if (arrowKey !== 'ArrowDown') {
return;
}
this.searchInput.blur();

// We only want to hightlight the Emoji if none was highlighted already
// If we already have a highlighted Emoji, lets just skip the first navigation
if (this.state.highlightedIndex !== -1) {
return;
}
}

// If nothing is highlighted and an arrow key is pressed
// select the first emoji
if (this.state.highlightedIndex === -1) {
Expand All @@ -153,8 +177,9 @@ class EmojiPickerMenu extends Component {
}

let newIndex = this.state.highlightedIndex;
const move = (steps, boundsCheck) => {
const move = (steps, boundsCheck, onBoundReached = () => {}) => {
if (boundsCheck()) {
onBoundReached();
return;
}

Expand All @@ -179,7 +204,19 @@ class EmojiPickerMenu extends Component {
move(1, () => this.state.highlightedIndex + 1 > this.state.filteredEmojis.length - 1);
break;
case 'ArrowUp':
move(-this.numColumns, () => this.state.highlightedIndex - this.numColumns < firstNonHeaderIndex);
move(
-this.numColumns,
() => this.state.highlightedIndex - this.numColumns < firstNonHeaderIndex,
() => {
if (!this.searchInput) {
return;
}

// Reaching start of the list, arrow up set the focus to searchInput.
this.searchInput.focus();
newIndex = -1;
},
);
break;
default:
break;
Expand Down Expand Up @@ -257,27 +294,6 @@ class EmojiPickerMenu extends Component {
this.setState({filteredEmojis: newFilteredEmojiList, headerIndices: [], highlightedIndex: 0});
}

/**
* Toggles which arrow keys can affect the cursor in the search input,
* depending on whether the arrow keys will affect the index of the highlighted emoji.
*
* @param {KeyboardEvent} arrowKeyBoardEvent
*/
toggleArrowKeysOnSearchInput(arrowKeyBoardEvent) {
let keysToIgnore = ['ArrowDown', 'ArrowRight', 'ArrowLeft', 'ArrowUp'];
if (this.state.highlightedIndex === 0 && this.state.filteredEmojis.length) {
keysToIgnore = ['ArrowDown', 'ArrowRight'];
} else if (this.state.highlightedIndex === this.state.filteredEmojis.length - 1) {
keysToIgnore = ['ArrowLeft', 'ArrowUp'];
}

// Moving the cursor is the default behavior for arrow key presses while an input is focused,
// so prevent it
if (keysToIgnore.includes(arrowKeyBoardEvent.key)) {
arrowKeyBoardEvent.preventDefault();
}
}

/**
* Given an emoji item object, render a component based on its type.
* Items with the code "SPACER" return nothing and are used to fill rows up to 8
Expand Down Expand Up @@ -329,6 +345,7 @@ class EmojiPickerMenu extends Component {
defaultValue=""
ref={el => this.searchInput = el}
autoFocus
selectTextOnFocus={this.state.selectTextOnFocus}
/>
</View>
)}
Expand Down

0 comments on commit 0287c8c

Please sign in to comment.