UNPKG

@mtg-rio/mui-mentions

Version:

@mention people in a MUI TextField

122 lines 7.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const material_1 = require("@mui/material"); const react_1 = tslib_1.__importStar(require("react")); const Highlighter_1 = tslib_1.__importDefault(require("./Highlighter")); const SuggestionsOverlay_1 = tslib_1.__importDefault(require("./SuggestionsOverlay")); const types_1 = require("./types"); const utils_1 = require("./utils/utils"); function MentionsTextField(props) { const [stateValue, setStateValue] = (0, react_1.useState)(props.defaultValue || ''); const [inputRef, setInputRef] = (0, react_1.useState)(null); const highlighterRef = (0, react_1.useRef)(null); const cursorRef = (0, react_1.useRef)(null); const suggestionsMouseDown = (0, react_1.useRef)(false); const [selectionStart, setSelectionStart] = (0, react_1.useState)(null); const [selectionEnd, setSelectionEnd] = (0, react_1.useState)(null); (0, react_1.useEffect)(() => { const input = inputRef; const onScroll = () => { if (!highlighterRef.current || !input) { return; } highlighterRef.current.scrollLeft = input.scrollLeft; highlighterRef.current.scrollTop = input.scrollTop; }; input === null || input === void 0 ? void 0 : input.addEventListener('scroll', onScroll); return () => input === null || input === void 0 ? void 0 : input.removeEventListener('scroll', onScroll); }, [inputRef, highlighterRef]); (0, react_1.useEffect)(() => { const input = inputRef; if (!input || (input.selectionStart === selectionStart && input.selectionEnd === selectionEnd)) { return; } input.setSelectionRange(selectionStart, selectionEnd); }, [selectionStart, selectionEnd, inputRef]); const { value, defaultValue: _defaultValue, dataSources, highlightColor } = props, others = tslib_1.__rest(props, ["value", "defaultValue", "dataSources", "highlightColor"]); const finalValue = value !== undefined ? value : stateValue; const handleBlur = () => { if (!suggestionsMouseDown.current) { setSelectionStart(null); setSelectionEnd(null); } suggestionsMouseDown.current = false; }; const handleSuggestionsMouseDown = () => { suggestionsMouseDown.current = true; }; const addMention = (suggestion, { childIndex, querySequenceStart, querySequenceEnd, plainTextValue }) => { const dataSource = dataSources[childIndex]; const { markup, displayTransform, appendSpaceOnAdd, onAdd } = dataSource; const start = (0, utils_1.mapPlainTextIndex)(finalValue, dataSources, querySequenceStart, 'START'); if (!(0, utils_1.isNumber)(start)) { return; } const end = start + querySequenceEnd - querySequenceStart; let insert = (0, utils_1.makeMentionsMarkup)(markup || types_1.DefaultMarkupTemplate, suggestion.id, suggestion.display); let displayValue = (displayTransform || types_1.DefaultDisplayTransform)(suggestion.id, suggestion.display); if (appendSpaceOnAdd) { insert += ' '; displayValue += ' '; } const newCaretPosition = querySequenceStart + displayValue.length; setSelectionStart(newCaretPosition); setSelectionEnd(newCaretPosition); // Propagate change const newValue = (0, utils_1.spliceString)(finalValue, start, end, insert); const mentions = (0, utils_1.getMentions)(newValue, dataSources); const newPlainTextValue = (0, utils_1.spliceString)(plainTextValue, querySequenceStart, querySequenceEnd, displayValue); const onChange = props.onChange || setStateValue; onChange(newValue, newPlainTextValue, mentions); onAdd === null || onAdd === void 0 ? void 0 : onAdd(suggestion, start, end); }; const handleChange = (ev) => { let newPlainTextValue = ev.target.value; let selectionStartBefore = selectionStart; if (!(0, utils_1.isNumber)(selectionStartBefore)) { selectionStartBefore = ev.target.selectionStart; } let selectionEndBefore = selectionEnd; if (!(0, utils_1.isNumber)(selectionEndBefore)) { selectionEndBefore = ev.target.selectionEnd; } // Derive the new value to set by applying the local change in the textarea's plain text const newValue = (0, utils_1.applyChangeToValue)(finalValue, newPlainTextValue, selectionStartBefore, selectionEndBefore, ev.target.selectionEnd || 0, dataSources, props.multiline); // In case a mention is deleted, also adjust the new plain text value newPlainTextValue = (0, utils_1.getPlainText)(newValue, dataSources); // Save current selection after change to be able to restore caret position after rerendering let selectionStartAfter = ev.target.selectionStart; let selectionEndAfter = ev.target.selectionEnd; // Adjust selection range in case a mention will be deleted by the characters outside of the // selection range that are automatically deleted const startOfMention = (0, utils_1.findStartOfMentionInPlainText)(finalValue, dataSources, ev.target.selectionStart || 0); if (startOfMention !== undefined && selectionEndAfter !== null && selectionEndAfter > startOfMention) { // only if a deletion has taken place const data = ev.nativeEvent.data; selectionStartAfter = startOfMention + (data ? data.length : 0); selectionEndAfter = selectionStartAfter; } setSelectionStart(selectionStartAfter); setSelectionEnd(selectionEndAfter); const mentions = (0, utils_1.getMentions)(newValue, dataSources); // Propagate change const onChange = props.onChange || setStateValue; onChange(newValue, newPlainTextValue, mentions); }; const handleSelect = (ev) => { var _a; setSelectionStart(ev.target.selectionStart); setSelectionEnd(ev.target.selectionEnd); (_a = props.onSelect) === null || _a === void 0 ? void 0 : _a.call(props, ev); }; const inputProps = Object.assign(Object.assign({}, others), { value: (0, utils_1.getPlainText)(finalValue, dataSources, props.multiline), onChange: handleChange, onSelect: handleSelect, onBlur: handleBlur, inputProps: { sx: { overscrollBehavior: 'none' }, } }); return (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement(Highlighter_1.default, { highlighterRef: highlighterRef, cursorRef: cursorRef, selectionStart: selectionStart, selectionEnd: selectionEnd, value: finalValue, dataSources: dataSources, inputRef: inputRef, multiline: inputProps.multiline, color: highlightColor || props.color }), react_1.default.createElement(material_1.TextField, Object.assign({ inputRef: (ref) => setInputRef(ref) }, inputProps)), react_1.default.createElement(SuggestionsOverlay_1.default, { value: finalValue, dataSources: dataSources, selectionStart: selectionStart, selectionEnd: selectionEnd, cursorRef: cursorRef, loading: false, onSelect: addMention, onMouseDown: handleSuggestionsMouseDown }))); } exports.default = MentionsTextField; //# sourceMappingURL=MentionsTextField.js.map