|
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