react-mention-input 1.1.2 → 1.1.3

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
  }
@@ -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,15 @@ 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
12
  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>');
13
+ return text.replace(/@([^\s]+(?: [^\s]+)?)(?=\s|$)/g, '<span class="mention-highlight">@$1</span>');
16
14
  };
17
15
  var restoreCaretPosition = function (node, caretOffset) {
18
16
  var range = document.createRange();
@@ -56,12 +54,11 @@ var MentionInput = function (_a) {
56
54
  var preCaretRange = range.cloneRange();
57
55
  preCaretRange.selectNodeContents(inputRef.current);
58
56
  preCaretRange.setEnd(range.startContainer, range.startOffset);
59
- newCaretOffset = preCaretRange.toString().length; // Calculate caret offset
57
+ newCaretOffset = preCaretRange.toString().length;
60
58
  }
61
- caretOffsetRef.current = newCaretOffset; // Update ref directly
59
+ caretOffsetRef.current = newCaretOffset;
62
60
  var plainText = inputRef.current.innerText;
63
61
  setInputValue(plainText);
64
- // Handle mentions and suggestions
65
62
  var mentionMatch = plainText.slice(0, newCaretOffset).match(/@(\S*)$/);
66
63
  if (mentionMatch) {
67
64
  var query_1 = mentionMatch[1].toLowerCase();
@@ -79,43 +76,16 @@ var MentionInput = function (_a) {
79
76
  }
80
77
  restoreCaretPosition(inputRef.current, newCaretOffset);
81
78
  };
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
79
  var renderSuggestions = function () {
110
80
  if (!showSuggestions || !inputRef.current)
111
81
  return null;
112
82
  var inputRect = inputRef.current.getBoundingClientRect();
113
83
  var styles = {
114
84
  position: 'absolute',
115
- zIndex: 1000
85
+ zIndex: 1000,
116
86
  };
117
- // Adjust position based on calculated tooltip position
118
- switch (tooltipPosition) {
87
+ // Use suggestionPosition prop to adjust tooltip position
88
+ switch (suggestionPosition) {
119
89
  case 'top':
120
90
  styles.left = "".concat(inputRect.left, "px");
121
91
  styles.top = "".concat(inputRect.top - 150, "px");
@@ -135,10 +105,39 @@ var MentionInput = function (_a) {
135
105
  default:
136
106
  break;
137
107
  }
138
- console.log("document.body", document.body);
139
108
  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
109
  );
141
110
  };
111
+ var handleSuggestionClick = function (user) {
112
+ if (!inputRef.current)
113
+ return;
114
+ var plainText = inputValue;
115
+ var caretOffset = caretOffsetRef.current;
116
+ var mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
117
+ if (!mentionMatch)
118
+ return;
119
+ var mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
120
+ var newValue = plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
121
+ setInputValue(newValue);
122
+ inputRef.current.innerText = newValue;
123
+ var htmlWithHighlights = highlightMentions(newValue);
124
+ inputRef.current.innerHTML = htmlWithHighlights;
125
+ setShowSuggestions(false);
126
+ var mentionEnd = mentionIndex + user.name.length + 1;
127
+ restoreCaretPosition(inputRef.current, mentionEnd);
128
+ };
129
+ var handleSendMessage = function () {
130
+ if (inputRef.current) {
131
+ var messageText = inputRef.current.innerText.trim(); // Plain text
132
+ var messageHTML = inputRef.current.innerHTML.trim(); // HTML with <span> highlighting
133
+ if (messageText && onSendMessage) {
134
+ onSendMessage({ messageText: messageText, messageHTML: messageHTML }); // Pass both plain text and HTML
135
+ setInputValue(""); // Clear state
136
+ setShowSuggestions(false); // Hide suggestions
137
+ inputRef.current.innerText = ""; // Clear input field
138
+ }
139
+ }
140
+ };
142
141
  return (React.createElement("div", { className: "mention-container ".concat(containerClassName || "") },
143
142
  React.createElement("div", { className: "mention-input-container ".concat(inputContainerClassName || "") },
144
143
  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.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -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,22 +32,19 @@ 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
45
  const highlightMentions = (text: string): string => {
48
46
  return text.replace(
49
- /@([^\s]+(?: [^\s]+)?)(?=\s|$)/g, // Match @ followed by one or two words, ending with a space or end of string
47
+ /@([^\s]+(?: [^\s]+)?)(?=\s|$)/g,
50
48
  '<span class="mention-highlight">@$1</span>'
51
49
  );
52
50
  };
@@ -84,76 +82,41 @@ const MentionInput: React.FC<MentionInputProps> = ({
84
82
 
85
83
  const handleInputChange = () => {
86
84
  if (!inputRef.current) return;
87
-
85
+
88
86
  const selection = window.getSelection();
89
87
  const range = selection?.getRangeAt(0);
90
-
88
+
91
89
  let newCaretOffset = 0;
92
90
  if (range && inputRef.current.contains(range.startContainer)) {
93
91
  const preCaretRange = range.cloneRange();
94
92
  preCaretRange.selectNodeContents(inputRef.current);
95
93
  preCaretRange.setEnd(range.startContainer, range.startOffset);
96
- newCaretOffset = preCaretRange.toString().length; // Calculate caret offset
94
+ newCaretOffset = preCaretRange.toString().length;
97
95
  }
98
-
99
- caretOffsetRef.current = newCaretOffset; // Update ref directly
100
-
96
+
97
+ caretOffsetRef.current = newCaretOffset;
98
+
101
99
  const plainText = inputRef.current.innerText;
102
100
  setInputValue(plainText);
103
-
104
- // Handle mentions and suggestions
101
+
105
102
  const mentionMatch = plainText.slice(0, newCaretOffset).match(/@(\S*)$/);
106
103
  if (mentionMatch) {
107
104
  const query = mentionMatch[1].toLowerCase();
108
105
  const filteredUsers = query === "" ? users : users.filter((user) => user.name.toLowerCase().startsWith(query));
109
-
106
+
110
107
  setSuggestions(filteredUsers);
111
108
  setShowSuggestions(filteredUsers.length > 0);
112
109
  } else {
113
110
  setShowSuggestions(false);
114
111
  }
115
-
112
+
116
113
  const previousHTML = inputRef.current.innerHTML;
117
114
  const htmlWithHighlights = highlightMentions(plainText);
118
115
  if (previousHTML !== htmlWithHighlights) {
119
116
  inputRef.current.innerHTML = htmlWithHighlights;
120
117
  }
121
-
122
- restoreCaretPosition(inputRef.current, newCaretOffset);
123
- };
124
118
 
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
- 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
- }
119
+ restoreCaretPosition(inputRef.current, newCaretOffset);
157
120
  };
158
121
 
159
122
  const renderSuggestions = () => {
@@ -162,11 +125,11 @@ const MentionInput: React.FC<MentionInputProps> = ({
162
125
  const inputRect = inputRef.current.getBoundingClientRect();
163
126
  const styles: React.CSSProperties = {
164
127
  position: 'absolute',
165
- zIndex: 1000
128
+ zIndex: 1000,
166
129
  };
167
130
 
168
- // Adjust position based on calculated tooltip position
169
- switch (tooltipPosition) {
131
+ // Use suggestionPosition prop to adjust tooltip position
132
+ switch (suggestionPosition) {
170
133
  case 'top':
171
134
  styles.left = `${inputRect.left}px`;
172
135
  styles.top = `${inputRect.top - 150}px`;
@@ -187,7 +150,6 @@ const MentionInput: React.FC<MentionInputProps> = ({
187
150
  break;
188
151
  }
189
152
 
190
- console.log("document.body",document.body);
191
153
  return ReactDOM.createPortal(
192
154
  <ul
193
155
  className={`suggestion-list ${suggestionListClassName || ''}`}
@@ -211,6 +173,48 @@ const MentionInput: React.FC<MentionInputProps> = ({
211
173
  );
212
174
  };
213
175
 
176
+ const handleSuggestionClick = (user: User) => {
177
+ if (!inputRef.current) return;
178
+
179
+ const plainText = inputValue;
180
+ const caretOffset = caretOffsetRef.current;
181
+ const mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
182
+
183
+ if (!mentionMatch) return;
184
+
185
+ const mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
186
+
187
+ const newValue =
188
+ plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
189
+
190
+ setInputValue(newValue);
191
+ inputRef.current.innerText = newValue;
192
+
193
+ const htmlWithHighlights = highlightMentions(newValue);
194
+ inputRef.current.innerHTML = htmlWithHighlights;
195
+
196
+ setShowSuggestions(false);
197
+
198
+ const mentionEnd = mentionIndex + user.name.length + 1;
199
+ restoreCaretPosition(inputRef.current, mentionEnd);
200
+ };
201
+
202
+ const handleSendMessage = () => {
203
+ if (inputRef.current) {
204
+ const messageText = inputRef.current.innerText.trim(); // Plain text
205
+ const messageHTML = inputRef.current.innerHTML.trim(); // HTML with <span> highlighting
206
+
207
+ if (messageText && onSendMessage) {
208
+ onSendMessage({messageText, messageHTML}); // Pass both plain text and HTML
209
+ setInputValue(""); // Clear state
210
+ setShowSuggestions(false); // Hide suggestions
211
+ inputRef.current.innerText = ""; // Clear input field
212
+ }
213
+ }
214
+ };
215
+
216
+
217
+
214
218
  return (
215
219
  <div className={`mention-container ${containerClassName || ""}`}>
216
220
  <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
-