@atlaskit/tooltip
Version:
A tooltip briefly describes an interactive element on mouse hover or keyboard focus.
791 lines (759 loc) • 34.5 kB
JavaScript
/* tooltip.tsx generated by @compiled/babel-plugin v3.0.2 */
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
require("./tooltip.compiled.css");
var _runtime = require("@compiled/react/runtime");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireWildcard(require("react"));
var _bindEventListener = require("bind-event-listener");
var _usePlatformLeafSyntheticEventHandler = require("@atlaskit/analytics-next/usePlatformLeafSyntheticEventHandler");
var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
var _useCloseOnEscapePress = _interopRequireDefault(require("@atlaskit/ds-lib/use-close-on-escape-press"));
var _useStableRef = _interopRequireDefault(require("@atlaskit/ds-lib/use-stable-ref"));
var _useNotifyOpenLayerObserver = require("@atlaskit/layering/use-notify-open-layer-observer");
var _exitingPersistence = _interopRequireDefault(require("@atlaskit/motion/exiting-persistence"));
var _fadeIn = _interopRequireDefault(require("@atlaskit/motion/fade-in"));
var _fg = require("@atlaskit/platform-feature-flags/fg");
var _main = require("@atlaskit/popper/main");
var _portal = _interopRequireDefault(require("@atlaskit/portal/portal"));
var _constants = require("@atlaskit/theme/constants");
var _index = require("@atlaskit/top-layer/placement-map/index");
var _popover = require("@atlaskit/top-layer/popover/popover");
var _useAnchoredPopover = require("@atlaskit/top-layer/use-anchored-popover");
var _useAnchoredPopoverAtPoint = require("@atlaskit/top-layer/use-anchored-popover-at-point");
var _dragManager = require("./internal/drag-manager");
var _getAnchorPoint = require("./internal/get-anchor-point");
var _getVirtualElementFromMousePos = require("./internal/get-virtual-element-from-mouse-pos");
var _tooltipManager = require("./internal/tooltip-manager");
var _useUniqueId = _interopRequireDefault(require("./internal/use-unique-id"));
var _tooltipContainer = _interopRequireDefault(require("./tooltip-container"));
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
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) { (0, _defineProperty2.default)(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; }
var tooltipZIndex = _constants.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 (!(0, _fg.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.default : _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.default : _ref2$onShow,
_ref2$onHide = _ref2.onHide,
onHide = _ref2$onHide === void 0 ? _noop.default : _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 = (0, _usePlatformLeafSyntheticEventHandler.usePlatformLeafSyntheticEventHandler)(_objectSpread({
fn: onShow,
action: 'displayed',
analyticsData: analyticsContext
}, analyticsAttributes));
var onHideHandler = (0, _usePlatformLeafSyntheticEventHandler.usePlatformLeafSyntheticEventHandler)(_objectSpread({
fn: onHide,
action: 'hidden',
analyticsData: analyticsContext
}, analyticsAttributes));
var apiRef = (0, _react.useRef)(null);
var _useState = (0, _react.useState)('hide'),
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
state = _useState2[0],
setState = _useState2[1];
var targetRef = (0, _react.useRef)(null);
var containerRef = (0, _react.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 = (0, _react.useCallback)(function (node) {
targetRef.current = node;
}, []);
// Putting a few things into refs so that we don't have to break memoization
var stableState = (0, _useStableRef.default)(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 = (0, _useStableRef.default)(onShowHandler);
var onHideHandlerStable = (0, _useStableRef.default)(onHideHandler);
var delayStable = (0, _useStableRef.default)(delay);
var canAppearStable = (0, _useStableRef.default)(canAppear);
var hasCalledShowHandler = (0, _react.useRef)(false);
var shouldAlwaysFadeInStable = (0, _useStableRef.default)(shouldAlwaysFadeIn);
var start = (0, _react.useCallback)(function (api) {
apiRef.current = api;
hasCalledShowHandler.current = false;
}, []);
var done = (0, _react.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 = (0, _react.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]);
(0, _react.useEffect)(function mount() {
return function unmount() {
if (apiRef.current) {
abort();
}
};
}, [abort]);
var isDraggingRef = (0, _react.useRef)(false);
(0, _react.useEffect)(function () {
return (0, _dragManager.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 = (0, _react.useRef)(false);
var tryShowTooltip = (0, _react.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 && (0, _fg.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 ((0, _fg.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 = (0, _tooltipManager.show)(entry);
start(api);
}, [canAppearStable, delayStable, done, start, abort, onShowHandlerStable, shouldAlwaysFadeInStable]);
var hideTooltipOnEsc = (0, _react.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
(0, _useCloseOnEscapePress.default)({
onClose: hideTooltipOnEsc,
isDisabled: state === 'hide' || state === 'fade-out' || state === 'top-layer-exit' || (0, _fg.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 = (0, _react.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 = (0, _react.useCallback)(function () {
var _apiRef$current4;
if ((0, _fg.fg)('platform-dst-top-layer-tooltip')) {
isTopLayerPointerDismissedRef.current = true;
}
(_apiRef$current4 = apiRef.current) === null || _apiRef$current4 === void 0 || _apiRef$current4.requestHide({
isImmediate: true
});
}, []);
(0, _react.useEffect)(function () {
if (state === 'hide') {
return _noop.default;
}
if (state === 'before-fade-out') {
setState('fade-out');
}
var unbind = (0, _bindEventListener.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 = (0, _react.useCallback)(function () {
// Native light dismiss hides on pointerup, not here. Recorded now so the
// tooltip stays dismissed afterwards.
if ((0, _fg.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' && (0, _fg.fg)('platform-dst-top-layer-tooltip');
if ((hideTooltipOnMouseDown || shouldCancelPendingShow) && apiRef.current) {
apiRef.current.requestHide({
isImmediate: true
});
}
}, [hideTooltipOnMouseDown, stableState]);
var onClick = (0, _react.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 = (0, _react.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
}) && (0, _fg.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 = (0, _react.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 = (0, _react.useCallback)(function () {
if (apiRef.current && apiRef.current.isActive()) {
apiRef.current.keep();
return;
}
}, []);
var onFocus = (0, _react.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 = (0, _react.useCallback)(function () {
// Focus leaving ends the dismissal, so focus coming back can show the tooltip.
if ((0, _fg.fg)('platform-dst-top-layer-tooltip')) {
isTopLayerPointerDismissedRef.current = false;
}
if (apiRef.current) {
apiRef.current.requestHide({
isImmediate: false
});
}
}, []);
var onAnimationFinished = (0, _react.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 = (0, _react.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).
(0, _useNotifyOpenLayerObserver.useNotifyOpenLayerObserver)({
// Layer is only visually open if both the tooltip popup (container) and children are rendered.
isOpen: (0, _fg.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 (0, _getVirtualElementFromMousePos.getVirtualElementFromMousePos)(apiRef.current.mousePos, {
targetElement: targetRef.current,
tooltipPosition: position
});
}
return targetRef.current || undefined;
};
var tooltipIdForHiddenContent = (0, _useUniqueId.default)('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';
(0, _react.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.default.createElement("span", {
"data-testid": testId ? "".concat(testId, "-hidden") : undefined,
hidden: true,
id: tooltipIdForHiddenContent
}, typeof content === 'function' ? content({}) : content) : null;
var PopperWrapper = shouldRenderToParent ? _react.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.default.createElement(_react.Fragment, null, children(_objectSpread(_objectSpread(_objectSpread({}, tooltipTriggerProps), testId ? {
testId: "".concat(testId, "--container")
} : {}), {}, {
'aria-describedby': tooltipIdForHiddenContent,
ref: setDirectRef
})), hiddenContent) : /*#__PURE__*/_react.default.createElement(CastTargetContainer, (0, _extends2.default)({}, 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 ((0, _fg.fg)('platform-dst-top-layer-tooltip')) {
var _apiRef$current$mouse, _apiRef$current8;
return /*#__PURE__*/_react.default.createElement(_react.Fragment, null, trigger, shouldRenderTooltipPopup ? /*#__PURE__*/_react.default.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.default.createElement(_react.Fragment, null, trigger, shouldRenderTooltipPopup ? /*#__PURE__*/_react.default.createElement(PopperWrapper, null, /*#__PURE__*/_react.default.createElement(_main.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.default.createElement(_exitingPersistence.default, {
appear: true
}, shouldRenderTooltipChildren && /*#__PURE__*/_react.default.createElement(_fadeIn.default, {
distance: "constant",
entranceDirection: direction,
exitDirection: direction,
onFinish: onAnimationFinished,
duration: state !== 'show-immediate' ? 'medium' : 'none'
}, function (_ref7) {
var className = _ref7.className;
return /*#__PURE__*/_react.default.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);
}
var _default = exports.default = Tooltip;
var TooltipPortal = function TooltipPortal(_ref8) {
var children = _ref8.children;
return /*#__PURE__*/_react.default.createElement(_portal.default, {
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 = (0, _react.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 = (0, _index.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
};
(0, _useAnchoredPopover.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.
(0, _useAnchoredPopoverAtPoint.useAnchoredPopoverAtPoint)(_objectSpread(_objectSpread({}, sharedPositioning), {}, {
isEnabled: isMouseStrategyActive,
getPoint: function getPoint() {
if (!mousePos || !targetRef.current || !isMousePosition) {
return null;
}
return (0, _getAnchorPoint.getAnchorPoint)({
cursor: mousePos,
triggerRect: targetRef.current.getBoundingClientRect(),
tooltipPosition: position,
placement: placement
});
}
}));
return /*#__PURE__*/_react.default.createElement(_popover.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.default.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.default
}) : content));
}