react-mention-input 1.1.2 → 1.1.4

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
@@ -37,7 +37,8 @@ A customizable input component with @mention functionality.
37
37
  | `suggestionListClassName` | `string` | Custom class name for the suggestion list. |
38
38
  | `suggestionItemClassName` | `string` | Custom class name for each suggestion item. |
39
39
  | `sendButtonIcon` | `ReactNode` | Custom icon for the send button (MUI icon or image path). |
40
- | `onSendMessage` | `(message: string) => void` | Callback function triggered on sending a message. |
40
+ | `onSendMessage` | `(obj: {messageText: string, messageHTML: string}) => void` | Callback function triggered on sending a message, providing both plain text and HTML. |
41
+ | `suggestionPosition` | `'top' | 'bottom' | 'left' | 'right'` | Position of the suggestion dropdown relative to the input. Default is `bottom`. |
41
42
 
42
43
  #### Example Usage
43
44
 
@@ -51,8 +52,9 @@ const users = [
51
52
  ];
52
53
 
53
54
  function App() {
54
- const handleSendMessage = (message: string) => {
55
- console.log('Message sent:', message);
55
+ const handleSendMessage = ({messageText, messageHTML}) => {
56
+ console.log('Message Text:', messageText);
57
+ console.log('Message HTML:', messageHTML);
56
58
  };
57
59
 
58
60
  return (
@@ -61,6 +63,7 @@ function App() {
61
63
  placeholder="Type @ to mention someone..."
62
64
  sendButtonIcon={<SendIcon />} // Optional MUI icon or image path
63
65
  onSendMessage={handleSendMessage}
66
+ suggestionPosition="top" // Customize suggestion position (top, bottom, left, right)
64
67
  />
65
68
  );
66
69
  }
@@ -95,4 +95,13 @@
95
95
  color: #aaa;
96
96
  pointer-events: none;
97
97
  position: absolute;
98
+ }
99
+
100
+ .link-highlight {
101
+ color: #007bff;
102
+ text-decoration: underline;
103
+ cursor: pointer;
104
+ }
105
+ .link-highlight:hover {
106
+ color: #0056b3;
98
107
  }
@@ -14,7 +14,11 @@ interface MentionInputProps {
14
14
  suggestionListClassName?: string;
15
15
  suggestionItemClassName?: string;
16
16
  sendButtonIcon?: ReactNode;
17
- onSendMessage?: (message: string) => void;
17
+ onSendMessage?: (obj: {
18
+ messageText: string;
19
+ messageHTML: string;
20
+ }) => void;
21
+ suggestionPosition?: 'top' | 'bottom' | 'left' | 'right';
18
22
  }
19
23
  declare const MentionInput: React.FC<MentionInputProps>;
20
24
  export default MentionInput;
@@ -2,17 +2,23 @@ import React, { useState, useRef } from "react";
2
2
  import ReactDOM from "react-dom";
3
3
  import "./MentionInput.css";
4
4
  var MentionInput = function (_a) {
5
- var users = _a.users, _b = _a.placeholder, placeholder = _b === void 0 ? "Type a message..." : _b, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, sendButtonIcon = _a.sendButtonIcon, onSendMessage = _a.onSendMessage;
6
- var _c = useState(""), inputValue = _c[0], setInputValue = _c[1]; // Plain text
7
- var _d = useState([]), suggestions = _d[0], setSuggestions = _d[1];
8
- var _e = useState(false), showSuggestions = _e[0], setShowSuggestions = _e[1];
9
- var _f = useState('bottom'), tooltipPosition = _f[0], setTooltipPosition = _f[1];
5
+ var users = _a.users, _b = _a.placeholder, placeholder = _b === void 0 ? "Type a message..." : _b, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, sendButtonIcon = _a.sendButtonIcon, onSendMessage = _a.onSendMessage, _c = _a.suggestionPosition, suggestionPosition = _c === void 0 ? 'bottom' : _c;
6
+ var _d = useState(""), inputValue = _d[0], setInputValue = _d[1]; // Plain text
7
+ var _e = useState([]), suggestions = _e[0], setSuggestions = _e[1];
8
+ var _f = useState(false), showSuggestions = _f[0], setShowSuggestions = _f[1];
10
9
  var inputRef = useRef(null);
11
10
  var suggestionListRef = useRef(null);
12
11
  var caretOffsetRef = useRef(0);
13
- var highlightMentions = function (text) {
14
- return text.replace(/@([^\s]+(?: [^\s]+)?)(?=\s|$)/g, // Match @ followed by one or two words, ending with a space or end of string
15
- '<span class="mention-highlight">@$1</span>');
12
+ var highlightMentionsAndLinks = function (text) {
13
+ // Regular expression for detecting links
14
+ var linkRegex = /(https?:\/\/[^\s]+)/g;
15
+ // Regular expression for mentions
16
+ var mentionRegex = /@([^\s]+(?: [^\s]+)?)(?=\s|$)/g;
17
+ // First, highlight mentions
18
+ var highlightedText = text.replace(mentionRegex, '<span class="mention-highlight">@$1</span>');
19
+ // Then, highlight links
20
+ highlightedText = highlightedText.replace(linkRegex, '<a href="$1" target="_blank" rel="noopener noreferrer" class="link-highlight">$1</a>');
21
+ return highlightedText;
16
22
  };
17
23
  var restoreCaretPosition = function (node, caretOffset) {
18
24
  var range = document.createRange();
@@ -56,12 +62,11 @@ var MentionInput = function (_a) {
56
62
  var preCaretRange = range.cloneRange();
57
63
  preCaretRange.selectNodeContents(inputRef.current);
58
64
  preCaretRange.setEnd(range.startContainer, range.startOffset);
59
- newCaretOffset = preCaretRange.toString().length; // Calculate caret offset
65
+ newCaretOffset = preCaretRange.toString().length;
60
66
  }
61
- caretOffsetRef.current = newCaretOffset; // Update ref directly
67
+ caretOffsetRef.current = newCaretOffset;
62
68
  var plainText = inputRef.current.innerText;
63
69
  setInputValue(plainText);
64
- // Handle mentions and suggestions
65
70
  var mentionMatch = plainText.slice(0, newCaretOffset).match(/@(\S*)$/);
66
71
  if (mentionMatch) {
67
72
  var query_1 = mentionMatch[1].toLowerCase();
@@ -73,49 +78,22 @@ var MentionInput = function (_a) {
73
78
  setShowSuggestions(false);
74
79
  }
75
80
  var previousHTML = inputRef.current.innerHTML;
76
- var htmlWithHighlights = highlightMentions(plainText);
81
+ var htmlWithHighlights = highlightMentionsAndLinks(plainText); // Updated function
77
82
  if (previousHTML !== htmlWithHighlights) {
78
83
  inputRef.current.innerHTML = htmlWithHighlights;
79
84
  }
80
85
  restoreCaretPosition(inputRef.current, newCaretOffset);
81
86
  };
82
- var handleSuggestionClick = function (user) {
83
- if (!inputRef.current)
84
- return;
85
- var plainText = inputValue; // Current input value
86
- var caretOffset = caretOffsetRef.current; // Use ref value for caret offset
87
- var mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
88
- if (!mentionMatch)
89
- return;
90
- var mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
91
- var newValue = plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
92
- setInputValue(newValue);
93
- inputRef.current.innerText = newValue;
94
- var htmlWithHighlights = highlightMentions(newValue);
95
- inputRef.current.innerHTML = htmlWithHighlights;
96
- setShowSuggestions(false);
97
- var mentionEnd = mentionIndex + user.name.length + 1;
98
- restoreCaretPosition(inputRef.current, mentionEnd);
99
- };
100
- var handleSendMessage = function () {
101
- if (inputValue.trim() && onSendMessage) {
102
- onSendMessage(inputValue.trim());
103
- setInputValue("");
104
- setShowSuggestions(false);
105
- if (inputRef.current)
106
- inputRef.current.innerText = "";
107
- }
108
- };
109
87
  var renderSuggestions = function () {
110
88
  if (!showSuggestions || !inputRef.current)
111
89
  return null;
112
90
  var inputRect = inputRef.current.getBoundingClientRect();
113
91
  var styles = {
114
92
  position: 'absolute',
115
- zIndex: 1000
93
+ zIndex: 1000,
116
94
  };
117
- // Adjust position based on calculated tooltip position
118
- switch (tooltipPosition) {
95
+ // Use suggestionPosition prop to adjust tooltip position
96
+ switch (suggestionPosition) {
119
97
  case 'top':
120
98
  styles.left = "".concat(inputRect.left, "px");
121
99
  styles.top = "".concat(inputRect.top - 150, "px");
@@ -135,10 +113,39 @@ var MentionInput = function (_a) {
135
113
  default:
136
114
  break;
137
115
  }
138
- console.log("document.body", document.body);
139
116
  return ReactDOM.createPortal(React.createElement("ul", { className: "suggestion-list ".concat(suggestionListClassName || ''), ref: suggestionListRef, style: styles }, suggestions.map(function (user) { return (React.createElement("li", { key: user.id, onClick: function () { return handleSuggestionClick(user); }, className: "suggestion-item ".concat(suggestionItemClassName || ''), role: "option", tabIndex: 0, "aria-selected": "false" }, user.name)); })), window.document.body // Render in portal
140
117
  );
141
118
  };
119
+ var handleSuggestionClick = function (user) {
120
+ if (!inputRef.current)
121
+ return;
122
+ var plainText = inputValue;
123
+ var caretOffset = caretOffsetRef.current;
124
+ var mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
125
+ if (!mentionMatch)
126
+ return;
127
+ var mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
128
+ var newValue = plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
129
+ setInputValue(newValue);
130
+ inputRef.current.innerText = newValue;
131
+ var htmlWithHighlights = highlightMentionsAndLinks(newValue);
132
+ inputRef.current.innerHTML = htmlWithHighlights;
133
+ setShowSuggestions(false);
134
+ var mentionEnd = mentionIndex + user.name.length + 1;
135
+ restoreCaretPosition(inputRef.current, mentionEnd);
136
+ };
137
+ var handleSendMessage = function () {
138
+ if (inputRef.current) {
139
+ var messageText = inputRef.current.innerText.trim(); // Plain text
140
+ var messageHTML = inputRef.current.innerHTML.trim(); // HTML with <span> highlighting
141
+ if (messageText && onSendMessage) {
142
+ onSendMessage({ messageText: messageText, messageHTML: messageHTML }); // Pass both plain text and HTML
143
+ setInputValue(""); // Clear state
144
+ setShowSuggestions(false); // Hide suggestions
145
+ inputRef.current.innerText = ""; // Clear input field
146
+ }
147
+ }
148
+ };
142
149
  return (React.createElement("div", { className: "mention-container ".concat(containerClassName || "") },
143
150
  React.createElement("div", { className: "mention-input-container ".concat(inputContainerClassName || "") },
144
151
  React.createElement("div", { ref: inputRef, contentEditable: true, suppressContentEditableWarning: true, className: "mention-input ".concat(inputClassName || ""), onInput: handleInputChange }),
@@ -67,3 +67,9 @@
67
67
  font-size: 16px;
68
68
  line-height: 1.5; /* Optimal line height for readability */
69
69
  }
70
+ .mention-highlight {
71
+ background-color: #e0f7fa;
72
+ color: #007bff;
73
+ padding: 2px 4px;
74
+ border-radius: 4px;
75
+ }
@@ -44,6 +44,6 @@ export var ShowMessageCard = function (_a) {
44
44
  React.createElement("h3", { className: "message-card-name ".concat(nameClassName || ""), style: nameStyle }, item[nameKey]),
45
45
  React.createElement("p", { className: "message-card-date ".concat(dateClassName || ""), style: dateStyle }, item[dateKey]))),
46
46
  React.createElement("div", { className: "message-card-body ".concat(bodyClassName || ""), style: bodyStyle },
47
- React.createElement("p", { className: "message-card-comment ".concat(commentClassName || ""), style: commentStyle }, item[commentKey]))));
47
+ React.createElement("p", { className: "message-card-comment ".concat(commentClassName || ""), style: commentStyle, dangerouslySetInnerHTML: { __html: item[commentKey] } }))));
48
48
  })));
