Skip to content

Commit

Permalink
feat: update search-bar for autocompletion
Browse files Browse the repository at this point in the history
  • Loading branch information
Kout95 committed Jun 14, 2024
1 parent 70dda20 commit a9636c3
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 205 deletions.
2 changes: 1 addition & 1 deletion frontend/public/off.html
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
<li class="search-li">
<div class="row collapse postfix-round search-bar">
<div class="columns">
<searchalicious-bar name="off" page-size="24" ></searchalicious-bar>
<searchalicious-bar name="off" page-size="24" taxonomies="brand,category"></searchalicious-bar>
</div>
<div class="columns">
<searchalicious-button search-name="off">
Expand Down
222 changes: 222 additions & 0 deletions frontend/src/mixins/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import {LitElement} from 'lit';
import {Constructor} from './utils';
import {property} from 'lit/decorators.js';
import {DebounceMixin, DebounceMixinInterface} from './debounce';

/**
* Interface for the Autocomplete mixin.
*/
export interface AutocompleteMixinInterface extends DebounceMixinInterface {
inputName: string;
options: AutocompleteOption[];
value: string;
currentIndex: number;
visible: boolean;
isLoading: boolean;
currentOption: AutocompleteOption | undefined;
getCurrentIndex: number;

onInput(event: InputEvent): void;
handleInput(value: string): void;
blurInput(): void;
resetInput(): void;
submit(isSuggestion?: boolean): void;
getAutocompleteValueByIndex(index: number): string;
handleArrowKey(direction: 'up' | 'down'): void;
handleEnter(event: KeyboardEvent): void;
handleEscape(): void;
onKeyDown(event: KeyboardEvent): void;
onClick(index: number): () => void;
onFocus(): void;
onBlur(): void;
}
/**
* Type for autocomplete option.
*/
export type AutocompleteOption = {
value: string;
label: string;
};
/**
* Type for autocomplete result.
*/
export type AutocompleteResult = {
value: string;
label?: string;
};

export const AutocompleteMixin = <T extends Constructor<LitElement>>(
superClass: T
): Constructor<AutocompleteMixinInterface> & T => {
class AutocompleteMixinClass extends DebounceMixin(superClass) {
@property({attribute: 'input-name'})
inputName = 'autocomplete';

/**
* The options for the autocomplete.
* It is provided by the parent component.
*/
@property({attribute: false, type: Array})
options: AutocompleteOption[] = [];

// selected values
@property()
value = '';

@property({attribute: false})
currentIndex = 0;

@property({attribute: false})
visible = false;

@property({attribute: false})
isLoading = false;

/**
* This method is used to get the current index.
* It remove the offset of 1 because the currentIndex is 1-based.
* @returns {number} The current index.
*/
get getCurrentIndex() {
return this.currentIndex - 1;
}

get currentOption() {
return this.options[this.getCurrentIndex];
}

/**
* Handles the input event on the autocomplete and dispatch custom event : "autocomplete-input".
* @param {InputEvent} event - The input event.
*/
onInput(event: InputEvent) {
const value = (event.target as HTMLInputElement).value;
this.value = value;
this.handleInput(value);
}

handleInput(value: string) {
throw new Error(`handleInput method must be implemented with ${value}`);
}
/**
* This method is used to remove focus from the input element.
* It is used to quit after selecting an option.
*/
blurInput() {
const input = this.shadowRoot!.querySelector('input');
if (input) {
input.blur();
}
}

/**
* This method is used to reset the input value and blur it.
* It is used to reset the input after a search.
*/
resetInput() {
this.value = '';
this.currentIndex = 0;
this.blurInput();
}

/**
* This method is used to submit the input value.
* It is used to submit the input value after selecting an option.
* @param {boolean} isSuggestion - A boolean value to check if the value is a suggestion.
*/
submit(isSuggestion = false) {
throw new Error(`submit method must be implemented with ${isSuggestion}`);
}

/**
* Handles keyboard event to navigate the suggestion list
* @param {string} direction - The direction of the arrow key event.
*/
handleArrowKey(direction: 'up' | 'down') {
const offset = direction === 'down' ? 1 : -1;
const maxIndex = this.options.length + 1;
this.currentIndex = (this.currentIndex + offset + maxIndex) % maxIndex;
}

/**
* When Enter is pressed:
* * if an option was selected (using keyboard arrows) it becomes the value
* * otherwise the input string is the value
* We then submit the value.
* @param event
*/
handleEnter(event: KeyboardEvent) {
let isAutoComplete = false;
if (this.currentIndex) {
isAutoComplete = true;
this.value = this.currentOption!.value;
} else {
const value = (event.target as HTMLInputElement).value;
this.value = value;
}
this.submit(isAutoComplete);
}

/**
* This method is used to handle the escape key event.
*/
handleEscape() {
this.blurInput();
}

/**
* dispatch key events according to the key pressed (arrows or enter)
* @param event
*/
onKeyDown(event: KeyboardEvent) {
switch (event.key) {
case 'ArrowDown':
this.handleArrowKey('down');
return;
case 'ArrowUp':
this.handleArrowKey('up');
return;
case 'Enter':
this.handleEnter(event);
return;
case 'Escape':
this.handleEscape();
return;
}
}

/**
* On a click on the autocomplete option, we select it as value and submit it.
* @param index
*/
onClick(index: number) {
return () => {
this.value = this.options[index].value;
// we need to increment the index because currentIndex is 1-based
this.currentIndex = index + 1;
this.submit(true);
};
}

/**
* This method is used to handle the focus event on the input element.
* It is used to show the autocomplete options when the input is focused.
*/
onFocus() {
this.visible = true;
}

/**
* This method is used to handle the blur event on the input element.
* It is used to hide the autocomplete options when the input is blurred.
* It is debounced to avoid to quit before select with click.
*/
onBlur() {
this.debounce(() => {
this.visible = false;
});
}
}

return AutocompleteMixinClass as Constructor<AutocompleteMixinInterface> & T;
};
1 change: 1 addition & 0 deletions frontend/src/search-a-licious.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export {SearchaliciousAutocomplete} from './search-autocomplete';
export {SearchaliciousSecondaryButton} from './secondary-button';
export {SearchaliciousButtonTransparent} from './button-transparent';
export {SearchaliciousIconCross} from './icons/cross';
export {SearchaliciousTermLine} from './search-term-line';
Loading

0 comments on commit a9636c3

Please sign in to comment.