|
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