UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

246 lines (245 loc) 7.93 kB
"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