Skip to content

Commit

Permalink
Refactor: focus trap handling function
Browse files Browse the repository at this point in the history
  • Loading branch information
orestbida committed Oct 21, 2023
1 parent fa4a85d commit d8d7325
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 40 deletions.
7 changes: 2 additions & 5 deletions src/core/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,13 @@ export class GlobalState {

/**
* Both of the arrays below have the same structure:
* [0]: holds reference to the FIRST focusable element inside modal
* [1]: holds reference to the LAST focusable element inside modal
* [0]: first focusable element inside modal
* [1]: last focusable element inside modal
*/

/** @type {HTMLElement[]} **/ _cmFocusableElements : [],
/** @type {HTMLElement[]} **/ _pmFocusableElements : [],

/** @type {HTMLDivElement} **/ _currentFocusedModal: null,
/** @type {HTMLDivElement[]} **/ _currentFocusEdges: [],

/**
* Keep track of enabled/disabled categories
* @type {boolean[]}
Expand Down
2 changes: 2 additions & 0 deletions src/core/modals/consentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getModalFocusableData,
addDataButtonListeners,
getSvgIcon,
handleFocusTrap,
fireEvent
} from '../../utils/general';

Expand Down Expand Up @@ -279,6 +280,7 @@ export const createConsentModal = (api, createMainContainer) => {
fireEvent(globalObj._customEvents._onModalReady, CONSENT_MODAL_NAME, dom._cm);
createMainContainer(api);
appendChild(dom._ccMain, dom._cmContainer);
handleFocusTrap(dom._cm);

/**
* Enable transition
Expand Down
2 changes: 0 additions & 2 deletions src/core/modals/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ export const generateHtml = (api) => {

if(!globalObj._config.lazyHtmlGeneration)
createPreferencesModal(api, createMainContainer);

handleFocusTrap();
};

export * from './consentModal';
Expand Down
2 changes: 2 additions & 0 deletions src/core/modals/preferencesModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isObject,
fireEvent,
getSvgIcon,
handleFocusTrap,
_log
} from '../../utils/general';

Expand Down Expand Up @@ -491,6 +492,7 @@ export const createPreferencesModal = (api, createMainContainer) => {
fireEvent(globalObj._customEvents._onModalReady, PREFERENCES_MODAL_NAME, dom._pm);
createMainContainer(api);
appendChild(dom._ccMain, dom._pmContainer);
handleFocusTrap(dom._pm);

/**
* Enable transition
Expand Down
69 changes: 36 additions & 33 deletions src/utils/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -739,16 +739,6 @@ export const focus = (el, modalId, toggleTabIndex) => {
el.focus();
}

if(modalId) {
globalObj._state._currentFocusedModal = modalId === 1
? globalObj._dom._cm
: globalObj._dom._pm;

globalObj._state._currentFocusEdges = modalId === 1
? globalObj._state._cmFocusableElements
: globalObj._state._pmFocusableElements;
}

/**
* Remove the `tabindex` attribute so
* that the html markup is valid again
Expand Down Expand Up @@ -833,52 +823,65 @@ export const getSvgIcon = (iconIndex = 0, strokeWidth = 1.5) => {
/**
* Trap focus inside modal and focus the first
* focusable element of current active modal
* @param {HTMLDivElement} modal
*/
export const handleFocusTrap = () => {
export const handleFocusTrap = (modal) => {

const dom = globalObj._dom;
const state = globalObj._state;
const trapFocusScope = globalObj._state._userConfig.disablePageInteraction
? dom._htmlDom
: dom._ccMain;

addEvent(trapFocusScope, 'keydown', (e) => {
/**
* @param {HTMLDivElement} modal
* @param {HTMLElement[]} focusableElements
*/
const trapFocus = (modal) => {

if(e.key !== 'Tab')
return;
const isConsentModal = modal === dom._cm;

/**
* Handle tab key only if at least one modal is visible
*/
if(!state._preferencesModalVisible && !state._consentModalVisible)
return;
const scope = state._userConfig.disablePageInteraction
? dom._htmlDom
: isConsentModal
? dom._ccMain
: dom._htmlDom;

const getFocusableElements = () => isConsentModal
? state._cmFocusableElements
: state._pmFocusableElements;

const focusableElements = state._currentFocusEdges;
const currentModal = state._currentFocusedModal;
const isModalVisible = () => isConsentModal
? state._consentModalVisible && !state._preferencesModalVisible
: state._preferencesModalVisible;

// If there is any element to focus
if(focusableElements.length > 0){
addEvent(scope, 'keydown', (e) => {

if (e.key !== 'Tab' || !isModalVisible())
return;

const currentActiveElement = getActiveElement();
const focusableElements = getFocusableElements();

if(focusableElements.length === 0)
return;

/**
* If reached natural end of the tab sequence => restart
* If current focused element is not inside modal => focus modal
*/
if(e.shiftKey){
if (currentActiveElement === focusableElements[0] || !currentModal.contains(currentActiveElement)) {
if (e.shiftKey) {
if (currentActiveElement === focusableElements[0] || !modal.contains(currentActiveElement)) {
preventDefault(e);
focus(focusableElements[1]);
}
}else{
if (currentActiveElement === focusableElements[1] || !currentModal.contains(currentActiveElement)) {
} else {
if (currentActiveElement === focusableElements[1] || !modal.contains(currentActiveElement)) {
preventDefault(e);
focus(focusableElements[0]);
}
}
}
}, true);
};

}, true);
trapFocus(modal);
};

/**
Expand All @@ -902,7 +905,7 @@ export const getModalFocusableData = (modalId) => {
/**
* Saves all focusable elements inside modal, into the array
* @param {HTMLElement} modal
* @param {Element[]} _array
* @param {Element[]} array
*/
const saveAllFocusableElements = (modal, array) => {

Expand Down

0 comments on commit d8d7325

Please sign in to comment.