UNPKG

react-mde

Version:
306 lines (305 loc) 16.2 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TextArea = void 0; var React = require("react"); var ClassNames_1 = require("../util/ClassNames"); var TextAreaCaretPosition_1 = require("../util/TextAreaCaretPosition"); var InsertTextAtPosition_1 = require("../util/InsertTextAtPosition"); var Math_1 = require("../util/Math"); var SuggestionsDropdown_1 = require("./SuggestionsDropdown"); var TextArea = /** @class */ (function (_super) { __extends(TextArea, _super); function TextArea(props) { var _this = _super.call(this, props) || this; _this.currentLoadSuggestionsPromise = Promise.resolve(undefined); /** * suggestionsPromiseIndex exists as a means to cancel what happens when the suggestions promise finishes loading. * * When the user is searching for suggestions, there is a promise that, when resolved, causes a re-render. * However, in case there is another promise to be resolved after the current one, it does not make sense to re-render * only to re-render again after the next one is complete. * * When there is a promise loading and the user cancels the suggestion, you don't want the status to go back to "active" * when the promise resolves. * * suggestionsPromiseIndex increments every time the mentions query */ _this.suggestionsPromiseIndex = 0; _this.getTextArea = function () { return _this.props.refObject.current; }; _this.handleOnChange = function (event) { var onChange = _this.props.onChange; onChange(event.target.value); }; _this.handleBlur = function () { var mention = _this.state.mention; if (mention) { _this.setState({ mention: { status: "inactive", suggestions: [] } }); } }; _this.startLoadingSuggestions = function (text) { var promiseIndex = ++_this.suggestionsPromiseIndex; var loadSuggestions = _this.props.loadSuggestions; _this.currentLoadSuggestionsPromise = _this.currentLoadSuggestionsPromise .then(function () { return loadSuggestions(text, _this.state.mention.triggeredBy); }) .then(function (suggestions) { if (_this.state.mention.status === "inactive") { // This means this promise resolved too late when the status has already been set to inactice return; } else if (_this.suggestionsPromiseIndex === promiseIndex) { if (!suggestions || !suggestions.length) { _this.setState({ mention: { status: "inactive", suggestions: [] } }); } else { _this.setState({ mention: __assign(__assign({}, _this.state.mention), { status: "active", suggestions: suggestions, focusIndex: 0 }) }); } _this.suggestionsPromiseIndex = 0; } return Promise.resolve(); }); }; _this.loadEmptySuggestion = function (target, key) { var caret = TextAreaCaretPosition_1.getCaretCoordinates(target, key); _this.startLoadingSuggestions(""); _this.setState({ mention: { status: "loading", startPosition: target.selectionStart + 1, caret: caret, suggestions: [], triggeredBy: key } }); }; _this.handleSuggestionSelected = function (index) { var mention = _this.state.mention; _this.getTextArea().selectionStart = mention.startPosition - 1; var textForInsert = _this.props.value.substr(_this.getTextArea().selectionStart, _this.getTextArea().selectionEnd - _this.getTextArea().selectionStart); InsertTextAtPosition_1.insertText(_this.getTextArea(), mention.suggestions[index].value + " "); _this.setState({ mention: { status: "inactive", suggestions: [] } }); }; _this.handleKeyDown = function (event) { if (_this.props.onPossibleKeyCommand) { var handled = _this.props.onPossibleKeyCommand(event); if (handled) { event.preventDefault(); // If the keydown resulted in a command being executed, we will just close the suggestions if they are open. // Resetting suggestionsPromiseIndex will cause any promise that is yet to be resolved to have no effect // when they finish loading. // TODO: The code below is duplicate, we need to clean this up _this.suggestionsPromiseIndex = 0; _this.setState({ mention: { status: "inactive", suggestions: [] } }); return; } } if (!_this.suggestionsEnabled()) { return; } var key = event.key, shiftKey = event.shiftKey, currentTarget = event.currentTarget; var selectionStart = currentTarget.selectionStart; var mention = _this.state.mention; switch (mention.status) { case "loading": case "active": if (key === "Escape" || (key === "Backspace" && selectionStart <= _this.state.mention.startPosition)) { // resetting suggestionsPromiseIndex will cause any promise that is yet to be resolved to have no effect // when they finish loading. _this.suggestionsPromiseIndex = 0; _this.setState({ mention: { status: "inactive", suggestions: [] } }); } else if (mention.status === "active" && (key === "ArrowUp" || key === "ArrowDown") && !shiftKey) { event.preventDefault(); var focusDelta = key === "ArrowUp" ? -1 : 1; _this.setState({ mention: __assign(__assign({}, mention), { focusIndex: Math_1.mod(mention.focusIndex + focusDelta, mention.suggestions.length) }) }); } else if (key === "Enter" && mention.status === "active" && mention.suggestions.length) { event.preventDefault(); _this.handleSuggestionSelected(mention.focusIndex); } break; default: // Ignore } }; _this.handleKeyUp = function (event) { var key = event.key; var mention = _this.state.mention; var _a = _this.props, suggestionTriggerCharacters = _a.suggestionTriggerCharacters, value = _a.value; switch (mention.status) { case "loading": case "active": if (key === "Backspace") { var searchText = value.substr(mention.startPosition, _this.getTextArea().selectionStart - mention.startPosition); _this.startLoadingSuggestions(searchText); if (mention.status !== "loading") { _this.setState({ mention: __assign(__assign({}, _this.state.mention), { status: "loading" }) }); } } break; case "inactive": if (key === "Backspace") { var prevChar = value.charAt(_this.getTextArea().selectionStart - 1); var isAtMention = suggestionTriggerCharacters.includes(value.charAt(_this.getTextArea().selectionStart - 1)); if (isAtMention) { _this.loadEmptySuggestion(event.currentTarget, prevChar); } } break; default: // Ignore } }; _this.handleKeyPress = function (event) { var _a = _this.props, suggestionTriggerCharacters = _a.suggestionTriggerCharacters, value = _a.value; var mention = _this.state.mention; var key = event.key; switch (mention.status) { case "loading": case "active": if (key === " ") { _this.setState({ mention: __assign(__assign({}, _this.state.mention), { status: "inactive" }) }); return; } var searchText = value.substr(mention.startPosition, _this.getTextArea().selectionStart - mention.startPosition) + key; // In this case, the mentions box was open but the user typed something else _this.startLoadingSuggestions(searchText); if (mention.status !== "loading") { _this.setState({ mention: __assign(__assign({}, _this.state.mention), { status: "loading" }) }); } break; case "inactive": if (suggestionTriggerCharacters.indexOf(event.key) === -1 || !/\s|\(|\[|^.{0}$/.test(value.charAt(_this.getTextArea().selectionStart - 1))) { return; } _this.loadEmptySuggestion(event.currentTarget, event.key); break; } }; _this.state = { mention: { status: "inactive", suggestions: [] } }; return _this; } TextArea.prototype.suggestionsEnabled = function () { return (this.props.suggestionTriggerCharacters && this.props.suggestionTriggerCharacters.length && this.props.loadSuggestions); }; TextArea.prototype.render = function () { var _this = this; var _a = this.props, classes = _a.classes, readOnly = _a.readOnly, textAreaProps = _a.textAreaProps, height = _a.height, heightUnits = _a.heightUnits, value = _a.value, suggestionTriggerCharacters = _a.suggestionTriggerCharacters, loadSuggestions = _a.loadSuggestions, suggestionsDropdownClasses = _a.suggestionsDropdownClasses, textAreaComponent = _a.textAreaComponent, onPaste = _a.onPaste, onDrop = _a.onDrop; var suggestionsEnabled = suggestionTriggerCharacters && suggestionTriggerCharacters.length && loadSuggestions; var mention = this.state.mention; var TextAreaComponent = (textAreaComponent || "textarea"); var heightVal = height && heightUnits ? height + heightUnits : height; return (React.createElement("div", { className: "mde-textarea-wrapper" }, React.createElement(TextAreaComponent, __assign({ className: ClassNames_1.classNames("mde-text", classes), style: { height: heightVal }, ref: this.props.refObject, readOnly: readOnly, value: value, "data-testid": "text-area" }, textAreaProps, { onChange: function (event) { var _a; (_a = textAreaProps === null || textAreaProps === void 0 ? void 0 : textAreaProps.onChange) === null || _a === void 0 ? void 0 : _a.call(textAreaProps, event); _this.handleOnChange(event); }, onBlur: function (event) { var _a; (_a = textAreaProps === null || textAreaProps === void 0 ? void 0 : textAreaProps.onBlur) === null || _a === void 0 ? void 0 : _a.call(textAreaProps, event); if (suggestionsEnabled) { _this.handleBlur(); } }, onKeyDown: function (event) { var _a; (_a = textAreaProps === null || textAreaProps === void 0 ? void 0 : textAreaProps.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(textAreaProps, event); _this.handleKeyDown(event); }, onKeyUp: function (event) { var _a; (_a = textAreaProps === null || textAreaProps === void 0 ? void 0 : textAreaProps.onKeyUp) === null || _a === void 0 ? void 0 : _a.call(textAreaProps, event); if (suggestionsEnabled) { _this.handleKeyUp(event); } }, onKeyPress: function (event) { var _a; (_a = textAreaProps === null || textAreaProps === void 0 ? void 0 : textAreaProps.onKeyPress) === null || _a === void 0 ? void 0 : _a.call(textAreaProps, event); if (suggestionsEnabled) { _this.handleKeyPress(event); } }, onPaste: function (event) { var _a; (_a = textAreaProps === null || textAreaProps === void 0 ? void 0 : textAreaProps.onPaste) === null || _a === void 0 ? void 0 : _a.call(textAreaProps, event); onPaste(event); }, onDragOver: function (event) { event.preventDefault(); event.stopPropagation(); }, onDrop: function (event) { var _a; (_a = textAreaProps === null || textAreaProps === void 0 ? void 0 : textAreaProps.onDrop) === null || _a === void 0 ? void 0 : _a.call(textAreaProps, event); onDrop(event); event.preventDefault(); } })), mention.status === "active" && mention.suggestions.length && (React.createElement(SuggestionsDropdown_1.SuggestionsDropdown, { classes: suggestionsDropdownClasses, caret: mention.caret, suggestions: mention.suggestions, onSuggestionSelected: this.handleSuggestionSelected, suggestionsAutoplace: this.props.suggestionsAutoplace, focusIndex: mention.focusIndex, textAreaRef: this.props.refObject })))); }; return TextArea; }(React.Component)); exports.TextArea = TextArea;