react-mentions 4.1.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  A React component that let's you mention people in a textarea like you are used to on Facebook or Twitter.
9
9
 
10
- Used in production at [Signavio](https://signavio.com), [State](https://state.com), [Snips](https://snips.ai), [Swat.io](https://swat.io), [GotDone](https://www.gotdone.me), [Volinspire](https://volinspire.com), [Marvin](https://amazingmarvin.com), [Timely](https://timelyapp.com), [GuideFitter](https://www.guidefitter.com/), [Evite](https://www.evite.com/), [Publer](https://publer.me/), [Kontentino](https://www.kontentino.com/), and [you?](https://github.com/signavio/react-mentions/edit/master/README.md)
10
+ Used in production at [Signavio](https://signavio.com), [State](https://state.com), [Snips](https://snips.ai), [Swat.io](https://swat.io), [GotDone](https://www.gotdone.me), [Volinspire](https://volinspire.com), [Marvin](https://amazingmarvin.com), [Timely](https://timelyapp.com), [GuideFitter](https://www.guidefitter.com/), [Evite](https://www.evite.com/), [Publer](https://publer.me/), [Kontentino](https://www.kontentino.com/), [Wix.com](https://wix.com) and [you?](https://github.com/signavio/react-mentions/edit/master/README.md)
11
11
 
12
12
  ## Getting started
13
13
 
@@ -76,7 +76,7 @@ Each data source is configured using a `Mention` component, which has the follow
76
76
  | markup | string | `'@[__display__](__id__)'` | A template string for the markup to use for mentions |
77
77
  | displayTransform | function (id, display) | returns `display` | Accepts a function for customizing the string that is displayed for a mention |
78
78
  | regex | RegExp | automatically derived from `markup` pattern | Allows providing a custom regular expression for parsing your markup and extracting the placeholder interpolations (optional) | |
79
- | onAdd | function (id, display) | empty function | Callback invoked when a suggestion has been added (optional) |
79
+ | onAdd | function (id, display, startPos, endPos) | empty function | Callback invoked when a suggestion has been added (optional) |
80
80
  | appendSpaceOnAdd | boolean | `false` | Append a space when a suggestion has been added (optional) |
81
81
 
82
82
  If a function is passed as the `data` prop, that function will be called with the current search query as first, and a callback function as second argument. The callback can be used to provide results asynchronously, e.g., after fetch requests. (It can even be called multiple times to update the list of suggestions.)
@@ -91,6 +91,11 @@ If you want to avoid global class names and use css modules instead, you can pro
91
91
 
92
92
  You can also assign `className` and `style` props to the `Mention` elements to define how to highlight the mentioned words.
93
93
 
94
+ ## Testing
95
+
96
+ Due to react-mentions' internal cursor tracking it is not good enough to simulate the editing of the textarea value using `ReactTestUtils.Simulate.change`.
97
+ We recommend using [@testing-library/user-event](https://github.com/testing-library/user-event) for a realistic simulation of events as they would happen in the browser as the user interacts the textarea.
98
+
94
99
  ## Contributing
95
100
 
96
101
  Spawn a development server with an example page and module hot loading all set up:
@@ -629,6 +629,10 @@ var getSubstringIndex = function getSubstringIndex(str, substr, ignoreAccents) {
629
629
  return normalizeString(str).indexOf(normalizeString(substr));
630
630
  };
631
631
 
632
+ var isIE = function isIE() {
633
+ return !!document.documentMode;
634
+ };
635
+
632
636
  var isNumber = function isNumber(val) {
633
637
  return typeof val === 'number';
634
638
  };
@@ -1239,6 +1243,7 @@ var propTypes = {
1239
1243
  singleLine: PropTypes.bool,
1240
1244
  allowSpaceInQuery: PropTypes.bool,
1241
1245
  allowSuggestionsAboveCursor: PropTypes.bool,
1246
+ forceSuggestionsAboveCursor: PropTypes.bool,
1242
1247
  ignoreAccents: PropTypes.bool,
1243
1248
  a11ySuggestionsListLabel: PropTypes.string,
1244
1249
  value: PropTypes.string,
@@ -1424,13 +1429,16 @@ function (_React$Component) {
1424
1429
  });
1425
1430
 
1426
1431
  _defineProperty(_assertThisInitialized(_this), "handleChange", function (ev) {
1427
- isComposing = false; // if we are inside iframe, we need to find activeElement within its contentDocument
1432
+ isComposing = false;
1428
1433
 
1429
- var currentDocument = document.activeElement && document.activeElement.contentDocument || document;
1434
+ if (isIE()) {
1435
+ // if we are inside iframe, we need to find activeElement within its contentDocument
1436
+ var currentDocument = document.activeElement && document.activeElement.contentDocument || document;
1430
1437
 
1431
- if (currentDocument.activeElement !== ev.target) {
1432
- // fix an IE bug (blur from empty input element with placeholder attribute trigger "input" event)
1433
- return;
1438
+ if (currentDocument.activeElement !== ev.target) {
1439
+ // fix an IE bug (blur from empty input element with placeholder attribute trigger "input" event)
1440
+ return;
1441
+ }
1434
1442
  }
1435
1443
 
1436
1444
  var value = _this.props.value || '';
@@ -1513,6 +1521,7 @@ function (_React$Component) {
1513
1521
 
1514
1522
  if (Object.values(KEY).indexOf(ev.keyCode) >= 0) {
1515
1523
  ev.preventDefault();
1524
+ ev.stopPropagation();
1516
1525
  }
1517
1526
 
1518
1527
  switch (ev.keyCode) {
@@ -1625,7 +1634,8 @@ function (_React$Component) {
1625
1634
  var caretPosition = _this.state.caretPosition;
1626
1635
  var _this$props5 = _this.props,
1627
1636
  suggestionsPortalHost = _this$props5.suggestionsPortalHost,
1628
- allowSuggestionsAboveCursor = _this$props5.allowSuggestionsAboveCursor;
1637
+ allowSuggestionsAboveCursor = _this$props5.allowSuggestionsAboveCursor,
1638
+ forceSuggestionsAboveCursor = _this$props5.forceSuggestionsAboveCursor;
1629
1639
 
1630
1640
  if (!caretPosition || !_this.suggestionsElement) {
1631
1641
  return;
@@ -1670,7 +1680,7 @@ function (_React$Component) {
1670
1680
  // is small enough to NOT cover up the caret
1671
1681
 
1672
1682
 
1673
- if (allowSuggestionsAboveCursor && top + suggestions.offsetHeight > viewportHeight && suggestions.offsetHeight < top - caretHeight) {
1683
+ if (allowSuggestionsAboveCursor && top + suggestions.offsetHeight > viewportHeight && suggestions.offsetHeight < top - caretHeight || forceSuggestionsAboveCursor) {
1674
1684
  position.top = Math.max(0, top - suggestions.offsetHeight - caretHeight);
1675
1685
  } else {
1676
1686
  position.top = top;
@@ -1885,7 +1895,7 @@ function (_React$Component) {
1885
1895
  _this.executeOnChange(eventMock, newValue, newPlainTextValue, mentions);
1886
1896
 
1887
1897
  if (onAdd) {
1888
- onAdd(id, display);
1898
+ onAdd(id, display, start, end);
1889
1899
  } // Make sure the suggestions overlay is closed
1890
1900
 
1891
1901
 
@@ -405,6 +405,8 @@ var _toConsumableArray = _interopDefault(require("@babel/runtime/helpers/toConsu
405
405
  return removeAccents(str).toLowerCase();
406
406
  }, getSubstringIndex = function(str, substr, ignoreAccents) {
407
407
  return ignoreAccents ? normalizeString(str).indexOf(normalizeString(substr)) : str.toLowerCase().indexOf(substr.toLowerCase());
408
+ }, isIE = function() {
409
+ return !!document.documentMode;
408
410
  }, isNumber = function(val) {
409
411
  return "number" == typeof val;
410
412
  }, keys = function(obj) {
@@ -785,6 +787,7 @@ var makeTriggerRegex = function(trigger) {
785
787
  singleLine: PropTypes.bool,
786
788
  allowSpaceInQuery: PropTypes.bool,
787
789
  allowSuggestionsAboveCursor: PropTypes.bool,
790
+ forceSuggestionsAboveCursor: PropTypes.bool,
788
791
  ignoreAccents: PropTypes.bool,
789
792
  a11ySuggestionsListLabel: PropTypes.string,
790
793
  value: PropTypes.string,
@@ -886,27 +889,26 @@ var makeTriggerRegex = function(trigger) {
886
889
  var _this$props4, _this$props$valueLink;
887
890
  return _this.props.onChange ? (_this$props4 = _this.props).onChange.apply(_this$props4, [ event ].concat(args)) : _this.props.valueLink ? (_this$props$valueLink = _this.props.valueLink).requestChange.apply(_this$props$valueLink, [ event.target.value ].concat(args)) : void 0;
888
891
  }), _defineProperty(_assertThisInitialized(_this), "handleChange", function(ev) {
889
- if (isComposing = !1, (document.activeElement && document.activeElement.contentDocument || document).activeElement === ev.target) {
890
- var value = _this.props.value || "", config = readConfigFromChildren(_this.props.children), newPlainTextValue = ev.target.value, newValue = applyChangeToValue(value, newPlainTextValue, {
891
- selectionStartBefore: _this.state.selectionStart,
892
- selectionEndBefore: _this.state.selectionEnd,
893
- selectionEndAfter: ev.target.selectionEnd
894
- }, config);
895
- newPlainTextValue = getPlainText(newValue, config);
896
- var selectionStart = ev.target.selectionStart, selectionEnd = ev.target.selectionEnd, setSelectionAfterMentionChange = !1, startOfMention = findStartOfMentionInPlainText(value, config, selectionStart);
897
- void 0 !== startOfMention && _this.state.selectionEnd > startOfMention && (selectionEnd = selectionStart = startOfMention,
898
- setSelectionAfterMentionChange = !0), _this.setState({
899
- selectionStart: selectionStart,
900
- selectionEnd: selectionEnd,
901
- setSelectionAfterMentionChange: setSelectionAfterMentionChange
902
- });
903
- var mentions = getMentions(newValue, config), eventMock = {
904
- target: {
905
- value: newValue
906
- }
907
- };
908
- _this.executeOnChange(eventMock, newValue, newPlainTextValue, mentions);
909
- }
892
+ if ((isComposing = !1, isIE()) && (document.activeElement && document.activeElement.contentDocument || document).activeElement !== ev.target) return;
893
+ var value = _this.props.value || "", config = readConfigFromChildren(_this.props.children), newPlainTextValue = ev.target.value, newValue = applyChangeToValue(value, newPlainTextValue, {
894
+ selectionStartBefore: _this.state.selectionStart,
895
+ selectionEndBefore: _this.state.selectionEnd,
896
+ selectionEndAfter: ev.target.selectionEnd
897
+ }, config);
898
+ newPlainTextValue = getPlainText(newValue, config);
899
+ var selectionStart = ev.target.selectionStart, selectionEnd = ev.target.selectionEnd, setSelectionAfterMentionChange = !1, startOfMention = findStartOfMentionInPlainText(value, config, selectionStart);
900
+ void 0 !== startOfMention && _this.state.selectionEnd > startOfMention && (selectionEnd = selectionStart = startOfMention,
901
+ setSelectionAfterMentionChange = !0), _this.setState({
902
+ selectionStart: selectionStart,
903
+ selectionEnd: selectionEnd,
904
+ setSelectionAfterMentionChange: setSelectionAfterMentionChange
905
+ });
906
+ var mentions = getMentions(newValue, config), eventMock = {
907
+ target: {
908
+ value: newValue
909
+ }
910
+ };
911
+ _this.executeOnChange(eventMock, newValue, newPlainTextValue, mentions);
910
912
  }), _defineProperty(_assertThisInitialized(_this), "handleSelect", function(ev) {
911
913
  if (_this.setState({
912
914
  selectionStart: ev.target.selectionStart,
@@ -917,8 +919,8 @@ var makeTriggerRegex = function(trigger) {
917
919
  _this.updateHighlighterScroll(), _this.props.onSelect(ev);
918
920
  }
919
921
  }), _defineProperty(_assertThisInitialized(_this), "handleKeyDown", function(ev) {
920
- if (0 !== countSuggestions(_this.state.suggestions) && _this.suggestionsElement) switch (Object.values(KEY).indexOf(ev.keyCode) >= 0 && ev.preventDefault(),
921
- ev.keyCode) {
922
+ if (0 !== countSuggestions(_this.state.suggestions) && _this.suggestionsElement) switch (Object.values(KEY).indexOf(ev.keyCode) >= 0 && (ev.preventDefault(),
923
+ ev.stopPropagation()), ev.keyCode) {
922
924
  case KEY.ESC:
923
925
  return void _this.clearSuggestions();
924
926
 
@@ -970,7 +972,7 @@ var makeTriggerRegex = function(trigger) {
970
972
  scrollFocusedIntoView: !1
971
973
  });
972
974
  }), _defineProperty(_assertThisInitialized(_this), "updateSuggestionsPosition", function() {
973
- var caretPosition = _this.state.caretPosition, _this$props5 = _this.props, suggestionsPortalHost = _this$props5.suggestionsPortalHost, allowSuggestionsAboveCursor = _this$props5.allowSuggestionsAboveCursor;
975
+ var caretPosition = _this.state.caretPosition, _this$props5 = _this.props, suggestionsPortalHost = _this$props5.suggestionsPortalHost, allowSuggestionsAboveCursor = _this$props5.allowSuggestionsAboveCursor, forceSuggestionsAboveCursor = _this$props5.forceSuggestionsAboveCursor;
974
976
  if (caretPosition && _this.suggestionsElement) {
975
977
  var suggestions = _this.suggestionsElement, highlighter = _this.highlighterElement, caretOffsetParentRect = highlighter.getBoundingClientRect(), caretHeight = getComputedStyleLengthProp(highlighter, "font-size"), viewportRelative = {
976
978
  left: caretOffsetParentRect.left + caretPosition.left,
@@ -985,7 +987,7 @@ var makeTriggerRegex = function(trigger) {
985
987
  left -= highlighter.scrollLeft, top -= highlighter.scrollTop;
986
988
  var viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
987
989
  left + suggestions.offsetWidth > viewportWidth ? position.left = Math.max(0, viewportWidth - suggestions.offsetWidth) : position.left = left,
988
- allowSuggestionsAboveCursor && top + suggestions.offsetHeight > viewportHeight && suggestions.offsetHeight < top - caretHeight ? position.top = Math.max(0, top - suggestions.offsetHeight - caretHeight) : position.top = top;
990
+ allowSuggestionsAboveCursor && top + suggestions.offsetHeight > viewportHeight && suggestions.offsetHeight < top - caretHeight || forceSuggestionsAboveCursor ? position.top = Math.max(0, top - suggestions.offsetHeight - caretHeight) : position.top = top;
989
991
  } else {
990
992
  var _left = caretPosition.left - highlighter.scrollLeft, _top = caretPosition.top - highlighter.scrollTop;
991
993
  _left + suggestions.offsetWidth > _this.containerElement.offsetWidth ? position.right = 0 : position.left = _left,
@@ -1074,7 +1076,7 @@ var makeTriggerRegex = function(trigger) {
1074
1076
  value: newValue
1075
1077
  }
1076
1078
  }, mentions = getMentions(newValue, config), newPlainTextValue = spliceString(plainTextValue, querySequenceStart, querySequenceEnd, displayValue);
1077
- _this.executeOnChange(eventMock, newValue, newPlainTextValue, mentions), onAdd && onAdd(id, display),
1079
+ _this.executeOnChange(eventMock, newValue, newPlainTextValue, mentions), onAdd && onAdd(id, display, start, end),
1078
1080
  _this.clearSuggestions();
1079
1081
  }), _defineProperty(_assertThisInitialized(_this), "isLoading", function() {
1080
1082
  var isLoading = !1;
@@ -621,6 +621,10 @@ var getSubstringIndex = function getSubstringIndex(str, substr, ignoreAccents) {
621
621
  return normalizeString(str).indexOf(normalizeString(substr));
622
622
  };
623
623
 
624
+ var isIE = function isIE() {
625
+ return !!document.documentMode;
626
+ };
627
+
624
628
  var isNumber = function isNumber(val) {
625
629
  return typeof val === 'number';
626
630
  };
@@ -1231,6 +1235,7 @@ var propTypes = {
1231
1235
  singleLine: PropTypes.bool,
1232
1236
  allowSpaceInQuery: PropTypes.bool,
1233
1237
  allowSuggestionsAboveCursor: PropTypes.bool,
1238
+ forceSuggestionsAboveCursor: PropTypes.bool,
1234
1239
  ignoreAccents: PropTypes.bool,
1235
1240
  a11ySuggestionsListLabel: PropTypes.string,
1236
1241
  value: PropTypes.string,
@@ -1416,13 +1421,16 @@ function (_React$Component) {
1416
1421
  });
1417
1422
 
1418
1423
  _defineProperty(_assertThisInitialized(_this), "handleChange", function (ev) {
1419
- isComposing = false; // if we are inside iframe, we need to find activeElement within its contentDocument
1424
+ isComposing = false;
1420
1425
 
1421
- var currentDocument = document.activeElement && document.activeElement.contentDocument || document;
1426
+ if (isIE()) {
1427
+ // if we are inside iframe, we need to find activeElement within its contentDocument
1428
+ var currentDocument = document.activeElement && document.activeElement.contentDocument || document;
1422
1429
 
1423
- if (currentDocument.activeElement !== ev.target) {
1424
- // fix an IE bug (blur from empty input element with placeholder attribute trigger "input" event)
1425
- return;
1430
+ if (currentDocument.activeElement !== ev.target) {
1431
+ // fix an IE bug (blur from empty input element with placeholder attribute trigger "input" event)
1432
+ return;
1433
+ }
1426
1434
  }
1427
1435
 
1428
1436
  var value = _this.props.value || '';
@@ -1505,6 +1513,7 @@ function (_React$Component) {
1505
1513
 
1506
1514
  if (Object.values(KEY).indexOf(ev.keyCode) >= 0) {
1507
1515
  ev.preventDefault();
1516
+ ev.stopPropagation();
1508
1517
  }
1509
1518
 
1510
1519
  switch (ev.keyCode) {
@@ -1617,7 +1626,8 @@ function (_React$Component) {
1617
1626
  var caretPosition = _this.state.caretPosition;
1618
1627
  var _this$props5 = _this.props,
1619
1628
  suggestionsPortalHost = _this$props5.suggestionsPortalHost,
1620
- allowSuggestionsAboveCursor = _this$props5.allowSuggestionsAboveCursor;
1629
+ allowSuggestionsAboveCursor = _this$props5.allowSuggestionsAboveCursor,
1630
+ forceSuggestionsAboveCursor = _this$props5.forceSuggestionsAboveCursor;
1621
1631
 
1622
1632
  if (!caretPosition || !_this.suggestionsElement) {
1623
1633
  return;
@@ -1662,7 +1672,7 @@ function (_React$Component) {
1662
1672
  // is small enough to NOT cover up the caret
1663
1673
 
1664
1674
 
1665
- if (allowSuggestionsAboveCursor && top + suggestions.offsetHeight > viewportHeight && suggestions.offsetHeight < top - caretHeight) {
1675
+ if (allowSuggestionsAboveCursor && top + suggestions.offsetHeight > viewportHeight && suggestions.offsetHeight < top - caretHeight || forceSuggestionsAboveCursor) {
1666
1676
  position.top = Math.max(0, top - suggestions.offsetHeight - caretHeight);
1667
1677
  } else {
1668
1678
  position.top = top;
@@ -1877,7 +1887,7 @@ function (_React$Component) {
1877
1887
  _this.executeOnChange(eventMock, newValue, newPlainTextValue, mentions);
1878
1888
 
1879
1889
  if (onAdd) {
1880
- onAdd(id, display);
1890
+ onAdd(id, display, start, end);
1881
1891
  } // Make sure the suggestions overlay is closed
1882
1892
 
1883
1893
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mentions",
3
- "version": "4.1.0",
3
+ "version": "4.3.1",
4
4
  "description": "React mentions input",
5
5
  "main": "dist/react-mentions.cjs.js",
6
6
  "module": "dist/react-mentions.esm.js",