UNPKG

@atlaskit/tooltip

Version:

A tooltip briefly describes an interactive element on mouse hover or keyboard focus.

782 lines (751 loc) 32.2 kB
/* tooltip.tsx generated by @compiled/babel-plugin v3.0.2 */ import _extends from "@babel/runtime/helpers/extends"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import "./tooltip.compiled.css"; import { ax, ix } from "@compiled/react/runtime"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'; import { bind } from 'bind-event-listener'; import { usePlatformLeafSyntheticEventHandler } from '@atlaskit/analytics-next/usePlatformLeafSyntheticEventHandler'; import noop from '@atlaskit/ds-lib/noop'; import useCloseOnEscapePress from '@atlaskit/ds-lib/use-close-on-escape-press'; import useStableRef from '@atlaskit/ds-lib/use-stable-ref'; import { useNotifyOpenLayerObserver } from '@atlaskit/layering/use-notify-open-layer-observer'; import ExitingPersistence from '@atlaskit/motion/exiting-persistence'; import FadeIn from '@atlaskit/motion/fade-in'; import { fg } from '@atlaskit/platform-feature-flags/fg'; import { Popper } from '@atlaskit/popper/main'; import Portal from '@atlaskit/portal/portal'; import { layers } from '@atlaskit/theme/constants'; import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map/index'; import { Popover } from '@atlaskit/top-layer/popover/popover'; import { useAnchoredPopover } from '@atlaskit/top-layer/use-anchored-popover'; import { useAnchoredPopoverAtPoint } from '@atlaskit/top-layer/use-anchored-popover-at-point'; import { register } from './internal/drag-manager'; import { getAnchorPoint } from './internal/get-anchor-point'; import { getVirtualElementFromMousePos } from './internal/get-virtual-element-from-mouse-pos'; import { show } from './internal/tooltip-manager'; import useUniqueId from './internal/use-unique-id'; import TooltipContainer from './tooltip-container'; var tooltipZIndex = layers.tooltip(); var analyticsAttributes = { componentName: 'tooltip', packageName: "@atlaskit/tooltip", packageVersion: "24.3.3" }; // Inverts motion direction var invertedDirection = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }; /** * Converts a Popper placement to it's general direction. * * @param placement - Popper Placement value, e.g. 'top-start' * @returns Popper Direction, e.g. 'top' */ var getDirectionFromPlacement = function getDirectionFromPlacement(placement) { return placement.split('-')[0]; }; /** * For the `platform-dst-top-layer-tooltip` path only. Always `false` when that * gate is off, so this cannot affect the legacy path even if it gains a caller * there. * * `mouseover` also fires when the pointer crosses boundaries between elements * _inside_ the trigger. A `relatedTarget` the trigger does not contain is what * distinguishes a real re-entry from one of those. */ function isTopLayerPointerReEntry(_ref) { var trigger = _ref.trigger, relatedTarget = _ref.relatedTarget; if (!fg('platform-dst-top-layer-tooltip')) { return false; } // Missing trigger, or pointer from outside the document. Treat both as an // entry so we cannot get stuck suppressed. if (!trigger || !(relatedTarget instanceof Node)) { return true; } return !trigger.contains(relatedTarget); } /** * __Tooltip__ * * A tooltip is a floating, non-actionable label used to explain a user interface element or feature. */ function Tooltip(_ref2) { var children = _ref2.children, _ref2$position = _ref2.position, position = _ref2$position === void 0 ? 'bottom' : _ref2$position, _ref2$mousePosition = _ref2.mousePosition, mousePosition = _ref2$mousePosition === void 0 ? 'bottom' : _ref2$mousePosition, content = _ref2.content, _ref2$truncate = _ref2.truncate, truncate = _ref2$truncate === void 0 ? false : _ref2$truncate, _ref2$component = _ref2.component, Container = _ref2$component === void 0 ? TooltipContainer : _ref2$component, _ref2$tag = _ref2.tag, TargetContainer = _ref2$tag === void 0 ? 'div' : _ref2$tag, testId = _ref2.testId, _ref2$delay = _ref2.delay, delay = _ref2$delay === void 0 ? 300 : _ref2$delay, _ref2$onShow = _ref2.onShow, onShow = _ref2$onShow === void 0 ? noop : _ref2$onShow, _ref2$onHide = _ref2.onHide, onHide = _ref2$onHide === void 0 ? noop : _ref2$onHide, canAppear = _ref2.canAppear, _ref2$hideTooltipOnCl = _ref2.hideTooltipOnClick, hideTooltipOnClick = _ref2$hideTooltipOnCl === void 0 ? false : _ref2$hideTooltipOnCl, _ref2$hideTooltipOnMo = _ref2.hideTooltipOnMouseDown, hideTooltipOnMouseDown = _ref2$hideTooltipOnMo === void 0 ? false : _ref2$hideTooltipOnMo, analyticsContext = _ref2.analyticsContext, _ref2$strategy = _ref2.strategy, strategy = _ref2$strategy === void 0 ? 'fixed' : _ref2$strategy, _ref2$ignoreTooltipPo = _ref2.ignoreTooltipPointerEvents, ignoreTooltipPointerEvents = _ref2$ignoreTooltipPo === void 0 ? false : _ref2$ignoreTooltipPo, _ref2$isScreenReaderA = _ref2.isScreenReaderAnnouncementDisabled, isScreenReaderAnnouncementDisabled = _ref2$isScreenReaderA === void 0 ? false : _ref2$isScreenReaderA, shortcut = _ref2.shortcut, _ref2$shouldAlwaysFad = _ref2.shouldAlwaysFadeIn, shouldAlwaysFadeIn = _ref2$shouldAlwaysFad === void 0 ? false : _ref2$shouldAlwaysFad, _ref2$shouldRenderToP = _ref2.shouldRenderToParent, shouldRenderToParent = _ref2$shouldRenderToP === void 0 ? false : _ref2$shouldRenderToP; // Not using a gate for this check. When the gate is disabled `mouse-y` and `mouse-x` are treated as `mouse`. var isMousePosition = position === 'mouse' || position === 'mouse-y' || position === 'mouse-x'; var tooltipPosition = isMousePosition ? mousePosition : position; var onShowHandler = usePlatformLeafSyntheticEventHandler(_objectSpread({ fn: onShow, action: 'displayed', analyticsData: analyticsContext }, analyticsAttributes)); var onHideHandler = usePlatformLeafSyntheticEventHandler(_objectSpread({ fn: onHide, action: 'hidden', analyticsData: analyticsContext }, analyticsAttributes)); var apiRef = useRef(null); var _useState = useState('hide'), _useState2 = _slicedToArray(_useState, 2), state = _useState2[0], setState = _useState2[1]; var targetRef = useRef(null); var containerRef = useRef(null); // This function is deliberately _not_ memoized as it needs to re-run every render // to pick up any child ref changes. If you use render props you don't have this issue. var setImplicitRefFromChildren = function setImplicitRefFromChildren(node) { containerRef.current = node; targetRef.current = node ? node.firstElementChild : null; }; // This is memoized and passed into the render props callback. var setDirectRef = useCallback(function (node) { targetRef.current = node; }, []); // Putting a few things into refs so that we don't have to break memoization var stableState = useStableRef(state); // These props are placed in separate refs instead of a single object to reduce memory usage. // Placing them in the same object previously caused an increase in the number of JavaScript event listeners // before garbage collection. var onShowHandlerStable = useStableRef(onShowHandler); var onHideHandlerStable = useStableRef(onHideHandler); var delayStable = useStableRef(delay); var canAppearStable = useStableRef(canAppear); var hasCalledShowHandler = useRef(false); var shouldAlwaysFadeInStable = useStableRef(shouldAlwaysFadeIn); var start = useCallback(function (api) { apiRef.current = api; hasCalledShowHandler.current = false; }, []); var done = useCallback(function () { if (!apiRef.current) { return; } // Only call onHideHandler if we have called onShowHandler if (hasCalledShowHandler.current) { onHideHandlerStable.current(); } apiRef.current = null; hasCalledShowHandler.current = false; // just in case setState('hide'); }, [onHideHandlerStable]); var abort = useCallback(function () { if (!apiRef.current) { return; } apiRef.current.abort(); // Only call onHideHandler if we have called onShowHandler if (hasCalledShowHandler.current) { onHideHandlerStable.current(); } apiRef.current = null; }, [onHideHandlerStable]); useEffect(function mount() { return function unmount() { if (apiRef.current) { abort(); } }; }, [abort]); var isDraggingRef = useRef(false); useEffect(function () { return register({ onRegister: function onRegister(_ref3) { var isDragging = _ref3.isDragging; isDraggingRef.current = isDragging; }, onDragStart: function onDragStart() { var _apiRef$current; /** * Hiding any visible tooltips when a drag starts because otherwise it * looks janky (disappears and reappears), and is not required. */ (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 || _apiRef$current.requestHide({ isImmediate: true }); isDraggingRef.current = true; }, onDragEnd: function onDragEnd() { isDraggingRef.current = false; } }); }, []); // Set while a pointer press has dismissed the tooltip. Every read and write is // behind `platform-dst-top-layer-tooltip`, where `popover="hint"` owns pointer // dismissal, so the legacy path never sees this. var isTopLayerPointerDismissedRef = useRef(false); var tryShowTooltip = useCallback(function (source) { var _canAppearStable$curr; /** * Prevent tooltips from being shown during a drag. This can occur with * the native drag and drop API, where some pointer events can fire * when they should not and lead to jank with tooltips. */ if (isDraggingRef.current) { return; } // Another tooltip is has been active but we still have the old `api` // around. We need to finish up the last usage. // Note: just being safe - this should not happen if (apiRef.current && !apiRef.current.isActive()) { abort(); } // This tooltip is already active, we can exit if (apiRef.current && apiRef.current.isActive()) { apiRef.current.keep(); return; } /** * Stay dismissed until the trigger is re-entered or blurred. Otherwise * light dismiss hides on pointerup and the next `mouseover` from a * boundary crossing inside the trigger re-shows it. Deliberately after * the "already active" branch, so a held press stays visible. */ if (isTopLayerPointerDismissedRef.current && fg('platform-dst-top-layer-tooltip')) { return; } /** * Check if tooltip is allowed to show. * * Once a tooltip has started, or has scheduled to start * we won't be checking `canAppear` again. * * - We don't want tooltips to disappear once they are shown * - For consistency, we start after a single positive `canAppear`. * Otherwise the amount of times we ask consumers would depend on * how many times we get a "mousemove", which _could_ lead to situations * where moving the mouse could result in a different outcome to if * the mouse was not moved. */ if (canAppearStable.current && !((_canAppearStable$curr = canAppearStable.current) !== null && _canAppearStable$curr !== void 0 && _canAppearStable$curr.call(canAppearStable))) { return; } var entry = { source: source, delay: delayStable.current, show: function show(_ref4) { var isImmediate = _ref4.isImmediate; // Call the onShow handler if it hasn't been called yet if (!hasCalledShowHandler.current) { hasCalledShowHandler.current = true; onShowHandlerStable.current(); } setState(!isImmediate ? 'fade-in' : 'show-immediate'); }, hide: function hide(_ref5) { var isImmediate = _ref5.isImmediate; if (isImmediate) { setState('hide'); } else if (fg('platform-dst-top-layer-tooltip')) { // Top-layer path: set state to 'top-layer-exit'. The component // stays mounted and Popover's isOpen prop transitions to // false, triggering the CSS exit animation internally. // finishHideAnimation is called after a brief delay matching // the CSS exit animation duration. setState('top-layer-exit'); } else { setState('before-fade-out'); } }, done: done, shouldAlwaysFadeIn: shouldAlwaysFadeInStable.current }; var api = show(entry); start(api); }, [canAppearStable, delayStable, done, start, abort, onShowHandlerStable, shouldAlwaysFadeInStable]); var hideTooltipOnEsc = useCallback(function () { var _apiRef$current2; (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 || _apiRef$current2.requestHide({ isImmediate: true }); }, [apiRef]); // When using top-layer, popover="auto" handles Escape natively useCloseOnEscapePress({ onClose: hideTooltipOnEsc, isDisabled: state === 'hide' || state === 'fade-out' || state === 'top-layer-exit' || fg('platform-dst-top-layer-tooltip') }); // ── Top-layer exit animation lifecycle ── // When state is 'top-layer-exit', Popover's isOpen transitions to false and // the CSS exit animation plays. The Popover's built-in `transitionend` // detection (with timeout fallback) calls `onExitFinish` when the exit // animation completes, which triggers the tooltip-manager lifecycle // (finishHideAnimation → done → onHide, setState('hide'), cleanup). var handleExitFinish = useCallback(function () { var _apiRef$current3; (_apiRef$current3 = apiRef.current) === null || _apiRef$current3 === void 0 || _apiRef$current3.finishHideAnimation(); }, []); // Browser dismiss (light dismiss on pointerup, or Escape). Recorded so we stay // hidden until the trigger is re-entered. Only wired up on the top-layer path, // but gated anyway to keep every use of the ref flag-checked. var handlePopoverClose = useCallback(function () { var _apiRef$current4; if (fg('platform-dst-top-layer-tooltip')) { isTopLayerPointerDismissedRef.current = true; } (_apiRef$current4 = apiRef.current) === null || _apiRef$current4 === void 0 || _apiRef$current4.requestHide({ isImmediate: true }); }, []); useEffect(function () { if (state === 'hide') { return noop; } if (state === 'before-fade-out') { setState('fade-out'); } var unbind = bind(window, { type: 'scroll', listener: function listener() { if (apiRef.current) { apiRef.current.requestHide({ isImmediate: true }); } }, options: { capture: true, passive: true, once: true } }); return unbind; }, [state]); var onMouseDown = useCallback(function () { // Native light dismiss hides on pointerup, not here. Recorded now so the // tooltip stays dismissed afterwards. if (fg('platform-dst-top-layer-tooltip')) { isTopLayerPointerDismissedRef.current = true; } // A press inside the show delay never reaches light dismiss, because no // popover is open on pointerup. Cancel the pending show so a quick click // does not surface a tooltip over content the press just changed. var shouldCancelPendingShow = stableState.current === 'hide' && fg('platform-dst-top-layer-tooltip'); if ((hideTooltipOnMouseDown || shouldCancelPendingShow) && apiRef.current) { apiRef.current.requestHide({ isImmediate: true }); } }, [hideTooltipOnMouseDown, stableState]); var onClick = useCallback(function () { if (hideTooltipOnClick && apiRef.current) { apiRef.current.requestHide({ isImmediate: true }); } }, [hideTooltipOnClick]); // Ideally we would be using onMouseEnter here, but // because we are binding the event to the target parent // we need to listen for the mouseover of all sub elements // This means when moving along a tooltip we are quickly toggling // between api.requestHide and api.keep. This it not ideal var onMouseOver = useCallback(function (event) { // Ignoring events from the container ref if (containerRef.current && event.target === containerRef.current) { return; } // Using prevent default as a signal that parent tooltips if (event.defaultPrevented) { return; } event.preventDefault(); // Re-arm on a real re-entry, not on a boundary crossing inside the trigger. if (isTopLayerPointerReEntry({ trigger: targetRef.current, relatedTarget: event.relatedTarget }) && fg('platform-dst-top-layer-tooltip')) { isTopLayerPointerDismissedRef.current = false; } var source = isMousePosition ? { type: 'mouse', clientX: event.clientX, clientY: event.clientY } : { type: 'keyboard' }; tryShowTooltip(source); }, [isMousePosition, tryShowTooltip]); // Ideally we would be using onMouseEnter here, but // because we are binding the event to the target parent // we need to listen for the mouseout of all sub elements // This means when moving along a tooltip we are quickly toggling // between api.requestHide and api.keep. This it not ideal var onMouseOut = useCallback(function (event) { // Ignoring events from the container ref if (containerRef.current && event.target === containerRef.current) { return; } // Using prevent default as a signal that parent tooltips if (event.defaultPrevented) { return; } event.preventDefault(); if (apiRef.current) { apiRef.current.requestHide({ isImmediate: false }); } }, []); var onMouseMove = isMousePosition ? function (event) { var _apiRef$current5; if ((_apiRef$current5 = apiRef.current) !== null && _apiRef$current5 !== void 0 && _apiRef$current5.isActive()) { apiRef.current.mousePos = { clientX: event.clientX, clientY: event.clientY }; } } : undefined; var onMouseOverTooltip = useCallback(function () { if (apiRef.current && apiRef.current.isActive()) { apiRef.current.keep(); return; } }, []); var onFocus = useCallback(function (e) { // Check if focus-visible // Prevents tooltips from showing when focus is not visible, // i.e., when focus is moved onto tooltip trigger inside a popup on open try { if (!e.target.matches(':focus-visible')) { return; } } catch (_unused) { // Ignore errors from environments that don't support :focus-visible } // TODO: this does not play well with `hideTooltipOnMouseDown` // as "focus" will occur after the "mousedown". tryShowTooltip({ type: 'keyboard' }); }, [tryShowTooltip]); var onBlur = useCallback(function () { // Focus leaving ends the dismissal, so focus coming back can show the tooltip. if (fg('platform-dst-top-layer-tooltip')) { isTopLayerPointerDismissedRef.current = false; } if (apiRef.current) { apiRef.current.requestHide({ isImmediate: false }); } }, []); var onAnimationFinished = useCallback(function (transition) { // Using lastState here because motion is not picking up the latest value if (transition === 'exiting' && stableState.current === 'fade-out' && apiRef.current) { apiRef.current.finishHideAnimation(); } }, [stableState]); // Doing a cast because typescript is struggling to narrow the type var CastTargetContainer = TargetContainer; var shouldRenderTooltipPopup = state !== 'hide' && Boolean(content); var shouldRenderHiddenContent = !isScreenReaderAnnouncementDisabled && shouldRenderTooltipPopup; var shouldRenderTooltipChildren = state !== 'hide' && state !== 'fade-out' && state !== 'top-layer-exit'; var handleOpenLayerObserverCloseSignal = useCallback(function () { var _apiRef$current6; (_apiRef$current6 = apiRef.current) === null || _apiRef$current6 === void 0 || _apiRef$current6.requestHide({ isImmediate: true }); }, []); // Registered unconditionally so the hook order never depends on the feature // flag value. On the top-layer path the Popover primitive (used by // TopLayerTooltipPopup) registers with the observer directly, so we pass // isOpen: false to avoid double-counting (the hook is a no-op while the // layer is not open). useNotifyOpenLayerObserver({ // Layer is only visually open if both the tooltip popup (container) and children are rendered. isOpen: fg('platform-dst-top-layer-tooltip') ? false : shouldRenderTooltipPopup && shouldRenderTooltipChildren, /** * We don't strictly need to provide an onClose callback at this time, as there is * already code that handles hiding the tooltip when a drag is started (and the only * usage right now is closing all layers when the user resizes the side nav). * * However, for future-proofing and semantic reasons, it makes sense to close the tooltip * whenever the open layer observer requests a close. */ onClose: handleOpenLayerObserverCloseSignal }); var getReferenceElement = function getReferenceElement() { var _apiRef$current7; if (isMousePosition && (_apiRef$current7 = apiRef.current) !== null && _apiRef$current7 !== void 0 && _apiRef$current7.mousePos && targetRef.current) { return getVirtualElementFromMousePos(apiRef.current.mousePos, { targetElement: targetRef.current, tooltipPosition: position }); } return targetRef.current || undefined; }; var tooltipIdForHiddenContent = useUniqueId('tooltip', shouldRenderHiddenContent); var tooltipTriggerProps = { onMouseOver: onMouseOver, onMouseOut: onMouseOut, onMouseMove: onMouseMove, onMouseDown: onMouseDown, onClick: onClick, onFocus: onFocus, onBlur: onBlur }; // This useEffect is purely for managing the aria attribute when using the // wrapped children approach. var isChildrenAFunction = typeof children === 'function'; useEffect(function () { if (isChildrenAFunction) { return; } // If `children` is _not_ a function, we are stepping outside of the public // API to add a `aria-describedby` attribute. var target = targetRef.current; if (!target || !tooltipIdForHiddenContent) { return; } target.setAttribute('aria-describedby', tooltipIdForHiddenContent); return function () { return target.removeAttribute('aria-describedby'); }; }, [isChildrenAFunction, tooltipIdForHiddenContent]); var hiddenContent = shouldRenderHiddenContent ? /*#__PURE__*/React.createElement("span", { "data-testid": testId ? "".concat(testId, "-hidden") : undefined, hidden: true, id: tooltipIdForHiddenContent }, typeof content === 'function' ? content({}) : content) : null; var PopperWrapper = shouldRenderToParent ? Fragment : TooltipPortal; var trigger = typeof children === 'function' ? /*#__PURE__*/ // once we deprecate the wrapped approach, we can put the aria // attribute back into the tooltipTriggerProps and make it required // instead of optional in `types` React.createElement(Fragment, null, children(_objectSpread(_objectSpread(_objectSpread({}, tooltipTriggerProps), testId ? { testId: "".concat(testId, "--container") } : {}), {}, { 'aria-describedby': tooltipIdForHiddenContent, ref: setDirectRef })), hiddenContent) : /*#__PURE__*/React.createElement(CastTargetContainer, _extends({}, tooltipTriggerProps, testId ? { 'data-testid': "".concat(testId, "--container") } : undefined, { ref: setImplicitRefFromChildren /** * TODO: Why is role="presentation" added? * - Is it only to "remove" the `Container` from screen readers? * - Why is it added only to the `Container` but not to `tooltipTriggerProps`? * - Should `role="presentation"` only be used if `shouldRenderHiddenContent == false`? */, role: "presentation" }), children, hiddenContent); if (fg('platform-dst-top-layer-tooltip')) { var _apiRef$current$mouse, _apiRef$current8; return /*#__PURE__*/React.createElement(Fragment, null, trigger, shouldRenderTooltipPopup ? /*#__PURE__*/React.createElement(TopLayerTooltipPopup, { targetRef: targetRef, tooltipPosition: tooltipPosition, mousePos: (_apiRef$current$mouse = (_apiRef$current8 = apiRef.current) === null || _apiRef$current8 === void 0 ? void 0 : _apiRef$current8.mousePos) !== null && _apiRef$current$mouse !== void 0 ? _apiRef$current$mouse : undefined, position: position, onMouseOut: onMouseOut, onMouseOverTooltip: onMouseOverTooltip, ignoreTooltipPointerEvents: ignoreTooltipPointerEvents, truncate: truncate, testId: testId, shortcut: shortcut, content: content, Container: Container, onClose: handlePopoverClose, onExitFinish: handleExitFinish, isOpen: state !== 'hide' && state !== 'top-layer-exit' }) : null); } return /*#__PURE__*/React.createElement(Fragment, null, trigger, shouldRenderTooltipPopup ? /*#__PURE__*/React.createElement(PopperWrapper, null, /*#__PURE__*/React.createElement(Popper, { placement: tooltipPosition, referenceElement: getReferenceElement(), strategy: strategy }, function (_ref6) { var ref = _ref6.ref, style = _ref6.style, update = _ref6.update, placement = _ref6.placement; var direction = isMousePosition ? undefined : invertedDirection[getDirectionFromPlacement(placement)]; return /*#__PURE__*/React.createElement(ExitingPersistence, { appear: true }, shouldRenderTooltipChildren && /*#__PURE__*/React.createElement(FadeIn, { distance: "constant", entranceDirection: direction, exitDirection: direction, onFinish: onAnimationFinished, duration: state !== 'show-immediate' ? 'medium' : 'none' }, function (_ref7) { var className = _ref7.className; return /*#__PURE__*/React.createElement(Container, { ref: ref /** * "Tooltip" classname is a hook used by tests to manipulate * and hide tooltips, including in VR snapshots */ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: "Tooltip ".concat(className), style: _objectSpread(_objectSpread({}, style), ignoreTooltipPointerEvents && { pointerEvents: 'none' }), truncate: truncate, placement: tooltipPosition, testId: getReferenceElement() ? testId : testId && "".concat(testId, "--unresolved"), onMouseOut: onMouseOut, onMouseOver: onMouseOverTooltip, shortcut: shortcut }, typeof content === 'function' ? content({ update: update }) : content); })); })) : null); } export default Tooltip; var TooltipPortal = function TooltipPortal(_ref8) { var children = _ref8.children; return /*#__PURE__*/React.createElement(Portal, { zIndex: tooltipZIndex }, children); }; var tooltipMouseAnimationStyles = { enter: "_j7hq1bym", exit: "_j7hq1a5s" }; /** * Top-layer tooltip popup component. * * Composes `Popover` (top-layer visibility + animation) with two positioning * hooks, of which EXACTLY ONE is enabled - two writing to the same popover would * fight for ownership, so `isEnabled` is derived from one boolean and negated: * * - `useAnchoredPopoverAtPoint` for cursor-tracking positions (`mouse`, * `mouse-x`, `mouse-y`) activated by a pointer. * - `useAnchoredPopover` for everything else, including a cursor position * activated via keyboard focus, where there is no cursor to track and * anchoring to the trigger is what keeps the tooltip beside the target. * * Exit animation is handled by `Popover`'s `isOpen` prop. When `isOpen` * transitions to `false`, the primitive calls `hidePopover()` internally and * the CSS exit animation plays via `allow-discrete`. No glue code needed. */ function TopLayerTooltipPopup(_ref9) { var targetRef = _ref9.targetRef, tooltipPosition = _ref9.tooltipPosition, mousePos = _ref9.mousePos, position = _ref9.position, onMouseOut = _ref9.onMouseOut, onMouseOverTooltip = _ref9.onMouseOverTooltip, ignoreTooltipPointerEvents = _ref9.ignoreTooltipPointerEvents, truncate = _ref9.truncate, testId = _ref9.testId, shortcut = _ref9.shortcut, content = _ref9.content, Container = _ref9.Container, onClose = _ref9.onClose, onExitFinish = _ref9.onExitFinish, isOpen = _ref9.isOpen; var popoverRef = useRef(null); // Translate the legacy Popper-style placement string ("right", // "bottom-start", etc.) once and pass the same object to the hook and // to `Popover`. var placement = fromLegacyPlacement({ legacy: tooltipPosition }); var isMousePosition = position === 'mouse' || position === 'mouse-x' || position === 'mouse-y'; var isMouseStrategyActive = isMousePosition && Boolean(mousePos); // One object so the two calls cannot drift on `placement` or `isOpen`. var sharedPositioning = { popoverRef: popoverRef, placement: placement, isOpen: isOpen }; useAnchoredPopover(_objectSpread(_objectSpread({}, sharedPositioning), {}, { anchorRef: targetRef, isEnabled: !isMouseStrategyActive })); // `getPoint()` is latched once per activation, which is per-show here because // `TopLayerTooltipPopup` mounts fresh on every show. `null` before the trigger // has mounted applies no positioning at all. useAnchoredPopoverAtPoint(_objectSpread(_objectSpread({}, sharedPositioning), {}, { isEnabled: isMouseStrategyActive, getPoint: function getPoint() { if (!mousePos || !targetRef.current || !isMousePosition) { return null; } return getAnchorPoint({ cursor: mousePos, triggerRect: targetRef.current.getBoundingClientRect(), tooltipPosition: position, placement: placement }); } })); return /*#__PURE__*/React.createElement(Popover, { ref: popoverRef, role: "tooltip", mode: "hint", isOpen: isOpen, onClose: onClose, onExitFinish: onExitFinish, testId: testId ? "".concat(testId, "--popover") : undefined, shouldAnimate: true, enteringAnimationXcss: isMouseStrategyActive && tooltipMouseAnimationStyles.enter, exitingAnimationXcss: isMouseStrategyActive && tooltipMouseAnimationStyles.exit, placement: placement }, /*#__PURE__*/React.createElement(Container // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- top-layer spike , { className: "Tooltip" // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- top-layer spike , style: ignoreTooltipPointerEvents ? { pointerEvents: 'none' } : undefined, truncate: truncate, placement: tooltipPosition, testId: testId, onMouseOut: onMouseOut, onMouseOver: onMouseOverTooltip, shortcut: shortcut, role: "presentation" }, typeof content === 'function' ? content({ update: noop }) : content)); }