Skip to content
This repository has been archived by the owner on Jun 11, 2021. It is now read-only.

Commit

Permalink
feat(core): allow input pause in keyboard navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
francoischalifour committed Feb 14, 2020
1 parent 0f4101b commit 0000499
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 44 deletions.
6 changes: 3 additions & 3 deletions packages/autocomplete-core/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export function getCompletion<TItem>({
props,
}: GetCompletionProps<TItem>): string | null {
if (
!props.showCompletion ||
state.highlightedIndex < 0 ||
!state.isOpen ||
props.showCompletion === false ||
state.isOpen === false ||
state.highlightedIndex === null ||
state.status === 'stalled'
) {
return null;
Expand Down
4 changes: 2 additions & 2 deletions packages/autocomplete-core/defaultProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function getDefaultProps<TItem>(
minLength: 1,
placeholder: '',
autoFocus: false,
defaultHighlightedIndex: 0,
defaultHighlightedIndex: null,
showCompletion: false,
stallThreshold: 300,
environment,
Expand All @@ -31,7 +31,7 @@ export function getDefaultProps<TItem>(
id: props.id ?? generateAutocompleteId(),
// The following props need to be deeply defaulted.
initialState: {
highlightedIndex: 0,
highlightedIndex: null,
query: '',
suggestions: [],
isOpen: false,
Expand Down
6 changes: 3 additions & 3 deletions packages/autocomplete-core/onKeyDown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function onKeyDown<TItem>({
);
nodeItem?.scrollIntoView(false);

if (store.getState().highlightedIndex >= 0) {
if (store.getState().highlightedIndex !== null) {
const { item, itemValue, itemUrl, source } = getHighlightedItem({
state: store.getState(),
});
Expand Down Expand Up @@ -76,7 +76,7 @@ export function onKeyDown<TItem>({
(event.target as HTMLInputElement).selectionStart ===
store.getState().query.length)) &&
props.showCompletion &&
store.getState().highlightedIndex >= 0
store.getState().highlightedIndex !== null
) {
event.preventDefault();

Expand Down Expand Up @@ -117,7 +117,7 @@ export function onKeyDown<TItem>({
} else if (event.key === 'Enter') {
// No item is selected, so we let the browser handle the native `onSubmit`
// form event.
if (store.getState().highlightedIndex < 0) {
if (store.getState().highlightedIndex === null) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/autocomplete-core/propGetters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export function getPropGetters<TItem>({
return {
'aria-autocomplete': props.showCompletion ? 'both' : 'list',
'aria-activedescendant':
store.getState().isOpen && store.getState().highlightedIndex >= 0
store.getState().isOpen && store.getState().highlightedIndex !== null
? `${props.id}-item-${store.getState().highlightedIndex}`
: null,
'aria-controls': store.getState().isOpen ? `${props.id}-menu` : null,
Expand Down Expand Up @@ -228,7 +228,7 @@ export function getPropGetters<TItem>({
);
props.onStateChange({ state: store.getState() });

if (store.getState().highlightedIndex >= 0) {
if (store.getState().highlightedIndex !== null) {
const { item, itemValue, itemUrl, source } = getHighlightedItem({
state: store.getState(),
});
Expand Down
16 changes: 9 additions & 7 deletions packages/autocomplete-core/stateReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ export const stateReducer = <TItem>(
return {
...state,
highlightedIndex: getNextHighlightedIndex(
action.value.shiftKey ? 5 : 1,
1,
state.highlightedIndex,
getItemsCount(state)
getItemsCount(state),
props.defaultHighlightedIndex
),
};
}
Expand All @@ -92,9 +93,10 @@ export const stateReducer = <TItem>(
return {
...state,
highlightedIndex: getNextHighlightedIndex(
action.value.shiftKey ? -5 : -1,
-1,
state.highlightedIndex,
getItemsCount(state)
getItemsCount(state),
props.defaultHighlightedIndex
),
};
}
Expand All @@ -119,7 +121,7 @@ export const stateReducer = <TItem>(
case 'submit': {
return {
...state,
highlightedIndex: -1,
highlightedIndex: null,
isOpen: false,
status: 'idle',
statusContext: {},
Expand All @@ -129,7 +131,7 @@ export const stateReducer = <TItem>(
case 'reset': {
return {
...state,
highlightedIndex: -1,
highlightedIndex: null,
isOpen: false,
status: 'idle',
statusContext: {},
Expand All @@ -151,7 +153,7 @@ export const stateReducer = <TItem>(
return {
...state,
isOpen: __DEV__ ? state.isOpen : false,
highlightedIndex: -1,
highlightedIndex: null,
};
}

Expand Down
6 changes: 3 additions & 3 deletions packages/autocomplete-core/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ export interface PublicAutocompleteOptions<TItem> {
/**
* The default item index to pre-select.
*
* @default 0
* @default null
*/
defaultHighlightedIndex?: number;
defaultHighlightedIndex?: number | null;
/**
* Whether to show the highlighted suggestion as completion in the input.
*
Expand Down Expand Up @@ -217,7 +217,7 @@ export interface AutocompleteOptions<TItem> {
onStateChange<TItem>(props: { state: AutocompleteState<TItem> }): void;
placeholder: string;
autoFocus: boolean;
defaultHighlightedIndex: number;
defaultHighlightedIndex: number | null;
showCompletion: boolean;
minLength: number;
stallThreshold: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/autocomplete-core/types/state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AutocompleteSuggestion } from './api';

export interface AutocompleteState<TItem> {
highlightedIndex: number;
highlightedIndex: number | null;
query: string;
suggestions: Array<AutocompleteSuggestion<TItem>>;
isOpen: boolean;
Expand Down
45 changes: 25 additions & 20 deletions packages/autocomplete-core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
AutocompleteSource,
GetSources,
AutocompleteSuggestion,
AutocompleteOptions,
} from './types';

export const noop = () => {};
Expand Down Expand Up @@ -70,30 +71,34 @@ export function normalizeGetSources<TItem>(
};
}

export function getNextHighlightedIndex(
export function getNextHighlightedIndex<TItem>(
moveAmount: number,
baseIndex: number,
itemCount: number
) {
const itemsLastIndex = itemCount - 1;

if (
typeof baseIndex !== 'number' ||
baseIndex < 0 ||
baseIndex >= itemCount
) {
baseIndex = moveAmount > 0 ? -1 : itemsLastIndex + 1;
baseIndex: number | null,
itemCount: number,
defaultHighlightedIndex: AutocompleteOptions<TItem>['defaultHighlightedIndex']
): number | null {
// We allow circular keyboard navigation from the base index.
// The base index can either be `null` (nothing is highlighted) or `0`
// (the first item is highlighted).
// The base index is allowed to get assigned `null` only if
// `props.defaultHighlightedIndex` is `null`. This pattern allows to "stop"
// by the actual query before navigating to other suggestions as seen on
// Google or Amazon.
if (baseIndex === null && moveAmount < 0) {
return itemCount - 1;
}

let newIndex = baseIndex + moveAmount;
if (defaultHighlightedIndex !== null && baseIndex === 0 && moveAmount < 0) {
return itemCount - 1;
}

const numericIndex = (baseIndex === null ? -1 : baseIndex) + moveAmount;

if (newIndex < 0) {
newIndex = itemsLastIndex;
} else if (newIndex > itemsLastIndex) {
newIndex = 0;
if (numericIndex <= -1 || numericIndex >= itemCount) {
return defaultHighlightedIndex === null ? null : 0;
}

return newIndex;
return numericIndex;
}

// We don't have access to the autocomplete source when we call `onKeyDown`
Expand All @@ -120,7 +125,7 @@ function getSuggestionFromHighlightedIndex<TItem>({

// Based on the accumulated counts, we can infer the index of the suggestion.
const suggestionIndex = accumulatedSuggestionsCount.reduce((acc, current) => {
if (current <= state.highlightedIndex) {
if (current <= state.highlightedIndex!) {
return acc + 1;
}

Expand Down Expand Up @@ -164,7 +169,7 @@ function getRelativeHighlightedIndex<TItem>({
counter++;
}

return state.highlightedIndex - previousItemsOffset;
return state.highlightedIndex! - previousItemsOffset;
}

export function getHighlightedItem<TItem>({
Expand Down
4 changes: 1 addition & 3 deletions stories/react.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ storiesOf('React', module)
<Autocomplete
placeholder="Search items…"
showCompletion={true}
defaultHighlightedIndex={-1}
dropdownContainer={dropdownContainer}
defaultHighlightedIndex={null}
getSources={() => {
return [
{
Expand Down Expand Up @@ -60,7 +60,6 @@ storiesOf('React', module)
<Autocomplete
placeholder="Search items…"
showCompletion={true}
defaultHighlightedIndex={-1}
dropdownContainer={dropdownContainer}
getSources={() => {
return [
Expand Down Expand Up @@ -101,7 +100,6 @@ storiesOf('React', module)
render(
<Autocomplete
placeholder="Search items…"
defaultHighlightedIndex={-1}
dropdownContainer={dropdownContainer}
getSources={() => {
return [
Expand Down

0 comments on commit 0000499

Please sign in to comment.