UNPKG

chayns-components

Version:

A set of beautiful React components for developing chayns® applications.

380 lines (372 loc) 14.4 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _clsx = _interopRequireDefault(require("clsx")); var _propTypes = _interopRequireDefault(require("prop-types")); var _react = _interopRequireWildcard(require("react")); var _server = require("react-dom/server"); var _Bubble = _interopRequireDefault(require("../../react-chayns-bubble/component/Bubble")); var _Icon = _interopRequireDefault(require("../../react-chayns-icon/component/Icon")); var _TextString = _interopRequireDefault(require("../../react-chayns-textstring/component/TextString")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * @component {./docs.md} */ /** * Gives people access to additional functionality related to onscreen items * without cluttering the interface. */ const ContextMenu = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => { let { parent, position: userPosition = null, minWidth, maxWidth, className, items = [], stopPropagation = false, childrenStyle, childrenClassName, showTriggerBackground = false, children = /*#__PURE__*/_react.default.createElement(_Icon.default, { icon: "ts-ellipsis_v" }), style, onChildrenClick, disableDialog = false, onLayerClick, onShow, onHide, removeParentSpace = false, coordinates, positionOnChildren = 1, arrowDistance = 0 } = _ref; const bubbleRef = (0, _react.useRef)(); const childrenRef = (0, _react.useRef)(); const [isBubbleShown, setIsBubbleShown] = (0, _react.useState)(false); const [position, setPosition] = (0, _react.useState)(null); const [x, setX] = (0, _react.useState)(0); const [y, setY] = (0, _react.useState)(0); const itemsHaveIcons = items.some(_ref2 => { let { icon } = _ref2; return typeof icon === 'string' ? icon : icon === null || icon === void 0 ? void 0 : icon.iconName; }); /** * Sync bubble with `isBubbleShown` state */ (0, _react.useEffect)(() => { if (!bubbleRef.current) return; if (isBubbleShown) { bubbleRef.current.show(); onShow === null || onShow === void 0 ? void 0 : onShow(); } else { bubbleRef.current.hide(); onHide === null || onHide === void 0 ? void 0 : onHide(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isBubbleShown]); /** * Update position of the bubble */ (0, _react.useEffect)(() => { let newX = (coordinates === null || coordinates === void 0 ? void 0 : coordinates.x) || 0; let top = (coordinates === null || coordinates === void 0 ? void 0 : coordinates.y) || 0; let bottom = (coordinates === null || coordinates === void 0 ? void 0 : coordinates.y) || 0; if (!coordinates && childrenRef.current) { const rect = childrenRef.current.getBoundingClientRect(); if (positionOnChildren === ContextMenu.positionOnChildren.LEFT) { newX = rect.left + arrowDistance; } else if (positionOnChildren === ContextMenu.positionOnChildren.CENTER) { newX = rect.left + rect.width / 2; } else if (positionOnChildren === ContextMenu.positionOnChildren.RIGHT) { newX = rect.left + rect.width - arrowDistance; } top = rect.top; bottom = rect.bottom; } let newPosition = userPosition; if (newPosition === null) { const shouldBeLeft = newX > window.innerWidth / 2; const shouldBeTop = (top + bottom) / 2 > (document.body.offsetHeight || window.innerHeight) / 2; if (shouldBeLeft && shouldBeTop) { newPosition = ContextMenu.position.TOP_LEFT; } else if (!shouldBeLeft && shouldBeTop) { newPosition = ContextMenu.position.TOP_RIGHT; } else if (shouldBeLeft && !shouldBeTop) { newPosition = ContextMenu.position.BOTTOM_LEFT; } else { newPosition = ContextMenu.position.BOTTOM_RIGHT; } } let newY = _Bubble.default.isPositionBottom(newPosition) ? bottom : top; if (removeParentSpace) { const parentRect = (parent || document.getElementsByClassName('tapp')[0] || document.body).getBoundingClientRect(); newX -= parentRect.left; newY -= parentRect.top; } if (chayns.env.isApp) { chayns.getWindowMetrics().then(_ref3 => { let { pageYOffset } = _ref3; setPosition(newPosition); setX(newX); setY(newY + pageYOffset); }); } else { setPosition(newPosition); setX(newX); setY(newY); } }, [arrowDistance, coordinates, parent, positionOnChildren, removeParentSpace, userPosition, isBubbleShown]); /** * Show the menu, either by opening a dialog or by showing the bubble. */ const show = (0, _react.useCallback)(async () => { if (!disableDialog && (chayns.env.isMobile || chayns.env.isTablet)) { const iconFallback = items.some(_ref4 => { let { icon } = _ref4; return typeof icon === 'string' ? icon : icon === null || icon === void 0 ? void 0 : icon.iconName; }) ? ' ' : ''; const { buttonType, selection } = await chayns.dialog.select({ type: 2, list: items.map((_ref5, index) => { let { text, icon, stringName } = _ref5; return { // eslint-disable-next-line no-nested-ternary name: stringName ? _TextString.default.getTextString(stringName, null, text) : /*#__PURE__*/_react.default.isValidElement(text) ? (0, _server.renderToStaticMarkup)(text) : text, value: index, icon: (typeof icon === 'string' ? icon : (icon === null || icon === void 0 ? void 0 : icon.iconName) && `fa-${icon === null || icon === void 0 ? void 0 : icon.iconName}`) || iconFallback }; }), buttons: [] }); if (buttonType === 1 && selection !== null && selection !== void 0 && selection[0]) { items[selection[0].value].onClick(); } else if (buttonType === -1 && onLayerClick) { onLayerClick(); } } else { setIsBubbleShown(true); } }, [disableDialog, items, onLayerClick]); const hide = (0, _react.useCallback)(() => { setIsBubbleShown(false); }, []); /** * Expose the `show` and `hide` methods via ref. */ (0, _react.useImperativeHandle)(ref, () => ({ show, hide }), [hide, show]); (0, _react.useEffect)(() => { window.addEventListener('blur', hide); return () => { window.removeEventListener('blur', hide); }; }, [hide]); /** * Hides the bubble when is it opened and the page is clicked. */ // eslint-disable-next-line consistent-return (0, _react.useEffect)(() => { if (isBubbleShown) { const handleLayerClick = event => { if (isBubbleShown) { if (onLayerClick) { onLayerClick(event); } else { hide(); } } }; document.addEventListener('click', handleLayerClick, true); return () => document.removeEventListener('click', handleLayerClick, true); } }, [hide, isBubbleShown, onLayerClick]); function handleChildrenClick(event) { if (onChildrenClick) { onChildrenClick(event); } else { show(); } if (stopPropagation) { event.stopPropagation(); } } return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_Bubble.default, { coordinates: { x, y }, parent: parent, position: position, style: { minWidth, maxWidth, ...style }, key: "bubble", ref: bubbleRef, className: className }, /*#__PURE__*/_react.default.createElement("ul", null, items.map((item, index) => /*#__PURE__*/_react.default.createElement("li", { className: (0, _clsx.default)('context-menu__item', item.className), onClick: e => { if (stopPropagation) { e.stopPropagation(); } item.onClick(e); }, key: // eslint-disable-next-line react/no-array-index-key item.stringName || (item.text.props && item.text.props.stringName ? item.text.props.stringName : item.text) + index }, item.icon || itemsHaveIcons ? /*#__PURE__*/_react.default.createElement("div", { className: "context-menu__item__icon" }, item.icon && /*#__PURE__*/_react.default.createElement(_Icon.default, { icon: item.icon })) : null, item.stringName ? /*#__PURE__*/_react.default.createElement(_TextString.default, { stringName: item.stringName }, /*#__PURE__*/_react.default.createElement("div", { className: "context-menu__item__text" }, item.text)) : /*#__PURE__*/_react.default.createElement("div", { className: "context-menu__item__text" }, item.text))))), !coordinates && /*#__PURE__*/_react.default.createElement("div", { key: "cc__contextMenu__children", ref: childrenRef, onClick: handleChildrenClick, style: childrenStyle, className: (0, _clsx.default)("accordion--no-trigger context-menu__children", childrenClassName, showTriggerBackground && 'image-tool') }, children)); }); ContextMenu.position = _Bubble.default.position; ContextMenu.positionOnChildren = { LEFT: 0, CENTER: 1, RIGHT: 2 }; ContextMenu.propTypes = { /** * This callback will be called when the `ContextMenu` is shown and the user * clicks away from it. */ onLayerClick: _propTypes.default.func, /** * This callback will be called when the context menu becomes visible */ onShow: _propTypes.default.func, /** * This callback will be called when the `ContextMenu` hides */ onHide: _propTypes.default.func, /** * The coordinates at which the context menu will get rendered. */ coordinates: _propTypes.default.shape({ x: _propTypes.default.number.isRequired, y: _propTypes.default.number.isRequired }), /** * The action items inside of the context menu. Their shape should look like * this: `{ className: <string>, onClick: <function>, text: <string>, * icon: <string> }, stringName: <string>`. */ items: _propTypes.default.arrayOf(_propTypes.default.shape({ className: _propTypes.default.string, onClick: _propTypes.default.func, text: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.node]).isRequired, stringName: _propTypes.default.string, icon: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.instanceOf(Object)]) })), /** * This specifies where the menu will appear relative to the components * provided in the `children`-prop. Possible values are: `0` for top left, * `1` for bottom left, `2` for bottom right, `3` for top right, `4` for top * center and `5` for bottom center. */ position: _propTypes.default.number, /** * The position of the arrow relative to the children. Possible values are * `0` for left, `1` for center and `2` for right. */ positionOnChildren: _propTypes.default.number, /** * Specifies the DOM node the context menu will be rendered into. */ parent: typeof Element !== 'undefined' ? _propTypes.default.instanceOf(Element) : () => {}, /** * The React node that the context menu refers to. */ children: _propTypes.default.node, /** * Called when the `onclick`-event is triggered on the children. */ onChildrenClick: _propTypes.default.func, /** * A React style object that will be applied to the children wrapper. */ childrenStyle: _propTypes.default.objectOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])), /** * A classname string that will be applied to the children wrapper. */ childrenClassName: _propTypes.default.string, /** * Wether to stop propagation of click events on the children elements. */ stopPropagation: _propTypes.default.bool, /** * The minimum width of the context menu. */ minWidth: _propTypes.default.number, /** * The maximum width of the context menu. */ maxWidth: _propTypes.default.number, /** * Adds a white background to the children wrapper, for when it would * otherwise be difficult to view (e.g. on images or video). */ showTriggerBackground: _propTypes.default.bool, /** * A classname string applied to the Bubble component. */ className: _propTypes.default.string, /** * A React style object that is applied to the Bubble component. */ // eslint-disable-next-line react/forbid-prop-types style: _propTypes.default.object, /** * Removes the parent padding to the page borders from the context menu * position. This is needed when the parent is padded and relatively * positioned. */ removeParentSpace: _propTypes.default.bool, /** * Disables the use of a dialog on mobile. */ disableDialog: _propTypes.default.bool, /** * Adjust the distance of the arrow and the end of the children. This only * applies if `positionOnChildren` is set to left (`0`) or right (`2`). */ arrowDistance: _propTypes.default.number }; ContextMenu.displayName = 'ContextMenu'; var _default = ContextMenu; exports.default = _default; //# sourceMappingURL=ContextMenu.js.map