@carbon/react
Version:
React components for the Carbon Design System
201 lines (199 loc) • 6.67 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.
*/
const require_runtime = require("../../_virtual/_rolldown/runtime.js");
const require_usePrefix = require("../../internal/usePrefix.js");
const require_keys = require("../../internal/keyboard/keys.js");
const require_match = require("../../internal/keyboard/match.js");
const require_useIsomorphicEffect = require("../../internal/useIsomorphicEffect.js");
const require_useId = require("../../internal/useId.js");
const require_index = require("../Popover/index.js");
const require_useDelayedState = require("../../internal/useDelayedState.js");
const require_useNoInteractiveChildren = require("../../internal/useNoInteractiveChildren.js");
let classnames = require("classnames");
classnames = require_runtime.__toESM(classnames);
let react = require("react");
react = require_runtime.__toESM(react);
let prop_types = require("prop-types");
prop_types = require_runtime.__toESM(prop_types);
let react_jsx_runtime = require("react/jsx-runtime");
//#region src/components/Tooltip/Tooltip.tsx
/**
* Copyright IBM Corp. 2016, 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.
*/
/**
* Event types that trigger a "drag" to stop.
*/
const DRAG_STOP_EVENT_TYPES = new Set([
"mouseup",
"touchend",
"touchcancel"
]);
const Tooltip = react.default.forwardRef(({ as, align = "top", className: customClassName, children, label, description, enterDelayMs = 100, leaveDelayMs = 300, defaultOpen = false, closeOnActivation = false, dropShadow = false, highContrast = true, ...rest }, ref) => {
const tooltipRef = (0, react.useRef)(null);
const [open, setOpen] = require_useDelayedState.useDelayedState(defaultOpen);
const [isDragging, setIsDragging] = (0, react.useState)(false);
const [focusByMouse, setFocusByMouse] = (0, react.useState)(false);
const [isPointerIntersecting, setIsPointerIntersecting] = (0, react.useState)(false);
const id = require_useId.useId("tooltip");
const prefix = require_usePrefix.usePrefix();
const child = react.default.Children.only(children);
const { "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy } = child?.props ?? {};
const hasLabel = !!label;
const triggerProps = {
onFocus: () => !focusByMouse && setOpen(true),
onBlur: () => {
setOpen(false);
setFocusByMouse(false);
},
onClick: () => closeOnActivation && setOpen(false),
onMouseEnter,
onMouseLeave,
onMouseDown,
onMouseMove,
onTouchStart: onDragStart,
"aria-labelledby": ariaLabelledBy ?? (hasLabel ? id : void 0),
"aria-describedby": ariaDescribedBy ?? (!hasLabel ? id : void 0)
};
function getChildEventHandlers(childProps) {
const eventHandlerFunctions = Object.keys(triggerProps).filter((prop) => prop.startsWith("on"));
const eventHandlers = {};
eventHandlerFunctions.forEach((functionName) => {
eventHandlers[functionName] = (evt) => {
triggerProps[functionName](evt);
if (childProps?.[functionName]) childProps?.[functionName](evt);
};
});
return eventHandlers;
}
const onKeyDown = (0, react.useCallback)((event) => {
if (open && require_match.match(event, require_keys.Escape)) {
event.stopPropagation();
setOpen(false);
}
if (open && closeOnActivation && (require_match.match(event, require_keys.Enter) || require_match.match(event, require_keys.Space))) setOpen(false);
}, [
closeOnActivation,
open,
setOpen
]);
require_useIsomorphicEffect.default(() => {
if (!open) return;
function handleKeyDown(event) {
if (require_match.match(event, require_keys.Escape)) onKeyDown(event);
}
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [open, onKeyDown]);
function onMouseEnter() {
if (!rest?.onMouseEnter) {
setIsPointerIntersecting(true);
setOpen(true, enterDelayMs);
}
}
function onMouseDown() {
setFocusByMouse(true);
onDragStart();
}
function onMouseLeave() {
setIsPointerIntersecting(false);
if (isDragging) return;
setOpen(false, leaveDelayMs);
}
function onMouseMove(evt) {
if (evt.buttons === 1) setIsDragging(true);
else setIsDragging(false);
}
function onDragStart() {
setIsDragging(true);
}
const onDragStop = (0, react.useCallback)(() => {
setIsDragging(false);
if (!isPointerIntersecting) setOpen(false, leaveDelayMs);
}, [
isPointerIntersecting,
leaveDelayMs,
setOpen
]);
require_useNoInteractiveChildren.useNoInteractiveChildren(tooltipRef, "The Tooltip component must have no interactive content rendered by the`label` or `description` prop");
(0, react.useEffect)(() => {
if (isDragging) DRAG_STOP_EVENT_TYPES.forEach((eventType) => {
document.addEventListener(eventType, onDragStop);
});
return () => {
DRAG_STOP_EVENT_TYPES.forEach((eventType) => {
document.removeEventListener(eventType, onDragStop);
});
};
}, [isDragging, onDragStop]);
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_index.Popover, {
as,
ref,
...rest,
align,
className: (0, classnames.default)(`${prefix}--tooltip`, customClassName),
dropShadow,
highContrast,
onKeyDown,
onMouseLeave,
open,
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: `${prefix}--tooltip-trigger__wrapper`,
children: typeof child !== "undefined" ? react.default.cloneElement(child, {
...triggerProps,
...getChildEventHandlers(child.props)
}) : null
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.PopoverContent, {
"aria-hidden": open ? "false" : "true",
className: `${prefix}--tooltip-content`,
id,
onMouseEnter,
role: "tooltip",
children: label || description
})]
});
});
Tooltip.propTypes = {
align: prop_types.default.oneOf([
"top",
"top-left",
"top-right",
"bottom",
"bottom-left",
"bottom-right",
"left",
"left-bottom",
"left-top",
"right",
"right-bottom",
"right-top",
"top-start",
"top-end",
"bottom-start",
"bottom-end",
"left-end",
"left-start",
"right-end",
"right-start"
]),
children: prop_types.default.node,
className: prop_types.default.string,
closeOnActivation: prop_types.default.bool,
defaultOpen: prop_types.default.bool,
description: prop_types.default.node,
dropShadow: prop_types.default.bool,
enterDelayMs: prop_types.default.number,
highContrast: prop_types.default.bool,
label: prop_types.default.node,
leaveDelayMs: prop_types.default.number
};
//#endregion
exports.Tooltip = Tooltip;