Skip to content

Commit

Permalink
improved focus handling
Browse files Browse the repository at this point in the history
  • Loading branch information
jadutter committed Oct 11, 2023
1 parent a2e81fe commit f2b7b2d
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"__MESSAGING_GLOBALS__": true,
"__ENV__": true
},
"plugins": ["prettier"],
"plugins": ["prettier","eslint-comments"],
"rules": {
"arrow-body-style": "off",
"unicorn/prefer-spread": "off",
Expand Down
12 changes: 4 additions & 8 deletions src/components/modal/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,14 @@ export function setupTabTrap() {
const tabArray = arrayFrom(document.querySelectorAll(focusableElementsString)).filter(
node => window.getComputedStyle(node).visibility === 'visible'
);
let nextElement;

// SHIFT + TAB
if (e.shiftKey && document.activeElement === tabArray[0]) {
nextElement = tabArray[tabArray.length - 1];
e.preventDefault();
tabArray[tabArray.length - 1].focus();
} else if (document.activeElement === tabArray[tabArray.length - 1]) {
// eslint-disable-next-line prefer-destructuring
nextElement = tabArray[0];
}

if (typeof nextElement !== 'undefined') {
e.preventDefault();
nextElement.focus();
tabArray[0].focus();
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/library/zoid/modal/containerTemplate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export default ({ uid, frame, prerenderFrame, doc, event, state, props: { cspNon
requestAnimationFrame(() => {
if (renderedModal) {
frame.focus();
} else if (window.document.activeElement !== prerenderFrame) {
prerenderFrame.focus();
}
});
});
Expand All @@ -66,7 +68,7 @@ export default ({ uid, frame, prerenderFrame, doc, event, state, props: { cspNon
};

const handleEscape = evt => {
if (state.open && (evt.key === 'Escape' || evt.key === 'Esc' || evt.charCode === 27)) {
if (state.open && (`${evt?.key}`.toLowerCase().startsWith('esc') || evt.charCode === 27)) {
handleHide();
}
};
Expand All @@ -78,7 +80,7 @@ export default ({ uid, frame, prerenderFrame, doc, event, state, props: { cspNon
.then(() => ZalgoPromise.delay(TRANSITION_DELAY))
.then(() => destroyElement(prerenderFrame))
.then(() => {
if (state.open) {
if (state.open && document.activeElement !== frame) {
frame.focus();
}
});
Expand Down
79 changes: 57 additions & 22 deletions src/library/zoid/modal/prerenderTemplate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { node, dom } from '@krakenjs/jsx-pragmatic/src';
import { Spinner } from '@paypal/common-components';
import { ZalgoPromise } from '@krakenjs/zalgo-promise/src';

export default ({ doc, props, event }) => {
export default ({ doc, props, event, state }) => {
const ERROR_DELAY = 15000;
const styles = `
@font-face {
Expand Down Expand Up @@ -97,15 +97,15 @@ export default ({ doc, props, event }) => {
height: 48px;
}
.error{
#modal-status{
color: white;
width: 200px;
height: 100px;
position: absolute;
top: 67%;
left: 50%;
left: calc( 50% - 10px );
margin-left: -60px;
display: none;
padding: 10px;
}
@media (max-width: 639px), (max-height: 539px){
Expand All @@ -119,47 +119,80 @@ export default ({ doc, props, event }) => {
}
`;
let renderedModal = false;
let closeBtn;

const closeModal = () => event.trigger('modal-hide');
const checkForErrors = element => {
ZalgoPromise.delay(ERROR_DELAY).then(() => {
const errorElement = element.querySelector('#errMsg');
// check to see if modal content class exists
if (errorElement) {
// looks like there is an error if modal content class does not exist.
const modalStatus = element.querySelector('#modal-status');
// if we have a place to put our status message,
// and we have not heard the 'zoid-rendered' event for the modal yet
if (modalStatus && !renderedModal) {
// assign variable to state and access in UI
errorElement.style.display = 'block';
errorElement.textContent = 'Error loading Modal';
modalStatus.style.display = 'block';
modalStatus.textContent = 'Error loading Modal';
// TODO: should we report this failure to our log endpoint?
}
});
};
const focusCloseButton = element => {
window.requestAnimationFrame(() => {
// TODO: determine how to get this to re-focus if the prerender is dismissed and re-opened
element.focus();

const focusCloseButton = () => {
if (closeBtn) {
window.requestAnimationFrame(() => {
// TODO: determine how to get this to re-focus if the prerender is dismissed and re-opened
closeBtn.focus();
});
}
};

const handleClose = () => {
event.trigger('modal-hide');
};

const handleEscape = evt => {
if (!renderedModal && state.open && (`${evt?.key}`.toLowerCase().startsWith('esc') || evt?.charCode === 27)) {
handleClose();
}
};

const handleRender = element => {
closeBtn = element.querySelector('#prerender-close-btn');
focusCloseButton();
ZalgoPromise.delay(ERROR_DELAY).then(() => {
return checkForErrors(element);
});
};

event.on('zoid-rendered', () => {
renderedModal = true;
});

return (
<html lang="en">
<head>
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style nonce={props.cspNonce}>{styles}</style>
<body onRender={checkForErrors}>
<body onRender={handleRender}>
<div class="modal" aria-errormessage="errMsg">
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */}
<div class="overlay" role="dialog" onClick={closeModal} />
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
<div
class="overlay"
role="dialog"
onClick={handleClose}
onKeyUp={handleEscape}
aria-keyshortcuts="escape"
/>
<div class="top-overlay" />
<div class="modal-content">
<div class="close-button">
<button
id="prerender-close-btn"
onClick={closeModal}
onClick={handleClose}
type="button"
aria-label="Close"
onRender={focusCloseButton}
tabindex="0"
>
<svg
aria-hidden="true"
Expand All @@ -186,7 +219,9 @@ export default ({ doc, props, event }) => {
</svg>
</button>
</div>
<div id="errMsg" class="error" />
<span id="modal-status" aria-label="modal-status" aria-live="polite">
Loading Modal
</span>
<Spinner nonce={props.cspNonce} />
</div>
</div>
Expand Down

0 comments on commit f2b7b2d

Please sign in to comment.