@@ -13,7 +13,7 @@ export interface TypeaheadProps {
13
13
itemsDelay ?: number ;
14
14
minLength ?: number ;
15
15
renderList ?: ( typeahead : TypeaheadController ) => React . ReactNode ;
16
- renderItem ?: ( item : unknown , query : string ) => React . ReactNode ;
16
+ renderItem ?: ( item : unknown , highlighter : TextHighlighter ) => React . ReactNode ;
17
17
onSelect ?: ( item : unknown , e : React . KeyboardEvent < any > | React . MouseEvent < any > ) => string | null ;
18
18
scrollHeight ?: number ;
19
19
inputAttrs ?: React . InputHTMLAttributes < HTMLInputElement > ;
@@ -257,6 +257,8 @@ export const Typeahead = React.forwardRef(function Typeahead(p: TypeaheadProps,
257
257
258
258
function renderDefaultList ( ) {
259
259
var items = controller . items ;
260
+
261
+ var highlighter = TextHighlighter . fromString ( controller . query ) ;
260
262
return (
261
263
< Dropdown . Menu align = { controller . rtl ? "end" : undefined } className = "typeahead" >
262
264
{
@@ -268,7 +270,7 @@ export const Typeahead = React.forwardRef(function Typeahead(p: TypeaheadProps,
268
270
onMouseLeave = { e => controller . handleElementMouseLeave ( e , i ) }
269
271
onMouseUp = { e => controller . handleMenuMouseUp ( e , i ) }
270
272
{ ...p . itemAttrs && p . itemAttrs ( item ) } >
271
- { p . renderItem ! ( item , controller . query ! ) }
273
+ { p . renderItem ! ( item , highlighter ) }
272
274
</ button > )
273
275
}
274
276
</ Dropdown . Menu >
@@ -293,7 +295,7 @@ Typeahead.defaultProps = {
293
295
getItems : undefined as any ,
294
296
itemsDelay : 200 ,
295
297
minLength : 1 ,
296
- renderItem : ( item , query ) => TypeaheadOptions . highlightedText ( item as string , query ) ,
298
+ renderItem : ( item , highlighter ) => highlighter . highlight ( item as string ) ,
297
299
onSelect : ( elem , event ) => ( elem as string ) ,
298
300
scrollHeight : 0 ,
299
301
@@ -302,57 +304,55 @@ Typeahead.defaultProps = {
302
304
303
305
304
306
export namespace TypeaheadOptions {
305
- export function highlightedText ( val : string , query ?: string ) : React . ReactChild {
306
307
307
- if ( query == undefined )
308
- return val ;
308
+ export function normalizeString ( str : string ) : string {
309
+ return str ;
310
+ }
311
+ }
309
312
310
- const index = val . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) ;
311
- if ( index == - 1 )
312
- return val ;
313
+ export class TextHighlighter {
314
+ query ?: string ;
315
+ parts ?: string [ ] ;
316
+ regex ?: RegExp ;
313
317
314
- return (
315
- < >
316
- { val . substr ( 0 , index ) }
317
- < strong key = { 0 } > { val . substr ( index , query . length ) } </ strong >
318
- { val . substr ( index + query . length ) }
319
- </ >
320
- ) ;
318
+ static fromString ( query : string | undefined ) {
319
+ var hl = new TextHighlighter ( query ?. split ( " " ) ) ;
320
+ hl . query = query ;
321
+ return hl ;
321
322
}
322
323
323
- export function highlightedTextAll ( val : string , query : string | undefined ) : React . ReactChild {
324
- if ( query == undefined )
325
- return val ;
324
+ constructor ( parts : string [ ] | undefined ) {
325
+ this . parts = parts ?. filter ( a => a != null && a . length > 0 ) . orderByDescending ( a => a . length ) ;
326
+ if ( this . parts ?. length )
327
+ this . regex = new RegExp ( this . parts . map ( p => RegExp . escape ( p ) ) . join ( "|" ) , "gi" ) ;
328
+ }
326
329
327
- const parts = query . toLocaleLowerCase ( ) . split ( " " ) . filter ( a => a . length > 0 ) . orderByDescending ( a => a . length ) ;
330
+ highlight ( text : string ) : React . ReactChild {
331
+ if ( ! text || ! this . regex )
332
+ return text ;
328
333
329
- function splitText ( str : string , partIndex : number ) : React . ReactChild {
334
+ var matches = Array . from ( text . matchAll ( this . regex ) ) ;
330
335
331
- if ( str . length == 0 )
332
- return str ;
336
+ if ( matches . length == 0 )
337
+ return text ;
333
338
334
- if ( parts . length <= partIndex )
335
- return str ;
339
+ var result = [ ] ;
336
340
337
- var part = parts [ partIndex ] ;
341
+ var pos = 0 ;
342
+ for ( var i = 0 ; i < matches . length ; i ++ ) {
343
+ var m = matches [ i ] ;
338
344
339
- const index = str . toLowerCase ( ) . indexOf ( part ) ;
340
- if ( index == - 1 )
341
- return splitText ( str , partIndex + 1 ) ;
345
+ if ( pos < m . index ! ) {
346
+ result . push ( text . substring ( pos , m . index ) ) ;
347
+ }
342
348
343
- return (
344
- < >
345
- { splitText ( str . substr ( 0 , index ) , partIndex + 1 ) }
346
- < strong key = { 0 } > { str . substr ( index , part . length ) } </ strong >
347
- { splitText ( str . substr ( index + part . length ) , partIndex + 1 ) }
348
- </ >
349
- ) ;
349
+ pos = m . index ! + m [ 0 ] . length ;
350
+ result . push ( < strong > { text . substring ( m . index ! , pos ) } </ strong > ) ;
350
351
}
351
352
352
- return splitText ( val , 0 ) ;
353
- }
353
+ if ( pos < text . length )
354
+ result . push ( text . substring ( pos ) ) ;
354
355
355
- export function normalizeString ( str : string ) : string {
356
- return str ;
356
+ return React . createElement ( React . Fragment , undefined , ...result ) ;
357
357
}
358
358
}
0 commit comments