forked from observablehq/inputs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.js
116 lines (109 loc) · 3.17 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
import {html} from "htl";
import {arrayify} 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";
export function search(data, {
locale,
format = formatResults(locale), // length format
label,
query = "", // initial search query
placeholder = "Search", // placeholder text to show when empty
columns = data.columns,
spellcheck,
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=${spellcheck === undefined ? false : spellcheck === null ? null : `${spellcheck}`} placeholder=${placeholder} value=${query} oninput=${oninput}>`;
const output = html`<output name=output>`;
const form = html`<form class=__ns__ onsubmit=${preventDefault} style=${maybeWidth(width)}>
${maybeLabel(label, input)}<div class=__ns__-input>
${input}${output}
</div>${list}
</form>`;
function oninput() {
value = input.value || required ? data.filter(filter(input.value)) : [];
if (columns !== undefined) value.columns = columns;
output.value = input.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(`\\b${escapeRegExp(term)}`, "i");
}
function escapeRegExp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
const formatResults = localize(locale => {
const formatNumber = formatLocaleNumber(locale);
return length => `${formatNumber(length)} result${length === 1 ? "" : "s"}`;
});