@carbon/ibm-products
Version:
Carbon for IBM Products
102 lines (97 loc) • 3.94 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
;
var react = require('@carbon/react');
var settings = require('../../../settings.js');
var React = require('react');
const getSpecificElement = (parentEl, elementId) => {
const element = parentEl?.querySelector(elementId);
return elementId && !element?.disabled ? element : null;
};
const useFocus = (modalRef, selectorPrimaryFocus) => {
const carbonPrefix = react.usePrefix();
const tearsheetBaseClass = `${settings.pkg.prefix}--tearsheet`;
const sidePanelBaseClass = `${settings.pkg.prefix}--side-panel`;
// Querying focusable element in the modal
// Query to exclude hidden elements in the modal from querySelectorAll() method
// feel free to include more if needed :)
const notQuery = `:not(.${carbonPrefix}--visually-hidden,.${tearsheetBaseClass}__header--no-close-icon,.${carbonPrefix}--btn--disabled,[aria-hidden="true"],[tabindex="-1"],[disabled])`;
// Queries to include element types button, input, select, textarea
const queryButton = `button${notQuery}`;
const queryInput = `input${notQuery}`;
const querySelect = `select${notQuery}`;
const queryTextarea = `textarea${notQuery}`;
const queryLink = `[href]${notQuery}`;
const queryAnchor = `a${notQuery}`;
const queryTabIndex = `[tabindex="0"]${notQuery}`;
const querySidePanelScroll = `.${sidePanelBaseClass}--scrolls`;
// Final query
const query = `${queryButton},${queryLink},${queryAnchor},${queryInput},${querySelect},${queryTextarea},${queryTabIndex},${querySidePanelScroll}`;
const getFocusable = React.useCallback(() => {
// Selecting all focusable elements based on the above conditions
let focusableElements = modalRef?.current?.querySelectorAll(`${query}`);
if (focusableElements?.length) {
focusableElements = Array.prototype.filter.call(focusableElements, el => window?.getComputedStyle(el)?.display !== 'none');
}
const first = focusableElements?.[0];
const last = focusableElements?.[focusableElements?.length - 1];
const all = focusableElements;
const specified = getSpecificElement(modalRef?.current, selectorPrimaryFocus);
return {
first,
last,
all,
specified
};
}, [modalRef, query, selectorPrimaryFocus]);
React.useEffect(() => {
getFocusable();
}, [getFocusable, modalRef]);
const handleKeyDown = async event => {
// Checking whether the key is tab or not
if (event.key === 'Tab') {
// updating the focusable elements list
const {
first,
last
} = getFocusable();
if (event.shiftKey && document?.activeElement === first) {
// Prevents the default "Tab" behavior
event.preventDefault();
// if the user press shift+tab and the current element not in focusable items
last?.focus();
} else if (!event.shiftKey && document?.activeElement === last) {
event.preventDefault();
// user pressing tab key only then
// focusing the first element if the current element is not in focusable items
first?.focus();
}
}
};
const claimFocus = React.useCallback(() => {
const {
first,
specified
} = getFocusable();
if (specified && window?.getComputedStyle(specified)?.display !== 'none' && specified?.tabIndex !== -1) {
setTimeout(() => specified.focus(), 0);
} else {
setTimeout(() => first?.focus(), 0);
}
}, [getFocusable]);
return {
firstElement: getFocusable()?.first,
lastElement: getFocusable()?.last,
allElements: getFocusable()?.all,
specifiedElement: getFocusable()?.specified,
keyDownListener: handleKeyDown,
getFocusable: getFocusable,
claimFocus
};
};
exports.getSpecificElement = getSpecificElement;
exports.useFocus = useFocus;