getTabDestination.js 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. 'use strict';
  2. var isDisabled = require('../misc/isDisabled.js');
  3. var isElementType = require('../misc/isElementType.js');
  4. var isVisible = require('../misc/isVisible.js');
  5. var selector = require('./selector.js');
  6. function getTabDestination(activeElement, shift) {
  7. const document = activeElement.ownerDocument;
  8. const focusableElements = document.querySelectorAll(selector.FOCUSABLE_SELECTOR);
  9. const enabledElements = Array.from(focusableElements).filter((el)=>el === activeElement || !(Number(el.getAttribute('tabindex')) < 0 || isDisabled.isDisabled(el)));
  10. // tabindex has no effect if the active element has negative tabindex
  11. if (Number(activeElement.getAttribute('tabindex')) >= 0) {
  12. enabledElements.sort((a, b)=>{
  13. const i = Number(a.getAttribute('tabindex'));
  14. const j = Number(b.getAttribute('tabindex'));
  15. if (i === j) {
  16. return 0;
  17. } else if (i === 0) {
  18. return 1;
  19. } else if (j === 0) {
  20. return -1;
  21. }
  22. return i - j;
  23. });
  24. }
  25. const checkedRadio = {};
  26. let prunedElements = [
  27. document.body
  28. ];
  29. const activeRadioGroup = isElementType.isElementType(activeElement, 'input', {
  30. type: 'radio'
  31. }) ? activeElement.name : undefined;
  32. enabledElements.forEach((currentElement)=>{
  33. const el = currentElement;
  34. // For radio groups keep only the active radio
  35. // If there is no active radio, keep only the checked radio
  36. // If there is no checked radio, treat like everything else
  37. if (isElementType.isElementType(el, 'input', {
  38. type: 'radio'
  39. }) && el.name) {
  40. // If the active element is part of the group, add only that
  41. if (el === activeElement) {
  42. prunedElements.push(el);
  43. return;
  44. } else if (el.name === activeRadioGroup) {
  45. return;
  46. }
  47. // If we stumble upon a checked radio, remove the others
  48. if (el.checked) {
  49. prunedElements = prunedElements.filter((e)=>!isElementType.isElementType(e, 'input', {
  50. type: 'radio',
  51. name: el.name
  52. }));
  53. prunedElements.push(el);
  54. checkedRadio[el.name] = el;
  55. return;
  56. }
  57. // If we already found the checked one, skip
  58. if (typeof checkedRadio[el.name] !== 'undefined') {
  59. return;
  60. }
  61. }
  62. prunedElements.push(el);
  63. });
  64. for(let index = prunedElements.findIndex((el)=>el === activeElement);;){
  65. index += shift ? -1 : 1;
  66. // loop at overflow
  67. if (index === prunedElements.length) {
  68. index = 0;
  69. } else if (index === -1) {
  70. index = prunedElements.length - 1;
  71. }
  72. if (prunedElements[index] === activeElement || prunedElements[index] === document.body || isVisible.isVisible(prunedElements[index])) {
  73. return prunedElements[index];
  74. }
  75. }
  76. }
  77. exports.getTabDestination = getTabDestination;