-
Notifications
You must be signed in to change notification settings - Fork 34
/
search.js
131 lines (124 loc) · 3.34 KB
/
search.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import {html} from "htl";
import {arrayify, maybeColumns} from "./array.js";
import {maybeWidth} from "./css.js";
import {maybeDatalist} from "./datalist.js";
import {preventDefault} from "./event.js";
import {formatLocaleNumber, localize, stringify} from "./format.js";
import {maybeLabel} from "./label.js";
import {onoff, truefalse} from "./text.js";
export function search(data, {
locale,
format = formatResults(locale), // length format
label,
query = "", // initial search query
placeholder = "Search", // placeholder text to show when empty
columns = maybeColumns(data),
spellcheck,
autocomplete,
autocapitalize,
filter = columns === undefined ? searchFilter : columnFilter(columns), // returns the filter function given query
datalist,
disabled,
required = true, // if true, the value is everything if nothing is selected
width
} = {}) {
let value = [];
data = arrayify(data);
required = !!required;
const [list, listId] = maybeDatalist(datalist);
const input = html`<input
name=input
type=search
list=${listId}
disabled=${disabled}
spellcheck=${truefalse(spellcheck)}
autocomplete=${onoff(autocomplete)}
autocapitalize=${onoff(autocapitalize)}
placeholder=${placeholder}
value=${query}
oninput=${oninput}
>`;
const output = html`<output name=output>`;
const form = html`<form class=__ns__ style=${maybeWidth(width)}>
${maybeLabel(label, input)}<div class=__ns__-input>
${input}${output}
</div>${list}
</form>`;
form.addEventListener("submit", preventDefault);
function oninput() {
value = input.value || required ? data.filter(filter(input.value)) : [];
if (columns !== undefined) value.columns = columns;
output.value = format(value.length);
}
oninput();
return Object.defineProperties(form, {
value: {
get() {
return value;
}
},
query: {
get() {
return query;
},
set(v) {
query = input.value = stringify(v);
oninput();
}
}
});
}
export function searchFilter(query) {
const filters = `${query}`.split(/\s+/g).filter(t => t).map(termFilter);
return d => {
if (d == null) return false;
if (typeof d === "object") {
out: for (const filter of filters) {
for (const value of valuesof(d)) {
if (filter.test(value)) {
continue out;
}
}
return false;
}
} else {
for (const filter of filters) {
if (!filter.test(d)) {
return false;
}
}
}
return true;
};
}
function columnFilter(columns) {
return query => {
const filters = `${query}`.split(/\s+/g).filter(t => t).map(termFilter);
return d => {
out: for (const filter of filters) {
for (const column of columns) {
if (filter.test(d[column])) {
continue out;
}
}
return false;
}
return true;
};
};
}
function* valuesof(d) {
for (const key in d) {
yield d[key];
}
}
function termFilter(term) {
return new RegExp(`(?:^|[^\\p{L}-])${escapeRegExp(term)}`, "iu");
}
function escapeRegExp(text) {
return text.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
}
const formatResults = localize(locale => {
const formatNumber = formatLocaleNumber(locale);
return length => `${formatNumber(length)} result${length === 1 ? "" : "s"}`;
});