UNPKG

@dr.pogodin/react-utils

Version:

Collection of generic ReactJS components and utils

125 lines (122 loc) 4.89 kB
/* global window */ import { useEffect, useRef, useState } from 'react'; import themed from '@dr.pogodin/react-themes'; import Tooltip, { PLACEMENTS } from "./Tooltip.js"; const defaultTheme = { "ad": "-dr-pogodin-react-utils___build-web-shared-components-WithTooltip-default-theme___ad___n6OiU9", "hoc": "-dr-pogodin-react-utils___build-web-shared-components-WithTooltip-default-theme___hoc___zwsjGc", "context": "-dr-pogodin-react-utils___build-web-shared-components-WithTooltip-default-theme___context___JVJPcU", "appearance": "-dr-pogodin-react-utils___build-web-shared-components-WithTooltip-default-theme___appearance___9U4YiR", "arrow": "-dr-pogodin-react-utils___build-web-shared-components-WithTooltip-default-theme___arrow___9n65k-", "container": "-dr-pogodin-react-utils___build-web-shared-components-WithTooltip-default-theme___container___uA1tHZ", "wrapper": "-dr-pogodin-react-utils___build-web-shared-components-WithTooltip-default-theme___wrapper___JFVmGf" }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * Implements a simple to use and themeable tooltip component, _e.g._ * ```js * <WithTooltip tip="This is example tooltip."> * <p>Hover to see the tooltip.</p> * </WithTooltip> * ``` * **Children:** Children are rendered in the place of `<WithTooltip>`, * and when hovered the tooltip is shown. By default the wrapper itself is * `<div>` block with `display: inline-block`. * @param tip &ndash; Anything React is able to render, * _e.g._ a tooltip text. This will be the tooltip content. * @param {WithTooltipTheme} props.theme _Ad hoc_ theme. */ const Wrapper = ({ children, placement = PLACEMENTS.ABOVE_CURSOR, tip, theme }) => { const { current: heap } = useRef({ lastCursorX: 0, lastCursorY: 0, timerId: undefined, triggeredByTouch: false }); const tooltipRef = useRef(null); const wrapperRef = useRef(null); const [showTooltip, setShowTooltip] = useState(false); const updatePortalPosition = (cursorX, cursorY) => { if (showTooltip) { const wrapperRect = wrapperRef.current.getBoundingClientRect(); if (cursorX < wrapperRect.left || cursorX > wrapperRect.right || cursorY < wrapperRect.top || cursorY > wrapperRect.bottom) { setShowTooltip(false); } else if (tooltipRef.current) { tooltipRef.current.pointTo(cursorX + window.scrollX, cursorY + window.scrollY, placement, wrapperRef.current); } } else { heap.lastCursorX = cursorX; heap.lastCursorY = cursorY; // If tooltip was triggered by a touch, we delay its opening by a bit, // to ensure it was not a touch-click - in the case of touch click we // want to do the click, rather than show the tooltip, and the delay // gives click handler a chance to abort the tooltip openning. if (heap.triggeredByTouch) { heap.timerId ??= setTimeout(() => { heap.triggeredByTouch = false; heap.timerId = undefined; setShowTooltip(true); }, 300); // Otherwise we can just open the tooltip right away. } else setShowTooltip(true); } }; useEffect(() => { if (showTooltip && tip !== null) { // This is necessary to ensure that even when a single mouse event // arrives to a tool-tipped component, the tooltip is correctly positioned // once opened (because similar call above does not have effect until // the tooltip is fully mounted, and that is delayed to future rendering // cycle due to the implementation). if (tooltipRef.current) { tooltipRef.current.pointTo(heap.lastCursorX + window.scrollX, heap.lastCursorY + window.scrollY, placement, wrapperRef.current); } const listener = () => { setShowTooltip(false); }; window.addEventListener('scroll', listener); return () => { window.removeEventListener('scroll', listener); }; } return undefined; }, [heap.lastCursorX, heap.lastCursorY, placement, showTooltip, tip]); return /*#__PURE__*/_jsxs("div", { className: theme.wrapper, onClick: () => { if (heap.timerId) { clearTimeout(heap.timerId); heap.timerId = undefined; heap.triggeredByTouch = false; } }, onMouseLeave: () => { setShowTooltip(false); }, onMouseMove: e => { updatePortalPosition(e.clientX, e.clientY); }, onTouchStart: () => { heap.triggeredByTouch = true; }, ref: wrapperRef, role: "presentation", children: [showTooltip && tip !== null ? /*#__PURE__*/_jsx(Tooltip, { ref: tooltipRef, theme: theme, children: tip }) : null, children] }); }; const ThemedWrapper = themed(Wrapper, 'WithTooltip', defaultTheme); const e = ThemedWrapper; e.PLACEMENTS = PLACEMENTS; export default e; //# sourceMappingURL=index.js.map