UNPKG

@geneui/components

Version:

The Gene UI components library designed for BI tools

388 lines (383 loc) 15.3 kB
import React__default, { useMemo, useState, useRef, useCallback, useEffect } from 'react'; import { c as classnames } from '../index-031ff73c.js'; import { s as stopEvent } from '../index-a0e4e333.js'; import useKeyDown from '../hooks/useKeyDown.js'; import '../configs-00612ce0.js'; import useClickOutside from '../hooks/useClickOutside.js'; import { k as keyDownKeys } from '../config-1053d64d.js'; import CustomScrollbar from '../Scrollbar/index.js'; import { s as styleInject } from '../style-inject.es-746bb8ed.js'; import { c as callAfterDelay } from '../callAfterDelay-7272faca.js'; import 'prop-types'; import '../dateValidation-67caec66.js'; import '../_commonjsHelpers-24198af3.js'; import 'react-dom'; import '../_rollupPluginBabelHelpers-e8fb2e5c.js'; const getCaretPos = (e, textarea, index, key) => { textarea.insertAdjacentHTML('afterend', "<div id='dummy'></div>"); const dummy = document.getElementById('dummy'); const style = getComputedStyle(textarea); ['fontFamily', 'fontSize', 'fontWeight', 'lineHeight', 'wordWrap', 'whiteSpace', 'borderLeftWidth', 'paddingRight', 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth'].forEach(key => { dummy.style[key] = style[key]; }); dummy.style.overflow = 'auto'; dummy.style.paddingLeft = '6px'; dummy.style.width = "".concat(textarea.offsetWidth, "px"); dummy.style.height = "".concat(textarea.offsetHeight - 20, "px"); dummy.style.position = 'absolute'; dummy.style.left = "".concat(textarea.offsetLeft, "px"); dummy.style.top = "".concat(textarea.offsetTop, "px"); const val = textarea.value.replaceAll(/\n/g, '<br/>'); let newVal = ''; let ind = 0; val.split('').forEach(i => { if (ind !== null && i === key) { ind++; } if (ind === index) { newVal += "<span id='lastId'>".concat(i, "</span>"); ind = null; } else { newVal += i === ' ' ? '&nbsp;' : i; } }); dummy.innerHTML = newVal; dummy.scrollTo(0, textarea.scrollTop); const lastId = document.getElementById('lastId'); const returnData = { top: lastId.getBoundingClientRect().top, left: lastId.getBoundingClientRect().left }; dummy.remove(); return returnData; }; const getCursorPos = input => { if ('selectionStart' in input && document.activeElement === input) { return { start: input.selectionStart, end: input.selectionEnd }; } if (input.createTextRange) { const sel = document.selection.createRange(); if (sel.parentElement() === input) { const rng = input.createTextRange(); rng.moveToBookmark(sel.getBookmark()); let len; for (len = 0; rng.compareEndPoints('EndToStart', rng) > 0; rng.moveEnd('character', -1)) { len++; } rng.setEndPoint('StartToStart', input.createTextRange()); let pos; for (pos = { start: 0, end: len }; rng.compareEndPoints('EndToStart', rng) > 0; rng.moveEnd('character', -1)) { pos.start++; pos.end++; } return pos; } } return -1; }; var css_248z = "[data-gene-ui-version=\"2.16.5\"] .suggestion-list{background:var(--background);border:1px solid #0000000d;border-radius:1rem;box-shadow:0 .2rem .4rem 0 #0000000d,0 0 0 1px #ffffff14;overflow:hidden;position:fixed;width:200px;z-index:400}[data-gene-ui-version=\"2.16.5\"] .suggestion-list .suggestion-rows{font:600 1.4rem/1.8rem var(--font-family);width:100%}[data-gene-ui-version=\"2.16.5\"] .suggestion-list .suggestion-rows ul{width:100%}[data-gene-ui-version=\"2.16.5\"] .suggestion-list .suggestion-rows ul li{align-items:center;cursor:pointer;display:flex;height:4rem;padding-left:16px;transition:background .4s,color .4s;width:100%}[data-gene-ui-version=\"2.16.5\"] .suggestion-list .suggestion-rows ul li.hover{background:rgba(var(--background-sc-rgb),.05)}"; styleInject(css_248z); const ROW_WIDTH = 300; const ROW_HEIGHT = 40; const ROW_COUNT = 5; function SuggestionList(_ref) { let { onChange, onHover, elemRef, data = [] } = _ref; const keys = useMemo(() => data.map(i => i.key), [data]); const [suggestionData, setSuggestionData] = useState(null); const [hoveredState, setHoveredState] = useState(0); const hoveredRowRef = useRef(null); const scrollRef = useRef(null); const realText = useRef(); const dataToShow = useMemo(() => { var _suggestionData$data; if (!suggestionData) return null; const returnedData = suggestionData.search ? (_suggestionData$data = suggestionData.data) === null || _suggestionData$data === void 0 ? void 0 : _suggestionData$data.filter(i => i.label.toUpperCase().includes(suggestionData.search.toUpperCase())) : suggestionData.data; return returnedData && returnedData.length ? returnedData : null; }, [suggestionData]); if (elemRef.current) { elemRef.current.isSuggestionListOpen = !!dataToShow; } const getKeyIndex = useCallback(inputValue => { const existingKey = { index: -1, key: null }; keys.forEach(i => { if (inputValue.lastIndexOf(i) > existingKey.index) { existingKey.index = inputValue.lastIndexOf(i); existingKey.key = i; } }); return existingKey.index > -1 ? existingKey : null; }, [keys]); const arrowDownHandler = useCallback(e => { stopEvent(e, true); if (hoveredState < suggestionData.data.length - 1) { setHoveredState(prev => prev + 1); hoverHandler(e, hoveredState + 1); } if (hoveredState === dataToShow.length - 1) return; const scrollElem = scrollRef.current.container.children[0]; if (hoveredRowRef.current && scrollElem) { if (hoveredRowRef.current.getBoundingClientRect().top - scrollElem.getBoundingClientRect().top > ROW_HEIGHT * 3) { scrollElem.scrollTo(0, (hoveredState - 3) * ROW_HEIGHT); } } }, [dataToShow, hoveredRowRef, scrollRef, hoveredState, suggestionData]); const arrowUpHandler = useCallback(e => { stopEvent(e, true); if (hoveredState > 0) { setHoveredState(prev => prev - 1); hoverHandler(e, hoveredState - 1); } if (hoveredState === dataToShow.length - 1) return; const scrollElem = scrollRef.current.container.children[0]; if (hoveredRowRef.current && scrollElem) { if (hoveredRowRef.current.getBoundingClientRect().top - scrollElem.getBoundingClientRect().top < ROW_HEIGHT * 2) { scrollElem.scrollTo(0, (hoveredState - 1) * ROW_HEIGHT); } } }, [dataToShow, hoveredRowRef, scrollRef, hoveredState]); const onEnterHandler = useCallback(e => { resetText(); if (dataToShow && dataToShow.length) { if (e) { stopEvent(e, true); } else { elemRef.current.focus(); } const to = getCursorPos(elemRef.current).end; callAfterDelay(() => { elemRef.current.selectionEnd = to + dataToShow[hoveredState].value.toString().length; }); onChange({ from: to - (suggestionData.search ? suggestionData.search.length : 0) - 1, data: dataToShow[hoveredState], key: suggestionData.key, to }); } setSuggestionData(null); }, [suggestionData, setSuggestionData, hoveredState, onChange, elemRef, dataToShow]); const hoverHandler = useCallback((e, hoverIndex) => { if (dataToShow && dataToShow.length) { if (!realText.current) { realText.current = elemRef.current.value; } if (e) { stopEvent(e, true); } else { elemRef.current.focus(); } const to = getCursorPos(elemRef.current).end; callAfterDelay(() => { elemRef.current.selectionEnd = to; }); onHover({ from: to - (suggestionData.search ? suggestionData.search.length : 0), data: dataToShow[hoverIndex], key: suggestionData.key, to }); } }, [suggestionData, onHover, elemRef, realText, dataToShow]); const onBackSpaseHandler = useCallback(e => { resetText(); if (keys.includes(e.target.value.slice(0, getCursorPos(elemRef.current).start).slice(-1))) { setSuggestionData(null); } else { callAfterDelay(() => { const inputValue = e.target.value; const { start } = getCursorPos(elemRef.current); if (!inputValue) { setSuggestionData(null); } else if (suggestionData && suggestionData.data.length) { const fromStartToKey = inputValue.slice(0, start); setSuggestionData({ ...suggestionData, search: fromStartToKey.slice(fromStartToKey.lastIndexOf(suggestionData.key) + 1) }); } else { const fromStartToKey = inputValue.slice(0, start); const lastKeyObj = getKeyIndex(fromStartToKey); if (lastKeyObj) { const stringAfterKey = inputValue.slice(lastKeyObj.index + 1, fromStartToKey.length); if (inputValue.length === 1 || stringAfterKey.search(keyDownKeys.space) === -1 && (lastKeyObj.index === 0 || inputValue[lastKeyObj.index - 1] === keyDownKeys.space || RegExp(/\n/g, 'u').test(inputValue[lastKeyObj.index - 1]))) { const list = data.find(i => i.key === lastKeyObj.key); if (list) { const caretPos = getCaretPos(e, elemRef.current, fromStartToKey.split(lastKeyObj.key).length - 1, lastKeyObj.key); setSuggestionData({ ...list, search: stringAfterKey, ...caretPos }); } } } } }); } }, [setSuggestionData, suggestionData, data, keys, elemRef, getKeyIndex]); const onKeyHandler = useCallback(e => { callAfterDelay(() => { const inputValue = e.target.value; const { key } = e; const fromStartToKey = inputValue.slice(0, getCursorPos(elemRef.current).start); if ((inputValue.length === 1 || fromStartToKey.at(-2) === keyDownKeys.space || RegExp(/\n/g, 'u').test(fromStartToKey.at(-2))) && !suggestionData) { const list = data.find(i => i.key === key); if (list) { const caretPos = getCaretPos(e, elemRef.current, fromStartToKey.split(key).length - 1, key); setSuggestionData({ ...list, ...caretPos }); } } else { setSuggestionData(null); } }); }, [elemRef, suggestionData, setSuggestionData, data]); const resetText = () => { if (realText.current) { const cursorPosition = elemRef.current.selectionStart; elemRef.current.value = realText.current; realText.current = undefined; elemRef.current.selectionStart = cursorPosition; elemRef.current.selectionEnd = cursorPosition; } }; const onRestKeysPressHandler = useCallback(e => { // Symbol is pressed if (e.key.length === 1) { resetText(); } callAfterDelay(() => { const inputValue = e.target.value; const fromStartToKey = inputValue.slice(0, getCursorPos(elemRef.current).start); const lastKeyObj = getKeyIndex(fromStartToKey); if (suggestionData && lastKeyObj) { setSuggestionData({ ...suggestionData, search: fromStartToKey.slice(fromStartToKey.lastIndexOf(suggestionData.key) + 1) }); } else if (lastKeyObj) { const stringAfterKey = inputValue.slice(lastKeyObj.index + 1, fromStartToKey.length); if (stringAfterKey.search(keyDownKeys.space) === -1 && inputValue[lastKeyObj.index - 1] === keyDownKeys.space && !RegExp(/\n/g, 'u').test(fromStartToKey.at(-1))) { const list = data.find(i => i.key === lastKeyObj.key); if (list) { const caretPos = getCaretPos(e, elemRef.current, fromStartToKey.split(lastKeyObj.key).length - 1, lastKeyObj.key); setSuggestionData({ ...list, search: stringAfterKey, ...caretPos }); } } } else { setSuggestionData(null); } }); }, [elemRef, getKeyIndex, suggestionData]); const handleKeyPress = useCallback(e => { const { key } = e; switch (key) { case keyDownKeys.enter: onEnterHandler(e); break; case keyDownKeys.tab: case keyDownKeys.escape: case keyDownKeys.space: case keyDownKeys.arrowLeft: case keyDownKeys.arrowRight: setSuggestionData(null); resetText(); break; case keyDownKeys.arrowUp: suggestionData && arrowUpHandler(e); break; case keyDownKeys.arrowDown: suggestionData && arrowDownHandler(e); break; case keyDownKeys.backspace: onBackSpaseHandler(e); break; case keys[keys.indexOf(key)]: onKeyHandler(e); resetText(); break; default: onRestKeysPressHandler(e); } }, [onEnterHandler, setSuggestionData, suggestionData, onBackSpaseHandler, onKeyHandler, onRestKeysPressHandler]); const onMouseEnter = (e, index) => { setHoveredState(index); hoverHandler(e, index); }; const height = useMemo(() => dataToShow ? dataToShow.length >= ROW_COUNT ? ROW_COUNT * ROW_HEIGHT : dataToShow.length * ROW_HEIGHT : 0, [dataToShow]); const handleOutsideClick = useClickOutside(() => { setSuggestionData(null); resetText(); }); useEffect(() => { var _elemRef$current; if (!elemRef) return; const handlePasteAnywhere = () => setSuggestionData(null); (_elemRef$current = elemRef.current) === null || _elemRef$current === void 0 ? void 0 : _elemRef$current.addEventListener('paste', handlePasteAnywhere); return () => { var _elemRef$current2; (_elemRef$current2 = elemRef.current) === null || _elemRef$current2 === void 0 ? void 0 : _elemRef$current2.removeEventListener('paste', handlePasteAnywhere); }; }, [elemRef]); useEffect(() => { setHoveredState(0); hoverHandler(undefined, 0); }, [suggestionData]); useKeyDown(handleKeyPress, [handleKeyPress], elemRef, []); const onItemClick = event => { event.stopPropagation(); onEnterHandler(); }; return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, dataToShow && /*#__PURE__*/React__default.createElement("div", { ref: handleOutsideClick, className: "suggestion-list", style: { top: suggestionData.top - (dataToShow.length >= ROW_COUNT ? ROW_HEIGHT * ROW_COUNT : dataToShow.length * ROW_HEIGHT), left: suggestionData.left, height, width: ROW_WIDTH } }, /*#__PURE__*/React__default.createElement("div", { className: "suggestion-rows" }, /*#__PURE__*/React__default.createElement(CustomScrollbar, { ref: scrollRef, autoHeight: true, autoHeightMax: ROW_COUNT * ROW_HEIGHT, size: "small" }, /*#__PURE__*/React__default.createElement("ul", null, dataToShow.map((i, index) => /*#__PURE__*/React__default.createElement("li", { className: classnames({ hover: index === hoveredState }), ref: index === hoveredState ? hoveredRowRef : null, onMouseEnter: e => onMouseEnter(e, index), onClick: onItemClick, key: i.label }, /*#__PURE__*/React__default.createElement("span", null, i.label)))))))); } export { SuggestionList as default };