@carbon/react
Version:
React components for the Carbon Design System
75 lines (73 loc) • 2.89 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { useEffect } from "react";
//#region src/internal/useNoInteractiveChildren.ts
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const useNoInteractiveChildren = (ref, message = "component should have no interactive child nodes") => {
useEffect(() => {
const { current } = ref;
const node = current ? getInteractiveContent(current) : null;
if (node) {
const errorMessage = `Error: ${message}.\n\nInstead found: ${node.outerHTML}`;
console.error(errorMessage);
throw new Error(errorMessage);
}
}, [message, ref]);
};
const useInteractiveChildrenNeedDescription = (ref, message = `interactive child node(s) should have an \`aria-describedby\` property`) => {
useEffect(() => {
const { current } = ref;
const node = current ? getInteractiveContent(current) : null;
if (node && !node.hasAttribute("aria-describedby")) throw new Error(`Error: ${message}.\n\nInstead found: ${node.outerHTML}`);
}, [message, ref]);
};
const findMatchingContent = (node, matcher) => {
if (matcher(node)) return node;
for (const child of node.children) {
if (!(child instanceof HTMLElement)) continue;
const matchingChild = findMatchingContent(child, matcher);
if (matchingChild) return matchingChild;
}
return null;
};
/**
* Finds an interactive node in a given DOM node, including the node itself.
*/
const getInteractiveContent = (node) => findMatchingContent(node, isFocusable);
const hasRole = (element) => {
const role = element.getAttribute("role");
return role !== null && role !== "";
};
/**
* Finds a node with a `role` in a given DOM node, including the node itself.
*/
const getRoleContent = (node) => findMatchingContent(node, hasRole);
/**
* Determines if the given element is focusable.
*
* @param element - The element to check.
* @returns Whether the element is focusable.
* @see https://github.com/w3c/aria-practices/blob/0553bb51588ffa517506e2a1b2ca1422ed438c5f/examples/js/utils.js#L68
*/
const isFocusable = (element) => {
if (element.tabIndex < 0) return false;
if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) {
if (element.disabled) return false;
}
switch (element.nodeName) {
case "A": return element instanceof HTMLAnchorElement && !!element.href && element.rel !== "ignore";
case "INPUT": return element instanceof HTMLInputElement && element.type !== "hidden";
default: return true;
}
};
//#endregion
export { getInteractiveContent, getRoleContent, useInteractiveChildrenNeedDescription, useNoInteractiveChildren };