UNPKG

coveo-search-ui

Version:

Coveo JavaScript Search Framework

290 lines (244 loc) • 8.47 kB
import { each, find, isUndefined } from 'underscore'; import { KEYBOARD } from '../Core'; import { $$ } from '../utils/Dom'; import { doMagicBoxExport } from './doMagicBoxExport'; import { Grammar } from './Grammar'; import { InputManager } from './InputManager'; import { MagicBoxClear } from './MagicBoxClear'; import { Result } from './Result/Result'; import { Suggestion, SuggestionsManager } from './SuggestionsManager'; export interface Options { inline?: boolean; selectableSuggestionClass?: string; selectedSuggestionClass?: string; suggestionTimeout?: number; } export class MagicBoxInstance { public onblur: () => void; public onfocus: () => void; public onchange: () => void; public onSuggestions: (suggestions: Suggestion[]) => void; public onsubmit: () => void; public onselect: (suggestion: Suggestion) => void; public onclear: () => void; public onmove: () => void; public ontabpress: () => void; public getSuggestions: () => Array<Promise<Suggestion[]> | Suggestion[]>; private inputManager: InputManager; private suggestionsManager: SuggestionsManager; private magicBoxClear: MagicBoxClear; private lastSuggestions: Suggestion[] = []; private result: Result; private displayedResult: Result; private get firstSuggestionWithText(): Suggestion { return find(this.lastSuggestions, suggestion => suggestion.text); } private get firstSuggestionText() { const firstSuggestionWithText = this.firstSuggestionWithText; return firstSuggestionWithText ? firstSuggestionWithText.text : ''; } constructor(public element: HTMLElement, public grammar: Grammar, public options: Options = {}) { if (isUndefined(this.options.inline)) { this.options.inline = false; } $$(element).addClass('magic-box'); if (this.options.inline) { $$(element).addClass('magic-box-inline'); } this.result = this.grammar.parse(''); this.displayedResult = this.result.clean(); let inputContainer = $$(element).find('.magic-box-input'); if (!inputContainer) { inputContainer = document.createElement('div'); inputContainer.className = 'magic-box-input'; element.appendChild(inputContainer); } this.inputManager = new InputManager( inputContainer, (text, wordCompletion) => { if (!wordCompletion) { this.setText(text); this.addSuggestions(); this.onchange && this.onchange(); } else { this.setText(text); this.onselect && this.onselect(this.firstSuggestionWithText); } }, this ); this.inputManager.ontabpress = () => { this.ontabpress && this.ontabpress(); }; const existingValue = this.inputManager.getValue(); if (existingValue) { this.displayedResult.input = existingValue; } this.inputManager.setResult(this.displayedResult); const suggestionsContainer = document.createElement('div'); suggestionsContainer.className = 'magic-box-suggestions'; this.element.appendChild(suggestionsContainer); this.suggestionsManager = new SuggestionsManager(suggestionsContainer, this.element, this.inputManager, { suggestionClass: this.options.selectableSuggestionClass, selectedClass: this.options.selectedSuggestionClass, timeout: this.options.suggestionTimeout }); this.magicBoxClear = new MagicBoxClear(this); this.setupHandler(); } public getResult() { return this.result; } public getDisplayedResult() { return this.displayedResult; } public setText(text: string) { $$(this.element).toggleClass('magic-box-notEmpty', text.length > 0); this.magicBoxClear.toggleTabindexAndAriaHidden(text.length > 0); this.result = this.grammar.parse(text); this.displayedResult = this.result.clean(); this.inputManager.setResult(this.displayedResult); } public setCursor(index: number) { this.inputManager.setCursor(index); } public getCursor() { return this.inputManager.getCursor(); } public resultAtCursor(match?: string | { (result: Result): boolean }): Result[] { return this.displayedResult.resultAt(this.getCursor(), match); } private setupHandler() { this.inputManager.onblur = () => { $$(this.element).removeClass('magic-box-hasFocus'); this.onblur && this.onblur(); if (!this.options.inline) { this.clearSuggestion(); } }; this.inputManager.onfocus = () => { $$(this.element).addClass('magic-box-hasFocus'); this.addSuggestions(); this.onfocus && this.onfocus(); }; this.inputManager.onkeydown = (key: number) => { if (this.shouldMoveInSuggestions(key)) { return false; } if (key === KEYBOARD.ENTER) { const suggestion = this.suggestionsManager.selectAndReturnKeyboardFocusedElement(); if (suggestion == null) { this.onsubmit && this.onsubmit(); } return false; } else if (key === KEYBOARD.ESCAPE) { this.clearSuggestion(); this.blur(); } else { this.suggestionsManager.clearKeyboardFocusedElement(); } return true; }; this.inputManager.onchangecursor = () => { this.addSuggestions(); }; this.inputManager.onkeyup = (key: number) => { this.onmove && this.onmove(); if (!this.shouldMoveInSuggestions(key)) { return true; } switch (key) { case KEYBOARD.UP_ARROW: this.suggestionsManager.moveUp(); break; case KEYBOARD.DOWN_ARROW: this.suggestionsManager.moveDown(); break; case KEYBOARD.LEFT_ARROW: this.suggestionsManager.moveLeft(); break; case KEYBOARD.RIGHT_ARROW: this.suggestionsManager.moveRight(); break; } if (this.suggestionsManager.selectedSuggestion) { this.focusOnSuggestion(this.suggestionsManager.selectedSuggestion); } this.onchange && this.onchange(); return false; }; } public async addSuggestions() { const suggestions = await this.suggestionsManager.receiveSuggestions(this.getSuggestions != null ? this.getSuggestions() : []); this.addSelectEventHandlers(suggestions); this.inputManager.setWordCompletion(this.firstSuggestionText); this.onSuggestions(suggestions); } private shouldMoveInSuggestions(key: KEYBOARD) { switch (key) { case KEYBOARD.UP_ARROW: case KEYBOARD.DOWN_ARROW: return true; case KEYBOARD.LEFT_ARROW: case KEYBOARD.RIGHT_ARROW: if (this.suggestionsManager.hasFocus && this.suggestionsManager.hasPreviews) { return true; } } return false; } private addSelectEventHandlers(suggestions: Suggestion[]) { each(suggestions, (suggestion: Suggestion) => { if (suggestion.onSelect == null && suggestion.text != null) { suggestion.onSelect = () => { this.setText(suggestion.text); this.onselect && this.onselect(suggestion); }; } }); } public focus() { $$(this.element).addClass('magic-box-hasFocus'); this.inputManager.focus(); } public blur() { this.inputManager.blur(); } public async clearSuggestion() { this.inputManager.setWordCompletion(null); this.suggestionsManager.clearSuggestions(); this.onSuggestions([]); } private focusOnSuggestion(suggestion: Suggestion) { if (suggestion == null || suggestion.text == null) { this.inputManager.setResult(this.displayedResult, this.firstSuggestionText); } else { this.inputManager.setResult(this.grammar.parse(suggestion.text).clean(), suggestion.text); } } public getText() { return this.inputManager.getValue(); } public getWordCompletion() { return this.inputManager.getWordCompletion(); } public clear() { this.setText(''); this.clearSuggestion(); this.focus(); this.onclear && this.onclear(); } public hasSuggestions() { return this.suggestionsManager.hasSuggestions; } } export function createMagicBox(element: HTMLElement, grammar: Grammar, options?: Options) { return new MagicBoxInstance(element, grammar, options); } export function requestAnimationFrame(callback: () => void) { if ('requestAnimationFrame' in window) { return window.requestAnimationFrame(callback); } return setTimeout(callback); } doMagicBoxExport();