getTabDestination.js 3.0 KB

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