UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

252 lines (251 loc) 8.06 kB
"use client"; import React, { isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import clsx from 'clsx'; 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"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; function TooltipWithEvents(props) { const { children, attributes, ...restProps } = props; const { open, target, skipPortal, noAnimation, showDelay, hideDelay, arrow, placement, align, fixedPosition, portalRootClass, triggerOffset, contentRef, size, keepInDOM = false, targetRefreshKey } = restProps; const { internalId, isControlled } = useContext(TooltipContext); const [isOpen, setIsOpen] = useState(open); const [isOverlayHovered, setOverlayHovered] = useState(false); const delayTimeout = useRef(undefined); const overlayDelayTimeout = useRef(undefined); const cloneRef = useRef(undefined); const previousDescribedByIdRef = useRef(null); const clearTimers = () => { clearTimeout(delayTimeout.current); }; const onMouseEnter = useCallback(e => { try { const elem = e.currentTarget; if (elem.getAttribute('data-autofocus')) { return undefined; } if (isTouch(e.type)) { elem.style.userSelect = 'none'; } } catch (e) { warn(e); } const run = () => { setIsOpen(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 (open) { return; } try { if (isTouch(e.type)) { const elem = e.currentTarget; elem.style.userSelect = ''; } } catch (e) { warn(e); } clearTimers(); const run = () => { setIsOpen(false); }; if (skipPortal) { delayTimeout.current = setTimeout(run, parseFloat(String(hideDelay))); } else { run(); } }, [open, 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 undefined; } 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 overlayOpen = Boolean(isOpen || isOverlayHovered); const describedById = overlayOpen ? internalId : null; const componentWrapper = useMemo(() => { if (isValidElement(target)) { return React.createElement(target.type, { ...target.props, 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) { setIsOpen(open); } else { setIsOpen(false); } }, [open, isControlled]); useEffect(() => { const targetElement = getRefElement(cloneRef); if (!(targetElement instanceof HTMLElement)) { previousDescribedByIdRef.current = null; return undefined; } 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 undefined; } 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 _jsxs(_Fragment, { children: [_jsx(Popover, { baseClassName: "dnb-tooltip", className: clsx(attributeClassName, 'dnb-tooltip', size && size !== 'default' && `dnb-tooltip--${size}`), id: internalId, open: overlayOpen, targetElement: cloneRef, arrowEdgeOffset: 4, hideDelay: hideDelay, skipPortal: skipPortal, keepInDOM: keepInDOM, noAnimation: noAnimation, triggerOffset: triggerOffset, targetRefreshKey: targetRefreshKey, arrowPosition: arrow, placement: placement, 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: children }), _jsx(AriaLive, { element: "span", priority: "low", children: overlayOpen ? children : null }), componentWrapper] }); } export default TooltipWithEvents; //# sourceMappingURL=TooltipWithEvents.js.map