@@ -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
}
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4