diff --git a/resources/js/components/global-search.js b/resources/js/components/global-search.js index 52c767642..b3719c8fd 100644 --- a/resources/js/components/global-search.js +++ b/resources/js/components/global-search.js @@ -1,8 +1,9 @@ +import {htmlToDom} from "../services/dom"; +import {debounce} from "../services/util"; + /** * @extends {Component} */ -import {htmlToDom} from "../services/dom"; - class GlobalSearch { setup() { @@ -10,23 +11,43 @@ class GlobalSearch { 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); @@ -35,6 +56,8 @@ class GlobalSearch { } 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(); diff --git a/resources/js/services/util.js b/resources/js/services/util.js index de2ca20c1..1a56ebf6c 100644 --- a/resources/js/services/util.js +++ b/resources/js/services/util.js @@ -6,9 +6,9 @@ * 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) { diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss index 522855b2e..ca2ab83a4 100644 --- a/resources/sass/_header.scss +++ b/resources/sass/_header.scss @@ -131,7 +131,7 @@ header .search-box { right: 16px; } svg { - margin-block-end: 0; + margin-inline-end: 0; } } @@ -163,17 +163,23 @@ header .search-box { -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 { diff --git a/resources/views/common/header.blade.php b/resources/views/common/header.blade.php index 5d94deee7..d5f4fa224 100644 --- a/resources/views/common/header.blade.php +++ b/resources/views/common/header.blade.php @@ -30,6 +30,7 @@ aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}" value="{{ $searchTerm ?? '' }}">
+
@include('common.loading-icon')