|
13 | 13 | // Indicates global variables for spatnav (starting position) |
14 | 14 | const spatNavManager = { |
15 | 15 | startingPosition: null, |
16 | | - useStandardName: true, |
| 16 | + useStandardName: true |
17 | 17 | }; |
18 | 18 |
|
19 | 19 | // Use non standard names by default, as per https://www.w3.org/2001/tag/doc/polyfills/#don-t-squat-on-proposed-names-in-speculative-polyfills |
|
33 | 33 | const ARROW_KEY_CODE = {37: 'left', 38: 'up', 39: 'right', 40: 'down'}; |
34 | 34 | const TAB_KEY_CODE = 9; |
35 | 35 | let spatialNaviagtionKeyMode = 'ARROW'; |
| 36 | + let mapOfBoundRect = new Map(); |
36 | 37 |
|
37 | 38 | function focusNavigationHeuristics() { |
38 | 39 |
|
|
57 | 58 | const currentKeyMode = (parent && parent.__spatialNavigation__.getKeyMode()) || window.__spatialNavigation__.getKeyMode(); |
58 | 59 | const eventTarget = document.activeElement; |
59 | 60 | const dir = ARROW_KEY_CODE[e.keyCode]; |
| 61 | + mapOfBoundRect = new Map(); |
60 | 62 |
|
61 | 63 | if (e.keyCode === TAB_KEY_CODE) |
62 | 64 | spatNavManager.startingPosition = null; |
|
374 | 376 | // to do |
375 | 377 | // Offscreen handling when originalContainer is not <HTML> |
376 | 378 | if (!isVisible(currentElm) && originalContainer.parentElement && container !== originalContainer) |
377 | | - eventTargetRect = originalContainer.getBoundingClientRect(); |
378 | | - else eventTargetRect = currentElm.getBoundingClientRect(); |
| 379 | + eventTargetRect = getBoundingClientRect(originalContainer); |
| 380 | + else eventTargetRect = getBoundingClientRect(currentElm); |
379 | 381 |
|
380 | 382 | // If D(dir) is null, let candidates be the same as visibles |
381 | 383 | if (dir === undefined) |
|
388 | 390 | */ |
389 | 391 | return candidates.filter(candidate => |
390 | 392 | container.contains(candidate.getSpatialNavigationContainer()) && |
391 | | - isOutside(candidate.getBoundingClientRect(), eventTargetRect, dir) |
| 393 | + isOutside(getBoundingClientRect(candidate), eventTargetRect, dir) |
392 | 394 | ); |
393 | 395 | } |
394 | 396 |
|
|
407 | 409 | let bestCandidate; |
408 | 410 | let minDistance = Number.POSITIVE_INFINITY; |
409 | 411 | let tempDistance = undefined; |
| 412 | + let eventTargetRect = getBoundingClientRect(currentElm); |
410 | 413 |
|
411 | 414 | for (let i = 0; i < candidates.length; i++) { |
412 | | - tempDistance = getDistance(currentElm.getBoundingClientRect(), candidates[i].getBoundingClientRect(), dir); |
| 415 | + tempDistance = getDistance(eventTargetRect, getBoundingClientRect(candidates[i]), dir); |
413 | 416 | if (tempDistance < minDistance) { |
414 | 417 | minDistance = tempDistance; |
415 | 418 | bestCandidate = candidates[i]; |
|
429 | 432 | * @returns {<Node>} the best candidate |
430 | 433 | **/ |
431 | 434 | function selectBestCandidateFromEdge(currentElm, candidates, dir) { |
432 | | - const eventTargetRect = currentElm.getBoundingClientRect(); |
| 435 | + const eventTargetRect = getBoundingClientRect(currentElm); |
433 | 436 | let minDistanceElement = undefined; |
434 | 437 | let minDistance = Number.POSITIVE_INFINITY; |
435 | 438 | let tempMinDistance = undefined; |
436 | 439 |
|
437 | 440 | if(candidates) { |
438 | 441 | for (let i = 0; i < candidates.length; i++) { |
439 | | - tempMinDistance = getInnerDistance(eventTargetRect, candidates[i].getBoundingClientRect(), dir); |
| 442 | + tempMinDistance = getInnerDistance(eventTargetRect, getBoundingClientRect(candidates[i]), dir); |
440 | 443 |
|
441 | 444 | // If the same distance, the candidate will be selected in the DOM order |
442 | 445 | if (tempMinDistance < minDistance) { |
|
791 | 794 | * @returns {Boolean} |
792 | 795 | **/ |
793 | 796 | function isEntirelyVisible(element) { |
794 | | - const rect = element.getBoundingClientRect(); |
795 | | - const containerRect = element.getSpatialNavigationContainer().getBoundingClientRect(); |
| 797 | + const rect = getBoundingClientRect(element); |
| 798 | + const containerRect = getBoundingClientRect(element.getSpatialNavigationContainer()); |
796 | 799 |
|
797 | 800 | // FIXME: when element is bigger than container? |
798 | 801 | const entirelyVisible = !((rect.left < containerRect.left) || |
|
830 | 833 | offsetX = isNaN(offsetX) ? 0 : offsetX; |
831 | 834 | offsetY = isNaN(offsetY) ? 0 : offsetY; |
832 | 835 |
|
833 | | - const elementRect = element.getBoundingClientRect(); |
| 836 | + const elementRect = getBoundingClientRect(element); |
834 | 837 |
|
835 | 838 | const middleElem = document.elementFromPoint((elementRect.left + elementRect.right) / 2, (elementRect.top + elementRect.bottom) / 2); |
836 | 839 | const leftTopElem = document.elementFromPoint(elementRect.left + offsetX, elementRect.top + offsetY); |
|
1139 | 1142 | return focusNavigableArrowKey; |
1140 | 1143 | } |
1141 | 1144 |
|
| 1145 | + function getBoundingClientRect(element) { |
| 1146 | + let rect = mapOfBoundRect.get(element); // memoization |
| 1147 | + if(!rect) { |
| 1148 | + const boundingClientRect = element.getBoundingClientRect(); |
| 1149 | + rect = { |
| 1150 | + top: Number(boundingClientRect.top.toFixed(2)), |
| 1151 | + right: Number(boundingClientRect.right.toFixed(2)), |
| 1152 | + bottom: Number(boundingClientRect.bottom.toFixed(2)), |
| 1153 | + left: Number(boundingClientRect.left.toFixed(2)), |
| 1154 | + width: Number(boundingClientRect.width.toFixed(2)), |
| 1155 | + height: Number(boundingClientRect.height.toFixed(2)) |
| 1156 | + }; |
| 1157 | + mapOfBoundRect.set(element, rect); |
| 1158 | + } |
| 1159 | + return rect; |
| 1160 | + } |
| 1161 | + |
1142 | 1162 | function setStandardName() { |
1143 | 1163 | spatNavManager.useStandardName = true; |
1144 | 1164 | } |
|
1295 | 1315 | getDistanceFromTarget: (element, candidateElement, dir) => { |
1296 | 1316 | if ((isContainer(element) || element.nodeName === 'BODY') && !(element.nodeName === 'INPUT')) { |
1297 | 1317 | if (element.focusableAreas().includes(candidateElement)) { |
1298 | | - return getInnerDistance(element.getBoundingClientRect(), candidateElement.getBoundingClientRect(), dir); |
| 1318 | + return getInnerDistance(getBoundingClientRect(element), getBoundingClientRect(candidateElement), dir); |
1299 | 1319 | } |
1300 | 1320 | } |
1301 | | - return getDistance(element.getBoundingClientRect(), candidateElement.getBoundingClientRect(), dir); |
| 1321 | + return getDistance(getBoundingClientRect(element), getBoundingClientRect(candidateElement), dir); |
1302 | 1322 | }, |
1303 | 1323 |
|
1304 | 1324 | setKeyMode : (option) => { |
|
0 commit comments