import _extends from "@babel/runtime/helpers/esm/extends"; import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; var _excluded = ["prefixCls", "className", "style", "prefix", "split", "notFoundContent", "value", "defaultValue", "children", "options", "open", "allowClear", "silent", "validateSearch", "filterOption", "onChange", "onKeyDown", "onKeyUp", "onPressEnter", "onSearch", "onSelect", "onFocus", "onBlur", "transitionName", "placement", "direction", "getPopupContainer", "dropdownClassName", "rows"], _excluded2 = ["suffix", "prefixCls", "defaultValue", "value", "allowClear", "onChange", "classNames", "className", "disabled", "onClear"]; import classNames from 'classnames'; import { BaseInput } from 'rc-input'; import TextArea from 'rc-textarea'; import toArray from "rc-util/es/Children/toArray"; import useMergedState from "rc-util/es/hooks/useMergedState"; import KeyCode from "rc-util/es/KeyCode"; import warning from "rc-util/es/warning"; import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import useEffectState from "./hooks/useEffectState"; import KeywordTrigger from "./KeywordTrigger"; import MentionsContext from "./MentionsContext"; import Option from "./Option"; import { filterOption as defaultFilterOption, validateSearch as defaultValidateSearch, getBeforeSelectionText, getLastMeasureIndex, replaceWithMeasure, setInputSelection } from "./util"; var InternalMentions = /*#__PURE__*/forwardRef(function (props, ref) { var prefixCls = props.prefixCls, className = props.className, style = props.style, _props$prefix = props.prefix, prefix = _props$prefix === void 0 ? '@' : _props$prefix, _props$split = props.split, split = _props$split === void 0 ? ' ' : _props$split, _props$notFoundConten = props.notFoundContent, notFoundContent = _props$notFoundConten === void 0 ? 'Not Found' : _props$notFoundConten, value = props.value, defaultValue = props.defaultValue, children = props.children, options = props.options, open = props.open, allowClear = props.allowClear, silent = props.silent, _props$validateSearch = props.validateSearch, validateSearch = _props$validateSearch === void 0 ? defaultValidateSearch : _props$validateSearch, _props$filterOption = props.filterOption, filterOption = _props$filterOption === void 0 ? defaultFilterOption : _props$filterOption, onChange = props.onChange, onKeyDown = props.onKeyDown, onKeyUp = props.onKeyUp, onPressEnter = props.onPressEnter, onSearch = props.onSearch, onSelect = props.onSelect, onFocus = props.onFocus, onBlur = props.onBlur, transitionName = props.transitionName, placement = props.placement, direction = props.direction, getPopupContainer = props.getPopupContainer, dropdownClassName = props.dropdownClassName, _props$rows = props.rows, rows = _props$rows === void 0 ? 1 : _props$rows, restProps = _objectWithoutProperties(props, _excluded); var mergedPrefix = useMemo(function () { return Array.isArray(prefix) ? prefix : [prefix]; }, [prefix]); // =============================== Refs =============================== var containerRef = useRef(null); var textareaRef = useRef(null); var measureRef = useRef(null); var getTextArea = function getTextArea() { var _textareaRef$current; return (_textareaRef$current = textareaRef.current) === null || _textareaRef$current === void 0 || (_textareaRef$current = _textareaRef$current.resizableTextArea) === null || _textareaRef$current === void 0 ? void 0 : _textareaRef$current.textArea; }; React.useImperativeHandle(ref, function () { var _textareaRef$current4; return { focus: function focus() { var _textareaRef$current2; return (_textareaRef$current2 = textareaRef.current) === null || _textareaRef$current2 === void 0 ? void 0 : _textareaRef$current2.focus(); }, blur: function blur() { var _textareaRef$current3; return (_textareaRef$current3 = textareaRef.current) === null || _textareaRef$current3 === void 0 ? void 0 : _textareaRef$current3.blur(); }, textarea: (_textareaRef$current4 = textareaRef.current) === null || _textareaRef$current4 === void 0 || (_textareaRef$current4 = _textareaRef$current4.resizableTextArea) === null || _textareaRef$current4 === void 0 ? void 0 : _textareaRef$current4.textArea, nativeElement: containerRef.current }; }); // ============================== State =============================== var _useState = useState(false), _useState2 = _slicedToArray(_useState, 2), measuring = _useState2[0], setMeasuring = _useState2[1]; var _useState3 = useState(''), _useState4 = _slicedToArray(_useState3, 2), measureText = _useState4[0], setMeasureText = _useState4[1]; var _useState5 = useState(''), _useState6 = _slicedToArray(_useState5, 2), measurePrefix = _useState6[0], setMeasurePrefix = _useState6[1]; var _useState7 = useState(0), _useState8 = _slicedToArray(_useState7, 2), measureLocation = _useState8[0], setMeasureLocation = _useState8[1]; var _useState9 = useState(0), _useState10 = _slicedToArray(_useState9, 2), activeIndex = _useState10[0], setActiveIndex = _useState10[1]; var _useState11 = useState(false), _useState12 = _slicedToArray(_useState11, 2), isFocus = _useState12[0], setIsFocus = _useState12[1]; // ============================== Value =============================== var _useMergedState = useMergedState('', { defaultValue: defaultValue, value: value }), _useMergedState2 = _slicedToArray(_useMergedState, 2), mergedValue = _useMergedState2[0], setMergedValue = _useMergedState2[1]; // =============================== Open =============================== useEffect(function () { // Sync measure div top with textarea for rc-trigger usage if (measuring && measureRef.current) { measureRef.current.scrollTop = getTextArea().scrollTop; } }, [measuring]); var _React$useMemo = React.useMemo(function () { if (open) { if (process.env.NODE_ENV !== 'production') { warning(false, '`open` of Mentions is only used for debug usage. Do not use in you production.'); } for (var i = 0; i < mergedPrefix.length; i += 1) { var curPrefix = mergedPrefix[i]; var index = mergedValue.lastIndexOf(curPrefix); if (index >= 0) { return [true, '', curPrefix, index]; } } } return [measuring, measureText, measurePrefix, measureLocation]; }, [open, measuring, mergedPrefix, mergedValue, measureText, measurePrefix, measureLocation]), _React$useMemo2 = _slicedToArray(_React$useMemo, 4), mergedMeasuring = _React$useMemo2[0], mergedMeasureText = _React$useMemo2[1], mergedMeasurePrefix = _React$useMemo2[2], mergedMeasureLocation = _React$useMemo2[3]; // ============================== Option ============================== var getOptions = React.useCallback(function (targetMeasureText) { var list; if (options && options.length > 0) { list = options.map(function (item) { var _item$key; return _objectSpread(_objectSpread({}, item), {}, { key: (_item$key = item === null || item === void 0 ? void 0 : item.key) !== null && _item$key !== void 0 ? _item$key : item.value }); }); } else { list = toArray(children).map(function (_ref) { var optionProps = _ref.props, key = _ref.key; return _objectSpread(_objectSpread({}, optionProps), {}, { label: optionProps.children, key: key || optionProps.value }); }); } return list.filter(function (option) { /** Return all result if `filterOption` is false. */ if (filterOption === false) { return true; } return filterOption(targetMeasureText, option); }); }, [children, options, filterOption]); var mergedOptions = React.useMemo(function () { return getOptions(mergedMeasureText); }, [getOptions, mergedMeasureText]); // ============================= Measure ============================== // Mark that we will reset input selection to target position when user select option var onSelectionEffect = useEffectState(); var startMeasure = function startMeasure(nextMeasureText, nextMeasurePrefix, nextMeasureLocation) { setMeasuring(true); setMeasureText(nextMeasureText); setMeasurePrefix(nextMeasurePrefix); setMeasureLocation(nextMeasureLocation); setActiveIndex(0); }; var stopMeasure = function stopMeasure(callback) { setMeasuring(false); setMeasureLocation(0); setMeasureText(''); onSelectionEffect(callback); }; // ============================== Change ============================== var triggerChange = function triggerChange(nextValue) { setMergedValue(nextValue); onChange === null || onChange === void 0 || onChange(nextValue); }; var onInternalChange = function onInternalChange(_ref2) { var nextValue = _ref2.target.value; triggerChange(nextValue); }; var selectOption = function selectOption(option) { var _getTextArea; var _option$value = option.value, mentionValue = _option$value === void 0 ? '' : _option$value; var _replaceWithMeasure = replaceWithMeasure(mergedValue, { measureLocation: mergedMeasureLocation, targetText: mentionValue, prefix: mergedMeasurePrefix, selectionStart: (_getTextArea = getTextArea()) === null || _getTextArea === void 0 ? void 0 : _getTextArea.selectionStart, split: split }), text = _replaceWithMeasure.text, selectionLocation = _replaceWithMeasure.selectionLocation; triggerChange(text); stopMeasure(function () { // We need restore the selection position setInputSelection(getTextArea(), selectionLocation); }); onSelect === null || onSelect === void 0 || onSelect(option, mergedMeasurePrefix); }; // ============================= KeyEvent ============================= // Check if hit the measure keyword var onInternalKeyDown = function onInternalKeyDown(event) { var which = event.which; onKeyDown === null || onKeyDown === void 0 || onKeyDown(event); // Skip if not measuring if (!mergedMeasuring) { return; } if (which === KeyCode.UP || which === KeyCode.DOWN) { // Control arrow function var optionLen = mergedOptions.length; var offset = which === KeyCode.UP ? -1 : 1; var newActiveIndex = (activeIndex + offset + optionLen) % optionLen; setActiveIndex(newActiveIndex); event.preventDefault(); } else if (which === KeyCode.ESC) { stopMeasure(); } else if (which === KeyCode.ENTER) { // Measure hit event.preventDefault(); // loading skip if (silent) { return; } if (!mergedOptions.length) { stopMeasure(); return; } var _option = mergedOptions[activeIndex]; selectOption(_option); } }; /** * When to start measure: * 1. When user press `prefix` * 2. When measureText !== prevMeasureText * - If measure hit * - If measuring * * When to stop measure: * 1. Selection is out of range * 2. Contains `space` * 3. ESC or select one */ var onInternalKeyUp = function onInternalKeyUp(event) { var key = event.key, which = event.which; var target = event.target; var selectionStartText = getBeforeSelectionText(target); var _getLastMeasureIndex = getLastMeasureIndex(selectionStartText, mergedPrefix), measureIndex = _getLastMeasureIndex.location, nextMeasurePrefix = _getLastMeasureIndex.prefix; // If the client implements an onKeyUp handler, call it onKeyUp === null || onKeyUp === void 0 || onKeyUp(event); // Skip if match the white key list if ([KeyCode.ESC, KeyCode.UP, KeyCode.DOWN, KeyCode.ENTER].indexOf(which) !== -1) { return; } if (measureIndex !== -1) { var nextMeasureText = selectionStartText.slice(measureIndex + nextMeasurePrefix.length); var validateMeasure = validateSearch(nextMeasureText, split); var matchOption = !!getOptions(nextMeasureText).length; if (validateMeasure) { if (key === nextMeasurePrefix || key === 'Shift' || mergedMeasuring || nextMeasureText !== mergedMeasureText && matchOption) { startMeasure(nextMeasureText, nextMeasurePrefix, measureIndex); } } else if (mergedMeasuring) { // Stop if measureText is invalidate stopMeasure(); } /** * We will trigger `onSearch` to developer since they may use for async update. * If met `space` means user finished searching. */ if (onSearch && validateMeasure) { onSearch(nextMeasureText, nextMeasurePrefix); } } else if (mergedMeasuring) { stopMeasure(); } }; var onInternalPressEnter = function onInternalPressEnter(event) { if (!mergedMeasuring && onPressEnter) { onPressEnter(event); } }; // ============================ Focus Blur ============================ var focusRef = useRef(); var onInternalFocus = function onInternalFocus(event) { window.clearTimeout(focusRef.current); if (!isFocus && event && onFocus) { onFocus(event); } setIsFocus(true); }; var onInternalBlur = function onInternalBlur(event) { focusRef.current = window.setTimeout(function () { setIsFocus(false); stopMeasure(); onBlur === null || onBlur === void 0 || onBlur(event); }, 0); }; var onDropdownFocus = function onDropdownFocus() { onInternalFocus(); }; var onDropdownBlur = function onDropdownBlur() { onInternalBlur(); }; // ============================== Render ============================== return /*#__PURE__*/React.createElement("div", { className: classNames(prefixCls, className), style: style, ref: containerRef }, /*#__PURE__*/React.createElement(TextArea, _extends({ ref: textareaRef, value: mergedValue }, restProps, { rows: rows, onChange: onInternalChange, onKeyDown: onInternalKeyDown, onKeyUp: onInternalKeyUp, onPressEnter: onInternalPressEnter, onFocus: onInternalFocus, onBlur: onInternalBlur })), mergedMeasuring && /*#__PURE__*/React.createElement("div", { ref: measureRef, className: "".concat(prefixCls, "-measure") }, mergedValue.slice(0, mergedMeasureLocation), /*#__PURE__*/React.createElement(MentionsContext.Provider, { value: { notFoundContent: notFoundContent, activeIndex: activeIndex, setActiveIndex: setActiveIndex, selectOption: selectOption, onFocus: onDropdownFocus, onBlur: onDropdownBlur } }, /*#__PURE__*/React.createElement(KeywordTrigger, { prefixCls: prefixCls, transitionName: transitionName, placement: placement, direction: direction, options: mergedOptions, visible: true, getPopupContainer: getPopupContainer, dropdownClassName: dropdownClassName }, /*#__PURE__*/React.createElement("span", null, mergedMeasurePrefix))), mergedValue.slice(mergedMeasureLocation + mergedMeasurePrefix.length))); }); var Mentions = /*#__PURE__*/forwardRef(function (_ref3, ref) { var suffix = _ref3.suffix, _ref3$prefixCls = _ref3.prefixCls, prefixCls = _ref3$prefixCls === void 0 ? 'rc-mentions' : _ref3$prefixCls, defaultValue = _ref3.defaultValue, customValue = _ref3.value, allowClear = _ref3.allowClear, onChange = _ref3.onChange, classes = _ref3.classNames, className = _ref3.className, disabled = _ref3.disabled, onClear = _ref3.onClear, rest = _objectWithoutProperties(_ref3, _excluded2); // =============================== Ref ================================ var holderRef = useRef(null); var mentionRef = useRef(null); useImperativeHandle(ref, function () { var _holderRef$current, _mentionRef$current; return _objectSpread(_objectSpread({}, mentionRef.current), {}, { nativeElement: ((_holderRef$current = holderRef.current) === null || _holderRef$current === void 0 ? void 0 : _holderRef$current.nativeElement) || ((_mentionRef$current = mentionRef.current) === null || _mentionRef$current === void 0 ? void 0 : _mentionRef$current.nativeElement) }); }); // ============================== Value =============================== var _useMergedState3 = useMergedState('', { defaultValue: defaultValue, value: customValue }), _useMergedState4 = _slicedToArray(_useMergedState3, 2), mergedValue = _useMergedState4[0], setMergedValue = _useMergedState4[1]; // ============================== Change ============================== var triggerChange = function triggerChange(currentValue) { setMergedValue(currentValue); onChange === null || onChange === void 0 || onChange(currentValue); }; // ============================== Reset =============================== var handleReset = function handleReset() { triggerChange(''); }; return /*#__PURE__*/React.createElement(BaseInput, { suffix: suffix, prefixCls: prefixCls, value: mergedValue, allowClear: allowClear, handleReset: handleReset, className: className, classNames: classes, disabled: disabled, ref: holderRef, onClear: onClear }, /*#__PURE__*/React.createElement(InternalMentions, _extends({ className: classes === null || classes === void 0 ? void 0 : classes.mentions, prefixCls: prefixCls, ref: mentionRef, onChange: triggerChange, disabled: disabled }, rest))); }); Mentions.Option = Option; export default Mentions;