@mskcc/carbon-react
Version:
Carbon react components for the MSKCC DSM
72 lines (66 loc) • 1.81 kB
JavaScript
/**
* MSKCC 2021, 2024
*/
import { useEffect } from 'react';
function useNoInteractiveChildren(ref) {
let message = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'component should have no interactive child nodes';
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
const node = ref.current ? getInteractiveContent(ref.current) : false;
if (node) {
throw new Error(`Error: ${message}.\n\nInstead found: ${node.outerHTML}`);
}
});
}
}
/**
* Determines if a given DOM node has interactive content, or is itself
* interactive. It returns the interactive node if one is found
*
* @param {HTMLElement} node
* @returns {HTMLElement}
*/
function getInteractiveContent(node) {
if (!node || !node.childNodes) {
return null;
}
if (isFocusable(node)) {
return node;
}
for (const childNode of node.childNodes) {
const interactiveNode = getInteractiveContent(childNode);
if (interactiveNode) {
return interactiveNode;
}
}
return null;
}
/**
* Determines if the given element is focusable, or not
*
* @param {HTMLElement} element
* @returns {boolean}
* @see https://github.com/w3c/aria-practices/blob/0553bb51588ffa517506e2a1b2ca1422ed438c5f/examples/js/utils.js#L68
*/
function isFocusable(element) {
if (element.tabIndex < 0) {
return false;
}
if (element.disabled) {
return false;
}
switch (element.nodeName) {
case 'A':
return !!element.href && element.rel !== 'ignore';
case 'INPUT':
return element.type !== 'hidden';
case 'BUTTON':
case 'SELECT':
case 'TEXTAREA':
return true;
default:
return false;
}
}
export { getInteractiveContent, useNoInteractiveChildren };