@@ -28,20 +28,27 @@ import { OpenChannel } from '@sendbird/chat/openChannel';
28
28
import { UserMessage } from '@sendbird/chat/message' ;
29
29
30
30
const TEXT_FIELD_ID = 'sendbird-message-input-text-field' ;
31
- const LINE_HEIGHT = 76 ;
32
- const DEFAULT_CHAT_VIEW_HEIGHT = 600 ;
33
31
const noop = ( ) => {
34
32
return null ;
35
33
} ;
36
34
37
- const displayCaret = ( element : HTMLInputElement , position : number ) => {
38
- const range = document . createRange ( ) ;
39
- const sel = window . getSelection ( ) ;
40
- range . setStart ( element . childNodes [ 0 ] , position ) ;
41
- range . collapse ( true ) ;
42
- sel ?. removeAllRanges ( ) ;
43
- sel ?. addRange ( range ) ;
44
- element . focus ( ) ;
35
+ const scrollToCaret = ( ) => {
36
+ const selection = window . getSelection ( ) ;
37
+ if ( selection && selection . rangeCount > 0 ) {
38
+ const range = selection . getRangeAt ( 0 ) ;
39
+ const caretNode = range . endContainer ;
40
+
41
+ // Ensure the caret is in a text node
42
+ if ( caretNode . nodeType === NodeTypes . TextNode ) {
43
+ const parentElement = caretNode . parentElement ;
44
+
45
+ // Scroll the parent element of the caret into view
46
+ parentElement ?. scrollIntoView ?.( {
47
+ behavior : 'smooth' ,
48
+ block : 'nearest' ,
49
+ } ) ;
50
+ }
51
+ }
45
52
} ;
46
53
47
54
const resetInput = ( ref : MutableRefObject < HTMLInputElement | null > | null ) => {
@@ -133,7 +140,7 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
133
140
acceptableMimeTypes,
134
141
} = props ;
135
142
136
- const internalRef = ( externalRef && 'current' in externalRef ) ? externalRef : null ;
143
+ const internalRef = ( externalRef && 'current' in externalRef ) ? externalRef : useRef ( null ) ;
137
144
const ghostInputRef = useRef < HTMLInputElement > ( null ) ;
138
145
139
146
const textFieldId = messageFieldId || TEXT_FIELD_ID ;
@@ -149,46 +156,15 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
149
156
const [ isInput , setIsInput ] = useState ( false ) ;
150
157
const [ mentionedUserIds , setMentionedUserIds ] = useState < string [ ] > ( [ ] ) ;
151
158
const [ targetStringInfo , setTargetStringInfo ] = useState ( { ...initialTargetStringInfo } ) ;
152
- const setHeight = useCallback (
153
- ( ) => {
154
- const elem = internalRef ?. current ;
155
- if ( ! elem ) return ;
156
-
157
- try {
158
- const estimatedChatViewHeight = window . document . body . offsetHeight || DEFAULT_CHAT_VIEW_HEIGHT ;
159
- const MAX_HEIGHT = estimatedChatViewHeight * 0.6 ;
160
- if ( elem . scrollHeight >= LINE_HEIGHT ) {
161
- if ( MAX_HEIGHT < elem . scrollHeight ) {
162
- elem . style . height = 'auto' ;
163
- elem . style . height = `${ MAX_HEIGHT } px` ;
164
- } else {
165
- elem . style . height = '' ;
166
- }
167
- }
168
- } catch ( error ) {
169
- // error
170
- }
171
- } ,
172
- [ ] ,
173
- ) ;
174
159
175
160
// #Edit mode
176
- // for easilly initialize input value from outside, but
161
+ // for easily initialize input value from outside, but
177
162
// useEffect(_, [channelUrl]) erase it
178
163
const initialValue = props ?. value ;
179
164
useEffect ( ( ) => {
180
165
const textField = internalRef ?. current ;
181
- try {
182
- if ( textField && initialValue ) {
183
- textField . innerHTML = initialValue ;
184
- displayCaret ( textField , initialValue ?. length ) ;
185
- }
186
- } catch {
187
- //
188
- }
189
166
setMentionedUserIds ( [ ] ) ;
190
167
setIsInput ( textField ?. textContent ? textField . textContent . trim ( ) . length > 0 : false ) ;
191
- setHeight ( ) ;
192
168
} , [ initialValue ] ) ;
193
169
194
170
// #Mention | Clear input value when channel changes
@@ -243,7 +219,6 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
243
219
setMentionedUserIds ( [ ] ) ;
244
220
}
245
221
setIsInput ( textField ?. textContent ? textField ?. textContent ?. trim ( ) . length > 0 : false ) ;
246
- setHeight ( ) ;
247
222
}
248
223
} , [ isEdit , message ] ) ;
249
224
@@ -314,7 +289,6 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
314
289
textField . focus ( ) ;
315
290
}
316
291
setTargetStringInfo ( { ...initialTargetStringInfo } ) ;
317
- setHeight ( ) ;
318
292
useMentionedLabelDetection ( ) ;
319
293
}
320
294
}
@@ -411,7 +385,6 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
411
385
}
412
386
413
387
setIsInput ( false ) ;
414
- setHeight ( ) ;
415
388
}
416
389
} catch ( error ) {
417
390
eventHandlers ?. message ?. onSendMessageFailed ?.( message , error ) ;
@@ -437,7 +410,6 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
437
410
setMentionedUsers,
438
411
channel,
439
412
setIsInput,
440
- setHeight,
441
413
} ) ;
442
414
443
415
const uploadFile = ( event : React . ChangeEvent < HTMLInputElement > ) => {
@@ -453,6 +425,12 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
453
425
}
454
426
} ;
455
427
428
+ const handleCommonBehavior = ( handleEvent ) => {
429
+ scrollToCaret ( ) ;
430
+ type CommonEvent < T > = React . KeyboardEvent < T > | React . MouseEvent < T > | React . ClipboardEvent < T > ;
431
+ return ( e : CommonEvent < HTMLDivElement > ) => handleEvent ( e ) ;
432
+ } ;
433
+
456
434
return (
457
435
< form className = { classnames (
458
436
...( Array . isArray ( className ) ? className : [ className ] ) ,
@@ -474,11 +452,11 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
474
452
contentEditable = { ! disabled }
475
453
role = "textbox"
476
454
aria-label = "Text Input"
477
- ref = { externalRef }
455
+ ref = { internalRef }
478
456
// @ts -ignore
479
457
disabled = { disabled }
480
458
maxLength = { maxLength }
481
- onKeyDown = { ( e ) => {
459
+ onKeyDown = { handleCommonBehavior ( ( e ) => {
482
460
const preventEvent = onKeyDown ( e ) ;
483
461
if ( preventEvent ) {
484
462
e . preventDefault ( ) ;
@@ -509,25 +487,24 @@ const MessageInput = React.forwardRef<HTMLInputElement, MessageInputProps>((prop
509
487
internalRef . current . removeChild ( internalRef . current . childNodes [ 1 ] ) ;
510
488
}
511
489
}
512
- } }
513
- onKeyUp = { ( e ) => {
490
+ } ) }
491
+ onKeyUp = { handleCommonBehavior ( ( e ) => {
514
492
const preventEvent = onKeyUp ( e ) ;
515
493
if ( preventEvent ) {
516
494
e . preventDefault ( ) ;
517
495
} else {
518
496
useMentionInputDetection ( ) ;
519
497
}
520
- } }
521
- onClick = { ( ) => {
498
+ } ) }
499
+ onClick = { handleCommonBehavior ( ( ) => {
522
500
useMentionInputDetection ( ) ;
523
- } }
524
- onInput = { ( ) => {
525
- setHeight ( ) ;
501
+ } ) }
502
+ onInput = { handleCommonBehavior ( ( ) => {
526
503
onStartTyping ( ) ;
527
504
setIsInput ( internalRef ?. current ?. textContent ? internalRef . current . textContent . trim ( ) . length > 0 : false ) ;
528
505
useMentionedLabelDetection ( ) ;
529
- } }
530
- onPaste = { onPaste }
506
+ } ) }
507
+ onPaste = { handleCommonBehavior ( onPaste ) }
531
508
/>
532
509
{ /* placeholder */ }
533
510
{ ( internalRef ?. current ?. textContent ?. length ?? 0 ) === 0 && (
0 commit comments