+import {htmlToDom} from "../services/dom";
+import {debounce} from "../services/util";
+
/**
* @extends {Component}
*/
-import {htmlToDom} from "../services/dom";
-
class GlobalSearch {
setup() {
this.input = this.$refs.input;
this.suggestions = this.$refs.suggestions;
this.suggestionResultsWrap = this.$refs.suggestionResults;
+ this.loadingWrap = this.$refs.loading;
this.setupListeners();
}
setupListeners() {
+ const updateSuggestionsDebounced = debounce(this.updateSuggestions.bind(this), 200, false);
+
+ // Handle search input changes
this.input.addEventListener('input', () => {
const value = this.input.value;
if (value.length > 0) {
- this.updateSuggestions(value);
+ this.loadingWrap.style.display = 'block';
+ this.suggestionResultsWrap.style.opacity = '0.5';
+ updateSuggestionsDebounced(value);
} else {
this.hideSuggestions();
}
});
+
+ // Allow double click to show auto-click suggestions
+ this.input.addEventListener('dblclick', () => {
+ this.input.setAttribute('autocomplete', 'on');
+ this.input.blur();
+ this.input.focus();
+ })
}
+ /**
+ * @param {String} search
+ */
async updateSuggestions(search) {
const {data: results} = await window.$http.get('/ajax/search/entities', {term: search, count: 5});
+ if (!this.input.value) {
+ return;
+ }
+
const resultDom = htmlToDom(results);
const childrenToTrim = Array.from(resultDom.children).slice(9);
}
this.suggestionResultsWrap.innerHTML = '';
+ this.suggestionResultsWrap.style.opacity = '1';
+ this.loadingWrap.style.display = 'none';
this.suggestionResultsWrap.append(resultDom);
if (!this.container.classList.contains('search-active')) {
this.showSuggestions();
* N milliseconds. If `immediate` is passed, trigger the function on the
* leading edge, instead of the trailing.
* @attribution https://davidwalsh.name/javascript-debounce-function
- * @param func
- * @param wait
- * @param immediate
+ * @param {Function} func
+ * @param {Number} wait
+ * @param {Boolean} immediate
* @returns {Function}
*/
export function debounce(func, wait, immediate) {
right: 16px;
}
svg {
- margin-block-end: 0;
+ margin-inline-end: 0;
}
}
-webkit-line-clamp: 2;
overflow: hidden;
}
+ .global-search-loading {
+ position: absolute;
+ width: 100%;
+ }
}
-.search-active:focus-within .global-search-suggestions {
- display: block;
-}
-header .search-box.search-active input {
- background-color: #EEE;
- color: #444;
- border-color: #DDD;
-}
-header .search-box.search-active #header-search-box-button {
- color: #444;
+header .search-box.search-active:focus-within {
+ .global-search-suggestions {
+ display: block;
+ }
+ input {
+ background-color: #EEE;
+ color: #444;
+ border-color: #DDD;
+ }
+ #header-search-box-button {
+ color: #444;
+ }
}
.logo {
aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}"
value="{{ $searchTerm ?? '' }}">
<div refs="global-search@suggestions" class="global-search-suggestions card">
+ <div refs="global-search@loading" class="text-center px-m global-search-loading">@include('common.loading-icon')</div>
<div refs="global-search@suggestion-results" class="px-m"></div>
<button class="text-button card-footer-link" type="submit">{{ trans('common.view_all') }}</button>
</div>