@mtg-rio/mui-mentions
Version:
@mention people in a MUI TextField
122 lines • 7.4 kB
JavaScript
"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