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
|
import { h, Children, concatClassName } from 'lib/h'
import * as Button from 'lib/button'
export function create(
attrs: object,
id: string,
keys: string[],
renderEntry: (entry: string) => Element,
onInput: (value: string) => void
): Element {
const completion = h('div', {})
const updateCompletion = (target: EventTarget, value: string) => {
const entries = search(value, keys)
mountOn(
completion,
renderCompletion(
renderEntry,
selected => {
(target as HTMLInputElement).value = selected
completion.remove
removeChildren(completion)
onInput(selected)
},
entries
)
)
}
const input = h('input',
concatClassName(
{ ...attrs,
id,
autocomplete: 'off',
onfocus: (e: Event) => {
if (e.target !== null) {
const target = e.target as HTMLInputElement
updateCompletion(target, target.value)
}
},
oninput: (e: Event) => {
if (e.target !== null) {
const target = e.target as HTMLInputElement
updateCompletion(target, target.value)
onInput(target.value)
}
}
},
'g-AutoComplete__Input'
)
) as HTMLInputElement
input.addEventListener('blur', (e: MouseEvent) => {
if (e.relatedTarget === null) {
removeChildren(completion)
}
})
return h('div',
{ className: 'g-AutoComplete' },
input,
completion,
Button.raw(
{ className: 'g-AutoComplete__Clear',
type: 'button',
onclick: () => {
onInput('')
input.value = ''
input.focus()
}
},
'x'
)
)
}
function renderCompletion(
renderEntry: (entry: string) => Element,
onSelect: (entry: string) => void,
entries: string[]
): Element {
return h('div',
{ className: 'g-AutoComplete__Completion' },
...entries.map(c =>
Button.raw(
{ className: 'g-AutoComplete__Entry',
type: 'button',
onclick: (e: Event) => {
e.stopPropagation()
e.preventDefault()
onSelect(c)
}
},
renderEntry(c)
)
)
)
}
function search(s: string, xs: string[]): string[] {
return xs.filter(x => x.includes(s))
}
function mountOn(base: Element, ...children: Element[]) {
removeChildren(base)
children.forEach(child => base.appendChild(child))
}
function removeChildren(base: Element) {
const firstChild = base.firstChild
if (firstChild !== null) {
base.removeChild(firstChild)
removeChildren(base)
}
}
|