react-mention-input 1.1.24 → 1.1.26

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.
@@ -27,7 +27,6 @@ interface MentionInputProps {
27
27
  name: string;
28
28
  }[];
29
29
  userSelectListName: string[];
30
- tags: string[];
31
30
  images?: File[];
32
31
  imageUrl?: string | null;
33
32
  }) => void;
@@ -34,12 +34,21 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
34
34
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
35
  }
36
36
  };
37
- import React, { useState, useRef } from "react";
37
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
38
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
39
+ if (ar || !(i in from)) {
40
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
41
+ ar[i] = from[i];
42
+ }
43
+ }
44
+ return to.concat(ar || Array.prototype.slice.call(from));
45
+ };
46
+ import React, { useState, useRef, useEffect } from "react";
38
47
  import ReactDOM from "react-dom";
39
48
  import "./MentionInput.css";
40
49
  var MentionInput = function (_a) {
41
50
  var _b;
42
- var users = _a.users, _c = _a.placeholder, placeholder = _c === void 0 ? "Type a message... (or drag & drop an image)" : _c, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, imgClassName = _a.imgClassName, imgStyle = _a.imgStyle, attachedImageContainerClassName = _a.attachedImageContainerClassName, attachedImageContainerStyle = _a.attachedImageContainerStyle, sendButtonIcon = _a.sendButtonIcon, attachmentButtonIcon = _a.attachmentButtonIcon, onSendMessage = _a.onSendMessage, _d = _a.suggestionPosition, suggestionPosition = _d === void 0 ? 'bottom' : _d, onImageUpload = _a.onImageUpload;
51
+ var users = _a.users, _c = _a.placeholder, placeholder = _c === void 0 ? "Type a message... (or drag & drop an image)" : _c, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, imgClassName = _a.imgClassName, imgStyle = _a.imgStyle, attachedImageContainerClassName = _a.attachedImageContainerClassName, attachedImageContainerStyle = _a.attachedImageContainerStyle, sendButtonIcon = _a.sendButtonIcon, attachmentButtonIcon = _a.attachmentButtonIcon, onSendMessage = _a.onSendMessage, _d = _a.suggestionPosition, suggestionPosition = _d === void 0 ? 'top' : _d, onImageUpload = _a.onImageUpload;
43
52
  var _e = useState(""), inputValue = _e[0], setInputValue = _e[1]; // Plain text
44
53
  var _f = useState([]), suggestions = _f[0], setSuggestions = _f[1];
45
54
  var _g = useState(false), showSuggestions = _g[0], setShowSuggestions = _g[1];
@@ -52,19 +61,12 @@ var MentionInput = function (_a) {
52
61
  var caretOffsetRef = useRef(0);
53
62
  var userSelectListRef = useRef([]); // Only unique names
54
63
  var userSelectListWithIdsRef = useRef([]); // Unique IDs with names
55
- var tagsListRef = useRef([]); // Store hashtags
56
64
  var fileInputRef = useRef(null);
57
65
  var highlightMentionsAndLinks = function (text) {
58
66
  // Regular expression for detecting links
59
67
  var linkRegex = /(https?:\/\/[^\s]+)/g;
60
- // Regular expression for detecting hashtags
61
- var hashtagRegex = /#[\w]+/g;
62
68
  // Highlight links
63
69
  var highlightedText = text.replace(linkRegex, '<a href="$1" target="_blank" rel="noopener noreferrer" class="link-highlight">$1</a>');
64
- // Highlight hashtags
65
- highlightedText = highlightedText.replace(hashtagRegex, function (match) {
66
- return "<span class=\"hashtag-highlight\">".concat(match, "</span>");
67
- });
68
70
  // Highlight mentions manually based on `userSelectListRef`
69
71
  userSelectListRef === null || userSelectListRef === void 0 ? void 0 : userSelectListRef.current.forEach(function (userName) {
70
72
  var mentionPattern = new RegExp("@".concat(userName, "(\\s|$)"), "g");
@@ -74,6 +76,24 @@ var MentionInput = function (_a) {
74
76
  });
75
77
  return highlightedText;
76
78
  };
79
+ useEffect(function () {
80
+ var handleClickOutside = function (event) {
81
+ var target = event.target;
82
+ if (showSuggestions &&
83
+ inputRef.current &&
84
+ !inputRef.current.contains(target) &&
85
+ suggestionListRef.current &&
86
+ !suggestionListRef.current.contains(target)) {
87
+ setShowSuggestions(false);
88
+ }
89
+ };
90
+ if (showSuggestions) {
91
+ document.addEventListener("mousedown", handleClickOutside);
92
+ }
93
+ return function () {
94
+ document.removeEventListener("mousedown", handleClickOutside);
95
+ };
96
+ }, [showSuggestions]);
77
97
  var restoreCaretPosition = function (node, caretOffset) {
78
98
  var range = document.createRange();
79
99
  var sel = window.getSelection();
@@ -106,19 +126,80 @@ var MentionInput = function (_a) {
106
126
  sel.addRange(range);
107
127
  }
108
128
  };
109
- var handleInputChange = function () {
129
+ var getCurrentCaretOffset = function () {
110
130
  if (!inputRef.current)
111
- return;
112
- // Store current selection before modifications
131
+ return 0;
113
132
  var selection = window.getSelection();
114
- var range = selection === null || selection === void 0 ? void 0 : selection.getRangeAt(0);
115
- var newCaretOffset = 0;
133
+ var range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
116
134
  if (range && inputRef.current.contains(range.startContainer)) {
117
135
  var preCaretRange = range.cloneRange();
118
136
  preCaretRange.selectNodeContents(inputRef.current);
119
137
  preCaretRange.setEnd(range.startContainer, range.startOffset);
120
- newCaretOffset = preCaretRange.toString().length;
138
+ return preCaretRange.toString().length;
139
+ }
140
+ return caretOffsetRef.current;
141
+ };
142
+ var findMentionAtOffset = function (plainText, caretOffset, direction) {
143
+ if (!userSelectListRef.current.length)
144
+ return null;
145
+ var names = __spreadArray([], userSelectListRef.current, true).sort(function (a, b) { return b.length - a.length; });
146
+ for (var _i = 0, names_1 = names; _i < names_1.length; _i++) {
147
+ var name_1 = names_1[_i];
148
+ var pattern = "@".concat(name_1);
149
+ var searchIndex = plainText.indexOf(pattern);
150
+ while (searchIndex !== -1) {
151
+ var endIndex = searchIndex + pattern.length;
152
+ if (plainText[endIndex] === " " ||
153
+ plainText[endIndex] === "\u00a0") {
154
+ endIndex += 1;
155
+ }
156
+ var isCaretInsideMention = direction === "backward"
157
+ ? caretOffset > searchIndex && caretOffset <= endIndex
158
+ : caretOffset >= searchIndex && caretOffset < endIndex;
159
+ if (isCaretInsideMention) {
160
+ return { name: name_1, start: searchIndex, end: endIndex };
161
+ }
162
+ searchIndex = plainText.indexOf(pattern, searchIndex + 1);
163
+ }
164
+ }
165
+ return null;
166
+ };
167
+ var removeMentionToken = function (direction) {
168
+ if (!inputRef.current)
169
+ return false;
170
+ var plainText = inputRef.current.innerText;
171
+ var caretOffset = getCurrentCaretOffset();
172
+ var mentionInfo = findMentionAtOffset(plainText, caretOffset, direction);
173
+ if (!mentionInfo)
174
+ return false;
175
+ var newText = plainText.slice(0, mentionInfo.start) + plainText.slice(mentionInfo.end);
176
+ var hasLinks = !!newText.match(/(https?:\/\/[^\s]+)/g);
177
+ if (userSelectListRef.current.length > 0 || hasLinks) {
178
+ var htmlWithHighlights = highlightMentionsAndLinks(newText);
179
+ inputRef.current.innerHTML = htmlWithHighlights;
180
+ }
181
+ else {
182
+ inputRef.current.innerText = newText;
121
183
  }
184
+ setInputValue(newText);
185
+ setShowSuggestions(false);
186
+ var newCaretOffset = mentionInfo.start;
187
+ caretOffsetRef.current = newCaretOffset;
188
+ if (inputRef.current) {
189
+ restoreCaretPosition(inputRef.current, newCaretOffset);
190
+ }
191
+ if (!newText.includes("@".concat(mentionInfo.name))) {
192
+ userSelectListRef.current = userSelectListRef.current.filter(function (storedName) { return storedName !== mentionInfo.name; });
193
+ userSelectListWithIdsRef.current =
194
+ userSelectListWithIdsRef.current.filter(function (user) { return user.name !== mentionInfo.name; });
195
+ }
196
+ return true;
197
+ };
198
+ var handleInputChange = function () {
199
+ if (!inputRef.current)
200
+ return;
201
+ // Store current selection before modifications
202
+ var newCaretOffset = getCurrentCaretOffset();
122
203
  caretOffsetRef.current = newCaretOffset;
123
204
  var plainText = inputRef.current.innerText;
124
205
  setInputValue(plainText);
@@ -135,17 +216,8 @@ var MentionInput = function (_a) {
135
216
  else {
136
217
  setShowSuggestions(false);
137
218
  }
138
- // Extract and store hashtags
139
- var hashtagMatches = plainText.match(/#[\w]+/g);
140
- if (hashtagMatches) {
141
- var uniqueTags = Array.from(new Set(hashtagMatches));
142
- tagsListRef.current = uniqueTags;
143
- }
144
- else {
145
- tagsListRef.current = [];
146
- }
147
- // Only apply highlighting if we have mentions, hashtags, or links to highlight
148
- if (userSelectListRef.current.length > 0 || plainText.match(/(https?:\/\/[^\s]+)/g) || plainText.match(/#[\w]+/g)) {
219
+ // Only apply highlighting if we have mentions or links to highlight
220
+ if (userSelectListRef.current.length > 0 || plainText.match(/(https?:\/\/[^\s]+)/g)) {
149
221
  var currentHTML = inputRef.current.innerHTML;
150
222
  var htmlWithHighlights = highlightMentionsAndLinks(plainText);
151
223
  // Only update if the highlighted HTML is different to avoid cursor jumping
@@ -157,6 +229,7 @@ var MentionInput = function (_a) {
157
229
  }
158
230
  };
159
231
  var renderSuggestions = function () {
232
+ var _a, _b, _c, _d, _e, _f;
160
233
  if (!showSuggestions || !inputRef.current)
161
234
  return null;
162
235
  var getInitials = function (name) {
@@ -168,27 +241,32 @@ var MentionInput = function (_a) {
168
241
  return initials;
169
242
  };
170
243
  var inputRect = inputRef.current.getBoundingClientRect();
244
+ var scrollLeft = (_c = (_b = (_a = window.scrollX) !== null && _a !== void 0 ? _a : window.pageXOffset) !== null && _b !== void 0 ? _b : document.documentElement.scrollLeft) !== null && _c !== void 0 ? _c : 0;
245
+ var scrollTop = (_f = (_e = (_d = window.scrollY) !== null && _d !== void 0 ? _d : window.pageYOffset) !== null && _e !== void 0 ? _e : document.documentElement.scrollTop) !== null && _f !== void 0 ? _f : 0;
171
246
  var styles = {
172
247
  position: 'absolute',
173
248
  zIndex: 1000,
249
+ minWidth: inputRect.width,
174
250
  };
175
251
  // Use suggestionPosition prop to adjust tooltip position
176
252
  switch (suggestionPosition) {
177
253
  case 'top':
178
- styles.left = "".concat(inputRect.left, "px");
179
- styles.top = "".concat(inputRect.top - 150, "px");
254
+ styles.left = "".concat(inputRect.left + scrollLeft, "px");
255
+ styles.top = "".concat(inputRect.top + scrollTop - 8, "px");
256
+ styles.transform = 'translateY(-100%)';
180
257
  break;
181
258
  case 'bottom':
182
- styles.left = "".concat(inputRect.left, "px");
183
- styles.top = "".concat(inputRect.bottom, "px");
259
+ styles.left = "".concat(inputRect.left + scrollLeft, "px");
260
+ styles.top = "".concat(inputRect.bottom + scrollTop + 8, "px");
184
261
  break;
185
262
  case 'left':
186
- styles.left = "".concat(inputRect.left - 150, "px");
187
- styles.top = "".concat(inputRect.top, "px");
263
+ styles.left = "".concat(inputRect.left + scrollLeft - 8, "px");
264
+ styles.top = "".concat(inputRect.top + scrollTop, "px");
265
+ styles.transform = "".concat(styles.transform ? "".concat(styles.transform, " ") : '', "translateX(-100%)");
188
266
  break;
189
267
  case 'right':
190
- styles.left = "".concat(inputRect.right, "px");
191
- styles.top = "".concat(inputRect.top, "px");
268
+ styles.left = "".concat(inputRect.right + scrollLeft + 8, "px");
269
+ styles.top = "".concat(inputRect.top + scrollTop, "px");
192
270
  break;
193
271
  default:
194
272
  break;
@@ -333,7 +411,6 @@ var MentionInput = function (_a) {
333
411
  messageHTML: messageHTML,
334
412
  userSelectListWithIds: userSelectListWithIdsRef.current,
335
413
  userSelectListName: userSelectListRef.current,
336
- tags: tagsListRef.current,
337
414
  images: selectedImage ? [selectedImage] : [],
338
415
  imageUrl: imageUrl
339
416
  });
@@ -344,7 +421,6 @@ var MentionInput = function (_a) {
344
421
  setImageUrl(null);
345
422
  userSelectListRef.current = [];
346
423
  userSelectListWithIdsRef.current = [];
347
- tagsListRef.current = [];
348
424
  }
349
425
  }
350
426
  };
@@ -352,6 +428,19 @@ var MentionInput = function (_a) {
352
428
  if (event.key === "Enter" && !event.shiftKey) {
353
429
  event.preventDefault(); // Prevent newline in content-editable
354
430
  handleSendMessage(); // Trigger the same function as the Send button
431
+ return;
432
+ }
433
+ if (event.key === "Backspace") {
434
+ var removed = removeMentionToken("backward");
435
+ if (removed) {
436
+ event.preventDefault();
437
+ }
438
+ }
439
+ if (event.key === "Delete") {
440
+ var removed = removeMentionToken("forward");
441
+ if (removed) {
442
+ event.preventDefault();
443
+ }
355
444
  }
356
445
  };
357
446
  return (React.createElement("div", { className: "mention-container ".concat(containerClassName || "") },
@@ -367,7 +456,7 @@ var MentionInput = function (_a) {
367
456
  React.createElement("div", { className: "mention-input-wrapper" },
368
457
  (!inputValue || !inputRef.current || ((_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.innerText.trim()) === "") && (React.createElement("span", { className: "placeholder" }, placeholder)),
369
458
  React.createElement("div", { ref: inputRef, contentEditable: true, suppressContentEditableWarning: true, className: "mention-input ".concat(inputClassName || ""), onInput: handleInputChange, onKeyDown: handleKeyDown, onFocus: function () { return document.execCommand('styleWithCSS', false, 'false'); } })),
370
- React.createElement("button", { onClick: handleSendMessage, className: "send-button ".concat(sendBtnClassName || ""), "aria-label": "Send message" }, sendButtonIcon || ""),
459
+ React.createElement("button", { onClick: handleSendMessage, className: "send-button ".concat(sendBtnClassName || ""), "aria-label": "Send message" }, sendButtonIcon || ""),
371
460
  React.createElement("input", { type: "file", ref: fileInputRef, accept: "image/*", onChange: handleImageSelect, style: { display: 'none' } }),
372
461
  isUploading && (React.createElement("div", { className: "upload-loading" },
373
462
  React.createElement("span", null, "Uploading...")))),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mention-input",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, ReactNode } from "react";
1
+ import React, { useState, useRef, ReactNode, useEffect } from "react";
2
2
  import ReactDOM from "react-dom";
3
3
  import "./MentionInput.css";
4
4
 
@@ -27,7 +27,6 @@ interface MentionInputProps {
27
27
  messageHTML: string;
28
28
  userSelectListWithIds: { id: number; name: string }[];
29
29
  userSelectListName: string[];
30
- tags: string[];
31
30
  images?: File[];
32
31
  imageUrl?: string | null;
33
32
  }) => void;
@@ -51,7 +50,7 @@ const MentionInput: React.FC<MentionInputProps> = ({
51
50
  sendButtonIcon,
52
51
  attachmentButtonIcon,
53
52
  onSendMessage,
54
- suggestionPosition = 'bottom',
53
+ suggestionPosition = 'top',
55
54
  onImageUpload,
56
55
  }) => {
57
56
  const [inputValue, setInputValue] = useState<string>(""); // Plain text
@@ -69,29 +68,17 @@ const MentionInput: React.FC<MentionInputProps> = ({
69
68
  const caretOffsetRef = useRef<number>(0);
70
69
  const userSelectListRef = useRef<string[]>([]); // Only unique names
71
70
  const userSelectListWithIdsRef = useRef<{ id: number; name: string }[]>([]); // Unique IDs with names
72
- const tagsListRef = useRef<string[]>([]); // Store hashtags
73
71
  const fileInputRef = useRef<HTMLInputElement>(null);
74
72
 
75
73
  const highlightMentionsAndLinks = (text: string): string => {
76
74
  // Regular expression for detecting links
77
75
  const linkRegex = /(https?:\/\/[^\s]+)/g;
78
-
79
- // Regular expression for detecting hashtags
80
- const hashtagRegex = /#[\w]+/g;
81
76
 
82
77
  // Highlight links
83
78
  let highlightedText = text.replace(
84
79
  linkRegex,
85
80
  '<a href="$1" target="_blank" rel="noopener noreferrer" class="link-highlight">$1</a>'
86
81
  );
87
-
88
- // Highlight hashtags
89
- highlightedText = highlightedText.replace(
90
- hashtagRegex,
91
- (match) => {
92
- return `<span class="hashtag-highlight">${match}</span>`;
93
- }
94
- );
95
82
 
96
83
  // Highlight mentions manually based on `userSelectListRef`
97
84
  userSelectListRef?.current.forEach((userName) => {
@@ -106,6 +93,29 @@ const MentionInput: React.FC<MentionInputProps> = ({
106
93
 
107
94
  return highlightedText;
108
95
  };
96
+
97
+ useEffect(() => {
98
+ const handleClickOutside = (event: MouseEvent) => {
99
+ const target = event.target as Node;
100
+ if (
101
+ showSuggestions &&
102
+ inputRef.current &&
103
+ !inputRef.current.contains(target) &&
104
+ suggestionListRef.current &&
105
+ !suggestionListRef.current.contains(target)
106
+ ) {
107
+ setShowSuggestions(false);
108
+ }
109
+ };
110
+
111
+ if (showSuggestions) {
112
+ document.addEventListener("mousedown", handleClickOutside);
113
+ }
114
+
115
+ return () => {
116
+ document.removeEventListener("mousedown", handleClickOutside);
117
+ };
118
+ }, [showSuggestions]);
109
119
 
110
120
 
111
121
  const restoreCaretPosition = (node: HTMLElement, caretOffset: number) => {
@@ -139,21 +149,112 @@ const MentionInput: React.FC<MentionInputProps> = ({
139
149
  }
140
150
  };
141
151
 
142
- const handleInputChange = () => {
143
- if (!inputRef.current) return;
144
-
145
- // Store current selection before modifications
152
+ const getCurrentCaretOffset = (): number => {
153
+ if (!inputRef.current) return 0;
146
154
  const selection = window.getSelection();
147
- const range = selection?.getRangeAt(0);
148
-
149
- let newCaretOffset = 0;
155
+ const range =
156
+ selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
157
+
150
158
  if (range && inputRef.current.contains(range.startContainer)) {
151
159
  const preCaretRange = range.cloneRange();
152
160
  preCaretRange.selectNodeContents(inputRef.current);
153
161
  preCaretRange.setEnd(range.startContainer, range.startOffset);
154
- newCaretOffset = preCaretRange.toString().length;
162
+ return preCaretRange.toString().length;
155
163
  }
156
-
164
+
165
+ return caretOffsetRef.current;
166
+ };
167
+
168
+ const findMentionAtOffset = (
169
+ plainText: string,
170
+ caretOffset: number,
171
+ direction: "backward" | "forward"
172
+ ): { name: string; start: number; end: number } | null => {
173
+ if (!userSelectListRef.current.length) return null;
174
+
175
+ const names = [...userSelectListRef.current].sort(
176
+ (a, b) => b.length - a.length
177
+ );
178
+
179
+ for (const name of names) {
180
+ const pattern = `@${name}`;
181
+ let searchIndex = plainText.indexOf(pattern);
182
+
183
+ while (searchIndex !== -1) {
184
+ let endIndex = searchIndex + pattern.length;
185
+ if (
186
+ plainText[endIndex] === " " ||
187
+ plainText[endIndex] === "\u00a0"
188
+ ) {
189
+ endIndex += 1;
190
+ }
191
+
192
+ const isCaretInsideMention =
193
+ direction === "backward"
194
+ ? caretOffset > searchIndex && caretOffset <= endIndex
195
+ : caretOffset >= searchIndex && caretOffset < endIndex;
196
+
197
+ if (isCaretInsideMention) {
198
+ return { name, start: searchIndex, end: endIndex };
199
+ }
200
+
201
+ searchIndex = plainText.indexOf(pattern, searchIndex + 1);
202
+ }
203
+ }
204
+
205
+ return null;
206
+ };
207
+
208
+ const removeMentionToken = (direction: "backward" | "forward"): boolean => {
209
+ if (!inputRef.current) return false;
210
+
211
+ const plainText = inputRef.current.innerText;
212
+ const caretOffset = getCurrentCaretOffset();
213
+ const mentionInfo = findMentionAtOffset(plainText, caretOffset, direction);
214
+
215
+ if (!mentionInfo) return false;
216
+
217
+ const newText =
218
+ plainText.slice(0, mentionInfo.start) + plainText.slice(mentionInfo.end);
219
+
220
+ const hasLinks = !!newText.match(/(https?:\/\/[^\s]+)/g);
221
+
222
+ if (userSelectListRef.current.length > 0 || hasLinks) {
223
+ const htmlWithHighlights = highlightMentionsAndLinks(newText);
224
+ inputRef.current.innerHTML = htmlWithHighlights;
225
+ } else {
226
+ inputRef.current.innerText = newText;
227
+ }
228
+
229
+ setInputValue(newText);
230
+ setShowSuggestions(false);
231
+
232
+ const newCaretOffset = mentionInfo.start;
233
+ caretOffsetRef.current = newCaretOffset;
234
+
235
+ if (inputRef.current) {
236
+ restoreCaretPosition(inputRef.current, newCaretOffset);
237
+ }
238
+
239
+ if (!newText.includes(`@${mentionInfo.name}`)) {
240
+ userSelectListRef.current = userSelectListRef.current.filter(
241
+ (storedName) => storedName !== mentionInfo.name
242
+ );
243
+ userSelectListWithIdsRef.current =
244
+ userSelectListWithIdsRef.current.filter(
245
+ (user) => user.name !== mentionInfo.name
246
+ );
247
+ }
248
+
249
+ return true;
250
+ };
251
+
252
+ const handleInputChange = () => {
253
+ if (!inputRef.current) return;
254
+
255
+ // Store current selection before modifications
256
+ const newCaretOffset = getCurrentCaretOffset();
257
+
157
258
  caretOffsetRef.current = newCaretOffset;
158
259
 
159
260
  const plainText = inputRef.current.innerText;
@@ -173,17 +274,8 @@ const MentionInput: React.FC<MentionInputProps> = ({
173
274
  setShowSuggestions(false);
174
275
  }
175
276
 
176
- // Extract and store hashtags
177
- const hashtagMatches = plainText.match(/#[\w]+/g);
178
- if (hashtagMatches) {
179
- const uniqueTags = Array.from(new Set(hashtagMatches));
180
- tagsListRef.current = uniqueTags;
181
- } else {
182
- tagsListRef.current = [];
183
- }
184
-
185
- // Only apply highlighting if we have mentions, hashtags, or links to highlight
186
- if (userSelectListRef.current.length > 0 || plainText.match(/(https?:\/\/[^\s]+)/g) || plainText.match(/#[\w]+/g)) {
277
+ // Only apply highlighting if we have mentions or links to highlight
278
+ if (userSelectListRef.current.length > 0 || plainText.match(/(https?:\/\/[^\s]+)/g)) {
187
279
  const currentHTML = inputRef.current.innerHTML;
188
280
  const htmlWithHighlights = highlightMentionsAndLinks(plainText);
189
281
 
@@ -210,28 +302,35 @@ const MentionInput: React.FC<MentionInputProps> = ({
210
302
  };
211
303
 
212
304
  const inputRect = inputRef.current.getBoundingClientRect();
305
+ const scrollLeft =
306
+ window.scrollX ?? window.pageXOffset ?? document.documentElement.scrollLeft ?? 0;
307
+ const scrollTop =
308
+ window.scrollY ?? window.pageYOffset ?? document.documentElement.scrollTop ?? 0;
213
309
  const styles: React.CSSProperties = {
214
310
  position: 'absolute',
215
311
  zIndex: 1000,
312
+ minWidth: inputRect.width,
216
313
  };
217
314
 
218
315
  // Use suggestionPosition prop to adjust tooltip position
219
316
  switch (suggestionPosition) {
220
317
  case 'top':
221
- styles.left = `${inputRect.left}px`;
222
- styles.top = `${inputRect.top - 150}px`;
318
+ styles.left = `${inputRect.left + scrollLeft}px`;
319
+ styles.top = `${inputRect.top + scrollTop - 8}px`;
320
+ styles.transform = 'translateY(-100%)';
223
321
  break;
224
322
  case 'bottom':
225
- styles.left = `${inputRect.left}px`;
226
- styles.top = `${inputRect.bottom}px`;
323
+ styles.left = `${inputRect.left + scrollLeft}px`;
324
+ styles.top = `${inputRect.bottom + scrollTop + 8}px`;
227
325
  break;
228
326
  case 'left':
229
- styles.left = `${inputRect.left - 150}px`;
230
- styles.top = `${inputRect.top}px`;
327
+ styles.left = `${inputRect.left + scrollLeft - 8}px`;
328
+ styles.top = `${inputRect.top + scrollTop}px`;
329
+ styles.transform = `${styles.transform ? `${styles.transform} ` : ''}translateX(-100%)`;
231
330
  break;
232
331
  case 'right':
233
- styles.left = `${inputRect.right}px`;
234
- styles.top = `${inputRect.top}px`;
332
+ styles.left = `${inputRect.right + scrollLeft + 8}px`;
333
+ styles.top = `${inputRect.top + scrollTop}px`;
235
334
  break;
236
335
  default:
237
336
  break;
@@ -395,7 +494,6 @@ const MentionInput: React.FC<MentionInputProps> = ({
395
494
  messageHTML,
396
495
  userSelectListWithIds: userSelectListWithIdsRef.current,
397
496
  userSelectListName: userSelectListRef.current,
398
- tags: tagsListRef.current,
399
497
  images: selectedImage ? [selectedImage] : [],
400
498
  imageUrl: imageUrl
401
499
  });
@@ -406,7 +504,6 @@ const MentionInput: React.FC<MentionInputProps> = ({
406
504
  setImageUrl(null);
407
505
  userSelectListRef.current = [];
408
506
  userSelectListWithIdsRef.current = [];
409
- tagsListRef.current = [];
410
507
  }
411
508
  }
412
509
  };
@@ -415,6 +512,21 @@ const MentionInput: React.FC<MentionInputProps> = ({
415
512
  if (event.key === "Enter" && !event.shiftKey) {
416
513
  event.preventDefault(); // Prevent newline in content-editable
417
514
  handleSendMessage(); // Trigger the same function as the Send button
515
+ return;
516
+ }
517
+
518
+ if (event.key === "Backspace") {
519
+ const removed = removeMentionToken("backward");
520
+ if (removed) {
521
+ event.preventDefault();
522
+ }
523
+ }
524
+
525
+ if (event.key === "Delete") {
526
+ const removed = removeMentionToken("forward");
527
+ if (removed) {
528
+ event.preventDefault();
529
+ }
418
530
  }
419
531
  };
420
532
 
@@ -478,7 +590,7 @@ const MentionInput: React.FC<MentionInputProps> = ({
478
590
  className={`send-button ${sendBtnClassName || ""}`}
479
591
  aria-label="Send message"
480
592
  >
481
- {sendButtonIcon || ""}
593
+ {sendButtonIcon || ""}
482
594
  </button>
483
595
 
484
596
  <input