in lib/resources/script.js [160:432]
function initializeSearch(input, index) {
input.disabled = false;
input.setAttribute('placeholder', 'Search API Docs');
// Handle grabbing focus when the users types / outside of the input
document.addEventListener('keypress', (event) => {
if (event.code === 'Slash' && !(document.activeElement instanceof HTMLInputElement)) {
event.preventDefault();
input.focus();
}
});
// Prepare elements
const parentForm = input.parentNode;
const wrapper = document.createElement('div');
wrapper.classList.add('tt-wrapper');
parentForm.replaceChild(wrapper, input);
const inputHint = document.createElement('input');
inputHint.setAttribute('type', 'text');
inputHint.setAttribute('autocomplete', 'off');
inputHint.setAttribute('readonly', 'true');
inputHint.setAttribute('spellcheck', 'false');
inputHint.setAttribute('tabindex', '-1');
inputHint.classList.add('typeahead', 'tt-hint');
wrapper.appendChild(inputHint);
input.setAttribute('autocomplete', 'off');
input.setAttribute('spellcheck', 'false');
input.classList.add('tt-input');
wrapper.appendChild(input);
const listBox = document.createElement('div');
listBox.setAttribute('role', 'listbox');
listBox.setAttribute('aria-expanded', 'false');
listBox.style.display = 'none';
listBox.classList.add('tt-menu');
const presentation = document.createElement('div');
presentation.classList.add('tt-elements');
listBox.appendChild(presentation);
wrapper.appendChild(listBox);
// Set up various search functionality
function highlight(text, query) {
query = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return text.replace(new RegExp(query, 'gi'), (matched) => {
return `<strong class='tt-highlight'>${matched}</strong>`;
});
}
function createSuggestion(query, match) {
const suggestion = document.createElement('div');
suggestion.setAttribute('data-href', match.href);
suggestion.classList.add('tt-suggestion');
const suggestionTitle = document.createElement('span');
suggestionTitle.classList.add('tt-suggestion-title');
suggestionTitle.innerHTML = highlight(`${match.name} ${match.type.toLowerCase()}`, query);
suggestion.appendChild(suggestionTitle);
if (match.enclosedBy) {
const fromLib = document.createElement('div');
fromLib.classList.add('search-from-lib');
fromLib.innerHTML = `from ${highlight(match.enclosedBy.name, query)}`;
suggestion.appendChild(fromLib);
}
suggestion.addEventListener('mousedown', event => {
event.preventDefault();
});
suggestion.addEventListener('click', event => {
if (match.href) {
window.location = baseHref + match.href;
event.preventDefault();
}
});
return suggestion;
}
let storedValue = null;
let actualValue = '';
let hint = null;
let suggestionElements = [];
let suggestionsInfo = [];
let selectedElement = null;
function setHint(value) {
hint = value;
inputHint.value = value || '';
}
function updateSuggestions(query, suggestions) {
suggestionsInfo = [];
suggestionElements = [];
presentation.textContent = '';
if (suggestions.length < minLength) {
setHint(null)
hideSuggestions();
return;
}
for (let i = 0; i < suggestions.length; i++) {
const element = createSuggestion(query, suggestions[i]);
suggestionElements.push(element);
presentation.appendChild(element);
}
suggestionsInfo = suggestions;
setHint(query + suggestions[0].name.slice(query.length));
selectedElement = null;
showSuggestions();
}
function handle(newValue, forceUpdate) {
if (actualValue === newValue && !forceUpdate) {
return;
}
if (newValue === null || newValue.length === 0) {
updateSuggestions('', []);
return;
}
const suggestions = findMatches(index, newValue).slice(0, suggestionLimit);
actualValue = newValue;
updateSuggestions(newValue, suggestions);
}
function showSuggestions() {
if (presentation.hasChildNodes()) {
listBox.style.display = 'block';
listBox.setAttribute('aria-expanded', 'true');
}
}
function hideSuggestions() {
listBox.style.display = 'none';
listBox.setAttribute('aria-expanded', 'false');
}
// Hook up events
input.addEventListener('focus', () => {
handle(input.value, true);
});
input.addEventListener('blur', () => {
selectedElement = null;
if (storedValue !== null) {
input.value = storedValue;
storedValue = null;
}
hideSuggestions();
setHint(null);
});
input.addEventListener('input', event => {
handle(event.target.value);
});
input.addEventListener('keydown', event => {
if (suggestionElements.length === 0) {
return;
}
if (event.code === 'Enter') {
const selectingElement = selectedElement || 0;
const href = suggestionElements[selectingElement].dataset.href;
if (href) {
window.location = baseHref + href;
}
return;
}
if (event.code === 'Tab') {
if (selectedElement === null) {
// The user wants to fill the field with the hint
if (hint !== null) {
input.value = hint;
handle(hint);
event.preventDefault();
}
} else {
// The user wants to fill the input field with their currently selected suggestion
handle(suggestionsInfo[selectedElement].name);
storedValue = null;
selectedElement = null;
event.preventDefault();
}
return;
}
const lastIndex = suggestionElements.length - 1;
const previousSelectedElement = selectedElement;
if (event.code === 'ArrowUp') {
if (selectedElement === null) {
selectedElement = lastIndex;
} else if (selectedElement === 0) {
selectedElement = null;
} else {
selectedElement--;
}
} else if (event.code === 'ArrowDown') {
if (selectedElement === null) {
selectedElement = 0;
} else if (selectedElement === lastIndex) {
selectedElement = null;
} else {
selectedElement++;
}
} else {
if (storedValue !== null) {
storedValue = null;
handle(input.value);
}
return;
}
if (previousSelectedElement !== null) {
suggestionElements[previousSelectedElement].classList.remove('tt-cursor');
}
if (selectedElement !== null) {
const selected = suggestionElements[selectedElement];
selected.classList.add('tt-cursor');
// Guarantee the selected element is visible
if (selectedElement === 0) {
listBox.scrollTop = 0;
} else if (selectedElement === lastIndex) {
listBox.scrollTop = listBox.scrollHeight;
} else {
const offsetTop = selected.offsetTop;
const parentOffsetHeight = listBox.offsetHeight;
if (offsetTop < parentOffsetHeight || parentOffsetHeight < (offsetTop + selected.offsetHeight)) {
selected.scrollIntoView({behavior: 'auto', block: 'nearest'});
}
}
if (storedValue === null) {
// Store the actual input value to display their currently selected item
storedValue = input.value;
}
input.value = suggestionsInfo[selectedElement].name;
setHint('');
} else if (storedValue !== null && previousSelectedElement !== null) {
// They are moving back to the input field, so return the stored value
input.value = storedValue;
setHint(storedValue + suggestionsInfo[0].name.slice(storedValue.length));
storedValue = null;
}
event.preventDefault();
});
}