UNPKG

@wordpress/components

Version:
230 lines (187 loc) 6.2 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 _lodash = require("lodash"); var _compose = require("@wordpress/compose"); var _popover = _interopRequireDefault(require("../popover")); var _shortcut = _interopRequireDefault(require("../shortcut")); var _next = require("./next"); /** * 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 = ({ eventHandlers, child, childrenWithPopover }) => (0, _element.cloneElement)((0, _element.createElement)("span", { className: "disabled-element-wrapper" }, (0, _element.cloneElement)(eventCatcher, eventHandlers), (0, _element.cloneElement)(child, { children: childrenWithPopover }), ","), eventHandlers); const getRegularElement = ({ child, eventHandlers, childrenWithPopover }) => (0, _element.cloneElement)(child, { ...eventHandlers, children: childrenWithPopover }); const addPopoverToGrandchildren = ({ grandchildren, isOver, position, text, shortcut }) => (0, _element.concatChildren)(grandchildren, isOver && (0, _element.createElement)(_popover.default, { focusOnMount: false, position: position, className: "components-tooltip", "aria-hidden": "true", animate: false, noArrow: true }, 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 (typeof child.props[eventName] === 'function') { child.props[eventName](event); } }; function Tooltip({ children, position, text, shortcut }) { /** * 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, TOOLTIP_DELAY); const createMouseDown = event => { // 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 => { 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 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 = (0, _lodash.includes)(['focus', 'mouseenter'], event.type); if (_isOver === isOver) { return; } if (isDelayed) { delayedSetIsOver(_isOver); } else { setIsOver(_isOver); } }; }; const clearOnUnmount = () => { delayedSetIsOver.cancel(); }; (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 = { isOver, position, text, shortcut }; const childrenWithPopover = addPopoverToGrandchildren({ grandchildren, ...popoverData }); return getElementWithPopover({ child, eventHandlers, childrenWithPopover }); } var _default = (0, _next.withNextComponent)(Tooltip); exports.default = _default; //# sourceMappingURL=index.js.map