|
1 | 1 | /* @flow strict */
|
2 | 2 |
|
3 | 3 | export function install(input: HTMLTextAreaElement | HTMLInputElement, list: HTMLElement): void {
|
| 4 | + input.addEventListener('compositionstart', trackComposition) |
| 5 | + input.addEventListener('compositionend', trackComposition) |
4 | 6 | input.addEventListener('keydown', keyboardBindings)
|
5 | 7 | list.addEventListener('click', commitWithElement)
|
6 | 8 | }
|
7 | 9 |
|
8 | 10 | export function uninstall(input: HTMLTextAreaElement | HTMLInputElement, list: HTMLElement): void {
|
9 | 11 | input.removeAttribute('aria-activedescendant')
|
| 12 | + input.removeEventListener('compositionstart', trackComposition) |
| 13 | + input.removeEventListener('compositionend', trackComposition) |
10 | 14 | input.removeEventListener('keydown', keyboardBindings)
|
11 | 15 | list.removeEventListener('click', commitWithElement)
|
12 | 16 | }
|
13 | 17 |
|
| 18 | +let isComposing = false |
14 | 19 | const ctrlBindings = !!navigator.userAgent.match(/Macintosh/)
|
15 | 20 |
|
16 | 21 | function keyboardBindings(event: KeyboardEvent) {
|
17 | 22 | if (event.shiftKey || event.metaKey || event.altKey) return
|
18 | 23 | const input = event.currentTarget
|
19 | 24 | if (!(input instanceof HTMLTextAreaElement || input instanceof HTMLInputElement)) return
|
| 25 | + if (isComposing) return |
20 | 26 | const list = document.getElementById(input.getAttribute('aria-owns') || '')
|
21 | 27 | if (!list) return
|
22 | 28 |
|
23 | 29 | switch (event.key) {
|
24 | 30 | case 'Enter':
|
25 | 31 | case 'Tab':
|
26 |
| - commit(input, list) |
27 |
| - event.preventDefault() |
| 32 | + if (commit(input, list)) { |
| 33 | + event.preventDefault() |
| 34 | + } |
| 35 | + break |
| 36 | + case 'Escape': |
| 37 | + clearSelection(list) |
28 | 38 | break
|
29 | 39 | case 'ArrowDown':
|
30 | 40 | navigate(input, list, 1)
|
@@ -57,10 +67,11 @@ function commitWithElement(event: MouseEvent) {
|
57 | 67 | event.preventDefault()
|
58 | 68 | }
|
59 | 69 |
|
60 |
| -function commit(input: HTMLTextAreaElement | HTMLInputElement, list: HTMLElement): void { |
| 70 | +function commit(input: HTMLTextAreaElement | HTMLInputElement, list: HTMLElement): boolean { |
61 | 71 | const target = list.querySelector('[aria-selected="true"]')
|
62 |
| - if (!target) return |
| 72 | + if (!target) return false |
63 | 73 | fireCommitEvent(target)
|
| 74 | + return true |
64 | 75 | }
|
65 | 76 |
|
66 | 77 | function fireCommitEvent(target: Element): void {
|
@@ -96,3 +107,20 @@ export function navigate(
|
96 | 107 | }
|
97 | 108 | }
|
98 | 109 | }
|
| 110 | + |
| 111 | +function clearSelection(list): void { |
| 112 | + const target = list.querySelector('[aria-selected="true"]') |
| 113 | + if (!target) return |
| 114 | + target.setAttribute('aria-selected', 'false') |
| 115 | +} |
| 116 | + |
| 117 | +function trackComposition(event: Event): void { |
| 118 | + const input = event.currentTarget |
| 119 | + if (!(input instanceof HTMLTextAreaElement || input instanceof HTMLInputElement)) return |
| 120 | + isComposing = event.type === 'compositionstart' |
| 121 | + |
| 122 | + const list = document.getElementById(input.getAttribute('aria-owns') || '') |
| 123 | + if (!list) return |
| 124 | + |
| 125 | + clearSelection(list) |
| 126 | +} |
0 commit comments