tabbable.js 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. function isTabbable(el) {
  2. const tag = el.tagName.toLowerCase();
  3. if (el.getAttribute('tabindex') === '-1') {
  4. return false;
  5. }
  6. if (el.hasAttribute('disabled')) {
  7. return false;
  8. }
  9. if (el.hasAttribute('aria-disabled') && el.getAttribute('aria-disabled') !== 'false') {
  10. return false;
  11. }
  12. if (el.hasAttribute('tabindex')) {
  13. return true;
  14. }
  15. if (el.hasAttribute('contenteditable') && el.getAttribute('contenteditable') !== 'false') {
  16. return true;
  17. }
  18. if ((tag === 'audio' || tag === 'video') && el.hasAttribute('controls')) {
  19. return true;
  20. }
  21. if (tag === 'input' && el.getAttribute('type') === 'radio' && !el.hasAttribute('checked')) {
  22. return false;
  23. }
  24. if (!el.offsetParent) {
  25. return false;
  26. }
  27. if (window.getComputedStyle(el).visibility === 'hidden') {
  28. return false;
  29. }
  30. return ['button', 'input', 'select', 'textarea', 'a', 'audio', 'video', 'summary'].includes(tag);
  31. }
  32. export function getTabbableElements(root) {
  33. const tabbableElements = [];
  34. if (root instanceof HTMLElement) {
  35. if (isTabbable(root)) {
  36. tabbableElements.push(root);
  37. }
  38. if (root.shadowRoot && root.shadowRoot.mode === 'open') {
  39. getTabbableElements(root.shadowRoot).map(el => tabbableElements.push(el));
  40. }
  41. if (root instanceof HTMLSlotElement) {
  42. root.assignedElements().map((slottedEl) => {
  43. getTabbableElements(slottedEl).map(el => tabbableElements.push(el));
  44. });
  45. }
  46. }
  47. [...root.querySelectorAll('*')].map((el) => {
  48. getTabbableElements(el).map(el => tabbableElements.push(el));
  49. });
  50. return tabbableElements;
  51. }
  52. export function getNearestTabbableElement(el) {
  53. const tabbableElements = getTabbableElements(el);
  54. return tabbableElements.length ? tabbableElements[0] : null;
  55. }