UNPKG

@wordpress/components

Version:
296 lines (243 loc) 8.66 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.TOOLTIP_DELAY = void 0; var _element = require("@wordpress/element"); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _classnames = _interopRequireDefault(require("classnames")); var _compose = require("@wordpress/compose"); var _popover = _interopRequireDefault(require("../popover")); var _shortcut = _interopRequireDefault(require("../shortcut")); // @ts-nocheck /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ /** * Time over children to wait before showing tooltip * * @type {number} */ const TOOLTIP_DELAY = 700; exports.TOOLTIP_DELAY = TOOLTIP_DELAY; const eventCatcher = (0, _element.createElement)("div", { className: "event-catcher" }); const getDisabledElement = _ref => { let { eventHandlers, child, childrenWithPopover, mergedRefs } = _ref; return (0, _element.cloneElement)((0, _element.createElement)("span", { className: "disabled-element-wrapper" }, (0, _element.cloneElement)(eventCatcher, eventHandlers), (0, _element.cloneElement)(child, { children: childrenWithPopover, ref: mergedRefs })), { ...eventHandlers }); }; const getRegularElement = _ref2 => { let { child, eventHandlers, childrenWithPopover, mergedRefs } = _ref2; return (0, _element.cloneElement)(child, { ...eventHandlers, children: childrenWithPopover, ref: mergedRefs }); }; const addPopoverToGrandchildren = _ref3 => { let { anchor, grandchildren, isOver, offset, position, shortcut, text, className, ...props } = _ref3; return (0, _element.concatChildren)(grandchildren, isOver && (0, _element.createElement)(_popover.default, (0, _extends2.default)({ focusOnMount: false, position: position, className: (0, _classnames.default)('components-tooltip', className), "aria-hidden": "true", animate: false, offset: offset, anchor: anchor, shift: true }, props), text, (0, _element.createElement)(_shortcut.default, { className: "components-tooltip__shortcut", shortcut: shortcut }))); }; const emitToChild = (children, eventName, event) => { if (_element.Children.count(children) !== 1) { return; } const child = _element.Children.only(children); // If the underlying element is disabled, do not emit the event. if (child.props.disabled) { return; } if (typeof child.props[eventName] === 'function') { child.props[eventName](event); } }; function Tooltip(props) { var _Children$toArray$; const { children, position = 'bottom middle', text, shortcut, delay = TOOLTIP_DELAY, ...popoverProps } = props; /** * Whether a mouse is currently pressed, used in determining whether * to handle a focus event as displaying the tooltip immediately. * * @type {boolean} */ const [isMouseDown, setIsMouseDown] = (0, _element.useState)(false); const [isOver, setIsOver] = (0, _element.useState)(false); const delayedSetIsOver = (0, _compose.useDebounce)(setIsOver, delay); // Using internal state (instead of a ref) for the popover anchor to make sure // that the component re-renders when the anchor updates. const [popoverAnchor, setPopoverAnchor] = (0, _element.useState)(null); // Create a reference to the Tooltip's child, to be passed to the Popover // so that the Tooltip can be correctly positioned. Also, merge with the // existing ref for the first child, so that its ref is preserved. const existingChildRef = (_Children$toArray$ = _element.Children.toArray(children)[0]) === null || _Children$toArray$ === void 0 ? void 0 : _Children$toArray$.ref; const mergedChildRefs = (0, _compose.useMergeRefs)([setPopoverAnchor, existingChildRef]); const createMouseDown = event => { // In firefox, the mouse down event is also fired when the select // list is chosen. // Cancel further processing because re-rendering of child components // causes onChange to be triggered with the old value. // See https://github.com/WordPress/gutenberg/pull/42483 if (event.target.tagName === 'OPTION') { return; } // Preserve original child callback behavior. emitToChild(children, 'onMouseDown', event); // On mouse down, the next `mouseup` should revert the value of the // instance property and remove its own event handler. The bind is // made on the document since the `mouseup` might not occur within // the bounds of the element. document.addEventListener('mouseup', cancelIsMouseDown); setIsMouseDown(true); }; const createMouseUp = event => { // In firefox, the mouse up event is also fired when the select // list is chosen. // Cancel further processing because re-rendering of child components // causes onChange to be triggered with the old value. // See https://github.com/WordPress/gutenberg/pull/42483 if (event.target.tagName === 'OPTION') { return; } emitToChild(children, 'onMouseUp', event); document.removeEventListener('mouseup', cancelIsMouseDown); setIsMouseDown(false); }; const createMouseEvent = type => { if (type === 'mouseUp') return createMouseUp; if (type === 'mouseDown') return createMouseDown; }; /** * Prebound `isInMouseDown` handler, created as a constant reference to * assure ability to remove in component unmount. * * @type {Function} */ const cancelIsMouseDown = createMouseEvent('mouseUp'); const createToggleIsOver = (eventName, isDelayed) => { return event => { // Preserve original child callback behavior. emitToChild(children, eventName, event); // Mouse events behave unreliably in React for disabled elements, // firing on mouseenter but not mouseleave. Further, the default // behavior for disabled elements in some browsers is to ignore // mouse events. Don't bother trying to handle them. // // See: https://github.com/facebook/react/issues/4251 if (event.currentTarget.disabled) { return; } // A focus event will occur as a result of a mouse click, but it // should be disambiguated between interacting with the button and // using an explicit focus shift as a cue to display the tooltip. if ('focus' === event.type && isMouseDown) { return; } // Needed in case unsetting is over while delayed set pending, i.e. // quickly blur/mouseleave before delayedSetIsOver is called. delayedSetIsOver.cancel(); const _isOver = ['focus', 'mouseenter'].includes(event.type); if (_isOver === isOver) { return; } if (isDelayed) { delayedSetIsOver(_isOver); } else { setIsOver(_isOver); } }; }; const clearOnUnmount = () => { delayedSetIsOver.cancel(); document.removeEventListener('mouseup', cancelIsMouseDown); }; // Ignore reason: updating the deps array here could cause unexpected changes in behavior. // Deferring until a more detailed investigation/refactor can be performed. // eslint-disable-next-line react-hooks/exhaustive-deps (0, _element.useEffect)(() => clearOnUnmount, []); if (_element.Children.count(children) !== 1) { if ('development' === process.env.NODE_ENV) { // eslint-disable-next-line no-console console.error('Tooltip should be called with only a single child element.'); } return children; } const eventHandlers = { onMouseEnter: createToggleIsOver('onMouseEnter', true), onMouseLeave: createToggleIsOver('onMouseLeave'), onClick: createToggleIsOver('onClick'), onFocus: createToggleIsOver('onFocus'), onBlur: createToggleIsOver('onBlur'), onMouseDown: createMouseEvent('mouseDown') }; const child = _element.Children.only(children); const { children: grandchildren, disabled } = child.props; const getElementWithPopover = disabled ? getDisabledElement : getRegularElement; const popoverData = { anchor: popoverAnchor, isOver, offset: 4, position, shortcut, text }; const childrenWithPopover = addPopoverToGrandchildren({ grandchildren, ...popoverData, ...popoverProps }); return getElementWithPopover({ child, eventHandlers, childrenWithPopover, mergedRefs: mergedChildRefs }); } var _default = Tooltip; exports.default = _default; //# sourceMappingURL=index.js.map