aboutsummaryrefslogtreecommitdiff
path: root/src/lib/autoComplete.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/autoComplete.ts')
-rw-r--r--src/lib/autoComplete.ts115
1 files changed, 115 insertions, 0 deletions
diff --git a/src/lib/autoComplete.ts b/src/lib/autoComplete.ts
new file mode 100644
index 0000000..b0a79eb
--- /dev/null
+++ b/src/lib/autoComplete.ts
@@ -0,0 +1,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)
+ }
+}