@wordpress/components
Version:
UI components for WordPress.
296 lines (243 loc) • 8.66 kB
JavaScript
;
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