|
18 | 18 | return; |
19 | 19 | } |
20 | 20 |
|
21 | | - function getFocusableSibling(container, isPrev) { |
| 21 | + function getFocusableSibling(container, isPrev, startA) { |
22 | 22 | if (!container) { |
23 | 23 | return |
24 | 24 | } |
25 | | - var focusA = container.querySelector(':focus'); |
26 | | - var focusLI = focusA; |
27 | | - while (focusLI && focusLI.tagName !== 'LI') { |
28 | | - focusLI = focusLI.parentElement; |
| 25 | + if (!startA) { |
| 26 | + startA = container.querySelector(':focus'); |
29 | 27 | } |
30 | | - if (!focusLI) { |
| 28 | + var startLI = startA; |
| 29 | + while (startLI && startLI.tagName !== 'LI') { |
| 30 | + startLI = startLI.parentElement; |
| 31 | + } |
| 32 | + if (!startLI) { |
31 | 33 | if (isPrev) { |
32 | | - focusLI = container.firstElementChild; |
| 34 | + startLI = container.firstElementChild; |
33 | 35 | } else { |
34 | | - focusLI = container.lastElementChild; |
| 36 | + startLI = container.lastElementChild; |
35 | 37 | } |
36 | 38 | } |
37 | | - if (!focusLI) { |
| 39 | + if (!startLI) { |
38 | 40 | return; |
39 | 41 | } |
40 | 42 |
|
41 | | - var siblingLI = focusLI; |
| 43 | + var siblingLI = startLI; |
42 | 44 | do { |
43 | 45 | if (isPrev) { |
44 | 46 | siblingLI = siblingLI.previousElementSibling; |
|
51 | 53 | siblingLI = container.firstElementChild; |
52 | 54 | } |
53 | 55 | } |
54 | | - } while (siblingLI !== focusLI && ( |
| 56 | + } while (siblingLI !== startLI && ( |
55 | 57 | siblingLI.classList.contains(classNone) || |
56 | 58 | siblingLI.classList.contains(classHeader) |
57 | 59 | )); |
58 | 60 |
|
59 | 61 | if (siblingLI) { |
60 | | - var newFocusA = siblingLI.querySelector('a'); |
61 | | - return newFocusA; |
| 62 | + var siblingA = siblingLI.querySelector('a'); |
| 63 | + return siblingA; |
62 | 64 | } |
63 | 65 | } |
64 | 66 |
|
| 67 | + function getMatchedFocusableSibling(container, isPrev, startA, buf, key) { |
| 68 | + var skipRound = buf === key; |
| 69 | + var matchKeyA; |
| 70 | + var firstCheckA; |
| 71 | + var secondCheckA; |
| 72 | + var a = startA; |
| 73 | + do { |
| 74 | + if (skipRound) { |
| 75 | + skipRound = false; |
| 76 | + continue; |
| 77 | + } |
| 78 | + if (!a) { |
| 79 | + continue; |
| 80 | + } |
| 81 | + |
| 82 | + // firstCheckA maybe a focused a that not belongs to the list |
| 83 | + // secondCheckA must be in the list |
| 84 | + if (!firstCheckA) { |
| 85 | + firstCheckA = a; |
| 86 | + } else if (firstCheckA === a) { |
| 87 | + return; |
| 88 | + } else if (firstCheckA && !secondCheckA) { |
| 89 | + secondCheckA = a |
| 90 | + } else if (secondCheckA === a) { |
| 91 | + return; |
| 92 | + } |
| 93 | + |
| 94 | + var textContent = (a.querySelector('.name') || a).textContent.toLowerCase(); |
| 95 | + if (buf.length <= textContent.length && textContent.substring(0, buf.length) === buf) { |
| 96 | + return a; |
| 97 | + } |
| 98 | + if (!matchKeyA && textContent[0] === key) { |
| 99 | + matchKeyA = a; |
| 100 | + } |
| 101 | + } while (a = getFocusableSibling(container, isPrev, a)); |
| 102 | + return matchKeyA; |
| 103 | + } |
| 104 | + |
65 | 105 | var UP = 'Up'; |
66 | 106 | var DOWN = 'Down'; |
67 | 107 | var LEFT = 'Left'; |
|
77 | 117 | var ARROW_LEFT_CODE = 37; |
78 | 118 | var ARROW_RIGHT_CODE = 39; |
79 | 119 |
|
80 | | - var skipTags = ['INPUT', 'BUTTON', 'TEXTAREA']; |
| 120 | + var SKIP_TAGS = ['INPUT', 'BUTTON', 'TEXTAREA']; |
| 121 | + |
| 122 | + var lookupKey = ''; |
| 123 | + var lookupBuffer = ''; |
| 124 | + var lookupStartA = null; |
| 125 | + var lookupTimer; |
| 126 | + |
| 127 | + function delayClearLookupContext() { |
| 128 | + clearTimeout(lookupTimer); |
| 129 | + lookupTimer = setTimeout(function () { |
| 130 | + lookupBuffer = ''; |
| 131 | + lookupStartA = null; |
| 132 | + }, 850); |
| 133 | + } |
| 134 | + |
| 135 | + function lookup(key) { |
| 136 | + key = key.toLowerCase(); |
| 137 | + |
| 138 | + if (key === lookupKey && key === lookupBuffer) { |
| 139 | + // same as last key, lookup next for the same key as prefix |
| 140 | + lookupStartA = itemList.querySelector(':focus'); |
| 141 | + lookupBuffer = lookupKey; |
| 142 | + } else { |
| 143 | + if (!lookupStartA) { |
| 144 | + lookupStartA = itemList.querySelector(':focus'); |
| 145 | + } |
| 146 | + lookupKey = key; |
| 147 | + lookupBuffer += key; |
| 148 | + } |
| 149 | + delayClearLookupContext(); |
| 150 | + return getMatchedFocusableSibling(itemList, false, lookupStartA, lookupBuffer, key); |
| 151 | + } |
81 | 152 |
|
82 | 153 | document.addEventListener('keydown', function (e) { |
83 | 154 | if ( |
84 | 155 | e.ctrlKey || |
85 | 156 | e.altKey || |
86 | | - e.shiftKey || |
87 | | - e.metaKey || |
88 | | - skipTags.indexOf(e.target.tagName) >= 0 |
| 157 | + SKIP_TAGS.indexOf(e.target.tagName) >= 0 |
89 | 158 | ) { |
90 | 159 | return; |
91 | 160 | } |
92 | 161 |
|
93 | 162 | var newFocusEl; |
| 163 | + |
94 | 164 | if (e.key) { |
95 | 165 | switch (e.key) { |
96 | 166 | case LEFT: |
97 | 167 | case ARROW_LEFT: |
98 | | - newFocusEl = getFocusableSibling(pathList, true); |
| 168 | + if (!e.shiftKey && !e.metaKey) { |
| 169 | + newFocusEl = getFocusableSibling(pathList, true); |
| 170 | + } |
99 | 171 | break; |
100 | 172 | case RIGHT: |
101 | 173 | case ARROW_RIGHT: |
102 | | - newFocusEl = getFocusableSibling(pathList, false); |
| 174 | + if (!e.shiftKey && !e.metaKey) { |
| 175 | + newFocusEl = getFocusableSibling(pathList, false); |
| 176 | + } |
103 | 177 | break; |
104 | 178 | case UP: |
105 | 179 | case ARROW_UP: |
106 | | - newFocusEl = getFocusableSibling(itemList, true); |
| 180 | + if (!e.shiftKey && !e.metaKey) { |
| 181 | + newFocusEl = getFocusableSibling(itemList, true); |
| 182 | + } |
107 | 183 | break; |
108 | 184 | case DOWN: |
109 | 185 | case ARROW_DOWN: |
110 | | - newFocusEl = getFocusableSibling(itemList, false); |
| 186 | + if (!e.shiftKey && !e.metaKey) { |
| 187 | + newFocusEl = getFocusableSibling(itemList, false); |
| 188 | + } |
| 189 | + break; |
| 190 | + default: |
| 191 | + if (e.key.length === 1) { |
| 192 | + newFocusEl = lookup(e.key); |
| 193 | + } |
111 | 194 | break; |
112 | 195 | } |
113 | 196 | } else if (e.keyCode) { |
114 | 197 | switch (e.keyCode) { |
115 | 198 | case ARROW_LEFT_CODE: |
116 | | - newFocusEl = getFocusableSibling(pathList, true); |
| 199 | + if (!e.shiftKey && !e.metaKey) { |
| 200 | + newFocusEl = getFocusableSibling(pathList, true); |
| 201 | + } |
117 | 202 | break; |
118 | 203 | case ARROW_RIGHT_CODE: |
119 | | - newFocusEl = getFocusableSibling(pathList, false); |
| 204 | + if (!e.shiftKey && !e.metaKey) { |
| 205 | + newFocusEl = getFocusableSibling(pathList, false); |
| 206 | + } |
120 | 207 | break; |
121 | 208 | case ARROW_UP_CODE: |
122 | | - newFocusEl = getFocusableSibling(itemList, true); |
| 209 | + if (!e.shiftKey && !e.metaKey) { |
| 210 | + newFocusEl = getFocusableSibling(itemList, true); |
| 211 | + } |
123 | 212 | break; |
124 | 213 | case ARROW_DOWN_CODE: |
125 | | - newFocusEl = getFocusableSibling(itemList, false); |
| 214 | + if (!e.shiftKey && !e.metaKey) { |
| 215 | + newFocusEl = getFocusableSibling(itemList, false); |
| 216 | + } |
126 | 217 | break; |
| 218 | + default: |
| 219 | + if (e.keyCode >= 32 && e.keyCode <= 126) { |
| 220 | + newFocusEl = lookup(String.fromCharCode(e.keyCode)); |
| 221 | + } |
127 | 222 | } |
128 | 223 | } |
129 | 224 | if (newFocusEl) { |
|
0 commit comments