49
49
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mention-input",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -95,4 +95,13 @@
95
95
  color: #aaa;
96
96
  pointer-events: none;
97
97
  position: absolute;
98
+ }
99
+
100
+ .link-highlight {
101
+ color: #007bff;
102
+ text-decoration: underline;
103
+ cursor: pointer;
104
+ }
105
+ .link-highlight:hover {
106
+ color: #0056b3;
98
107
  }
@@ -16,8 +16,9 @@ interface MentionInputProps {
16
16
  sendBtnClassName?: string;
17
17
  suggestionListClassName?: string;
18
18
  suggestionItemClassName?: string;
19
- sendButtonIcon?: ReactNode; // New prop for button icon (MUI icon or image path)
20
- onSendMessage?: (message: string) => void;
19
+ sendButtonIcon?: ReactNode; // Button icon (MUI icon or image path)
20
+ onSendMessage?: (obj:{messageText: string, messageHTML: string}) => void;
21
+ suggestionPosition?: 'top' | 'bottom' | 'left' | 'right'; // New prop for tooltip position
21
22
  }
22
23
 
23
24
  const MentionInput: React.FC<MentionInputProps> = ({
@@ -31,24 +32,36 @@ const MentionInput: React.FC<MentionInputProps> = ({
31
32
  suggestionItemClassName,
32
33
  sendButtonIcon,
33
34
  onSendMessage,
35
+ suggestionPosition = 'bottom', // Default position is bottom
34
36
  }) => {
35
37
  const [inputValue, setInputValue] = useState<string>(""); // Plain text
36
38
  const [suggestions, setSuggestions] = useState<User[]>([]);
37
39
  const [showSuggestions, setShowSuggestions] = useState<boolean>(false);
38
- const [tooltipPosition, setTooltipPosition] = useState<'top' | 'bottom' | 'left' | 'right'>(
39
- 'bottom'
40
- );
41
40
 
42
41
  const inputRef = useRef<HTMLDivElement>(null);
43
42
  const suggestionListRef = useRef<HTMLUListElement>(null);
44
43
  const caretOffsetRef = useRef<number>(0);
45
-
46
44
 
47
- const highlightMentions = (text: string): string => {
48
- return text.replace(
49
- /@([^\s]+(?: [^\s]+)?)(?=\s|$)/g, // Match @ followed by one or two words, ending with a space or end of string
45
+ const highlightMentionsAndLinks = (text: string): string => {
46
+ // Regular expression for detecting links
47
+ const linkRegex = /(https?:\/\/[^\s]+)/g;
48
+
49
+ // Regular expression for mentions
50
+ const mentionRegex = /@([^\s]+(?: [^\s]+)?)(?=\s|$)/g;
51
+
52
+ // First, highlight mentions
53
+ let highlightedText = text.replace(
54
+ mentionRegex,
50
55
  '<span class="mention-highlight">@$1</span>'
51
56
  );
57
+
58
+ // Then, highlight links
59
+ highlightedText = highlightedText.replace(
60
+ linkRegex,
61
+ '<a href="$1" target="_blank" rel="noopener noreferrer" class="link-highlight">$1</a>'
62
+ );
63
+
64
+ return highlightedText;
52
65
  };
53
66
 
54
67
  const restoreCaretPosition = (node: HTMLElement, caretOffset: number) => {
@@ -93,15 +106,14 @@ const MentionInput: React.FC<MentionInputProps> = ({
93
106
  const preCaretRange = range.cloneRange();
94
107
  preCaretRange.selectNodeContents(inputRef.current);
95
108
  preCaretRange.setEnd(range.startContainer, range.startOffset);
96
- newCaretOffset = preCaretRange.toString().length; // Calculate caret offset
109
+ newCaretOffset = preCaretRange.toString().length;
97
110
  }
98
111
 
99
- caretOffsetRef.current = newCaretOffset; // Update ref directly
112
+ caretOffsetRef.current = newCaretOffset;
100
113
 
101
114
  const plainText = inputRef.current.innerText;
102
115
  setInputValue(plainText);
103
116
 
104
- // Handle mentions and suggestions
105
117
  const mentionMatch = plainText.slice(0, newCaretOffset).match(/@(\S*)$/);
106
118
  if (mentionMatch) {
107
119
  const query = mentionMatch[1].toLowerCase();
@@ -114,47 +126,14 @@ const MentionInput: React.FC<MentionInputProps> = ({
114
126
  }
115
127
 
116
128
  const previousHTML = inputRef.current.innerHTML;
117
- const htmlWithHighlights = highlightMentions(plainText);
129
+ const htmlWithHighlights = highlightMentionsAndLinks(plainText); // Updated function
118
130
  if (previousHTML !== htmlWithHighlights) {
119
131
  inputRef.current.innerHTML = htmlWithHighlights;
120
132
  }
121
133
 
122
134
  restoreCaretPosition(inputRef.current, newCaretOffset);
123
135
  };
124
-
125
- const handleSuggestionClick = (user: User) => {
126
- if (!inputRef.current) return;
127
-
128
- const plainText = inputValue; // Current input value
129
- const caretOffset = caretOffsetRef.current; // Use ref value for caret offset
130
- const mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
131
-
132
- if (!mentionMatch) return;
133
-
134
- const mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
135
136
 
136
- const newValue =
137
- plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
138
-
139
- setInputValue(newValue);
140
- inputRef.current.innerText = newValue;
141
-
142
- const htmlWithHighlights = highlightMentions(newValue);
143
- inputRef.current.innerHTML = htmlWithHighlights;
144
-
145
- setShowSuggestions(false);
146
-
147
- const mentionEnd = mentionIndex + user.name.length + 1;
148
- restoreCaretPosition(inputRef.current, mentionEnd);
149
- };
150
- const handleSendMessage = () => {
151
- if (inputValue.trim() && onSendMessage) {
152
- onSendMessage(inputValue.trim());
153
- setInputValue("");
154
- setShowSuggestions(false);
155
- if (inputRef.current) inputRef.current.innerText = "";
156
- }
157
- };
158
137
 
159
138
  const renderSuggestions = () => {
160
139
  if (!showSuggestions || !inputRef.current) return null;
@@ -162,11 +141,11 @@ const MentionInput: React.FC<MentionInputProps> = ({
162
141
  const inputRect = inputRef.current.getBoundingClientRect();
163
142
  const styles: React.CSSProperties = {
164
143
  position: 'absolute',
165
- zIndex: 1000
144
+ zIndex: 1000,
166
145
  };
167
146
 
168
- // Adjust position based on calculated tooltip position
169
- switch (tooltipPosition) {
147
+ // Use suggestionPosition prop to adjust tooltip position
148
+ switch (suggestionPosition) {
170
149
  case 'top':
171
150
  styles.left = `${inputRect.left}px`;
172
151
  styles.top = `${inputRect.top - 150}px`;
@@ -187,7 +166,6 @@ const MentionInput: React.FC<MentionInputProps> = ({
187
166
  break;
188
167
  }
189
168
 
190
- console.log("document.body",document.body);
191
169
  return ReactDOM.createPortal(
192
170
  <ul
193
171
  className={`suggestion-list ${suggestionListClassName || ''}`}
@@ -211,6 +189,48 @@ const MentionInput: React.FC<MentionInputProps> = ({
211
189
  );
212
190
  };
213
191
 
192
+ const handleSuggestionClick = (user: User) => {
193
+ if (!inputRef.current) return;
194
+
195
+ const plainText = inputValue;
196
+ const caretOffset = caretOffsetRef.current;
197
+ const mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
198
+
199
+ if (!mentionMatch) return;
200
+
201
+ const mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
202
+
203
+ const newValue =
204
+ plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
205
+
206
+ setInputValue(newValue);
207
+ inputRef.current.innerText = newValue;
208
+
209
+ const htmlWithHighlights = highlightMentionsAndLinks(newValue);
210
+ inputRef.current.innerHTML = htmlWithHighlights;
211
+
212
+ setShowSuggestions(false);
213
+
214
+ const mentionEnd = mentionIndex + user.name.length + 1;
215
+ restoreCaretPosition(inputRef.current, mentionEnd);
216
+ };
217
+
218
+ const handleSendMessage = () => {
219
+ if (inputRef.current) {
220
+ const messageText = inputRef.current.innerText.trim(); // Plain text
221
+ const messageHTML = inputRef.current.innerHTML.trim(); // HTML with <span> highlighting
222
+
223
+ if (messageText && onSendMessage) {
224
+ onSendMessage({messageText, messageHTML}); // Pass both plain text and HTML
225
+ setInputValue(""); // Clear state
226
+ setShowSuggestions(false); // Hide suggestions
227
+ inputRef.current.innerText = ""; // Clear input field
228
+ }
229
+ }
230
+ };
231
+
232
+
233
+
214
234
  return (
215
235
  <div className={`mention-container ${containerClassName || ""}`}>
216
236
  <div className={`mention-input-container ${inputContainerClassName || ""}`}>
@@ -67,3 +67,9 @@
67
67
  font-size: 16px;
68
68
  line-height: 1.5; /* Optimal line height for readability */
69
69
  }
70
+ .mention-highlight {
71
+ background-color: #e0f7fa;
72
+ color: #007bff;
73
+ padding: 2px 4px;
74
+ border-radius: 4px;
75
+ }
@@ -146,12 +146,17 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
146
146
  className={`message-card-body ${bodyClassName || ""}`}
147
147
  style={bodyStyle}
148
148
  >
149
- <p
149
+ {/* <p
150
150
  className={`message-card-comment ${commentClassName || ""}`}
151
151
  style={commentStyle}
152
152
  >
153
153
  {item[commentKey]}
154
- </p>
154
+ </p> */}
155
+ <p
156
+ className={`message-card-comment ${commentClassName || ""}`}
157
+ style={commentStyle}
158
+ dangerouslySetInnerHTML={{ __html: item[commentKey] }}
159
+ ></p>
155
160
  </div>
156
161
  </div>
157
162
  );
@@ -160,4 +165,3 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
160
165
  );
161
166
  };
162
167
 
163
-