From 2e9e9f3427e3ec7205e0d8b5fa0bab3d59218101 Mon Sep 17 00:00:00 2001 From: Chuck Carpenter Date: Mon, 14 Jul 2025 15:44:55 -0700 Subject: [PATCH 1/7] =?UTF-8?q?=E2=99=BF=20Add=20checks=20on=20focusable?= =?UTF-8?q?=20elements=20to=20include=20target=20element?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shepherd.js/src/components/shepherd-element.svelte | 10 +++++++++- shepherd.js/src/step.ts | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/shepherd.js/src/components/shepherd-element.svelte b/shepherd.js/src/components/shepherd-element.svelte index 81f3978ec..a96feccd5 100644 --- a/shepherd.js/src/components/shepherd-element.svelte +++ b/shepherd.js/src/components/shepherd-element.svelte @@ -38,6 +38,7 @@ ); firstFocusableElement = focusableElements[0]; lastFocusableElement = focusableElements[focusableElements.length - 1]; + window.addEventListener("keydown", handleKeyDown, false); }); afterUpdate(() => { @@ -100,6 +101,14 @@ } } else { if (document.activeElement === lastFocusableElement) { + e.preventDefault(); + // Focus target element when tabbing forward from last element + if (step.target) { + step.target.focus(); + } else { + firstFocusableElement.focus(); + } + } else if (document.activeElement === step.target) { e.preventDefault(); firstFocusableElement.focus(); } @@ -140,7 +149,6 @@ class:shepherd-has-title={hasTitle} class:shepherd-element={true} {...dataStepId} - on:keydown={handleKeyDown} open="true" > {#if step.options.arrow && step.options.attachTo && step.options.attachTo.element && step.options.attachTo.on} diff --git a/shepherd.js/src/step.ts b/shepherd.js/src/step.ts index 191c8499c..c9e26e56a 100644 --- a/shepherd.js/src/step.ts +++ b/shepherd.js/src/step.ts @@ -649,6 +649,7 @@ export class Step extends Evented { target.classList.add(`${this.classPrefix}shepherd-enabled`); target.classList.add(`${this.classPrefix}shepherd-target`); + target.setAttribute('tabindex', '0'); content.classList.add('shepherd-enabled'); extraHighlightElements?.forEach((el) => { From bfc2ef8654e4c6c49a0eb0ec8b813164957f305f Mon Sep 17 00:00:00 2001 From: Chuck Carpenter Date: Mon, 14 Jul 2025 16:09:39 -0700 Subject: [PATCH 2/7] =?UTF-8?q?=E2=99=BF=20Add=20function=20to=20just=20ha?= =?UTF-8?q?ndle=20focus=20trap=20on=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/shepherd-element.svelte | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/shepherd.js/src/components/shepherd-element.svelte b/shepherd.js/src/components/shepherd-element.svelte index a96feccd5..027276b11 100644 --- a/shepherd.js/src/components/shepherd-element.svelte +++ b/shepherd.js/src/components/shepherd-element.svelte @@ -38,7 +38,7 @@ ); firstFocusableElement = focusableElements[0]; lastFocusableElement = focusableElements[focusableElements.length - 1]; - window.addEventListener("keydown", handleKeyDown, false); + window.addEventListener('keydown', handleFocus, false); }); afterUpdate(() => { @@ -75,6 +75,37 @@ return classes.split(' ').filter((className) => !!className.length); } + function handleFocus(e) { + if (e.keyCode === KEY_TAB) { + if (focusableElements.length === 0) { + e.preventDefault(); + } + // Backward tab + if (e.shiftKey) { + if ( + document.activeElement === firstFocusableElement || + document.activeElement.classList.contains('shepherd-element') + ) { + e.preventDefault(); + lastFocusableElement.focus(); + } + } else { + if (document.activeElement === lastFocusableElement) { + e.preventDefault(); + // Focus target element when tabbing forward from last element + if (step.target) { + step.target.focus(); + } else { + firstFocusableElement.focus(); + } + } else if (document.activeElement === step.target) { + e.preventDefault(); + firstFocusableElement.focus(); + } + } + } + } + /** * Setup keydown events to allow closing the modal with ESC * @@ -85,35 +116,6 @@ const handleKeyDown = (e) => { const { tour } = step; switch (e.keyCode) { - case KEY_TAB: - if (focusableElements.length === 0) { - e.preventDefault(); - break; - } - // Backward tab - if (e.shiftKey) { - if ( - document.activeElement === firstFocusableElement || - document.activeElement.classList.contains('shepherd-element') - ) { - e.preventDefault(); - lastFocusableElement.focus(); - } - } else { - if (document.activeElement === lastFocusableElement) { - e.preventDefault(); - // Focus target element when tabbing forward from last element - if (step.target) { - step.target.focus(); - } else { - firstFocusableElement.focus(); - } - } else if (document.activeElement === step.target) { - e.preventDefault(); - firstFocusableElement.focus(); - } - } - break; case KEY_ESC: if (tour.options.exitOnEsc) { e.preventDefault(); @@ -149,6 +151,7 @@ class:shepherd-has-title={hasTitle} class:shepherd-element={true} {...dataStepId} + on:keydown={handleKeyDown} open="true" > {#if step.options.arrow && step.options.attachTo && step.options.attachTo.element && step.options.attachTo.on} From 69d75f8ee1c5744d624f96a08678f37877890439 Mon Sep 17 00:00:00 2001 From: Chuck Carpenter Date: Mon, 14 Jul 2025 16:56:43 -0700 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=9D=20Add=20function=20doc=20and?= =?UTF-8?q?=20tabindex=20to=20non=20body=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shepherd.js/src/components/shepherd-element.svelte | 6 +++++- shepherd.js/src/step.ts | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/shepherd.js/src/components/shepherd-element.svelte b/shepherd.js/src/components/shepherd-element.svelte index 027276b11..2b6017c8d 100644 --- a/shepherd.js/src/components/shepherd-element.svelte +++ b/shepherd.js/src/components/shepherd-element.svelte @@ -74,7 +74,11 @@ function getClassesArray(classes) { return classes.split(' ').filter((className) => !!className.length); } - + /** + * Handle tab focus within the dialog and the target element + * + * @private + */ function handleFocus(e) { if (e.keyCode === KEY_TAB) { if (focusableElements.length === 0) { diff --git a/shepherd.js/src/step.ts b/shepherd.js/src/step.ts index c9e26e56a..acb626323 100644 --- a/shepherd.js/src/step.ts +++ b/shepherd.js/src/step.ts @@ -649,7 +649,10 @@ export class Step extends Evented { target.classList.add(`${this.classPrefix}shepherd-enabled`); target.classList.add(`${this.classPrefix}shepherd-target`); - target.setAttribute('tabindex', '0'); + // adds the target element to the focus trap (if not body), but not for document.body + if (target !== document.body) { + target.setAttribute('tabindex', '0'); + } content.classList.add('shepherd-enabled'); extraHighlightElements?.forEach((el) => { From e87a8c1c9ea543c665841f64c778f86755025436 Mon Sep 17 00:00:00 2001 From: Chuck Carpenter Date: Mon, 14 Jul 2025 16:58:40 -0700 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shepherd.js/src/components/shepherd-element.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shepherd.js/src/components/shepherd-element.svelte b/shepherd.js/src/components/shepherd-element.svelte index 2b6017c8d..956926a70 100644 --- a/shepherd.js/src/components/shepherd-element.svelte +++ b/shepherd.js/src/components/shepherd-element.svelte @@ -76,7 +76,7 @@ } /** * Handle tab focus within the dialog and the target element - * + * * @private */ function handleFocus(e) { From 3cb0778e103fd07c1251195ad599aa41ace05166 Mon Sep 17 00:00:00 2001 From: Chuck Carpenter Date: Mon, 14 Jul 2025 17:21:11 -0700 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9C=85=20Add=20target=20as=20part=20of?= =?UTF-8?q?=20focus=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/cypress/integration/a11y.cy.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/cypress/integration/a11y.cy.js b/test/cypress/integration/a11y.cy.js index 2944120b9..a978e513a 100644 --- a/test/cypress/integration/a11y.cy.js +++ b/test/cypress/integration/a11y.cy.js @@ -75,8 +75,9 @@ describe('a11y', () => { cy.document().then(() => { cy.wait(1000); - // Tabbing out of the modal should not be possible and we test this by tabbing from the body - cy.get('body').tab().tab().tab().tab().tab().tab(); + // Tabbing out of the shepherd context (this should include the highlighted element) + // should not be possible and we test this by tabbing from the body + cy.get('body').tab().tab().tab().tab().tab().tab().tab().tab(); cy.get('[data-test-popper-link]').should('have.focus'); }); }); From 39bd68cc954a41295101ec59e88caff41c649fa3 Mon Sep 17 00:00:00 2001 From: Chuck Carpenter Date: Thu, 17 Jul 2025 16:39:29 -0700 Subject: [PATCH 6/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20to=20remove?= =?UTF-8?q?=20listener?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- shepherd.js/src/components/shepherd-element.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shepherd.js/src/components/shepherd-element.svelte b/shepherd.js/src/components/shepherd-element.svelte index 956926a70..df9272cb7 100644 --- a/shepherd.js/src/components/shepherd-element.svelte +++ b/shepherd.js/src/components/shepherd-element.svelte @@ -41,6 +41,9 @@ window.addEventListener('keydown', handleFocus, false); }); + onDestroy(() => { + window.removeEventListener('keydown', handleFocus, false); + }); afterUpdate(() => { if (classes !== step.options.classes) { updateDynamicClasses(); From 6479ec0092158b8acfdfb88909fe85e15ea8b79d Mon Sep 17 00:00:00 2001 From: Chuck Carpenter Date: Thu, 17 Jul 2025 16:42:57 -0700 Subject: [PATCH 7/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20import=20of=20on?= =?UTF-8?q?Destroy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shepherd.js/src/components/shepherd-element.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shepherd.js/src/components/shepherd-element.svelte b/shepherd.js/src/components/shepherd-element.svelte index df9272cb7..5c6f37aa7 100644 --- a/shepherd.js/src/components/shepherd-element.svelte +++ b/shepherd.js/src/components/shepherd-element.svelte @@ -1,5 +1,5 @@