From 420ae03301cee3fe0a050ceac3f479ed5bb26102 Mon Sep 17 00:00:00 2001 From: Ryan Tessier Date: Fri, 20 Mar 2020 21:44:18 +1100 Subject: [PATCH] fix(tab): Keep focus on the document if there are no focusable elements --- __tests__/react/tab.js | 10 ++++++++++ src/index.js | 23 ++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/__tests__/react/tab.js b/__tests__/react/tab.js index 5a6944bd..97636af9 100644 --- a/__tests__/react/tab.js +++ b/__tests__/react/tab.js @@ -265,3 +265,13 @@ it("should not focus disabled elements", () => { userEvent.tab(); expect(five).toHaveFocus(); }); + +it("should keep focus on the document if there are no enabled, focusable elements", () => { + render(); + + userEvent.tab(); + expect(document.body).toHaveFocus(); + + userEvent.tab({ shift: true }); + expect(document.body).toHaveFocus(); +}); diff --git a/src/index.js b/src/index.js index 075e3edb..352ce9e0 100644 --- a/src/index.js +++ b/src/index.js @@ -245,10 +245,16 @@ const userEvent = { "input, button, select, textarea, a[href], [tabindex]" ); - let list = Array.prototype.filter - .call(focusableElements, function(item) { - return item.getAttribute("tabindex") !== "-1" && !item.disabled; - }) + const enabledElements = Array.prototype.filter.call( + focusableElements, + function(el) { + return el.getAttribute("tabindex") !== "-1" && !el.disabled; + } + ); + + if (enabledElements.length === 0) return; + + const orderedElements = enabledElements .map((el, idx) => ({ el, idx })) .sort((a, b) => { const tabIndexA = a.el.getAttribute("tabindex"); @@ -259,12 +265,15 @@ const userEvent = { return diff !== 0 ? diff : a.idx - b.idx; }); - const index = list.findIndex(({ el }) => el === document.activeElement); + const index = orderedElements.findIndex( + ({ el }) => el === document.activeElement + ); let nextIndex = shift ? index - 1 : index + 1; - let defaultIndex = shift ? list.length - 1 : 0; + let defaultIndex = shift ? orderedElements.length - 1 : 0; - const { el: next } = list[nextIndex] || list[defaultIndex]; + const { el: next } = + orderedElements[nextIndex] || orderedElements[defaultIndex]; if (next.getAttribute("tabindex") === null) { next.setAttribute("tabindex", "0"); // jsdom requires tabIndex=0 for an item to become 'document.activeElement' (the browser does not)