@@ -4,7 +4,7 @@ import lodashDebounce from 'lodash/debounce';
44import React , { useCallback , useEffect , useImperativeHandle , useMemo , useRef , useState } from 'react' ;
55import type { TextInputKeyPressEvent , TextInputSelectionChangeEvent } from 'react-native' ;
66import { DeviceEventEmitter , StyleSheet } from 'react-native' ;
7- import type { ComposerProps , ComposerRef } from '@components/Composer/types' ;
7+ import type { ComposerProps } from '@components/Composer/types' ;
88import { useSession } from '@components/OnyxListItemProvider' ;
99import type { AnimatedMarkdownTextInputRef } from '@components/RNMarkdownTextInput' ;
1010import RNMarkdownTextInput from '@components/RNMarkdownTextInput' ;
@@ -64,7 +64,7 @@ function Composer({
6464 const addAuthTokenToImageURL = useCallback ( ( url : string ) => addEncryptedAuthTokenToURL ( url , encryptedAuthToken ) , [ encryptedAuthToken ] ) ;
6565 const markdownStyle = useMarkdownStyle ( textContainsOnlyEmojis , ! isGroupPolicyReport ? excludeReportMentionStyle : excludeNoStyles ) ;
6666 const StyleUtils = useStyleUtils ( ) ;
67- const textInputRef = useRef < AnimatedMarkdownTextInputRef | null > ( null ) ;
67+ const textInput = useRef < AnimatedMarkdownTextInputRef | null > ( null ) ;
6868 const [ selection , setSelection ] = useState <
6969 | {
7070 start : number ;
@@ -79,7 +79,7 @@ function Composer({
7979 } ) ;
8080 const [ isRendered , setIsRendered ] = useState ( false ) ;
8181
82- const isScrollBarVisible = useIsScrollBarVisible ( textInputRef , value ?? '' ) ;
82+ const isScrollBarVisible = useIsScrollBarVisible ( textInput , value ?? '' ) ;
8383 const [ prevScroll , setPrevScroll ] = useState < number | undefined > ( ) ;
8484 const [ prevHeight , setPrevHeight ] = useState < number | undefined > ( ) ;
8585 const isReportFlatListScrolling = useRef ( false ) ;
@@ -95,26 +95,19 @@ function Composer({
9595 /**
9696 * Adds the cursor position to the selection change event.
9797 */
98- const addCursorPositionToSelectionChange = useCallback (
99- ( event : TextInputSelectionChangeEvent ) => {
100- const sel = window . getSelection ( ) ;
101- const canCalculateCaretPosition = shouldCalculateCaretPosition && isRendered && sel ;
102- if ( ! canCalculateCaretPosition ) {
103- onSelectionChange ( event ) ;
104- setSelection ( event . nativeEvent . selection ) ;
105- return ;
106- }
107-
98+ const addCursorPositionToSelectionChange = ( event : TextInputSelectionChangeEvent ) => {
99+ const sel = window . getSelection ( ) ;
100+ if ( shouldCalculateCaretPosition && isRendered && sel ) {
108101 const range = sel . getRangeAt ( 0 ) . cloneRange ( ) ;
109102 range . collapse ( true ) ;
110103 const rect = range . getClientRects ( ) [ 0 ] || range . startContainer . parentElement ?. getClientRects ( ) [ 0 ] ;
111- const containerRect = textInputRef . current ?. getBoundingClientRect ( ) ;
104+ const containerRect = textInput . current ?. getBoundingClientRect ( ) ;
112105
113106 let x = 0 ;
114107 let y = 0 ;
115108 if ( rect && containerRect ) {
116109 x = rect . left - containerRect . left ;
117- y = rect . top - containerRect . top + ( textInputRef ?. current ?. scrollTop ?? 0 ) - rect . height / 2 ;
110+ y = rect . top - containerRect . top + ( textInput ?. current ?. scrollTop ?? 0 ) - rect . height / 2 ;
118111 }
119112
120113 const selectionValue = {
@@ -132,9 +125,11 @@ function Composer({
132125 } ,
133126 } ) ;
134127 setSelection ( selectionValue ) ;
135- } ,
136- [ isRendered , onSelectionChange , shouldCalculateCaretPosition ] ,
137- ) ;
128+ } else {
129+ onSelectionChange ( event ) ;
130+ setSelection ( event . nativeEvent . selection ) ;
131+ }
132+ } ;
138133
139134 /**
140135 * Check the paste event for an attachment, parse the data and call onPasteFile from props with the selected file,
@@ -143,14 +138,14 @@ function Composer({
143138 const handlePaste = useCallback (
144139 ( event : ClipboardEvent ) => {
145140 const isVisible = checkComposerVisibility ( ) ;
146- const isFocused = textInputRef . current ?. isFocused ( ) ;
141+ const isFocused = textInput . current ?. isFocused ( ) ;
147142 const isContenteditableDivFocused = document . activeElement ?. nodeName === 'DIV' && document . activeElement ?. hasAttribute ( 'contenteditable' ) ;
148143
149144 if ( ! ( isVisible || isFocused ) ) {
150145 return true ;
151146 }
152147
153- if ( textInputRef . current !== event . target && ! ( isContenteditableDivFocused && ! event . clipboardData ?. files . length ) ) {
148+ if ( textInput . current !== event . target && ! ( isContenteditableDivFocused && ! event . clipboardData ?. files . length ) ) {
154149 const eventTarget = event . target as HTMLInputElement | HTMLTextAreaElement | null ;
155150 // To make sure the composer does not capture paste events from other inputs, we check where the event originated
156151 // If it did originate in another input, we return early to prevent the composer from handling the paste
@@ -159,7 +154,7 @@ function Composer({
159154 return true ;
160155 }
161156
162- textInputRef . current ?. focus ( ) ;
157+ textInput . current ?. focus ( ) ;
163158 }
164159
165160 event . preventDefault ( ) ;
@@ -214,23 +209,19 @@ function Composer({
214209 ) ;
215210
216211 useEffect ( ( ) => {
217- if ( ! textInputRef . current ) {
212+ if ( ! textInput . current ) {
218213 return ;
219214 }
220-
221- const inputRef = textInputRef . current ;
222-
223215 const debouncedSetPrevScroll = lodashDebounce ( ( ) => {
224- if ( ! inputRef ) {
216+ if ( ! textInput . current ) {
225217 return ;
226218 }
227- setPrevScroll ( inputRef . scrollTop ) ;
219+ setPrevScroll ( textInput . current . scrollTop ) ;
228220 } , 100 ) ;
229221
230- inputRef . addEventListener ( 'scroll' , debouncedSetPrevScroll ) ;
231-
222+ textInput . current . addEventListener ( 'scroll' , debouncedSetPrevScroll ) ;
232223 return ( ) => {
233- inputRef ?. removeEventListener ( 'scroll' , debouncedSetPrevScroll ) ;
224+ textInput . current ?. removeEventListener ( 'scroll' , debouncedSetPrevScroll ) ;
234225 } ;
235226 } , [ ] ) ;
236227
@@ -243,8 +234,6 @@ function Composer({
243234 } , [ ] ) ;
244235
245236 useEffect ( ( ) => {
246- const inputRef = textInputRef . current ;
247-
248237 const handleWheel = ( e : MouseEvent ) => {
249238 if ( isReportFlatListScrolling . current ) {
250239 e . preventDefault ( ) ;
@@ -253,40 +242,40 @@ function Composer({
253242
254243 // When the composer has no scrollable content, the stopPropagation will prevent the inverted wheel event handler on the Chat body
255244 // which defaults to the browser wheel behavior. This causes the chat body to scroll in the opposite direction creating jerky behavior.
256- if ( inputRef && inputRef . scrollHeight <= inputRef . clientHeight ) {
245+ if ( textInput . current && textInput . current . scrollHeight <= textInput . current . clientHeight ) {
257246 return ;
258247 }
259248 e . stopPropagation ( ) ;
260249 } ;
261- inputRef ?. addEventListener ( 'wheel' , handleWheel , { passive : false } ) ;
250+ textInput . current ?. addEventListener ( 'wheel' , handleWheel , { passive : false } ) ;
262251
263252 return ( ) => {
264- inputRef ?. removeEventListener ( 'wheel' , handleWheel ) ;
253+ textInput . current ?. removeEventListener ( 'wheel' , handleWheel ) ;
265254 } ;
266255 } , [ ] ) ;
267256
268257 useEffect ( ( ) => {
269- if ( ! textInputRef . current || prevScroll === undefined || prevHeight === undefined ) {
258+ if ( ! textInput . current || prevScroll === undefined || prevHeight === undefined ) {
270259 return ;
271260 }
272- textInputRef . current . scrollTop = prevScroll + prevHeight - textInputRef . current . clientHeight ;
261+ textInput . current . scrollTop = prevScroll + prevHeight - textInput . current . clientHeight ;
273262 // eslint-disable-next-line react-hooks/exhaustive-deps
274263 } , [ isComposerFullSize ] ) ;
275264
276265 const isActive = useIsFocused ( ) ;
277- useHtmlPaste ( textInputRef , handlePaste , isActive ) ;
266+ useHtmlPaste ( textInput , handlePaste , isActive ) ;
278267
279268 useEffect ( ( ) => {
280269 setIsRendered ( true ) ;
281270 } , [ ] ) ;
282271
283272 const clear = useCallback ( ( ) => {
284- if ( ! textInputRef . current ) {
273+ if ( ! textInput . current ) {
285274 return ;
286275 }
287276
288- const currentText = textInputRef . current . value ;
289- textInputRef . current . clear ( ) ;
277+ const currentText = textInput . current . value ;
278+ textInput . current . clear ( ) ;
290279
291280 // We need to reset the selection to 0,0 manually after clearing the text input on web
292281 const selectionEvent = {
@@ -304,22 +293,22 @@ function Composer({
304293 } , [ onClear , onSelectionChange ] ) ;
305294
306295 useImperativeHandle ( ref , ( ) => {
307- const textInput = textInputRef . current ;
308- if ( ! textInput ) {
309- throw new Error ( 'textInput is not available. This should never happen and indicates a developer error.' ) ;
296+ const textInputRef = textInput . current ;
297+ if ( ! textInputRef ) {
298+ throw new Error ( 'textInputRef is not available. This should never happen and indicates a developer error.' ) ;
310299 }
311300
312301 return {
313- ...textInput ,
302+ ...textInputRef ,
314303 // Overwrite clear with our custom implementation, which mimics how the native TextInput's clear method works
315304 clear,
316305 // We have to redefine these methods as they are inherited by prototype chain and are not accessible directly
317- blur : ( ) => textInput . blur ( ) ,
318- focus : ( ) => textInput . focus ( ) ,
306+ blur : ( ) => textInputRef . blur ( ) ,
307+ focus : ( ) => textInputRef . focus ( ) ,
319308 get scrollTop ( ) {
320- return textInput . scrollTop ;
309+ return textInputRef . scrollTop ;
321310 } ,
322- } as ComposerRef ;
311+ } ;
323312 } , [ clear ] ) ;
324313
325314 const handleKeyPress = useCallback (
@@ -361,7 +350,9 @@ function Composer({
361350 autoComplete = "off"
362351 autoCorrect = { ! isMobileSafari ( ) }
363352 placeholderTextColor = { theme . placeholderText }
364- ref = { textInputRef }
353+ ref = { ( el ) => {
354+ textInput . current = el ;
355+ } }
365356 selection = { selection }
366357 style = { [ inputStyleMemo ] }
367358 markdownStyle = { markdownStyle }
0 commit comments