UNPKG

13 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
3import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
4import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
5import _createClass from "@babel/runtime/helpers/esm/createClass";
6import _inherits from "@babel/runtime/helpers/esm/inherits";
7import _createSuper from "@babel/runtime/helpers/esm/createSuper";
8import classNames from 'classnames';
9import toArray from "rc-util/es/Children/toArray";
10import KeyCode from "rc-util/es/KeyCode";
11import * as React from 'react';
12import TextArea from 'rc-textarea';
13import KeywordTrigger from './KeywordTrigger';
14import { MentionsContextProvider } from './MentionsContext';
15import Option from './Option';
16import { filterOption as defaultFilterOption, getBeforeSelectionText, getLastMeasureIndex, omit, replaceWithMeasure, setInputSelection, validateSearch as defaultValidateSearch } from './util';
17
18var Mentions = /*#__PURE__*/function (_React$Component) {
19 _inherits(Mentions, _React$Component);
20
21 var _super = _createSuper(Mentions);
22
23 function Mentions(props) {
24 var _this;
25
26 _classCallCheck(this, Mentions);
27
28 _this = _super.call(this, props);
29 _this.focusId = undefined;
30
31 _this.triggerChange = function (value) {
32 var onChange = _this.props.onChange;
33
34 if (!('value' in _this.props)) {
35 _this.setState({
36 value: value
37 });
38 }
39
40 if (onChange) {
41 onChange(value);
42 }
43 };
44
45 _this.onChange = function (_ref) {
46 var value = _ref.target.value;
47
48 _this.triggerChange(value);
49 }; // Check if hit the measure keyword
50
51
52 _this.onKeyDown = function (event) {
53 var which = event.which;
54 var _this$state = _this.state,
55 activeIndex = _this$state.activeIndex,
56 measuring = _this$state.measuring; // Skip if not measuring
57
58 if (!measuring) {
59 return;
60 }
61
62 if (which === KeyCode.UP || which === KeyCode.DOWN) {
63 // Control arrow function
64 var optionLen = _this.getOptions().length;
65
66 var offset = which === KeyCode.UP ? -1 : 1;
67 var newActiveIndex = (activeIndex + offset + optionLen) % optionLen;
68
69 _this.setState({
70 activeIndex: newActiveIndex
71 });
72
73 event.preventDefault();
74 } else if (which === KeyCode.ESC) {
75 _this.stopMeasure();
76 } else if (which === KeyCode.ENTER) {
77 // Measure hit
78 event.preventDefault();
79
80 var options = _this.getOptions();
81
82 if (!options.length) {
83 _this.stopMeasure();
84
85 return;
86 }
87
88 var option = options[activeIndex];
89
90 _this.selectOption(option);
91 }
92 };
93 /**
94 * When to start measure:
95 * 1. When user press `prefix`
96 * 2. When measureText !== prevMeasureText
97 * - If measure hit
98 * - If measuring
99 *
100 * When to stop measure:
101 * 1. Selection is out of range
102 * 2. Contains `space`
103 * 3. ESC or select one
104 */
105
106
107 _this.onKeyUp = function (event) {
108 var key = event.key,
109 which = event.which;
110 var _this$state2 = _this.state,
111 prevMeasureText = _this$state2.measureText,
112 measuring = _this$state2.measuring;
113 var _this$props = _this.props,
114 _this$props$prefix = _this$props.prefix,
115 prefix = _this$props$prefix === void 0 ? '' : _this$props$prefix,
116 onSearch = _this$props.onSearch,
117 validateSearch = _this$props.validateSearch;
118 var target = event.target;
119 var selectionStartText = getBeforeSelectionText(target);
120
121 var _getLastMeasureIndex = getLastMeasureIndex(selectionStartText, prefix),
122 measureIndex = _getLastMeasureIndex.location,
123 measurePrefix = _getLastMeasureIndex.prefix; // Skip if match the white key list
124
125
126 if ([KeyCode.ESC, KeyCode.UP, KeyCode.DOWN, KeyCode.ENTER].indexOf(which) !== -1) {
127 return;
128 }
129
130 if (measureIndex !== -1) {
131 var measureText = selectionStartText.slice(measureIndex + measurePrefix.length);
132 var validateMeasure = validateSearch(measureText, _this.props);
133 var matchOption = !!_this.getOptions(measureText).length;
134
135 if (validateMeasure) {
136 if (key === measurePrefix || key === 'Shift' || measuring || measureText !== prevMeasureText && matchOption) {
137 _this.startMeasure(measureText, measurePrefix, measureIndex);
138 }
139 } else if (measuring) {
140 // Stop if measureText is invalidate
141 _this.stopMeasure();
142 }
143 /**
144 * We will trigger `onSearch` to developer since they may use for async update.
145 * If met `space` means user finished searching.
146 */
147
148
149 if (onSearch && validateMeasure) {
150 onSearch(measureText, measurePrefix);
151 }
152 } else if (measuring) {
153 _this.stopMeasure();
154 }
155 };
156
157 _this.onPressEnter = function (event) {
158 var measuring = _this.state.measuring;
159 var onPressEnter = _this.props.onPressEnter;
160
161 if (!measuring && onPressEnter) {
162 onPressEnter(event);
163 }
164 };
165
166 _this.onInputFocus = function (event) {
167 _this.onFocus(event);
168 };
169
170 _this.onInputBlur = function (event) {
171 _this.onBlur(event);
172 };
173
174 _this.onDropdownFocus = function () {
175 _this.onFocus();
176 };
177
178 _this.onDropdownBlur = function () {
179 _this.onBlur();
180 };
181
182 _this.onFocus = function (event) {
183 window.clearTimeout(_this.focusId);
184 var isFocus = _this.state.isFocus;
185 var onFocus = _this.props.onFocus;
186
187 if (!isFocus && event && onFocus) {
188 onFocus(event);
189 }
190
191 _this.setState({
192 isFocus: true
193 });
194 };
195
196 _this.onBlur = function (event) {
197 _this.focusId = window.setTimeout(function () {
198 var onBlur = _this.props.onBlur;
199
200 _this.setState({
201 isFocus: false
202 });
203
204 _this.stopMeasure();
205
206 if (onBlur) {
207 onBlur(event);
208 }
209 }, 0);
210 };
211
212 _this.selectOption = function (option) {
213 var _this$state3 = _this.state,
214 value = _this$state3.value,
215 measureLocation = _this$state3.measureLocation,
216 measurePrefix = _this$state3.measurePrefix;
217 var _this$props2 = _this.props,
218 split = _this$props2.split,
219 onSelect = _this$props2.onSelect;
220 var _option$value = option.value,
221 mentionValue = _option$value === void 0 ? '' : _option$value;
222
223 var _replaceWithMeasure = replaceWithMeasure(value, {
224 measureLocation: measureLocation,
225 targetText: mentionValue,
226 prefix: measurePrefix,
227 selectionStart: _this.textarea.selectionStart,
228 split: split
229 }),
230 text = _replaceWithMeasure.text,
231 selectionLocation = _replaceWithMeasure.selectionLocation;
232
233 _this.triggerChange(text);
234
235 _this.stopMeasure(function () {
236 // We need restore the selection position
237 setInputSelection(_this.textarea, selectionLocation);
238 });
239
240 if (onSelect) {
241 onSelect(option, measurePrefix);
242 }
243 };
244
245 _this.setActiveIndex = function (activeIndex) {
246 _this.setState({
247 activeIndex: activeIndex
248 });
249 };
250
251 _this.setTextAreaRef = function (element) {
252 var _element$resizableTex;
253
254 _this.textarea = element === null || element === void 0 ? void 0 : (_element$resizableTex = element.resizableTextArea) === null || _element$resizableTex === void 0 ? void 0 : _element$resizableTex.textArea;
255 };
256
257 _this.setMeasureRef = function (element) {
258 _this.measure = element;
259 };
260
261 _this.getOptions = function (measureText) {
262 var targetMeasureText = measureText || _this.state.measureText || '';
263 var _this$props3 = _this.props,
264 children = _this$props3.children,
265 filterOption = _this$props3.filterOption;
266 var list = toArray(children).map(function (_ref2) {
267 var props = _ref2.props,
268 key = _ref2.key;
269 return _objectSpread(_objectSpread({}, props), {}, {
270 key: key || props.value
271 });
272 }).filter(function (option) {
273 /** Return all result if `filterOption` is false. */
274 if (filterOption === false) {
275 return true;
276 }
277
278 return filterOption(targetMeasureText, option);
279 });
280 return list;
281 };
282
283 _this.state = {
284 value: props.defaultValue || props.value || '',
285 measuring: false,
286 measureLocation: 0,
287 measureText: null,
288 measurePrefix: '',
289 activeIndex: 0,
290 isFocus: false
291 };
292 return _this;
293 }
294
295 _createClass(Mentions, [{
296 key: "componentDidUpdate",
297 value: function componentDidUpdate() {
298 var measuring = this.state.measuring; // Sync measure div top with textarea for rc-trigger usage
299
300 if (measuring) {
301 this.measure.scrollTop = this.textarea.scrollTop;
302 }
303 }
304 }, {
305 key: "startMeasure",
306 value: function startMeasure(measureText, measurePrefix, measureLocation) {
307 this.setState({
308 measuring: true,
309 measureText: measureText,
310 measurePrefix: measurePrefix,
311 measureLocation: measureLocation,
312 activeIndex: 0
313 });
314 }
315 }, {
316 key: "stopMeasure",
317 value: function stopMeasure(callback) {
318 this.setState({
319 measuring: false,
320 measureLocation: 0,
321 measureText: null
322 }, callback);
323 }
324 }, {
325 key: "focus",
326 value: function focus() {
327 this.textarea.focus();
328 }
329 }, {
330 key: "blur",
331 value: function blur() {
332 this.textarea.blur();
333 }
334 }, {
335 key: "render",
336 value: function render() {
337 var _this$state4 = this.state,
338 value = _this$state4.value,
339 measureLocation = _this$state4.measureLocation,
340 measurePrefix = _this$state4.measurePrefix,
341 measuring = _this$state4.measuring,
342 activeIndex = _this$state4.activeIndex;
343
344 var _this$props4 = this.props,
345 prefixCls = _this$props4.prefixCls,
346 placement = _this$props4.placement,
347 direction = _this$props4.direction,
348 transitionName = _this$props4.transitionName,
349 className = _this$props4.className,
350 style = _this$props4.style,
351 autoFocus = _this$props4.autoFocus,
352 notFoundContent = _this$props4.notFoundContent,
353 getPopupContainer = _this$props4.getPopupContainer,
354 restProps = _objectWithoutProperties(_this$props4, ["prefixCls", "placement", "direction", "transitionName", "className", "style", "autoFocus", "notFoundContent", "getPopupContainer"]);
355
356 var inputProps = omit(restProps, 'value', 'defaultValue', 'prefix', 'split', 'children', 'validateSearch', 'filterOption', 'onSelect', 'onSearch');
357 var options = measuring ? this.getOptions() : [];
358 return /*#__PURE__*/React.createElement("div", {
359 className: classNames(prefixCls, className),
360 style: style
361 }, /*#__PURE__*/React.createElement(TextArea, _extends({
362 autoFocus: autoFocus,
363 ref: this.setTextAreaRef,
364 value: value
365 }, inputProps, {
366 onChange: this.onChange,
367 onKeyDown: this.onKeyDown,
368 onKeyUp: this.onKeyUp,
369 onPressEnter: this.onPressEnter,
370 onFocus: this.onInputFocus,
371 onBlur: this.onInputBlur
372 })), measuring && /*#__PURE__*/React.createElement("div", {
373 ref: this.setMeasureRef,
374 className: "".concat(prefixCls, "-measure")
375 }, value.slice(0, measureLocation), /*#__PURE__*/React.createElement(MentionsContextProvider, {
376 value: {
377 notFoundContent: notFoundContent,
378 activeIndex: activeIndex,
379 setActiveIndex: this.setActiveIndex,
380 selectOption: this.selectOption,
381 onFocus: this.onDropdownFocus,
382 onBlur: this.onDropdownBlur
383 }
384 }, /*#__PURE__*/React.createElement(KeywordTrigger, {
385 prefixCls: prefixCls,
386 transitionName: transitionName,
387 placement: placement,
388 direction: direction,
389 options: options,
390 visible: true,
391 getPopupContainer: getPopupContainer
392 }, /*#__PURE__*/React.createElement("span", null, measurePrefix))), value.slice(measureLocation + measurePrefix.length)));
393 }
394 }], [{
395 key: "getDerivedStateFromProps",
396 value: function getDerivedStateFromProps(props, prevState) {
397 var newState = {};
398
399 if ('value' in props && props.value !== prevState.value) {
400 newState.value = props.value || '';
401 }
402
403 return newState;
404 }
405 }]);
406
407 return Mentions;
408}(React.Component);
409
410Mentions.Option = Option;
411Mentions.defaultProps = {
412 prefixCls: 'rc-mentions',
413 prefix: '@',
414 split: ' ',
415 validateSearch: defaultValidateSearch,
416 filterOption: defaultFilterOption,
417 notFoundContent: 'Not Found',
418 rows: 1
419};
420export default Mentions;
\No newline at end of file