UNPKG

@chayns-components/core

Version:

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

395 lines (394 loc) 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = require("motion/react"); var _react2 = _interopRequireWildcard(require("react")); var _reactDom = require("react-dom"); var _mentionFinder = require("../../constants/mentionFinder"); var _MentionFinderItem = _interopRequireDefault(require("./mention-finder-item/MentionFinderItem")); var _MentionFinder = require("./MentionFinder.styles"); 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 DRAG_CLOSE_THRESHOLD_IN_PX = 60; const isTextInputElement = element => { if (!element) { return false; } const tagName = element.tagName.toLowerCase(); return tagName === 'input' && element.type !== 'button' || tagName === 'textarea' || element.isContentEditable; }; const findTouchByIdentifier = (touchList, identifier) => { if (identifier === null) { return null; } for (let i = 0; i < touchList.length; i += 1) { const touch = touchList.item(i); if (touch && touch.identifier === identifier) { return touch; } } return null; }; const MentionFinder = ({ inputValue, members, onSelect, popupAlignment, enableDragHandle = false, dragCloseThresholdInPx = DRAG_CLOSE_THRESHOLD_IN_PX, overlayContainerSelector }) => { const [activeMember, setActiveMember] = (0, _react2.useState)(members[0]); const [focusedIndex, setFocusedIndex] = (0, _react2.useState)(0); const [shouldShowPopup, setShouldShowPopup] = (0, _react2.useState)(true); const popupRef = (0, _react2.useRef)(null); const listRef = (0, _react2.useRef)(null); const inputElementRef = (0, _react2.useRef)(null); const dragStartYRef = (0, _react2.useRef)(null); const hasTriggeredDragCloseRef = (0, _react2.useRef)(false); const activePointerIdRef = (0, _react2.useRef)(null); const activeTouchIdRef = (0, _react2.useRef)(null); const [isDraggingHandle, setIsDraggingHandle] = (0, _react2.useState)(false); const [dragOffset, setDragOffset] = (0, _react2.useState)(0); const [dragProgress, setDragProgress] = (0, _react2.useState)(0); const [overlayContainer, setOverlayContainer] = (0, _react2.useState)(null); const [fullMatch, searchString] = (0, _react2.useMemo)(() => { var _regExpMatchArray$; // eslint-disable-next-line no-irregular-whitespace const regExpMatchArray = inputValue.match(/@(?!\s)([^\s​]*)/); return [regExpMatchArray === null || regExpMatchArray === void 0 ? void 0 : regExpMatchArray[0], (regExpMatchArray === null || regExpMatchArray === void 0 || (_regExpMatchArray$ = regExpMatchArray[1]) === null || _regExpMatchArray$ === void 0 ? void 0 : _regExpMatchArray$.toLowerCase()) ?? '']; }, [inputValue]); const filteredMembers = (0, _react2.useMemo)(() => searchString !== '' ? members.filter(({ id, info, name }) => id.toLowerCase().includes(searchString) || (info === null || info === void 0 ? void 0 : info.replace('chayns', '').toLowerCase().includes(searchString)) || name.toLowerCase().includes(searchString)) : members, [members, searchString]); const shouldRenderPopup = shouldShowPopup && !!fullMatch && filteredMembers.length > 0; const handleContainerBlur = (0, _react2.useCallback)(event => { var _popupRef$current; const nextFocusedElement = event.relatedTarget; const currentContainer = event.currentTarget; if (!nextFocusedElement || !currentContainer.contains(nextFocusedElement) && !((_popupRef$current = popupRef.current) !== null && _popupRef$current !== void 0 && _popupRef$current.contains(nextFocusedElement))) { setShouldShowPopup(false); } }, []); const handleKeyDown = (0, _react2.useCallback)(event => { const targetElement = event.target; if (isTextInputElement(targetElement)) { inputElementRef.current = targetElement; } if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && shouldRenderPopup) { var _listRef$current; event.preventDefault(); const children = (_listRef$current = listRef.current) === null || _listRef$current === void 0 ? void 0 : _listRef$current.children; if (children && children.length > 0) { const newIndex = focusedIndex !== null ? (focusedIndex + (event.key === 'ArrowUp' ? -1 : 1) + children.length) % children.length : 0; if (focusedIndex !== null) { const prevElement = children[focusedIndex]; prevElement.tabIndex = -1; } setFocusedIndex(newIndex); const member = filteredMembers[newIndex]; setActiveMember(member); const newElement = children[newIndex]; newElement.tabIndex = 0; newElement.focus(); } } else if (event.key === 'Enter' && shouldRenderPopup) { event.preventDefault(); event.stopPropagation(); if (fullMatch && activeMember) { onSelect({ fullMatch, member: activeMember }); } } else if (event.key === 'Escape' && shouldRenderPopup) { event.preventDefault(); event.stopPropagation(); setShouldShowPopup(false); window.requestAnimationFrame(() => { var _inputElementRef$curr; (_inputElementRef$curr = inputElementRef.current) === null || _inputElementRef$curr === void 0 || _inputElementRef$curr.focus(); }); } }, [activeMember, filteredMembers, focusedIndex, fullMatch, onSelect, shouldRenderPopup]); const handleMemberClick = (0, _react2.useCallback)(member => { if (fullMatch) { onSelect({ fullMatch, member }); } }, [fullMatch, onSelect]); const handleMemberHover = (0, _react2.useCallback)(member => { setActiveMember(member); }, []); (0, _react2.useEffect)(() => { if (filteredMembers.length > 0) { const isActiveMemberShown = filteredMembers.some(({ id }) => id === (activeMember === null || activeMember === void 0 ? void 0 : activeMember.id)); if (!isActiveMemberShown) { setActiveMember(filteredMembers[0]); } } }, [activeMember === null || activeMember === void 0 ? void 0 : activeMember.id, filteredMembers]); const items = (0, _react2.useMemo)(() => filteredMembers.map(member => /*#__PURE__*/_react2.default.createElement(_MentionFinderItem.default, { isActive: member.id === (activeMember === null || activeMember === void 0 ? void 0 : activeMember.id), key: member.id, member: member, onClick: handleMemberClick, onHover: handleMemberHover })), [activeMember, filteredMembers, handleMemberClick, handleMemberHover]); (0, _react2.useEffect)(() => { setDragOffset(0); setDragProgress(0); hasTriggeredDragCloseRef.current = false; setShouldShowPopup(true); }, [inputValue]); (0, _react2.useEffect)(() => { if (!shouldRenderPopup) { return; } const activeElement = document.activeElement; if (isTextInputElement(activeElement)) { inputElementRef.current = activeElement; } }, [shouldRenderPopup]); (0, _react2.useEffect)(() => { if (shouldShowPopup) { window.addEventListener('keydown', handleKeyDown, true); } return () => { window.removeEventListener('keydown', handleKeyDown, true); }; }, [handleKeyDown, shouldShowPopup]); const closePopupViaDrag = (0, _react2.useCallback)(() => { if (hasTriggeredDragCloseRef.current) { return; } hasTriggeredDragCloseRef.current = true; setDragOffset(0); setDragProgress(0); setShouldShowPopup(false); }, []); const handleOverlayClick = (0, _react2.useCallback)(() => { closePopupViaDrag(); }, [closePopupViaDrag]); const updateDragByClientY = (0, _react2.useCallback)(clientY => { if (dragStartYRef.current === null) { return false; } const closingDirectionMultiplier = popupAlignment === _mentionFinder.MentionFinderPopupAlignment.Bottom ? -1 : 1; const rawDelta = clientY - dragStartYRef.current; const normalizedDelta = rawDelta * closingDirectionMultiplier; const positiveDelta = Math.max(0, normalizedDelta); const limitedDelta = Math.min(positiveDelta, dragCloseThresholdInPx); setDragOffset(limitedDelta * closingDirectionMultiplier); setDragProgress(Math.min(positiveDelta / dragCloseThresholdInPx, 1)); if (positiveDelta >= dragCloseThresholdInPx) { dragStartYRef.current = null; setIsDraggingHandle(false); closePopupViaDrag(); return true; } return false; }, [closePopupViaDrag, dragCloseThresholdInPx, popupAlignment]); const handleDragPointerDown = (0, _react2.useCallback)(event => { if (event.pointerType === 'touch') { return; } if (event.pointerType === 'mouse' && event.button !== 0) { return; } if (dragStartYRef.current !== null) { return; } activePointerIdRef.current = event.pointerId; dragStartYRef.current = event.clientY; hasTriggeredDragCloseRef.current = false; setDragOffset(0); setDragProgress(0); setIsDraggingHandle(true); event.currentTarget.setPointerCapture(event.pointerId); event.stopPropagation(); }, []); const handleDragPointerMove = (0, _react2.useCallback)(event => { if (event.pointerType === 'touch' || activePointerIdRef.current !== event.pointerId) { return; } if (dragStartYRef.current === null) { return; } event.preventDefault(); event.stopPropagation(); const hasClosed = updateDragByClientY(event.clientY); if (hasClosed) { event.currentTarget.releasePointerCapture(event.pointerId); activePointerIdRef.current = null; } }, [updateDragByClientY]); const resetDragState = (0, _react2.useCallback)((shouldClose = false) => { dragStartYRef.current = null; setIsDraggingHandle(false); if (shouldClose) { closePopupViaDrag(); } else { setDragOffset(0); setDragProgress(0); } }, [closePopupViaDrag]); const handleDragPointerUp = (0, _react2.useCallback)(event => { if (event.pointerType === 'touch' || activePointerIdRef.current !== event.pointerId) { return; } event.stopPropagation(); const shouldClose = dragProgress >= 1; resetDragState(shouldClose); event.currentTarget.releasePointerCapture(event.pointerId); activePointerIdRef.current = null; }, [dragProgress, resetDragState]); const handleDragPointerCancel = (0, _react2.useCallback)(event => { if (event.pointerType === 'touch' || activePointerIdRef.current !== event.pointerId) { return; } event.stopPropagation(); resetDragState(); event.currentTarget.releasePointerCapture(event.pointerId); activePointerIdRef.current = null; }, [resetDragState]); const handleDragTouchStart = (0, _react2.useCallback)(event => { if (event.touches.length === 0 || dragStartYRef.current !== null) { return; } const touch = event.touches[0]; if (!touch) return; activeTouchIdRef.current = touch.identifier; dragStartYRef.current = touch.clientY; hasTriggeredDragCloseRef.current = false; setDragOffset(0); setDragProgress(0); setIsDraggingHandle(true); event.preventDefault(); event.stopPropagation(); }, []); const handleDragTouchMove = (0, _react2.useCallback)(event => { if (dragStartYRef.current === null || activeTouchIdRef.current === null) { return; } const touch = findTouchByIdentifier(event.changedTouches, activeTouchIdRef.current) ?? findTouchByIdentifier(event.touches, activeTouchIdRef.current); if (!touch) return; event.preventDefault(); event.stopPropagation(); const hasClosed = updateDragByClientY(touch.clientY); if (hasClosed) { activeTouchIdRef.current = null; } }, [updateDragByClientY]); const handleDragTouchEnd = (0, _react2.useCallback)(event => { if (activeTouchIdRef.current === null) return; const touch = findTouchByIdentifier(event.changedTouches, activeTouchIdRef.current); if (!touch) return; event.preventDefault(); event.stopPropagation(); activeTouchIdRef.current = null; const shouldClose = dragProgress >= 1; resetDragState(shouldClose); }, [dragProgress, resetDragState]); const handleDragTouchCancel = (0, _react2.useCallback)(event => { if (activeTouchIdRef.current === null) return; const touch = findTouchByIdentifier(event.changedTouches, activeTouchIdRef.current); if (!touch) return; event.preventDefault(); event.stopPropagation(); activeTouchIdRef.current = null; resetDragState(); }, [resetDragState]); const dragHandle = (0, _react2.useMemo)(() => { if (!enableDragHandle) { return null; } return /*#__PURE__*/_react2.default.createElement(_MentionFinder.StyledMentionFinderDragHandle, { $alignment: popupAlignment }, /*#__PURE__*/_react2.default.createElement(_MentionFinder.StyledMentionFinderDragHandleInner, { $alignment: popupAlignment, $dragOffset: dragOffset, $dragProgress: dragProgress, $isDragging: isDraggingHandle, onPointerDown: handleDragPointerDown, onPointerMove: handleDragPointerMove, onPointerUp: handleDragPointerUp, onPointerCancel: handleDragPointerCancel, onTouchStart: handleDragTouchStart, onTouchMove: handleDragTouchMove, onTouchEnd: handleDragTouchEnd, onTouchCancel: handleDragTouchCancel })); }, [enableDragHandle, popupAlignment, dragOffset, dragProgress, isDraggingHandle, handleDragPointerDown, handleDragPointerMove, handleDragPointerUp, handleDragPointerCancel, handleDragTouchStart, handleDragTouchMove, handleDragTouchEnd, handleDragTouchCancel]); (0, _react2.useEffect)(() => { var _popupRef$current2; if (!enableDragHandle || !shouldRenderPopup) { setOverlayContainer(null); return; } const container = (_popupRef$current2 = popupRef.current) === null || _popupRef$current2 === void 0 ? void 0 : _popupRef$current2.closest(overlayContainerSelector ?? '.dialog-inner, .chayns-threads, .page-provider, .tapp'); if (container) { setOverlayContainer(container); return; } if (typeof document !== 'undefined') { setOverlayContainer(document.body); } }, [enableDragHandle, overlayContainerSelector, shouldRenderPopup]); const overlayPortal = overlayContainer && enableDragHandle ? /*#__PURE__*/(0, _reactDom.createPortal)(/*#__PURE__*/_react2.default.createElement(_react.AnimatePresence, { initial: false }, shouldRenderPopup && /*#__PURE__*/_react2.default.createElement(_MentionFinder.StyledMentionFinderOverlay, { key: "mention-finder-overlay", animate: { opacity: 1 }, exit: { opacity: 0 }, initial: { opacity: 0 }, onClick: handleOverlayClick })), overlayContainer) : null; return /*#__PURE__*/_react2.default.createElement(_react2.default.Fragment, null, overlayPortal, /*#__PURE__*/_react2.default.createElement(_MentionFinder.StyledMentionFinder, { className: "beta-chayns-mention-finder", onBlur: handleContainerBlur }, /*#__PURE__*/_react2.default.createElement(_react.AnimatePresence, { initial: false }, shouldRenderPopup && /*#__PURE__*/_react2.default.createElement(_MentionFinder.StyledMotionMentionFinderPopup, { ref: popupRef, animate: { height: 'auto', opacity: 1, y: dragOffset }, className: "prevent-lose-focus", exit: { height: 0, opacity: 0 }, initial: { height: 0, opacity: 0 }, $popupAlignment: popupAlignment, $isDragging: enableDragHandle, transition: { duration: 0.15 }, tabIndex: 0 }, enableDragHandle && popupAlignment === _mentionFinder.MentionFinderPopupAlignment.Top && dragHandle, /*#__PURE__*/_react2.default.createElement(_MentionFinder.StyledMentionFinderItemList, { ref: listRef }, items), enableDragHandle && popupAlignment === _mentionFinder.MentionFinderPopupAlignment.Bottom && dragHandle)))); }; MentionFinder.displayName = 'MentionFinder'; var _default = exports.default = MentionFinder; //# sourceMappingURL=MentionFinder.js.map