UNPKG

rsuite

Version:

A suite of react components

388 lines (375 loc) 16.6 kB
'use client'; "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = exports.OverlayCloseCause = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var _react = _interopRequireWildcard(require("react")); var _get = _interopRequireDefault(require("lodash/get")); var _isNil = _interopRequireDefault(require("lodash/isNil")); var _isUndefined = _interopRequireDefault(require("lodash/isUndefined")); var _contains = _interopRequireDefault(require("dom-lib/contains")); var _OverlayContext = _interopRequireDefault(require("./OverlayContext")); var _Overlay = _interopRequireDefault(require("./Overlay")); var _hooks = require("../hooks"); var _utils = require("../utils"); var _ReactChildren = require("../utils/ReactChildren"); var _excluded = ["children", "container", "controlId", "defaultOpen", "trigger", "disabled", "followCursor", "readOnly", "plaintext", "open", "delay", "delayOpen", "delayClose", "enterable", "placement", "speaker", "rootClose", "onClick", "onMouseOver", "onMouseMove", "onMouseOut", "onContextMenu", "onFocus", "onBlur", "onOpen", "onClose", "onExited"]; function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function mergeEvents(events, props) { if (events === void 0) { events = {}; } if (props === void 0) { props = {}; } var nextEvents = {}; Object.keys(events).forEach(function (eventName) { if (events[eventName]) { var _props; nextEvents[eventName] = (0, _utils.createChainedFunction)(events[eventName], (_props = props) === null || _props === void 0 ? void 0 : _props[eventName]); } }); return nextEvents; } /** * The reason that triggers closing of an overlay * - Clicking outside of the overlay * - Direct invocation of triggerRef.current.close() */ var OverlayCloseCause = exports.OverlayCloseCause = /*#__PURE__*/function (OverlayCloseCause) { OverlayCloseCause[OverlayCloseCause["ClickOutside"] = 0] = "ClickOutside"; OverlayCloseCause[OverlayCloseCause["ImperativeHandle"] = 1] = "ImperativeHandle"; return OverlayCloseCause; }({}); /** * Useful for mouseover and mouseout. * In order to resolve the node entering the mouseover element, a mouseout event and a mouseover event will be triggered. * https://javascript.info/mousemove-mouseover-mouseout-mouseenter-mouseleave * @param handler * @param event */ function onMouseEventHandler(handler, event, delay) { var target = event.currentTarget; var related = event.relatedTarget || (0, _get.default)(event, ['nativeEvent', 'toElement']); if ((!related || related !== target) && !(0, _contains.default)(target, related)) { handler(event, delay); } } var defaultTrigger = ['hover', 'focus']; /** * OverlayTrigger is used to display floating elements on another component. * @private */ var OverlayTrigger = /*#__PURE__*/_react.default.forwardRef(function (props, ref) { var _useContext = (0, _react.useContext)(_OverlayContext.default), overlayContainer = _useContext.overlayContainer; var children = props.children, _props$container = props.container, container = _props$container === void 0 ? overlayContainer : _props$container, controlId = props.controlId, defaultOpen = props.defaultOpen, _props$trigger = props.trigger, trigger = _props$trigger === void 0 ? defaultTrigger : _props$trigger, disabled = props.disabled, followCursor = props.followCursor, readOnly = props.readOnly, plaintext = props.plaintext, openProp = props.open, delay = props.delay, delayOpenProp = props.delayOpen, delayCloseProp = props.delayClose, enterable = props.enterable, _props$placement = props.placement, placement = _props$placement === void 0 ? 'bottomStart' : _props$placement, speaker = props.speaker, _props$rootClose = props.rootClose, rootClose = _props$rootClose === void 0 ? true : _props$rootClose, onClick = props.onClick, onMouseOver = props.onMouseOver, onMouseMove = props.onMouseMove, onMouseOut = props.onMouseOut, onContextMenu = props.onContextMenu, onFocus = props.onFocus, onBlur = props.onBlur, onOpen = props.onOpen, onClose = props.onClose, onExited = props.onExited, rest = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded); var _usePortal = (0, _hooks.usePortal)({ container: container }), Portal = _usePortal.Portal, containerElement = _usePortal.target; var triggerRef = (0, _react.useRef)(null); var overlayRef = (0, _react.useRef)(); var _useControlled = (0, _hooks.useControlled)(openProp, defaultOpen), open = _useControlled[0], setOpen = _useControlled[1]; var _useState = (0, _react.useState)(null), cursorPosition = _useState[0], setCursorPosition = _useState[1]; // Delay the timer to close/open the overlay // When the cursor moves from the trigger to the overlay, the overlay will be closed. // In order to keep the overlay open, a timer is used to delay the closing. var delayOpenTimer = (0, _react.useRef)(null); var delayCloseTimer = (0, _react.useRef)(null); var delayOpen = (0, _isNil.default)(delayOpenProp) ? delay : delayOpenProp; var delayClose = (0, _isNil.default)(delayCloseProp) ? delay : delayCloseProp; // Whether the cursor is on the overlay var isOnOverlay = (0, _react.useRef)(false); // Whether the cursor is on the trigger var isOnTrigger = (0, _react.useRef)(false); (0, _react.useEffect)(function () { return function () { if (!(0, _isNil.default)(delayOpenTimer.current)) { clearTimeout(delayOpenTimer.current); } if (!(0, _isNil.default)(delayCloseTimer.current)) { clearTimeout(delayCloseTimer.current); } }; }, []); // Whether the cursor is on the overlay var mouseEnter = (0, _react.useRef)(false); var handleOpenChange = (0, _react.useCallback)(function (nextOpen, closeCause) { // if the overlay open state is not changed, do not fire the event if (nextOpen === open) return; if (nextOpen) { onOpen === null || onOpen === void 0 || onOpen(); } else { onClose === null || onClose === void 0 || onClose(closeCause); } setOpen(nextOpen); }, [open, onOpen, onClose, setOpen]); var handleOpen = (0, _react.useCallback)(function (delay) { var ms = (0, _isUndefined.default)(delay) ? delayOpen : delay; if (ms && typeof ms === 'number') { return delayOpenTimer.current = setTimeout(function () { delayOpenTimer.current = null; if (mouseEnter.current) { handleOpenChange(true); } }, ms); } handleOpenChange(true); }, [delayOpen, handleOpenChange]); var handleClose = (0, _react.useCallback)(function (delay, closeCause) { var ms = (0, _isUndefined.default)(delay) ? delayClose : delay; if (ms && typeof ms === 'number') { return delayCloseTimer.current = setTimeout(function () { delayCloseTimer.current = null; handleOpenChange(false, closeCause); }, ms); } handleOpenChange(false, closeCause); }, [delayClose, handleOpenChange]); var handleExited = (0, _react.useCallback)(function () { setCursorPosition(null); }, []); (0, _react.useImperativeHandle)(ref, function () { return { get root() { return triggerRef.current; }, get overlay() { var _overlayRef$current; return (_overlayRef$current = overlayRef.current) === null || _overlayRef$current === void 0 ? void 0 : _overlayRef$current.child; }, getState: function getState() { return { open: open }; }, open: handleOpen, close: function close(delay) { return handleClose(delay, OverlayCloseCause.ImperativeHandle); }, updatePosition: function updatePosition() { var _overlayRef$current2, _overlayRef$current2$; (_overlayRef$current2 = overlayRef.current) === null || _overlayRef$current2 === void 0 || (_overlayRef$current2$ = _overlayRef$current2.updatePosition) === null || _overlayRef$current2$ === void 0 || _overlayRef$current2$.call(_overlayRef$current2); } }; }); /** * Close after the cursor leaves. */ var handleCloseWhenLeave = (0, _react.useCallback)(function () { // When the cursor is not on the overlay and not on the trigger, it is closed. if (!isOnOverlay.current && !isOnTrigger.current) { handleClose(undefined, OverlayCloseCause.ClickOutside); } }, [handleClose]); var handleDelayedOpen = (0, _react.useCallback)(function () { mouseEnter.current = true; if (!enterable) { return handleOpen(); } isOnTrigger.current = true; if (!(0, _isNil.default)(delayCloseTimer.current)) { clearTimeout(delayCloseTimer.current); delayCloseTimer.current = null; return handleOpen(); } if (open) { return; } handleOpen(); }, [enterable, open, handleOpen]); /** * Toggle open and closed state. */ var handleOpenState = (0, _react.useCallback)(function () { if (open) { handleCloseWhenLeave(); } else { handleDelayedOpen(); } }, [open, handleCloseWhenLeave, handleDelayedOpen]); var handleDelayedClose = (0, _react.useCallback)(function () { mouseEnter.current = false; if (!enterable) { return handleClose(); } isOnTrigger.current = false; if (!(0, _isNil.default)(delayOpenTimer.current)) { clearTimeout(delayOpenTimer.current); delayOpenTimer.current = null; return; } if (!open || !(0, _isNil.default)(delayCloseTimer.current)) { return; } delayCloseTimer.current = setTimeout(function () { if (!(0, _isNil.default)(delayCloseTimer.current)) { clearTimeout(delayCloseTimer.current); delayCloseTimer.current = null; } handleCloseWhenLeave(); }, 200); }, [enterable, open, handleClose, handleCloseWhenLeave]); var handleSpeakerMouseEnter = (0, _react.useCallback)(function () { isOnOverlay.current = true; }, []); var handleSpeakerMouseLeave = (0, _react.useCallback)(function () { isOnOverlay.current = false; if (!(0, _utils.isOneOf)('click', trigger) && !(0, _utils.isOneOf)('contextMenu', trigger) && !(0, _utils.isOneOf)('active', trigger)) { handleCloseWhenLeave(); } }, [handleCloseWhenLeave, trigger]); var handledMoveOverlay = (0, _react.useCallback)(function (event) { setCursorPosition(function () { return { top: event.pageY, left: event.pageX, clientTop: event.clientX, clientLeft: event.clientY }; }); }, []); var preventDefault = (0, _react.useCallback)(function (event) { event.preventDefault(); }, []); var triggerEvents = (0, _react.useMemo)(function () { // Pass events by props var events = { onClick: onClick, onContextMenu: onContextMenu, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onFocus: onFocus, onBlur: onBlur, onMouseMove: onMouseMove }; // When trigger is disabled, no predefined event listeners are added. if (disabled || readOnly || plaintext || trigger === 'none') { return events; } // Get the cursor position through onMouseMove. // https://rsuitejs.com/components/tooltip/#follow-cursor if (followCursor) { events.onMouseMove = (0, _utils.createChainedFunction)(handledMoveOverlay, onMouseMove); } // The `click` event is usually used in `toggle` scenarios. // The first click will open and the second click will close. if ((0, _utils.isOneOf)('click', trigger)) { events.onClick = (0, _utils.createChainedFunction)(handleOpenState, events.onClick); return events; } // The difference between it and the click event is that it does not trigger the close. if ((0, _utils.isOneOf)('active', trigger)) { events.onClick = (0, _utils.createChainedFunction)(handleDelayedOpen, events.onClick); return events; } if ((0, _utils.isOneOf)('hover', trigger)) { var onMouseOverListener = function onMouseOverListener(e) { return onMouseEventHandler(handleDelayedOpen, e); }; var onMouseOutListener = function onMouseOutListener(e) { return onMouseEventHandler(handleDelayedClose, e); }; events.onMouseOver = (0, _utils.createChainedFunction)(onMouseOverListener, events.onMouseOver); events.onMouseOut = (0, _utils.createChainedFunction)(onMouseOutListener, events.onMouseOut); } if ((0, _utils.isOneOf)('focus', trigger)) { events.onFocus = (0, _utils.createChainedFunction)(handleDelayedOpen, events.onFocus); events.onBlur = (0, _utils.createChainedFunction)(handleDelayedClose, events.onBlur); } if ((0, _utils.isOneOf)('contextMenu', trigger)) { events.onContextMenu = (0, _utils.createChainedFunction)(preventDefault, handleOpenState, events.onContextMenu); } return events; }, [disabled, followCursor, handleDelayedClose, handleDelayedOpen, handleOpenState, handledMoveOverlay, onBlur, onClick, onContextMenu, onFocus, onMouseMove, onMouseOut, onMouseOver, plaintext, preventDefault, readOnly, trigger]); var renderOverlay = function renderOverlay() { var overlayProps = (0, _extends2.default)({}, rest, { rootClose: rootClose, triggerTarget: triggerRef, onClose: trigger !== 'none' ? function () { return handleClose(undefined, OverlayCloseCause.ClickOutside); } : undefined, onExited: (0, _utils.createChainedFunction)(followCursor ? handleExited : undefined, onExited), placement: placement, container: containerElement, open: open }); var speakerProps = { id: controlId }; // The purpose of adding mouse entry and exit events to the Overlay is to record whether the current cursor is on the Overlay. // When `trigger` is equal to `hover`, if the cursor leaves the `triggerTarget` and stays on the Overlay, // the Overlay will continue to remain open. if (trigger !== 'none' && enterable) { speakerProps.onMouseEnter = handleSpeakerMouseEnter; speakerProps.onMouseLeave = handleSpeakerMouseLeave; } return /*#__PURE__*/_react.default.createElement(_Overlay.default, (0, _extends2.default)({}, overlayProps, { ref: overlayRef, childrenProps: speakerProps, followCursor: followCursor, cursorPosition: cursorPosition }), typeof speaker === 'function' ? function (props, ref) { return speaker((0, _extends2.default)({}, props, { onClose: handleClose }), ref); } : speaker); }; var triggerElement = (0, _react.useMemo)(function () { if (typeof children === 'function') { return children(triggerEvents, triggerRef); } else if ((0, _ReactChildren.isFragment)(children) || ! /*#__PURE__*/(0, _react.isValidElement)(children)) { return /*#__PURE__*/_react.default.createElement("span", (0, _extends2.default)({ ref: triggerRef, "aria-describedby": controlId }, triggerEvents), children); } return /*#__PURE__*/(0, _react.cloneElement)(children, (0, _extends2.default)({ ref: triggerRef, 'aria-describedby': controlId }, mergeEvents(triggerEvents, children.props))); }, [children, controlId, triggerEvents]); return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, triggerElement, /*#__PURE__*/_react.default.createElement(Portal, null, renderOverlay())); }); OverlayTrigger.displayName = 'OverlayTrigger'; var _default = exports.default = OverlayTrigger;