@geneui/components
Version:
The Gene UI components library designed for BI tools
388 lines (383 loc) • 15.3 kB
JavaScript
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 === ' ' ? ' ' : 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 };