From ec51f10a562f112dd02e4191d2a2ca1a0019407e Mon Sep 17 00:00:00 2001 From: Mike Lumetta Date: Tue, 8 Dec 2020 12:33:34 -0500 Subject: [PATCH 1/4] Allow focus to remain on the targets of hash links when the element navigated to should be focusable: 1. natively focusable elements (a, input, textarea, select, button) 2. elements where the target had a tabindex prior to the navigation event This change allows consumers of react-router-hash-link to apply focus styles to the target element. --- src/index.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index b5538d9..9e97ca1 100644 --- a/src/index.js +++ b/src/index.js @@ -16,6 +16,11 @@ function reset() { } } +function isInteractiveElement(element) { + const interactiveTags = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA']; + return interactiveTags.includes(element.tagName) && !element.hasAttribute('disabled'); +} + function getElAndScroll() { let element = null; if (hashFragment === '#') { @@ -43,11 +48,16 @@ function getElAndScroll() { let originalTabIndex = element.getAttribute('tabindex'); if (originalTabIndex === null) element.setAttribute('tabindex', -1); element.focus({ preventScroll: true }); - // for some reason calling blur() in safari resets the focus region to where it was previously, - // if blur() is not called it works in safari, but then are stuck with default focus styles - // on an element that otherwise might never had focus styles applied, so not an option - element.blur(); - if (originalTabIndex === null) element.removeAttribute('tabindex'); + if (originalTabIndex === null) { + if (!isInteractiveElement(element)) { + // for some reason calling blur() in safari resets the focus region to where it was previously, + // if blur() is not called it works in safari, but then are stuck with default focus styles + // on an element that otherwise might never had focus styles applied, so not an option + element.blur(); + } + + element.removeAttribute('tabindex'); + } reset(); return true; From 74f9351679ee39e88410d5b9eb5812dfe6c69487 Mon Sep 17 00:00:00 2001 From: Mike Lumetta Date: Tue, 8 Dec 2020 13:40:04 -0500 Subject: [PATCH 2/4] Add brief documentation on how React Router Hash Link handles programmatic focus --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cf6d394..fa4cf4d 100644 --- a/README.md +++ b/README.md @@ -104,3 +104,9 @@ const MyComponent = () => ( ); ``` + +## Focus Management + +`react-router-hash-link` attempts to recreate the native browser focusing behavior as closely as possible. For non-interactive elements, it calls `element.focus()` followed by `element.blur()` (using a temporary `tabindex` to ensure that the element can be focused programmatically) so that focus _moves_ to the target element but does not remain on it or trigger any style changes. For interactive elements, it calls `element.focus()` and leaves focus on the target element. + +If you would like to leave focus on a non-interactive element - for example, to augment the navigation interaction with a visual focus indicator - you can optionally set a `tabindex` on the target element. `react-router-hash-link` will respect the `tabindex` and leave focus on the target in that case. From 0619a70fd387ff2f90a36ad796cacfa1ae2b2d10 Mon Sep 17 00:00:00 2001 From: Mike Lumetta Date: Tue, 8 Dec 2020 17:48:08 -0500 Subject: [PATCH 3/4] Add to the list of interactive elements that should retain focus on navigation --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 9e97ca1..00cd70d 100644 --- a/src/index.js +++ b/src/index.js @@ -17,7 +17,7 @@ function reset() { } function isInteractiveElement(element) { - const interactiveTags = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA']; + const interactiveTags = ['A', 'AREA', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA']; return interactiveTags.includes(element.tagName) && !element.hasAttribute('disabled'); } From 83dba7c9c5b18c1b19ffd4f58ea603f3101d1102 Mon Sep 17 00:00:00 2001 From: Mike Lumetta Date: Wed, 9 Dec 2020 09:53:39 -0500 Subject: [PATCH 4/4] Add condition that requires and elements to have an href to be interactive --- src/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 00cd70d..3166c18 100644 --- a/src/index.js +++ b/src/index.js @@ -17,8 +17,10 @@ function reset() { } function isInteractiveElement(element) { - const interactiveTags = ['A', 'AREA', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA']; - return interactiveTags.includes(element.tagName) && !element.hasAttribute('disabled'); + const formTags = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA']; + const linkTags = ['A', 'AREA']; + return (formTags.includes(element.tagName) && !element.hasAttribute('disabled')) + || (linkTags.includes(element.tagName) && element.hasAttribute('href')); } function getElAndScroll() {