1- import debounce from 'lodash.debounce' ;
21import { StateStore } from 'stream-chat' ;
32import type {
43 Channel ,
@@ -16,7 +15,82 @@ import type {
1615 UserSort ,
1716} from 'stream-chat' ;
1817import type { DefaultStreamChatGenerics } from '../../types' ;
19- import type { DebouncedFunc } from 'lodash' ;
18+
19+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20+ interface DebouncedFunc < T extends ( ...args : any [ ] ) => any > {
21+ /**
22+ * Call the original function, but applying the debounce rules.
23+ *
24+ * If the debounced function can be run immediately, this calls it and returns its return
25+ * value.
26+ *
27+ * Otherwise, it returns the return value of the last invocation, or undefined if the debounced
28+ * function was not invoked yet.
29+ */
30+ ( ...args : Parameters < T > ) : ReturnType < T > | undefined ;
31+
32+ /**
33+ * Throw away any pending invocation of the debounced function.
34+ */
35+ cancel ( ) : void ;
36+
37+ /**
38+ * If there is a pending invocation of the debounced function, invoke it immediately and return
39+ * its return value.
40+ *
41+ * Otherwise, return the value from the last invocation, or undefined if the debounced function
42+ * was never invoked.
43+ */
44+ flush ( ) : ReturnType < T > | undefined ;
45+ }
46+
47+ // works exactly the same as lodash.debounce
48+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49+ export const debounce = < T extends ( ...args : any [ ] ) => any > (
50+ fn : T ,
51+ timeout = 0 ,
52+ { leading = false , trailing = true } : { leading ?: boolean ; trailing ?: boolean } = { } ,
53+ ) : DebouncedFunc < T > => {
54+ let runningTimeout : null | NodeJS . Timeout = null ;
55+ let argsForTrailingExecution : Parameters < T > | null = null ;
56+ let lastResult : ReturnType < T > | undefined ;
57+
58+ const debouncedFn = ( ...args : Parameters < T > ) => {
59+ if ( runningTimeout ) {
60+ clearTimeout ( runningTimeout ) ;
61+ } else if ( leading ) {
62+ lastResult = fn ( ...args ) ;
63+ }
64+ if ( trailing ) argsForTrailingExecution = args ;
65+
66+ const timeoutHandler = ( ) => {
67+ if ( argsForTrailingExecution ) {
68+ lastResult = fn ( ...argsForTrailingExecution ) ;
69+ argsForTrailingExecution = null ;
70+ }
71+ runningTimeout = null ;
72+ } ;
73+
74+ runningTimeout = setTimeout ( timeoutHandler , timeout ) ;
75+ return lastResult ;
76+ } ;
77+
78+ debouncedFn . cancel = ( ) => {
79+ if ( runningTimeout ) clearTimeout ( runningTimeout ) ;
80+ } ;
81+
82+ debouncedFn . flush = ( ) => {
83+ if ( runningTimeout ) {
84+ clearTimeout ( runningTimeout ) ;
85+ runningTimeout = null ;
86+ if ( argsForTrailingExecution ) {
87+ lastResult = fn ( ...argsForTrailingExecution ) ;
88+ }
89+ }
90+ return lastResult ;
91+ } ;
92+ return debouncedFn ;
93+ } ;
2094
2195// eslint-disable-next-line @typescript-eslint/ban-types
2296export type SearchSourceType = 'channels' | 'users' | 'messages' | ( string & { } ) ;
@@ -67,7 +141,7 @@ export type SearchSourceOptions = {
67141} ;
68142
69143const DEFAULT_SEARCH_SOURCE_OPTIONS : Required < SearchSourceOptions > = {
70- debounceMs : 300 ,
144+ debounceMs : 5000 ,
71145 isActive : false ,
72146 pageSize : 10 ,
73147} as const ;
@@ -151,6 +225,15 @@ export abstract class BaseSearchSource<T> implements SearchSource<T> {
151225 } ;
152226
153227 async executeQuery ( searchQuery : string ) {
228+ const hasNewSearchQuery = typeof searchQuery !== 'undefined' ;
229+ if ( ! this . isActive || this . isLoading || ! this . hasMore || ! searchQuery ) return ;
230+
231+ if ( hasNewSearchQuery ) {
232+ this . resetState ( { isActive : this . isActive , isLoading : true , searchQuery } ) ;
233+ } else {
234+ this . state . partialNext ( { isLoading : true } ) ;
235+ }
236+
154237 const stateUpdate : Partial < SearchSourceState < T > > = { } ;
155238 try {
156239 const results = await this . query ( searchQuery ) ;
@@ -180,21 +263,6 @@ export abstract class BaseSearchSource<T> implements SearchSource<T> {
180263 }
181264
182265 search = async ( searchQuery ?: string ) => {
183- if ( ! this . isActive ) return ;
184- const hasNewSearchQuery = typeof searchQuery !== 'undefined' ;
185- const preventLoadMore =
186- ( ! hasNewSearchQuery && ! this . hasMore ) ||
187- this . isLoading ||
188- ( ! hasNewSearchQuery && ! this . searchQuery ) ;
189- const preventSearchStart = hasNewSearchQuery && this . isLoading ;
190- if ( preventLoadMore || preventSearchStart ) return ;
191-
192- if ( hasNewSearchQuery ) {
193- this . resetState ( { isActive : this . isActive , isLoading : true , searchQuery } ) ;
194- } else {
195- this . state . partialNext ( { isLoading : true } ) ;
196- }
197-
198266 await new Promise ( ( resolve ) => {
199267 this . resolveDebouncedSearch = resolve ;
200268 this . searchDebounced ( searchQuery ?? this . searchQuery ) ;
@@ -416,7 +484,6 @@ export type SearchControllerState<
416484 Sources extends SearchSource [ ] = DefaultSearchSources < StreamChatGenerics >
417485> = {
418486 isActive : boolean ;
419- queriesInProgress : Array < Sources [ number ] [ 'type' ] > ;
420487 searchQuery : string ;
421488 sources : Sources ;
422489 // FIXME: focusedMessage should live in a MessageListController class that does not exist yet.
@@ -448,7 +515,6 @@ export class SearchController<
448515 constructor ( { config, sources } : SearchControllerOptions < StreamChatGenerics , Sources > = { } ) {
449516 this . state = new StateStore < SearchControllerState < StreamChatGenerics , Sources > > ( {
450517 isActive : false ,
451- queriesInProgress : [ ] ,
452518 searchQuery : '' ,
453519 sources : sources ?? ( ( [ ] as unknown ) as Sources ) ,
454520 } ) ;
@@ -470,10 +536,6 @@ export class SearchController<
470536 return this . state . getLatestValue ( ) . isActive ;
471537 }
472538
473- get queriesInProgress ( ) {
474- return this . state . getLatestValue ( ) . queriesInProgress ;
475- }
476-
477539 get searchQuery ( ) {
478540 return this . state . getLatestValue ( ) . searchQuery ;
479541 }
@@ -482,14 +544,6 @@ export class SearchController<
482544 return this . sources . map ( ( s ) => s . type ) as Sources [ number ] [ 'type' ] [ ] ;
483545 }
484546
485- get isCleared ( ) {
486- return this . activeSources . every ( ( s ) => ! s . hasResults && ! s . isLoading && ! s . searchQuery ) ;
487- }
488-
489- get isLoading ( ) {
490- return this . state . getLatestValue ( ) . queriesInProgress . length > 0 ;
491- }
492-
493547 setInputElement = ( input : HTMLInputElement ) => {
494548 this . state . partialNext ( { input } ) ;
495549 } ;
@@ -558,13 +612,9 @@ export class SearchController<
558612 search = async ( searchQuery ?: string ) => {
559613 const searchedSources = this . activeSources ;
560614 this . state . partialNext ( {
561- queriesInProgress : searchedSources . map ( ( s ) => s . type ) ,
562615 searchQuery,
563616 } ) ;
564617 await Promise . all ( searchedSources . map ( ( source ) => source . search ( searchQuery ) ) ) ;
565- this . state . partialNext ( {
566- queriesInProgress : [ ] ,
567- } ) ;
568618 } ;
569619
570620 cancelSearchQueries = ( ) => {
0 commit comments