UNPKG

@grafana/ui

Version:
533 lines (530 loc) • 18.2 kB
import { jsxs, jsx } from 'react/jsx-runtime'; import { css, cx } from '@emotion/css'; import { useRef, useReducer, useLayoutEffect } from 'react'; import { createPortal } from 'react-dom'; import { DashboardCursorSync } from '@grafana/schema'; import { useStyles2 } from '../../../themes/ThemeContext.mjs'; import { getPortalContainer } from '../../Portal/Portal.mjs'; import { CloseButton } from './CloseButton.mjs'; "use strict"; const DEFAULT_TOOLTIP_WIDTH = void 0; const TOOLTIP_OFFSET = 10; var TooltipHoverMode = /* @__PURE__ */ ((TooltipHoverMode2) => { TooltipHoverMode2[TooltipHoverMode2["xOne"] = 0] = "xOne"; TooltipHoverMode2[TooltipHoverMode2["xAll"] = 1] = "xAll"; TooltipHoverMode2[TooltipHoverMode2["xyOne"] = 2] = "xyOne"; return TooltipHoverMode2; })(TooltipHoverMode || {}); function mergeState(prevState, nextState) { return { ...prevState, ...nextState, style: { ...prevState.style, ...nextState.style } }; } function initState() { return { style: { transform: "", pointerEvents: "none" }, isHovering: false, isPinned: false, contents: null, plot: null, dismiss: () => { } }; } const MIN_ZOOM_DIST = 5; const maybeZoomAction = (e) => e != null && !e.ctrlKey && !e.metaKey; const getDataLinksFallback = () => []; const getAdHocFiltersFallback = () => []; const userAgentIsMobile = /Android|iPhone|iPad/i.test(navigator.userAgent); const TooltipPlugin2 = ({ config, hoverMode, render, clientZoom = false, queryZoom, onSelectRange, maxWidth, syncMode = DashboardCursorSync.Off, syncScope = "global", // eventsScope getDataLinks = getDataLinksFallback, getAdHocFilters = getAdHocFiltersFallback }) => { const domRef = useRef(null); const portalRoot = useRef(null); if (portalRoot.current == null) { portalRoot.current = getPortalContainer(); } const [{ plot, isHovering, isPinned, contents, style, dismiss }, setState] = useReducer(mergeState, null, initState); const sizeRef = useRef(); const styles = useStyles2(getStyles, maxWidth); const renderRef = useRef(render); renderRef.current = render; const getLinksRef = useRef(getDataLinks); getLinksRef.current = getDataLinks; const getAdHocFiltersRef = useRef(getAdHocFilters); getAdHocFiltersRef.current = getAdHocFilters; useLayoutEffect(() => { var _a; (_a = sizeRef.current) == null ? void 0 : _a.observer.disconnect(); sizeRef.current = { width: 0, height: 0, observer: new ResizeObserver((entries) => { var _a2; let size = sizeRef.current; for (const entry of entries) { if (((_a2 = entry.borderBoxSize) == null ? void 0 : _a2.length) > 0) { size.width = entry.borderBoxSize[0].inlineSize; size.height = entry.borderBoxSize[0].blockSize; } else { size.width = entry.contentRect.width; size.height = entry.contentRect.height; } } }) }; let yZoomed = false; let yDrag = false; let _plot = plot; let _isHovering = isHovering; let _someSeriesIdx = false; let _isPinned = isPinned; let _style = style; let plotVisible = false; const syncTooltip = syncMode === DashboardCursorSync.Tooltip; if (syncMode !== DashboardCursorSync.Off && config.scales[0].props.isTime) { config.setCursor({ sync: { key: syncScope, scales: ["x", null] } }); } const updateHovering = () => { if (viaSync) { _isHovering = plotVisible && _someSeriesIdx && syncTooltip; } else { _isHovering = closestSeriesIdx != null || hoverMode === 1 /* xAll */ && _someSeriesIdx; } }; let offsetX = 0; let offsetY = 0; let selectedRange = null; let seriesIdxs = []; let closestSeriesIdx = null; let viaSync = false; let dataLinks = []; let adHocFilters = []; let persistentLinks = []; let pendingRender = false; let pendingPinned = false; const scheduleRender = (setPinned = false) => { if (!pendingRender) { if (!_isHovering) { setTimeout(_render, 100); } else { queueMicrotask(_render); } pendingRender = true; } if (setPinned) { pendingPinned = true; } }; const downEventOutside = (e) => { const isModalOrPortaled = '[role="dialog"], #grafana-portal-container'; if (e.target.closest(isModalOrPortaled) == null) { dismiss2(); } }; const _render = () => { pendingRender = false; if (pendingPinned) { _style = { pointerEvents: _isPinned ? "all" : "none" }; _plot.cursor._lock = _isPinned; if (_isPinned) { document.addEventListener("mousedown", downEventOutside, true); document.addEventListener("keydown", downEventOutside, true); } else { document.removeEventListener("mousedown", downEventOutside, true); document.removeEventListener("keydown", downEventOutside, true); } pendingPinned = false; } let state = { style: _style, isPinned: _isPinned, isHovering: _isHovering, contents: _isHovering || selectedRange != null ? renderRef.current( _plot, seriesIdxs, closestSeriesIdx, _isPinned, dismiss2, selectedRange, viaSync, _isPinned ? dataLinks : closestSeriesIdx != null ? persistentLinks[closestSeriesIdx] : [], _isPinned ? adHocFilters : [] ) : null, dismiss: dismiss2 }; setState(state); selectedRange = null; }; const dismiss2 = () => { let prevIsPinned = _isPinned; _isPinned = false; _isHovering = false; _plot.setCursor({ left: -10, top: -10 }); dataLinks = []; adHocFilters = []; scheduleRender(prevIsPinned); }; config.addHook("init", (u) => { setState({ plot: _plot = u }); if (clientZoom) { u.over.addEventListener( "mousedown", (e) => { if (!maybeZoomAction(e)) { return; } if (e.button === 0 && e.shiftKey) { yDrag = true; u.cursor.drag.x = false; u.cursor.drag.y = true; let onUp = (e2) => { u.cursor.drag.x = true; u.cursor.drag.y = false; document.removeEventListener("mouseup", onUp, true); }; document.addEventListener("mouseup", onUp, true); } }, true ); } u.over.addEventListener("click", (e) => { var _a2; if (e.target === u.over) { if (e.ctrlKey || e.metaKey) { let xVal; const isXAxisHorizontal = u.scales.x.ori === 0; if (isXAxisHorizontal) { xVal = u.posToVal(u.cursor.left, "x"); } else { xVal = u.posToVal(u.select.top + u.select.height, "x"); } selectedRange = { from: xVal, to: xVal }; scheduleRender(false); } else if (_isHovering && !_isPinned && closestSeriesIdx != null) { dataLinks = getLinksRef.current(closestSeriesIdx, seriesIdxs[closestSeriesIdx]); adHocFilters = getAdHocFiltersRef.current(closestSeriesIdx, seriesIdxs[closestSeriesIdx]); const oneClickLink = dataLinks.find((dataLink) => dataLink.oneClick === true); if (oneClickLink != null) { window.open(oneClickLink.href, (_a2 = oneClickLink.target) != null ? _a2 : "_self"); } else { setTimeout(() => { _isPinned = true; scheduleRender(true); }, 0); } } } }); }); config.addHook("setSelect", (u) => { const isXAxisHorizontal = u.scales.x.ori === 0; if (!viaSync && (clientZoom || queryZoom != null)) { if (maybeZoomAction(u.cursor.event)) { if (onSelectRange != null) { let selections = []; const yDrag2 = Boolean(u.cursor.drag.y); const xDrag = Boolean(u.cursor.drag.x); let xSel = null; let ySels = []; if (xDrag) { xSel = { from: isXAxisHorizontal ? u.posToVal(u.select.left, "x") : u.posToVal(u.select.top + u.select.height, "x"), to: isXAxisHorizontal ? u.posToVal(u.select.left + u.select.width, "x") : u.posToVal(u.select.top, "x") }; } if (yDrag2) { config.scales.forEach((scale) => { const key = scale.props.scaleKey; if (key !== "x") { let ySel = { from: isXAxisHorizontal ? u.posToVal(u.select.top + u.select.height, key) : u.posToVal(u.select.left + u.select.width, key), to: isXAxisHorizontal ? u.posToVal(u.select.top, key) : u.posToVal(u.select.left, key) }; ySels.push(ySel); } }); } if (xDrag) { if (yDrag2) { selections = ySels.map((ySel) => ({ x: xSel, y: ySel })); } else { selections = [{ x: xSel }]; } } else { if (yDrag2) { selections = ySels.map((ySel) => ({ y: ySel })); } } onSelectRange(selections); } else if (clientZoom && yDrag) { if (u.select.height >= MIN_ZOOM_DIST) { for (let key in u.scales) { if (key !== "x") { const maxY = isXAxisHorizontal ? u.posToVal(u.select.top, key) : u.posToVal(u.select.left + u.select.width, key); const minY = isXAxisHorizontal ? u.posToVal(u.select.top + u.select.height, key) : u.posToVal(u.select.left, key); u.setScale(key, { min: minY, max: maxY }); } } yZoomed = true; } yDrag = false; } else if (queryZoom != null) { if (u.select.width >= MIN_ZOOM_DIST) { const minX = isXAxisHorizontal ? u.posToVal(u.select.left, "x") : u.posToVal(u.select.top + u.select.height, "x"); const maxX = isXAxisHorizontal ? u.posToVal(u.select.left + u.select.width, "x") : u.posToVal(u.select.top, "x"); queryZoom({ from: minX, to: maxX }); yZoomed = false; } } } else { selectedRange = { from: isXAxisHorizontal ? u.posToVal(u.select.left, "x") : u.posToVal(u.select.top + u.select.height, "x"), to: isXAxisHorizontal ? u.posToVal(u.select.left + u.select.width, "x") : u.posToVal(u.select.top, "x") }; scheduleRender(true); } } u.setSelect({ left: 0, width: 0, top: 0, height: 0 }, false); }); if (clientZoom || queryZoom != null) { config.setCursor({ bind: { dblclick: (u) => () => { if (!maybeZoomAction(u.cursor.event)) { return null; } if (clientZoom && yZoomed) { for (let key in u.scales) { if (key !== "x") { u.setScale(key, { min: null, max: null }); } } yZoomed = false; } else if (queryZoom != null) { let xScale = u.scales.x; const frTs = xScale.min; const toTs = xScale.max; const pad = (toTs - frTs) / 2; queryZoom({ from: frTs - pad, to: toTs + pad }); } return null; } } }); } config.addHook("setData", (u) => { yZoomed = false; yDrag = false; if (_isPinned) { dismiss2(); } }); config.addHook("setSeries", (u, seriesIdx) => { closestSeriesIdx = seriesIdx; viaSync = u.cursor.event == null; updateHovering(); scheduleRender(); }); config.addHook("setLegend", (u) => { seriesIdxs = _plot == null ? void 0 : _plot.cursor.idxs.slice(); _someSeriesIdx = seriesIdxs.some((v, i) => i > 0 && v != null); if (persistentLinks.length === 0) { persistentLinks = seriesIdxs.map((v, seriesIdx) => { if (seriesIdx > 0) { const links = getDataLinks(seriesIdx, seriesIdxs[seriesIdx]); const oneClickLink = links.find((dataLink) => dataLink.oneClick === true); if (oneClickLink) { return [oneClickLink]; } } return []; }); } viaSync = u.cursor.event == null; let prevIsHovering = _isHovering; updateHovering(); if (_isHovering || _isHovering !== prevIsHovering) { scheduleRender(); } }); const scrollbarWidth = 16; let winWid = 0; let winHgt = 0; const updateWinSize = () => { _isHovering && !_isPinned && dismiss2(); winWid = window.innerWidth - scrollbarWidth; winHgt = window.innerHeight - scrollbarWidth; }; const updatePlotVisible = () => { plotVisible = _plot.rect.bottom <= winHgt && _plot.rect.top >= 0 && _plot.rect.left >= 0 && _plot.rect.right <= winWid; }; updateWinSize(); config.addHook("ready", updatePlotVisible); config.addHook("setCursor", (u) => { viaSync = u.cursor.event == null; if (!_isHovering) { return; } let { left = -10, top = -10 } = u.cursor; if (left >= 0 || top >= 0) { let clientX = u.rect.left + left; let clientY = u.rect.top + top; let transform = ""; let { width, height } = sizeRef.current; width += TOOLTIP_OFFSET; height += TOOLTIP_OFFSET; if (offsetY !== 0) { if (clientY + height < winHgt || clientY - height < 0) { offsetY = 0; } else if (offsetY !== -height) { offsetY = -height; } } else { if (clientY + height > winHgt && clientY - height >= 0) { offsetY = -height; } } if (offsetX !== 0) { if (clientX + width < winWid || clientX - width < 0) { offsetX = 0; } else if (offsetX !== -width) { offsetX = -width; } } else { if (clientX + width > winWid && clientX - width >= 0) { offsetX = -width; } } const shiftX = clientX + (offsetX === 0 ? TOOLTIP_OFFSET : -TOOLTIP_OFFSET); const shiftY = clientY + (offsetY === 0 ? TOOLTIP_OFFSET : -TOOLTIP_OFFSET); const reflectX = offsetX === 0 ? "" : "translateX(-100%)"; const reflectY = offsetY === 0 ? "" : "translateY(-100%)"; transform = `translateX(${shiftX}px) ${reflectX} translateY(${shiftY}px) ${reflectY}`; if (domRef.current != null) { domRef.current.style.transform = transform; } else { _style.transform = transform; scheduleRender(); } } }); const onscroll = (e) => { updatePlotVisible(); _isHovering && e.target instanceof Node && e.target.contains(_plot.root) && dismiss2(); }; window.addEventListener("resize", updateWinSize); window.addEventListener("scroll", onscroll, true); return () => { var _a2; (_a2 = sizeRef.current) == null ? void 0 : _a2.observer.disconnect(); window.removeEventListener("resize", updateWinSize); window.removeEventListener("scroll", onscroll, true); document.removeEventListener("mousedown", downEventOutside, true); document.removeEventListener("keydown", downEventOutside, true); }; }, [config]); useLayoutEffect(() => { const size = sizeRef.current; if (domRef.current != null) { size.observer.disconnect(); size.observer.observe(domRef.current); const { width, height } = domRef.current.getBoundingClientRect(); size.width = width; size.height = height; let event = plot.cursor.event; if (event != null) { const isMobile = event.type !== "mousemove" || userAgentIsMobile; if (isMobile) { event = new MouseEvent("mousemove", { view: window, bubbles: true, cancelable: true, clientX: event.clientX, clientY: event.clientY, screenX: event.screenX, screenY: event.screenY }); } const isStaleEvent = isMobile ? false : performance.now() - event.timeStamp > 16; !isStaleEvent && plot.over.dispatchEvent(event); } else { plot.setCursor( { left: plot.cursor.left, top: plot.cursor.top }, true ); } } else { size.width = 0; size.height = 0; } }, [isHovering]); if (plot && isHovering) { return createPortal( /* @__PURE__ */ jsxs( "div", { className: cx(styles.tooltipWrapper, isPinned && styles.pinned), style, "aria-live": "polite", "aria-atomic": "true", ref: domRef, children: [ isPinned && /* @__PURE__ */ jsx(CloseButton, { onClick: dismiss }), contents ] } ), portalRoot.current ); } return null; }; const getStyles = (theme, maxWidth) => ({ tooltipWrapper: css({ top: 0, left: 0, zIndex: theme.zIndex.tooltip, whiteSpace: "pre", borderRadius: theme.shape.radius.default, position: "fixed", background: theme.colors.background.elevated, border: `1px solid ${theme.colors.border.weak}`, boxShadow: theme.shadows.z2, userSelect: "text", maxWidth: maxWidth != null ? maxWidth : "none" }), pinned: css({ boxShadow: theme.shadows.z3 }) }); export { DEFAULT_TOOLTIP_WIDTH, TOOLTIP_OFFSET, TooltipHoverMode, TooltipPlugin2 }; //# sourceMappingURL=TooltipPlugin2.mjs.map