1- import { useCallback , useEffect , useLayoutEffect , useMemo , useRef , useState } from 'react' ;
1+ import { useCallback , useLayoutEffect , useMemo , useRef , useState } from 'react' ;
22import cx from 'classnames' ;
3- import Fuse from 'fuse.js' ;
43import { Popover , Textarea , UnstyledButton } from '@mantine/core' ;
54
65import { AUTOCOMPLETE_MIN_CHARS , AUTOCOMPLETE_SUGGESTIONS_LIMIT } from '@/config' ;
@@ -82,8 +81,6 @@ export default function AutocompleteInput({
8281 const [ selectedQueryHistoryIndex , setSelectedQueryHistoryIndex ] =
8382 useState ( - 1 ) ;
8483
85- const [ page , setPage ] = useState ( 0 ) ;
86-
8784 // query search history
8885 const [ queryHistory , setQueryHistory ] = useQueryHistory ( queryHistoryType ) ;
8986 const queryHistoryList = useMemo ( ( ) => {
@@ -102,16 +99,6 @@ export default function AutocompleteInput({
10299 queryHistoryList . length > 0 &&
103100 queryHistoryType ;
104101
105- const fuse = useMemo (
106- ( ) =>
107- new Fuse ( autocompleteOptions ?? [ ] , {
108- keys : [ 'value' ] ,
109- threshold : 0 ,
110- ignoreLocation : true ,
111- } ) ,
112- [ autocompleteOptions ] ,
113- ) ;
114-
115102 const debouncedValue = useDebounce ( value ?? '' , 200 ) ;
116103 const suggestedProperties = useMemo ( ( ) => {
117104 const lastToken = getLastToken ( debouncedValue ) ;
@@ -121,25 +108,19 @@ export default function AutocompleteInput({
121108 }
122109 if ( ! lastToken . length ) return [ ] ;
123110
124- const fuseTerm = extractFuseSearchTerm ( lastToken ) ;
111+ const searchTerm = extractFuseSearchTerm ( lastToken ) ;
125112 // bare `field:` (empty value portion) or pure wildcard → show all fetched options
126- if ( ! fuseTerm . length ) return autocompleteOptions ?? [ ] ;
113+ if ( ! searchTerm . length ) return autocompleteOptions ?? [ ] ;
127114 // enforce minimum character threshold before filtering
128- if ( fuseTerm . length < AUTOCOMPLETE_MIN_CHARS ) return [ ] ;
129-
130- return fuse . search ( fuseTerm ) . map ( result => result . item ) ;
131- } , [ debouncedValue , fuse , autocompleteOptions , showSuggestionsOnEmpty ] ) ;
115+ if ( searchTerm . length < AUTOCOMPLETE_MIN_CHARS ) return [ ] ;
132116
133- // Reset pagination and selection whenever the suggestion list changes
134- useEffect ( ( ) => {
135- setPage ( 0 ) ;
136- setSelectedAutocompleteIndex ( - 1 ) ;
137- } , [ suggestedProperties ] ) ;
117+ const lower = searchTerm . toLowerCase ( ) ;
118+ return ( autocompleteOptions ?? [ ] ) . filter ( opt =>
119+ opt . value . toLowerCase ( ) . includes ( lower ) ,
120+ ) ;
121+ } , [ debouncedValue , autocompleteOptions , showSuggestionsOnEmpty ] ) ;
138122
139- const totalPages = Math . ceil ( suggestedProperties . length / pageSize ) ;
140- const pageStart = page * pageSize ;
141- const pageEnd = Math . min ( ( page + 1 ) * pageSize , suggestedProperties . length ) - 1 ;
142- const pagedSuggestions = suggestedProperties . slice ( pageStart , pageEnd + 1 ) ;
123+ const visibleSuggestions = suggestedProperties . slice ( 0 , pageSize ) ;
143124
144125 const onSelectSearchHistory = ( query : string ) => {
145126 setSelectedQueryHistoryIndex ( - 1 ) ;
@@ -235,13 +216,13 @@ export default function AutocompleteInput({
235216 // Autocomplete Navigation/Acceptance Keys
236217 if ( e . key === 'Tab' && e . target instanceof HTMLTextAreaElement ) {
237218 if (
238- suggestedProperties . length > 0 &&
219+ visibleSuggestions . length > 0 &&
239220 selectedAutocompleteIndex >= 0 &&
240- selectedAutocompleteIndex < suggestedProperties . length
221+ selectedAutocompleteIndex < visibleSuggestions . length
241222 ) {
242223 e . preventDefault ( ) ;
243224 onAcceptSuggestion (
244- suggestedProperties [ selectedAutocompleteIndex ] . value ,
225+ visibleSuggestions [ selectedAutocompleteIndex ] . value ,
245226 ) ;
246227 }
247228 }
@@ -250,13 +231,13 @@ export default function AutocompleteInput({
250231 e . target instanceof HTMLTextAreaElement
251232 ) {
252233 if (
253- suggestedProperties . length > 0 &&
234+ visibleSuggestions . length > 0 &&
254235 selectedAutocompleteIndex >= 0 &&
255- selectedAutocompleteIndex < suggestedProperties . length
236+ selectedAutocompleteIndex < visibleSuggestions . length
256237 ) {
257238 e . preventDefault ( ) ;
258239 onAcceptSuggestion (
259- suggestedProperties [ selectedAutocompleteIndex ] . value ,
240+ visibleSuggestions [ selectedAutocompleteIndex ] . value ,
260241 ) ;
261242 } else {
262243 // Allow shift+enter to still create new lines
@@ -273,29 +254,18 @@ export default function AutocompleteInput({
273254 e . key === 'ArrowDown' &&
274255 e . target instanceof HTMLTextAreaElement
275256 ) {
276- if ( suggestedProperties . length > 0 ) {
277- const next = Math . min (
278- selectedAutocompleteIndex + 1 ,
279- suggestedProperties . length - 1 ,
257+ if ( visibleSuggestions . length > 0 ) {
258+ setSelectedAutocompleteIndex ( i =>
259+ Math . min ( i + 1 , visibleSuggestions . length - 1 ) ,
280260 ) ;
281- // Advance page when crossing the page boundary
282- if ( next > pageEnd && page < totalPages - 1 ) {
283- setPage ( p => p + 1 ) ;
284- }
285- setSelectedAutocompleteIndex ( next ) ;
286261 }
287262 }
288263 if (
289264 e . key === 'ArrowUp' &&
290265 e . target instanceof HTMLTextAreaElement
291266 ) {
292- if ( suggestedProperties . length > 0 ) {
293- const prev = Math . max ( selectedAutocompleteIndex - 1 , 0 ) ;
294- // Retreat page when crossing the page boundary
295- if ( prev < pageStart && page > 0 ) {
296- setPage ( p => p - 1 ) ;
297- }
298- setSelectedAutocompleteIndex ( prev ) ;
267+ if ( visibleSuggestions . length > 0 ) {
268+ setSelectedAutocompleteIndex ( i => Math . max ( i - 1 , 0 ) ) ;
299269 }
300270 }
301271 } }
@@ -317,70 +287,32 @@ export default function AutocompleteInput({
317287 < div className = { styles . aboveSuggestions } > { aboveSuggestions } </ div >
318288 ) }
319289 < div >
320- { suggestedProperties . length > 0 && (
290+ { visibleSuggestions . length > 0 && (
321291 < div className = { styles . suggestionsSection } >
322292 < div className = { styles . suggestionsHeaderRow } >
323293 < div className = { styles . suggestionsHeader } >
324294 { suggestionsHeader }
325295 </ div >
326- { totalPages > 1 ? (
327- < div className = { styles . pagination } >
328- < UnstyledButton
329- className = { styles . pageButton }
330- disabled = { page === 0 }
331- onClick = { ( ) => {
332- setPage ( p => p - 1 ) ;
333- setSelectedAutocompleteIndex ( - 1 ) ;
334- } }
335- aria-label = "Previous suggestions"
336- >
337- ‹
338- </ UnstyledButton >
339- < span className = { styles . pageIndicator } >
340- { page + 1 } / { totalPages }
341- </ span >
342- < UnstyledButton
343- className = { styles . pageButton }
344- disabled = { page === totalPages - 1 }
345- onClick = { ( ) => {
346- setPage ( p => p + 1 ) ;
347- setSelectedAutocompleteIndex ( - 1 ) ;
348- } }
349- aria-label = "Next suggestions"
350- >
351- ›
352- </ UnstyledButton >
296+ { suggestedProperties . length > pageSize && (
297+ < div className = { styles . suggestionsLimit } >
298+ (Showing Top { pageSize } )
353299 </ div >
354- ) : (
355- suggestedProperties . length > pageSize && (
356- < div className = { styles . suggestionsLimit } >
357- (Showing Top { pageSize } )
358- </ div >
359- )
360300 ) }
361301 </ div >
362- { pagedSuggestions . map ( ( { value, label } , i ) => {
363- const absoluteIdx = pageStart + i ;
364- return (
365- < div
366- className = { cx (
367- styles . suggestionItem ,
368- selectedAutocompleteIndex === absoluteIdx &&
369- styles . selected ,
370- ) }
371- role = "button"
372- key = { value }
373- onMouseOver = { ( ) => {
374- setSelectedAutocompleteIndex ( absoluteIdx ) ;
375- } }
376- onClick = { ( ) => {
377- onAcceptSuggestion ( value ) ;
378- } }
379- >
380- < span className = { styles . suggestionLabel } > { label } </ span >
381- </ div >
382- ) ;
383- } ) }
302+ { visibleSuggestions . map ( ( { value, label } , i ) => (
303+ < div
304+ className = { cx (
305+ styles . suggestionItem ,
306+ selectedAutocompleteIndex === i && styles . selected ,
307+ ) }
308+ role = "button"
309+ key = { value }
310+ onMouseOver = { ( ) => setSelectedAutocompleteIndex ( i ) }
311+ onClick = { ( ) => onAcceptSuggestion ( value ) }
312+ >
313+ < span className = { styles . suggestionLabel } > { label } </ span >
314+ </ div >
315+ ) ) }
384316 </ div >
385317 ) }
386318 </ div >
0 commit comments