Merge pull request 'fix: tutorial focus trap polish for #57' (#204) from fix/57 into main
Some checks failed
Smoke Test / smoke (push) Has been cancelled
Some checks failed
Smoke Test / smoke (push) Has been cancelled
This commit was merged in pull request #204.
This commit is contained in:
@@ -234,21 +234,56 @@ function nextTutorialStep() {
|
||||
renderTutorialStep(_tutorialStep);
|
||||
}
|
||||
|
||||
// Keyboard support: Enter/Right to advance, Escape to close
|
||||
document.addEventListener('keydown', function tutorialKeyHandler(e) {
|
||||
if (!document.getElementById('tutorial-overlay')) return;
|
||||
if (e.key === 'Enter' || e.key === 'ArrowRight') {
|
||||
function getTutorialFocusableElements(root = document) {
|
||||
const overlay = root && typeof root.getElementById === 'function'
|
||||
? root.getElementById('tutorial-overlay')
|
||||
: null;
|
||||
if (!overlay || typeof overlay.querySelectorAll !== 'function') return [];
|
||||
return Array.from(
|
||||
overlay.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
|
||||
).filter(el => !el.disabled && !el.hidden && el.offsetParent !== null);
|
||||
}
|
||||
|
||||
function trapTutorialFocus(e, root = document) {
|
||||
if (!e || e.key !== 'Tab') return false;
|
||||
const focusable = getTutorialFocusableElements(root);
|
||||
if (focusable.length < 2) return false;
|
||||
|
||||
const active = root.activeElement;
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
if (e.shiftKey && (active === first || !focusable.includes(active))) {
|
||||
e.preventDefault();
|
||||
if (_tutorialStep >= TUTORIAL_STEPS.length - 1) {
|
||||
closeTutorial();
|
||||
} else {
|
||||
nextTutorialStep();
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
closeTutorial();
|
||||
last.focus();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (!e.shiftKey && (active === last || !focusable.includes(active))) {
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keyboard support: Enter/Right to advance, Escape to close
|
||||
if (typeof document !== 'undefined' && document.addEventListener) {
|
||||
document.addEventListener('keydown', function tutorialKeyHandler(e) {
|
||||
if (!document.getElementById('tutorial-overlay')) return;
|
||||
if (trapTutorialFocus(e, document)) return;
|
||||
if (e.key === 'Enter' || e.key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
if (_tutorialStep >= TUTORIAL_STEPS.length - 1) {
|
||||
closeTutorial();
|
||||
} else {
|
||||
nextTutorialStep();
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
closeTutorial();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeTutorial() {
|
||||
const overlay = document.getElementById('tutorial-overlay');
|
||||
@@ -269,3 +304,19 @@ function startTutorial() {
|
||||
// Small delay so the page renders first
|
||||
setTimeout(() => renderTutorialStep(0), 300);
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
TUTORIAL_KEY,
|
||||
TUTORIAL_STEPS,
|
||||
isTutorialDone,
|
||||
markTutorialDone,
|
||||
createTutorialStyles,
|
||||
renderTutorialStep,
|
||||
nextTutorialStep,
|
||||
getTutorialFocusableElements,
|
||||
trapTutorialFocus,
|
||||
closeTutorial,
|
||||
startTutorial,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user