@@ -108,11 +108,87 @@ $.when($.ready).then(() => {
108108 }
109109 } ) ;
110110
111+ // Autocomplete functionality
112+ let autocompleteTimeout = null ;
113+ let currentAutocompleteRequest = null ;
114+
115+ function hideAutocomplete ( ) {
116+ $ ( "#search-autocomplete" ) . remove ( ) ;
117+ }
118+
119+ function showAutocomplete ( suggestions , inputElement ) {
120+ hideAutocomplete ( ) ;
121+
122+ if ( ! suggestions || suggestions . length === 0 ) {
123+ return ;
124+ }
125+
126+ const $input = $ ( inputElement ) ;
127+ const position = $input . position ( ) ;
128+ const width = $input . outerWidth ( ) ;
129+
130+ const $autocomplete = $ ( '<div id="search-autocomplete"></div>' ) ;
131+
132+ suggestions . forEach ( ( suggestion ) => {
133+ const $item = $ ( '<div class="autocomplete-item"></div>' )
134+ . text ( suggestion )
135+ . on ( "click" , ( ) => {
136+ $input . val ( suggestion ) ;
137+ hideAutocomplete ( ) ;
138+ $input . closest ( "form" ) . submit ( ) ;
139+ } ) ;
140+ $autocomplete . append ( $item ) ;
141+ } ) ;
142+
143+ $autocomplete . css ( {
144+ position : "absolute" ,
145+ top : `${ position . top + $input . outerHeight ( ) } px` ,
146+ left : `${ position . left } px` ,
147+ width : `${ width } px` ,
148+ } ) ;
149+
150+ $input . closest ( "#search-container" ) . append ( $autocomplete ) ;
151+ }
152+
153+ function fetchAutocomplete ( query , provider ) {
154+ // Cancel previous request if any
155+ if ( currentAutocompleteRequest ) {
156+ currentAutocompleteRequest . abort ( ) ;
157+ }
158+
159+ if ( ! query || query . trim ( ) . length < 2 ) {
160+ hideAutocomplete ( ) ;
161+ return ;
162+ }
163+
164+ currentAutocompleteRequest = $ . ajax ( {
165+ url : `${ base } search/autocomplete` ,
166+ method : "GET" ,
167+ data : {
168+ q : query ,
169+ provider,
170+ } ,
171+ success ( data ) {
172+ const inputElement = $ ( "#search-container input[name=q]" ) [ 0 ] ;
173+ showAutocomplete ( data , inputElement ) ;
174+ } ,
175+ error ( ) {
176+ hideAutocomplete ( ) ;
177+ } ,
178+ complete ( ) {
179+ currentAutocompleteRequest = null ;
180+ } ,
181+ } ) ;
182+ }
183+
111184 $ ( "#search-container" )
112185 . on ( "input" , "input[name=q]" , function ( ) {
113186 const search = this . value ;
114187 const items = $ ( "#sortable" ) . find ( ".item-container" ) ;
115- if ( $ ( "#search-container select[name=provider]" ) . val ( ) === "tiles" ) {
188+ const provider = $ ( "#search-container select[name=provider]" ) . val ( ) ;
189+
190+ if ( provider === "tiles" ) {
191+ hideAutocomplete ( ) ;
116192 if ( search . length > 0 ) {
117193 items . hide ( ) ;
118194 items
@@ -126,6 +202,12 @@ $.when($.ready).then(() => {
126202 }
127203 } else {
128204 items . show ( ) ;
205+
206+ // Debounce autocomplete requests
207+ clearTimeout ( autocompleteTimeout ) ;
208+ autocompleteTimeout = setTimeout ( ( ) => {
209+ fetchAutocomplete ( search , provider ) ;
210+ } , 300 ) ;
129211 }
130212 } )
131213 . on ( "change" , "select[name=provider]" , function ( ) {
@@ -147,9 +229,24 @@ $.when($.ready).then(() => {
147229 } else {
148230 $ ( "#search-container button" ) . show ( ) ;
149231 items . show ( ) ;
232+ hideAutocomplete ( ) ;
150233 }
151234 } ) ;
152235
236+ // Hide autocomplete when clicking outside
237+ $ ( document ) . on ( "click" , ( e ) => {
238+ if ( ! $ ( e . target ) . closest ( "#search-container" ) . length ) {
239+ hideAutocomplete ( ) ;
240+ }
241+ } ) ;
242+
243+ // Hide autocomplete on Escape key
244+ $ ( document ) . on ( "keydown" , ( e ) => {
245+ if ( e . key === "Escape" ) {
246+ hideAutocomplete ( ) ;
247+ }
248+ } ) ;
249+
153250 $ ( "#search-container select[name=provider]" ) . trigger ( "change" ) ;
154251
155252 $ ( "#app" )
0 commit comments