@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
246 lines (245 loc) • 7.93 kB
JavaScript
"use client";
import _extends from "@babel/runtime-corejs3/helpers/esm/extends";
import React, { cloneElement, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import classnames from 'classnames';
import { combineDescribedBy, warn } from "../../shared/component-helper.js";
import { isTouch } from "./TooltipHelpers.js";
import Popover from "../popover/Popover.js";
import getRefElement from "../../shared/internal/getRefElement.js";
import { TooltipContext } from "./TooltipContext.js";
import AriaLive from "../AriaLive.js";
function TooltipWithEvents(props) {
const {
children,
attributes,
...restProps
} = props;
const {
active,
target,
skipPortal,
noAnimation,
showDelay,
hideDelay,
arrow,
position,
align,
fixedPosition,
portalRootClass,
triggerOffset,
contentRef,
size,
keepInDOM = false,
targetRefreshKey,
forceActive
} = restProps;
const {
internalId,
isControlled
} = useContext(TooltipContext);
const [isActive, setIsActive] = useState(active);
const [isOverlayHovered, setOverlayHovered] = useState(false);
const delayTimeout = useRef();
const overlayDelayTimeout = useRef();
const cloneRef = useRef();
const previousDescribedByIdRef = useRef(null);
const clearTimers = () => {
clearTimeout(delayTimeout.current);
};
const onMouseEnter = useCallback(e => {
try {
const elem = e.currentTarget;
if (elem.getAttribute('data-autofocus')) {
return;
}
if (isTouch(e.type)) {
elem.style.userSelect = 'none';
}
} catch (e) {
warn(e);
}
const run = () => {
setIsActive(true);
};
if (noAnimation || globalThis.IS_TEST) {
run();
} else {
clearTimers();
delayTimeout.current = setTimeout(run, parseFloat(String(showDelay)) || 1);
}
}, [noAnimation, showDelay]);
const onFocus = useCallback(e => {
return onMouseEnter(e);
}, [onMouseEnter]);
const onMouseLeave = useCallback(e => {
if (active) {
return;
}
try {
if (isTouch(e.type)) {
const elem = e.currentTarget;
elem.style.userSelect = '';
}
} catch (e) {
warn(e);
}
clearTimers();
const run = () => {
setIsActive(false);
};
if (skipPortal) {
delayTimeout.current = setTimeout(run, parseFloat(String(hideDelay)));
} else {
run();
}
}, [active, hideDelay, skipPortal]);
const addEvents = useCallback(element => {
try {
element.addEventListener('focus', onFocus);
element.addEventListener('blur', onMouseLeave);
element.addEventListener('mouseenter', onMouseEnter);
element.addEventListener('mouseleave', onMouseLeave);
element.addEventListener('touchstart', onMouseEnter);
element.addEventListener('touchend', onMouseLeave);
} catch (e) {
warn(e);
}
}, [onFocus, onMouseLeave, onMouseEnter]);
const removeEvents = useCallback(element => {
if (!(element instanceof HTMLElement)) {
return;
}
try {
element.removeEventListener('focus', onFocus);
element.removeEventListener('blur', onMouseLeave);
element.removeEventListener('mouseenter', onMouseEnter);
element.removeEventListener('mouseleave', onMouseLeave);
element.removeEventListener('touchstart', onMouseEnter);
element.removeEventListener('touchend', onMouseLeave);
} catch (e) {
warn(e);
}
}, [onFocus, onMouseEnter, onMouseLeave]);
const overlayActive = Boolean(isActive || isOverlayHovered || forceActive);
const describedById = overlayActive ? internalId : null;
const componentWrapper = useMemo(() => {
if (isValidElement(target)) {
return cloneElement(target, {
ref: cloneRef,
'aria-describedby': combineDescribedBy(target.props['aria-describedby'], describedById)
});
}
cloneRef.current = target;
return null;
}, [describedById, target]);
useEffect(() => {
if (!target) {
return () => clearTimers();
}
const element = getRefElement(cloneRef);
if (!(element instanceof HTMLElement) || isControlled) {
return () => clearTimers();
}
addEvents(element);
return () => {
clearTimers();
removeEvents(element);
};
}, [addEvents, removeEvents, isControlled, target]);
useEffect(() => {
if (isControlled) {
setIsActive(active);
}
}, [active, isControlled]);
useEffect(() => {
const targetElement = getRefElement(cloneRef);
if (!(targetElement instanceof HTMLElement)) {
previousDescribedByIdRef.current = null;
return;
}
const updateAriaDescribedBy = nextId => {
var _targetElement$getAtt, _targetElement$getAtt2;
const existingValues = (_targetElement$getAtt = (_targetElement$getAtt2 = targetElement.getAttribute('aria-describedby')) === null || _targetElement$getAtt2 === void 0 ? void 0 : _targetElement$getAtt2.split(/\s+/).map(value => value.trim()).filter(Boolean)) !== null && _targetElement$getAtt !== void 0 ? _targetElement$getAtt : [];
const withoutPrevious = previousDescribedByIdRef.current !== null ? existingValues.filter(value => value !== previousDescribedByIdRef.current) : existingValues;
let nextValues = withoutPrevious;
if (nextId) {
nextValues = nextValues.filter(value => value !== nextId);
nextValues = [...nextValues, nextId];
}
if (nextValues.length > 0) {
targetElement.setAttribute('aria-describedby', nextValues.join(' '));
} else {
targetElement.removeAttribute('aria-describedby');
}
previousDescribedByIdRef.current = nextId;
};
updateAriaDescribedBy(describedById);
return () => {
updateAriaDescribedBy(null);
};
}, [cloneRef, describedById, target]);
const clearOverlayTimers = () => {
clearTimeout(overlayDelayTimeout.current);
};
useEffect(() => clearOverlayTimers, []);
const handleOverlayMouseEnter = useCallback(() => {
clearOverlayTimers();
if (!isControlled) {
setOverlayHovered(true);
}
}, [isControlled]);
const handleOverlayMouseLeave = useCallback(() => {
if (isControlled) {
return;
}
const run = () => setOverlayHovered(false);
clearOverlayTimers();
if (skipPortal) {
overlayDelayTimeout.current = setTimeout(run, parseFloat(String(hideDelay)) || 1);
} else {
run();
}
}, [hideDelay, isControlled, skipPortal]);
const {
className: attributeClassName,
...restAttributes
} = attributes || {};
return React.createElement(React.Fragment, null, React.createElement(Popover, _extends({
baseClassName: "dnb-tooltip",
className: classnames(attributeClassName, 'dnb-tooltip', size && `dnb-tooltip--${size}`),
theme: "dark",
id: internalId,
open: overlayActive,
targetElement: cloneRef,
arrowEdgeOffset: 4,
hideDelay: hideDelay,
skipPortal: skipPortal,
keepInDOM: keepInDOM,
noAnimation: noAnimation,
triggerOffset: triggerOffset,
targetRefreshKey: targetRefreshKey,
arrowPosition: arrow,
placement: position,
alignOnTarget: align,
fixedPosition: fixedPosition,
portalRootClass: portalRootClass,
contentClassName: "dnb-tooltip__content",
contentRef: contentRef,
focusOnOpen: false,
restoreFocus: false,
preventClose: false,
noInnerSpace: true,
noMaxWidth: true,
hideOutline: true,
hideCloseButton: true,
disableFocusTrap: true,
onMouseEnter: handleOverlayMouseEnter,
onMouseLeave: handleOverlayMouseLeave,
role: "tooltip"
}, restAttributes), children), React.createElement(AriaLive, {
element: "span",
priority: "low"
}, overlayActive ? children : null), componentWrapper);
}
export default TooltipWithEvents;
//# sourceMappingURL=TooltipWithEvents.js.map