tab.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.tab = tab;
  6. var _dom = require("@testing-library/dom");
  7. var _utils = require("./utils");
  8. var _focus = require("./focus");
  9. var _blur = require("./blur");
  10. function getNextElement(currentIndex, shift, elements, focusTrap) {
  11. if ((0, _utils.isDocument)(focusTrap) && (currentIndex === 0 && shift || currentIndex === elements.length - 1 && !shift)) {
  12. return focusTrap.body;
  13. }
  14. const nextIndex = shift ? currentIndex - 1 : currentIndex + 1;
  15. const defaultIndex = shift ? elements.length - 1 : 0;
  16. return elements[nextIndex] || elements[defaultIndex];
  17. }
  18. function tab({
  19. shift = false,
  20. focusTrap
  21. } = {}) {
  22. var _focusTrap$ownerDocum, _focusTrap;
  23. const doc = (_focusTrap$ownerDocum = (_focusTrap = focusTrap) == null ? void 0 : _focusTrap.ownerDocument) != null ? _focusTrap$ownerDocum : document;
  24. const previousElement = (0, _utils.getActiveElement)(doc);
  25. if (!focusTrap) {
  26. focusTrap = doc;
  27. }
  28. const focusableElements = focusTrap.querySelectorAll(_utils.FOCUSABLE_SELECTOR);
  29. const enabledElements = Array.from(focusableElements).filter(el => el === previousElement || el.getAttribute('tabindex') !== '-1' && !(0, _utils.isDisabled)(el) && // Hidden elements are not tabable
  30. (0, _utils.isVisible)(el));
  31. if (enabledElements.length === 0) return;
  32. const orderedElements = enabledElements.map((el, idx) => ({
  33. el,
  34. idx
  35. })).sort((a, b) => {
  36. // tabindex has no effect if the active element has tabindex="-1"
  37. if (previousElement && previousElement.getAttribute('tabindex') === '-1') {
  38. return a.idx - b.idx;
  39. }
  40. const tabIndexA = Number(a.el.getAttribute('tabindex'));
  41. const tabIndexB = Number(b.el.getAttribute('tabindex'));
  42. const diff = tabIndexA - tabIndexB;
  43. return diff === 0 ? a.idx - b.idx : diff;
  44. }).map(({
  45. el
  46. }) => el); // TODO: verify/remove type casts
  47. const checkedRadio = {};
  48. let prunedElements = [];
  49. orderedElements.forEach(currentElement => {
  50. // For radio groups keep only the active radio
  51. // If there is no active radio, keep only the checked radio
  52. // If there is no checked radio, treat like everything else
  53. const el = currentElement;
  54. if (el.type === 'radio' && el.name) {
  55. // If the active element is part of the group, add only that
  56. const prev = previousElement;
  57. if (prev && prev.type === el.type && prev.name === el.name) {
  58. if (el === prev) {
  59. prunedElements.push(el);
  60. }
  61. return;
  62. } // If we stumble upon a checked radio, remove the others
  63. if (el.checked) {
  64. prunedElements = prunedElements.filter(e => e.type !== el.type || e.name !== el.name);
  65. prunedElements.push(el);
  66. checkedRadio[el.name] = el;
  67. return;
  68. } // If we already found the checked one, skip
  69. if (typeof checkedRadio[el.name] !== 'undefined') {
  70. return;
  71. }
  72. }
  73. prunedElements.push(el);
  74. });
  75. const index = prunedElements.findIndex(el => el === previousElement);
  76. const nextElement = getNextElement(index, shift, prunedElements, focusTrap);
  77. const shiftKeyInit = {
  78. key: 'Shift',
  79. keyCode: 16,
  80. shiftKey: true
  81. };
  82. const tabKeyInit = {
  83. key: 'Tab',
  84. keyCode: 9,
  85. shiftKey: shift
  86. };
  87. let continueToTab = true; // not sure how to make it so there's no previous element...
  88. // istanbul ignore else
  89. if (previousElement) {
  90. // preventDefault on the shift key makes no difference
  91. if (shift) _dom.fireEvent.keyDown(previousElement, { ...shiftKeyInit
  92. });
  93. continueToTab = _dom.fireEvent.keyDown(previousElement, { ...tabKeyInit
  94. });
  95. }
  96. const keyUpTarget = !continueToTab && previousElement ? previousElement : nextElement;
  97. if (continueToTab) {
  98. if (nextElement === doc.body) {
  99. /* istanbul ignore else */
  100. if (previousElement) {
  101. (0, _blur.blur)(previousElement);
  102. }
  103. } else {
  104. (0, _focus.focus)(nextElement);
  105. }
  106. }
  107. _dom.fireEvent.keyUp(keyUpTarget, { ...tabKeyInit
  108. });
  109. if (shift) {
  110. _dom.fireEvent.keyUp(keyUpTarget, { ...shiftKeyInit,
  111. shiftKey: false
  112. });
  113. }
  114. }
  115. /*
  116. eslint
  117. complexity: "off",
  118. max-statements: "off",
  119. */