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 +7 -2
- package/dist/react-mentions.cjs.dev.js +18 -8
- package/dist/react-mentions.cjs.prod.js +28 -26
- package/dist/react-mentions.esm.js +18 -8
- package/package.json +1 -1
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;
|
1432
|
+
isComposing = false;
|
1428
1433
|
|
1429
|
-
|
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
|
-
|
1432
|
-
|
1433
|
-
|
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
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
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;
|
1424
|
+
isComposing = false;
|
1420
1425
|
|
1421
|
-
|
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
|
-
|
1424
|
-
|
1425
|
-
|
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
|
|