UNPKG

@chayns-components/core

Version:

A set of beautiful React components for developing your own applications with chayns.

349 lines (348 loc) • 14.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _chaynsApi = require("chayns-api"); var _react = require("motion/react"); var _react2 = _interopRequireWildcard(require("react")); var _reactDom = require("react-dom"); var _uuid = require("../../hooks/uuid"); var _environment = require("../../utils/environment"); var _Icon = _interopRequireDefault(require("../icon/Icon")); var _ContextMenuContent = _interopRequireDefault(require("./context-menu-content/ContextMenuContent")); var _ContextMenu = require("./ContextMenu.styles"); var _ContextMenu2 = require("./ContextMenu.types"); var _ContextMenu3 = require("./ContextMenu.utils"); var _useKeyboardFocusHighlighting = require("../../hooks/useKeyboardFocusHighlighting"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (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 (const 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); } const ContextMenu = /*#__PURE__*/(0, _react2.forwardRef)(({ alignment, className, children = /*#__PURE__*/_react2.default.createElement(_Icon.default, { icons: ['ts-ellipsis_v'], size: 18 }), container, coordinates, headline, items, onHide, onShow, shouldCloseOnPopupClick = true, shouldDisableClick = false, shouldHidePopupArrow = false, shouldShowHoverEffect = false, shouldEnableKeyboardHighlighting, shouldUseDefaultTriggerStyles = true, style, yOffset = 0, zIndex = 20, dialogText }, ref) => { const [internalCoordinates, setInternalCoordinates] = (0, _react2.useState)({ x: 0, y: 0 }); const [internalAlignment, setInternalAlignment] = (0, _react2.useState)(_ContextMenu2.ContextMenuAlignment.TopLeft); const [newContainer, setNewContainer] = (0, _react2.useState)(container ?? null); const [focusedIndex, setFocusedIndex] = (0, _react2.useState)((0, _ContextMenu3.getDefaultFocusedIndex)(items)); const [isContentShown, setIsContentShown] = (0, _react2.useState)(false); const [portal, setPortal] = (0, _react2.useState)(); const [isHovered, setIsHovered] = (0, _react2.useState)(false); const [shouldUseFocusableWrapper, setShouldUseFocusableWrapper] = (0, _react2.useState)(false); const uuid = (0, _uuid.useUuid)(); const contextMenuContentRef = (0, _react2.useRef)(null); const contextMenuRef = (0, _react2.useRef)(null); const shouldSkipNextContextMenuOpenRef = (0, _react2.useRef)(false); const shouldPreventNextNativeContextMenuRef = (0, _react2.useRef)(false); const isTouch = (0, _environment.useIsTouch)(); const shouldShowKeyboardHighlighting = (0, _useKeyboardFocusHighlighting.useKeyboardFocusHighlighting)(shouldEnableKeyboardHighlighting && !shouldDisableClick); const shouldShowWrapperKeyboardHighlighting = shouldShowKeyboardHighlighting && shouldUseFocusableWrapper; const shouldUseKeyboardFocusableWrapper = shouldUseFocusableWrapper && !shouldDisableClick; (0, _react2.useEffect)(() => { if (isContentShown) { setFocusedIndex((0, _ContextMenu3.getDefaultFocusedIndex)(items)); } }, [isContentShown, items]); (0, _react2.useEffect)(() => { if (contextMenuRef.current && !container) { const el = contextMenuRef.current; const element = el.closest('.dialog-inner, .page-provider, .tapp, body'); setNewContainer(element); } }, [container]); (0, _react2.useEffect)(() => { if (container instanceof Element) { setNewContainer(container); } }, [container]); const handleHide = (0, _react2.useCallback)(() => { setIsContentShown(false); }, []); const handleContainerBlur = (0, _react2.useCallback)(event => { var _contextMenuContentRe; const nextFocusedElement = event.relatedTarget; const currentContainer = event.currentTarget; if (!nextFocusedElement || !currentContainer.contains(nextFocusedElement) && !((_contextMenuContentRe = contextMenuContentRef.current) !== null && _contextMenuContentRe !== void 0 && _contextMenuContentRe.contains(nextFocusedElement))) { handleHide(); } }, [handleHide]); const handleShow = (0, _react2.useCallback)(async () => { if (isTouch) { const { result } = await (0, _chaynsApi.createDialog)({ type: _chaynsApi.DialogType.SELECT, text: dialogText, buttons: [], list: items.map(({ icons, text, isSelected }, index) => ({ name: text, id: index, isSelected, icon: /*#__PURE__*/(0, _react2.isValidElement)(icons) ? undefined : icons === null || icons === void 0 ? void 0 : icons[0] })) }).open(); if (result && typeof result[0] === 'number') { var _items$result$; void ((_items$result$ = items[result[0]]) === null || _items$result$ === void 0 ? void 0 : _items$result$.onClick()); } } else if (contextMenuRef.current) { if (!newContainer) { return; } const { height: childrenHeight, left: childrenLeft, top: childrenTop, width: childrenWidth } = contextMenuRef.current.getBoundingClientRect(); const { height, width, top, left } = newContainer.getBoundingClientRect(); const zoomX = width / newContainer.offsetWidth; const zoomY = height / newContainer.offsetHeight; const x = (childrenLeft + childrenWidth / 2 - left) / zoomX + newContainer.scrollLeft; const y = (childrenTop + childrenHeight / 2 - top) / zoomY + newContainer.scrollTop - yOffset; setInternalCoordinates({ x, y }); if (x < width / 2) { if (y < height / 2) { setInternalAlignment(_ContextMenu2.ContextMenuAlignment.BottomRight); } else { setInternalAlignment(_ContextMenu2.ContextMenuAlignment.TopRight); } } else if (y < height / 2) { setInternalAlignment(_ContextMenu2.ContextMenuAlignment.BottomLeft); } else { setInternalAlignment(_ContextMenu2.ContextMenuAlignment.TopLeft); } setIsContentShown(true); } }, [isTouch, items, newContainer, yOffset, dialogText]); const handleClick = (0, _react2.useCallback)(event => { if (shouldDisableClick) { return; } event.preventDefault(); event.stopPropagation(); void handleShow(); }, [handleShow, shouldDisableClick]); const handleKeyDown = (0, _react2.useCallback)(event => { if (shouldDisableClick) { return; } const isActivationKey = event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar'; if (isContentShown && event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); handleHide(); return; } if (isContentShown && !isHovered && event.key === 'ArrowDown') { event.preventDefault(); event.stopPropagation(); setFocusedIndex(prev => prev >= items.length - 1 ? 0 : prev + 1); return; } if (isContentShown && !isHovered && event.key === 'ArrowUp') { event.preventDefault(); event.stopPropagation(); setFocusedIndex(prev => prev <= 0 ? items.length - 1 : prev - 1); return; } if (isContentShown && isActivationKey) { if ((0, _ContextMenu3.selectItem)({ index: (0, _ContextMenu3.getActiveItemIndex)({ contextMenuContentElement: contextMenuContentRef.current, focusedIndex }), items, onClose: handleHide, shouldCloseOnPopupClick })) { event.preventDefault(); event.stopPropagation(); } return; } if (event.currentTarget !== event.target) { return; } const isContextMenuShortcut = event.key === 'ContextMenu' || event.key === 'F10' && event.shiftKey; if (isContextMenuShortcut) { event.preventDefault(); event.stopPropagation(); shouldSkipNextContextMenuOpenRef.current = true; void handleShow(); return; } if (shouldUseFocusableWrapper && isActivationKey) { event.preventDefault(); event.stopPropagation(); void handleShow(); } }, [focusedIndex, handleHide, handleShow, isHovered, isContentShown, items, items.length, shouldCloseOnPopupClick, shouldDisableClick, shouldUseFocusableWrapper]); const handleContextMenu = (0, _react2.useCallback)(event => { event.preventDefault(); event.stopPropagation(); if (shouldDisableClick) { return; } if (shouldSkipNextContextMenuOpenRef.current) { shouldSkipNextContextMenuOpenRef.current = false; return; } void handleShow(); }, [handleShow, shouldDisableClick]); (0, _react2.useEffect)(() => { const handleGlobalKeyDown = event => { if (shouldDisableClick || !contextMenuRef.current) { return; } const isContextMenuShortcut = event.key === 'ContextMenu' || event.key === 'F10' && event.shiftKey; if (!isContextMenuShortcut) { return; } const { activeElement } = document; if (!(activeElement instanceof HTMLElement) || activeElement === document.body || activeElement === document.documentElement) { return; } const shouldOpenFromFocusedParent = activeElement.contains(contextMenuRef.current) && !contextMenuRef.current.contains(activeElement); if (!shouldOpenFromFocusedParent) { return; } event.preventDefault(); event.stopPropagation(); shouldPreventNextNativeContextMenuRef.current = true; void handleShow(); }; const handleNativeContextMenuCapture = event => { if (!shouldPreventNextNativeContextMenuRef.current) { return; } shouldPreventNextNativeContextMenuRef.current = false; event.preventDefault(); event.stopPropagation(); }; document.addEventListener('keydown', handleGlobalKeyDown, true); document.addEventListener('contextmenu', handleNativeContextMenuCapture, true); return () => { document.removeEventListener('keydown', handleGlobalKeyDown, true); document.removeEventListener('contextmenu', handleNativeContextMenuCapture, true); }; }, [handleShow, shouldDisableClick]); (0, _react2.useImperativeHandle)(ref, () => ({ hide: handleHide, show: handleShow }), [handleHide, handleShow]); (0, _react2.useEffect)(() => { const handleDocumentClick = event => { var _contextMenuContentRe2; if (!shouldCloseOnPopupClick && (_contextMenuContentRe2 = contextMenuContentRef.current) !== null && _contextMenuContentRe2 !== void 0 && _contextMenuContentRe2.contains(event.target)) { return; } handleHide(); }; if (isContentShown) { document.addEventListener('click', handleDocumentClick, true); window.addEventListener('blur', handleHide); if (typeof onShow === 'function') { onShow(); } } else if (typeof onHide === 'function') { onHide(); } return () => { document.removeEventListener('click', handleDocumentClick, true); window.removeEventListener('blur', handleHide); }; }, [handleHide, isContentShown, onHide, onShow, shouldCloseOnPopupClick]); (0, _react2.useEffect)(() => { if (!newContainer) { return; } setPortal(() => /*#__PURE__*/(0, _reactDom.createPortal)(/*#__PURE__*/_react2.default.createElement(_react.AnimatePresence, { initial: false }, isContentShown && /*#__PURE__*/_react2.default.createElement(_ContextMenuContent.default, { coordinates: coordinates ?? internalCoordinates, items: items, zIndex: zIndex, headline: headline, shouldHidePopupArrow: shouldHidePopupArrow, key: `contextMenu_${uuid}`, alignment: alignment ?? internalAlignment, ref: contextMenuContentRef, focusedIndex: focusedIndex, onItemFocus: setFocusedIndex, onMouseEnter: () => { setIsHovered(true); setFocusedIndex(-1); }, onMouseLeave: () => { setIsHovered(false); } })), newContainer)); }, [alignment, newContainer, coordinates, internalAlignment, internalCoordinates, isContentShown, items, uuid, zIndex, shouldHidePopupArrow, headline, focusedIndex, handleHide]); (0, _react2.useEffect)(() => { if (!contextMenuRef.current) { return; } const focusableChildSelector = 'button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable="true"]'; const hasFocusableChild = Boolean(contextMenuRef.current.querySelector(focusableChildSelector)); setShouldUseFocusableWrapper(!hasFocusableChild); }, [children]); return /*#__PURE__*/_react2.default.createElement(_react2.default.Fragment, null, /*#__PURE__*/_react2.default.createElement(_ContextMenu.StyledContextMenu, { className: className ? `beta-chayns-context-menu ${className}` : 'beta-chayns-context-menu', $isActive: isContentShown && shouldShowHoverEffect, $shouldAddHoverEffect: !isTouch && shouldShowHoverEffect, $shouldShowWrapperKeyboardHighlighting: shouldShowWrapperKeyboardHighlighting, $shouldUseDefaultTriggerStyles: shouldUseDefaultTriggerStyles, onClick: handleClick, onContextMenuCapture: handleContextMenu, onContextMenu: handleContextMenu, onKeyDown: handleKeyDown, onBlur: handleContainerBlur, ref: contextMenuRef, tabIndex: shouldUseKeyboardFocusableWrapper ? 0 : undefined, role: shouldUseKeyboardFocusableWrapper ? 'button' : undefined, "aria-haspopup": shouldUseKeyboardFocusableWrapper ? 'menu' : undefined, "aria-expanded": shouldUseKeyboardFocusableWrapper ? isContentShown : undefined, style: style }, children), portal); }); ContextMenu.displayName = 'ContextMenu'; var _default = exports.default = ContextMenu; //# sourceMappingURL=ContextMenu.js.map