1
- import { useRef , useEffect } from 'react' ;
1
+ import { useRef , useEffect , useState , forwardRef , useImperativeHandle } from 'react' ;
2
2
import { useSelector } from 'react-redux' ;
3
3
import cn from 'classnames' ;
4
4
5
5
import { GlobalState } from 'src/store/types' ;
6
6
7
+ import { getCaretIndex , isFirefox , updateCaret , insertNodeAtCaret , getSelection } from '../../../../../../utils/contentEditable'
7
8
const send = require ( '../../../../../../../assets/send_button.svg' ) as string ;
9
+ const emoji = require ( '../../../../../../../assets/icon-smiley.svg' ) as string ;
10
+ const brRegex = / < b r > / g;
8
11
9
12
import './style.scss' ;
10
13
@@ -14,41 +17,123 @@ type Props = {
14
17
autofocus : boolean ;
15
18
sendMessage : ( event : any ) => void ;
16
19
buttonAlt : string ;
20
+ onPressEmoji : ( ) => void ;
21
+ onChangeSize : ( event : any ) => void ;
17
22
onTextInputChange ?: ( event : any ) => void ;
18
23
}
19
24
20
- function Sender ( { sendMessage, placeholder, disabledInput, autofocus, onTextInputChange, buttonAlt } : Props ) {
25
+ function Sender ( { sendMessage, placeholder, disabledInput, autofocus, onTextInputChange, buttonAlt, onPressEmoji , onChangeSize } : Props , ref ) {
21
26
const showChat = useSelector ( ( state : GlobalState ) => state . behavior . showChat ) ;
22
- const inputRef = useRef < HTMLSpanElement > ( null ) ;
27
+ const inputRef = useRef < HTMLDivElement > ( null ! ) ;
28
+ const refContainer = useRef < HTMLDivElement > ( null ) ;
29
+ const [ enter , setEnter ] = useState ( false )
30
+ const [ firefox , setFirefox ] = useState ( false ) ;
31
+ const [ height , setHeight ] = useState ( 0 )
23
32
// @ts -ignore
24
33
useEffect ( ( ) => { if ( showChat && autofocus ) inputRef . current ?. focus ( ) ; } , [ showChat ] ) ;
34
+ useEffect ( ( ) => { setFirefox ( isFirefox ( ) ) } , [ ] )
35
+
36
+ useImperativeHandle ( ref , ( ) => {
37
+ return {
38
+ onSelectEmoji : handlerOnSelectEmoji ,
39
+ } ;
40
+ } ) ;
25
41
26
42
const handlerOnChange = ( event ) => {
27
43
onTextInputChange && onTextInputChange ( event )
28
44
}
29
45
30
46
const handlerSendMessage = ( ) => {
31
- const { current } = inputRef
32
- if ( current ? .innerHTML ) {
33
- sendMessage ( current . innerText ) ;
34
- current . innerHTML = ''
47
+ const el = inputRef . current ;
48
+ if ( el . innerHTML ) {
49
+ sendMessage ( el . innerText ) ;
50
+ el . innerHTML = ''
35
51
}
36
52
}
37
53
54
+ const handlerOnSelectEmoji = ( emoji ) => {
55
+ const el = inputRef . current ;
56
+ const { start, end } = getSelection ( el )
57
+ if ( el . innerHTML ) {
58
+ const firstPart = el . innerHTML . substring ( 0 , start ) ;
59
+ const secondPart = el . innerHTML . substring ( end ) ;
60
+ el . innerHTML = ( `${ firstPart } ${ emoji . native } ${ secondPart } ` )
61
+ } else {
62
+ el . innerHTML = emoji . native
63
+ }
64
+ updateCaret ( el , start , emoji . native . length )
65
+ }
66
+
38
67
const handlerOnKeyPress = ( event ) => {
68
+ const el = inputRef . current ;
69
+
39
70
if ( event . charCode == 13 && ! event . shiftKey ) {
40
71
event . preventDefault ( )
41
72
handlerSendMessage ( ) ;
42
73
}
74
+ if ( event . charCode === 13 && event . shiftKey ) {
75
+ event . preventDefault ( )
76
+ insertNodeAtCaret ( el ) ;
77
+ setEnter ( true )
78
+ }
79
+ }
80
+
81
+ // TODO use a context for checkSize and toggle picker
82
+ const checkSize = ( ) => {
83
+ const senderEl = refContainer . current
84
+ if ( senderEl && height !== senderEl . clientHeight ) {
85
+ const { clientHeight} = senderEl ;
86
+ setHeight ( clientHeight )
87
+ onChangeSize ( clientHeight ? clientHeight - 1 : 0 )
88
+ }
89
+ }
90
+
91
+ const handlerOnKeyUp = ( event ) => {
92
+ const el = inputRef . current ;
93
+ if ( ! el ) return true ;
94
+ // Conditions need for firefox
95
+ if ( firefox && event . key === 'Backspace' ) {
96
+ if ( el . innerHTML . length === 1 && enter ) {
97
+ el . innerHTML = '' ;
98
+ setEnter ( false ) ;
99
+ }
100
+ else if ( brRegex . test ( el . innerHTML ) ) {
101
+ el . innerHTML = el . innerHTML . replace ( brRegex , '' ) ;
102
+ }
103
+ }
104
+ checkSize ( ) ;
105
+ }
106
+
107
+ const handlerOnKeyDown = ( event ) => {
108
+ const el = inputRef . current ;
109
+
110
+ if ( event . key === 'Backspace' && el ) {
111
+ const caretPosition = getCaretIndex ( inputRef . current ) ;
112
+ const character = el . innerHTML . charAt ( caretPosition - 1 ) ;
113
+ if ( character === "\n" ) {
114
+ event . preventDefault ( ) ;
115
+ event . stopPropagation ( ) ;
116
+ el . innerHTML = ( el . innerHTML . substring ( 0 , caretPosition - 1 ) + el . innerHTML . substring ( caretPosition ) )
117
+ updateCaret ( el , caretPosition , - 1 )
118
+ }
119
+ }
120
+ }
121
+
122
+ const handlerPressEmoji = ( ) => {
123
+ onPressEmoji ( ) ;
124
+ checkSize ( ) ;
43
125
}
44
126
45
127
return (
46
- < div className = "rcw-sender" >
128
+ < div ref = { refContainer } className = "rcw-sender" >
129
+ < button className = 'rcw-picker-btn' type = "submit" onClick = { handlerPressEmoji } >
130
+ < img src = { emoji } className = "rcw-picker-icon" alt = "" />
131
+ </ button >
47
132
< div className = { cn ( 'rcw-new-message' , {
48
133
'rcw-message-disable' : disabledInput ,
49
134
} )
50
135
} >
51
- < span
136
+ < div
52
137
spellCheck
53
138
className = "rcw-input"
54
139
role = "textbox"
@@ -57,7 +142,10 @@ function Sender({ sendMessage, placeholder, disabledInput, autofocus, onTextInpu
57
142
placeholder = { placeholder }
58
143
onInput = { handlerOnChange }
59
144
onKeyPress = { handlerOnKeyPress }
145
+ onKeyUp = { handlerOnKeyUp }
146
+ onKeyDown = { handlerOnKeyDown }
60
147
/>
148
+
61
149
</ div >
62
150
< button type = "submit" className = "rcw-send" onClick = { handlerSendMessage } >
63
151
< img src = { send } className = "rcw-send-icon" alt = { buttonAlt } />
@@ -66,4 +154,4 @@ function Sender({ sendMessage, placeholder, disabledInput, autofocus, onTextInpu
66
154
) ;
67
155
}
68
156
69
- export default Sender ;
157
+ export default forwardRef ( Sender ) ;
0 commit comments