react-native-gifted-chat 2.7.3 → 2.8.1

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.
Files changed (47) hide show
  1. package/README.md +14 -6
  2. package/lib/Bubble/index.d.ts +2 -26
  3. package/lib/Bubble/index.js +151 -123
  4. package/lib/Bubble/index.js.map +1 -1
  5. package/lib/Day/index.d.ts +1 -1
  6. package/lib/Day/index.js +2 -2
  7. package/lib/Day/index.js.map +1 -1
  8. package/lib/GiftedChat/index.d.ts +1 -1
  9. package/lib/GiftedChat/index.js +1 -2
  10. package/lib/GiftedChat/index.js.map +1 -1
  11. package/lib/GiftedChat/types.d.ts +7 -7
  12. package/lib/GiftedChatContext.d.ts +1 -2
  13. package/lib/GiftedChatContext.js +3 -3
  14. package/lib/GiftedChatContext.js.map +1 -1
  15. package/lib/InputToolbar.js +1 -0
  16. package/lib/InputToolbar.js.map +1 -1
  17. package/lib/Message/index.js +7 -12
  18. package/lib/Message/index.js.map +1 -1
  19. package/lib/Message/types.d.ts +2 -2
  20. package/lib/MessageContainer/components/Item/index.d.ts +2 -1
  21. package/lib/MessageContainer/components/Item/index.js +5 -6
  22. package/lib/MessageContainer/components/Item/index.js.map +1 -1
  23. package/lib/MessageContainer/components/Item/types.d.ts +4 -5
  24. package/lib/MessageContainer/index.js +61 -33
  25. package/lib/MessageContainer/index.js.map +1 -1
  26. package/lib/MessageContainer/types.d.ts +5 -5
  27. package/lib/MessageText.js +2 -2
  28. package/lib/MessageText.js.map +1 -1
  29. package/lib/Time.d.ts +1 -1
  30. package/lib/Time.js +3 -3
  31. package/lib/Time.js.map +1 -1
  32. package/package.json +13 -15
  33. package/src/Bubble/index.tsx +170 -146
  34. package/src/Day/index.tsx +2 -2
  35. package/src/GiftedChat/index.tsx +7 -9
  36. package/src/GiftedChat/types.ts +7 -7
  37. package/src/GiftedChatContext.ts +3 -3
  38. package/src/InputToolbar.tsx +1 -0
  39. package/src/Message/index.tsx +10 -16
  40. package/src/Message/types.ts +2 -2
  41. package/src/MessageContainer/components/Item/index.tsx +5 -16
  42. package/src/MessageContainer/components/Item/types.ts +4 -5
  43. package/src/MessageContainer/index.tsx +98 -54
  44. package/src/MessageContainer/types.ts +5 -4
  45. package/src/MessageText.tsx +2 -2
  46. package/src/Time.tsx +5 -3
  47. package/src/__tests__/__snapshots__/MessageContainer.test.tsx.snap +72 -279
@@ -3,7 +3,7 @@ import { AvatarProps } from '../Avatar'
3
3
  import { SystemMessageProps } from '../SystemMessage'
4
4
  import { DayProps } from '../Day'
5
5
  import { IMessage, User, LeftRightStyle } from '../types'
6
- import Bubble from '../Bubble'
6
+ import { BubbleProps } from '../Bubble'
7
7
 
8
8
  export interface MessageProps<TMessage extends IMessage> {
9
9
  showUserAvatar?: boolean
@@ -14,7 +14,7 @@ export interface MessageProps<TMessage extends IMessage> {
14
14
  user: User
15
15
  inverted?: boolean
16
16
  containerStyle?: LeftRightStyle<ViewStyle>
17
- renderBubble?(props: Bubble['props']): React.ReactNode
17
+ renderBubble?(props: BubbleProps<TMessage>): React.ReactNode
18
18
  renderDay?(props: DayProps): React.ReactNode
19
19
  renderSystemMessage?(props: SystemMessageProps<TMessage>): React.ReactNode
20
20
  renderAvatar?(props: AvatarProps<TMessage>): React.ReactNode
@@ -91,9 +91,8 @@ const DayWrapper = forwardRef<View, MessageProps<IMessage>>((props, ref) => {
91
91
  )
92
92
  })
93
93
 
94
- const Item = (props: ItemProps) => {
94
+ const Item = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {
95
95
  const {
96
- onRefDayWrapper,
97
96
  renderMessage: renderMessageProp,
98
97
  scrolledY,
99
98
  daysPositions,
@@ -134,29 +133,19 @@ const Item = (props: ItemProps) => {
134
133
  ),
135
134
  }), [relativeScrolledPositionToBottomOfDay, dayContainerHeight, dayTopOffset])
136
135
 
137
- const handleRef = useCallback((ref: unknown) => {
138
- onRefDayWrapper(
139
- ref,
140
- props.currentMessage._id,
141
- new Date(props.currentMessage.createdAt).getTime()
142
- )
143
- }, [onRefDayWrapper, props.currentMessage])
144
-
145
136
  return (
137
+ // do not remove key. it helps to get correct position of the day container
146
138
  <View key={props.currentMessage._id.toString()}>
147
139
  <Animated.View
148
140
  style={style}
149
141
  onLayout={handleLayoutDayContainer}
150
142
  >
151
- <DayWrapper
152
- {...rest as MessageProps<IMessage>}
153
- ref={handleRef}
154
- />
143
+ <DayWrapper {...rest as MessageProps<TMessage>} />
155
144
  </Animated.View>
156
145
  {
157
146
  renderMessageProp
158
- ? renderMessageProp(rest as MessageProps<IMessage>)
159
- : <Message {...rest as MessageProps<IMessage>} />
147
+ ? renderMessageProp(rest as MessageProps<TMessage>)
148
+ : <Message {...rest as MessageProps<TMessage>} />
160
149
  }
161
150
  </View>
162
151
  )
@@ -1,11 +1,10 @@
1
1
  import { MessageContainerProps, DaysPositions } from '../../types'
2
2
  import { IMessage } from '../../../types'
3
3
 
4
- export interface ItemProps extends MessageContainerProps<IMessage> {
5
- onRefDayWrapper: (ref: unknown, id: string | number, createdAt: number) => void
6
- currentMessage: IMessage
7
- previousMessage?: IMessage
8
- nextMessage?: IMessage
4
+ export interface ItemProps<TMessage extends IMessage> extends MessageContainerProps<TMessage> {
5
+ currentMessage: TMessage
6
+ previousMessage?: TMessage
7
+ nextMessage?: TMessage
9
8
  position: 'left' | 'right'
10
9
  scrolledY: { value: number }
11
10
  daysPositions: { value: DaysPositions }
@@ -1,12 +1,14 @@
1
- import React, { useCallback, useMemo, useState } from 'react'
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react'
2
2
  import {
3
3
  View,
4
4
  TouchableOpacity,
5
5
  Text,
6
6
  Platform,
7
7
  LayoutChangeEvent,
8
+ ListRenderItemInfo,
9
+ FlatList,
10
+ CellRendererProps,
8
11
  } from 'react-native'
9
- import { FlashList, ListRenderItemInfo } from '@shopify/flash-list'
10
12
  import Animated, { runOnJS, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
11
13
  import { ReanimatedScrollEvent } from 'react-native-reanimated/lib/typescript/hook/commonTypes'
12
14
  import DayAnimated from './components/DayAnimated'
@@ -21,10 +23,11 @@ import { ItemProps } from './components/Item/types'
21
23
  import { warning } from '../logging'
22
24
  import stylesCommon from '../styles'
23
25
  import styles from './styles'
26
+ import { isSameDay } from '../utils'
24
27
 
25
28
  export * from './types'
26
29
 
27
- const AnimatedFlashList = Animated.createAnimatedComponent(FlashList)
30
+ const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
28
31
 
29
32
  function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageContainerProps<TMessage>) {
30
33
  const {
@@ -77,7 +80,7 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
77
80
  }, [renderFooterProp, renderTypingIndicator, props])
78
81
 
79
82
  const renderLoadEarlier = useCallback(() => {
80
- if (loadEarlier === true) {
83
+ if (loadEarlier) {
81
84
  if (renderLoadEarlierProp)
82
85
  return renderLoadEarlierProp(props)
83
86
 
@@ -136,31 +139,6 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
136
139
  makeScrollToBottomHidden()
137
140
  }, [handleOnScrollProp, inverted, scrollToBottomOffset, scrollToBottomOpacity])
138
141
 
139
- const handleLayoutDayWrapper = useCallback((ref: unknown, id: string | number, createdAt: number) => {
140
- setTimeout(() => { // do not delete "setTimeout". It's necessary for get correct layout.
141
- const itemLayout = forwardRef?.current?.recyclerlistview_unsafe?.getLayout(messages.findIndex(m => m._id === id))
142
-
143
- if (ref && itemLayout)
144
- daysPositions.modify(value => {
145
- 'worklet'
146
-
147
- // @ts-expect-error: https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue#remarks
148
- value[id] = {
149
- ...itemLayout,
150
- createdAt,
151
- }
152
- return value
153
- })
154
- else if (daysPositions.value[id] != null)
155
- daysPositions.modify(value => {
156
- 'worklet'
157
-
158
- delete value[id]
159
- return value
160
- })
161
- }, 100)
162
- }, [messages, daysPositions, forwardRef])
163
-
164
142
  const renderItem = useCallback(({ item, index }: ListRenderItemInfo<unknown>): React.ReactElement | null => {
165
143
  const messageItem = item as TMessage
166
144
 
@@ -185,25 +163,24 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
185
163
  const nextMessage =
186
164
  (inverted ? messages[index - 1] : messages[index + 1]) || {}
187
165
 
188
- const messageProps: ItemProps = {
166
+ const messageProps: ItemProps<TMessage> = {
189
167
  ...restProps,
190
168
  currentMessage: messageItem,
191
169
  previousMessage,
192
170
  nextMessage,
193
171
  position: messageItem.user._id === user._id ? 'right' : 'left',
194
- onRefDayWrapper: handleLayoutDayWrapper,
195
172
  scrolledY,
196
173
  daysPositions,
197
174
  listHeight,
198
175
  }
199
176
 
200
177
  return (
201
- <Item {...messageProps} />
178
+ <Item<TMessage> {...messageProps} />
202
179
  )
203
180
  }
204
181
 
205
182
  return null
206
- }, [props, inverted, handleLayoutDayWrapper, scrolledY, daysPositions, listHeight, user])
183
+ }, [props, inverted, scrolledY, daysPositions, listHeight, user])
207
184
 
208
185
  const renderChatEmpty = useCallback(() => {
209
186
  if (renderChatEmptyProp)
@@ -243,21 +220,18 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
243
220
  return null
244
221
 
245
222
  return (
246
- <Animated.View
247
- style={[
248
- stylesCommon.centerItems,
249
- styles.scrollToBottomStyle,
250
- scrollToBottomStyle,
251
- scrollToBottomStyleAnim,
252
- ]}
253
- >
254
- <TouchableOpacity
255
- onPress={() => doScrollToBottom()}
256
- hitSlop={{ top: 5, left: 5, right: 5, bottom: 5 }}
223
+ <TouchableOpacity onPress={() => doScrollToBottom()}>
224
+ <Animated.View
225
+ style={[
226
+ stylesCommon.centerItems,
227
+ styles.scrollToBottomStyle,
228
+ scrollToBottomStyle,
229
+ scrollToBottomStyleAnim,
230
+ ]}
257
231
  >
258
232
  {renderScrollBottomComponent()}
259
- </TouchableOpacity>
260
- </Animated.View>
233
+ </Animated.View>
234
+ </TouchableOpacity>
261
235
  )
262
236
  }, [scrollToBottomStyle, renderScrollBottomComponent, doScrollToBottom, scrollToBottomStyleAnim, isScrollToBottomVisible])
263
237
 
@@ -288,6 +262,54 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
288
262
 
289
263
  const keyExtractor = useCallback((item: unknown) => (item as TMessage)._id.toString(), [])
290
264
 
265
+ const renderCell = useCallback((props: CellRendererProps<unknown>) => {
266
+ const handleOnLayout = (event: LayoutChangeEvent) => {
267
+ props.onLayout?.(event)
268
+
269
+ const { y, height } = event.nativeEvent.layout
270
+
271
+ const newValue = {
272
+ y,
273
+ height,
274
+ createdAt: new Date((props.item as IMessage).createdAt).getTime(),
275
+ }
276
+
277
+ daysPositions.modify(value => {
278
+ 'worklet'
279
+
280
+ const isSameDay = (date1: number, date2: number) => {
281
+ const d1 = new Date(date1)
282
+ const d2 = new Date(date2)
283
+
284
+ return (
285
+ d1.getDate() === d2.getDate() &&
286
+ d1.getMonth() === d2.getMonth() &&
287
+ d1.getFullYear() === d2.getFullYear()
288
+ )
289
+ }
290
+
291
+ for (const [key, item] of Object.entries(value))
292
+ if (isSameDay(newValue.createdAt, item.createdAt) && (inverted ? item.y <= newValue.y : item.y >= newValue.y)) {
293
+ delete value[key]
294
+ break
295
+ }
296
+
297
+ // @ts-expect-error: https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue#remarks
298
+ value[props.item._id] = newValue
299
+ return value
300
+ })
301
+ }
302
+
303
+ return (
304
+ <View
305
+ {...props}
306
+ onLayout={handleOnLayout}
307
+ >
308
+ {props.children}
309
+ </View>
310
+ )
311
+ }, [daysPositions, inverted])
312
+
291
313
  const scrollHandler = useAnimatedScrollHandler({
292
314
  onScroll: event => {
293
315
  scrolledY.value = event.contentOffset.y
@@ -296,6 +318,28 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
296
318
  },
297
319
  }, [handleOnScroll])
298
320
 
321
+ // removes unrendered days positions when messages are added/removed
322
+ useEffect(() => {
323
+ Object.keys(daysPositions.value).forEach(key => {
324
+ const messageIndex = messages.findIndex(m => m._id.toString() === key)
325
+ let shouldRemove = messageIndex === -1
326
+
327
+ if (!shouldRemove) {
328
+ const prevMessage = messages[messageIndex + (inverted ? 1 : -1)]
329
+ const message = messages[messageIndex]
330
+ shouldRemove = !!prevMessage && isSameDay(message, prevMessage)
331
+ }
332
+
333
+ if (shouldRemove)
334
+ daysPositions.modify(value => {
335
+ 'worklet'
336
+
337
+ delete value[key]
338
+ return value
339
+ })
340
+ })
341
+ }, [messages, daysPositions, inverted])
342
+
299
343
  return (
300
344
  <View
301
345
  style={[
@@ -303,15 +347,15 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
303
347
  alignTop ? styles.containerAlignTop : stylesCommon.fill,
304
348
  ]}
305
349
  >
306
- <AnimatedFlashList
307
- ref={forwardRef}
308
- extraData={[extraData, isTyping]}
350
+ <AnimatedFlatList
351
+ extraData={extraData}
352
+ ref={forwardRef as React.Ref<FlatList<unknown>>}
309
353
  keyExtractor={keyExtractor}
310
- automaticallyAdjustContentInsets={false}
311
- inverted={inverted}
312
354
  data={messages}
313
- style={stylesCommon.fill}
314
355
  renderItem={renderItem}
356
+ inverted={inverted}
357
+ automaticallyAdjustContentInsets={false}
358
+ style={stylesCommon.fill}
315
359
  {...invertibleScrollViewProps}
316
360
  ListEmptyComponent={renderChatEmpty}
317
361
  ListFooterComponent={
@@ -321,12 +365,12 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
321
365
  inverted ? ListFooterComponent : ListHeaderComponent
322
366
  }
323
367
  onScroll={scrollHandler}
324
- scrollEventThrottle={16}
368
+ scrollEventThrottle={1}
325
369
  onEndReached={onEndReached}
326
370
  onEndReachedThreshold={0.1}
327
- estimatedItemSize={100}
328
371
  {...listViewProps}
329
372
  onLayout={onLayoutList}
373
+ CellRendererComponent={renderCell}
330
374
  />
331
375
  {isScrollToBottomEnabled
332
376
  ? renderScrollToBottomWrapper()
@@ -1,25 +1,26 @@
1
1
  import React, { Component, RefObject } from 'react'
2
2
  import {
3
+ FlatListProps,
3
4
  LayoutChangeEvent,
4
5
  StyleProp,
5
6
  ViewStyle,
6
7
  } from 'react-native'
7
- import { FlashList, FlashListProps } from '@shopify/flash-list'
8
8
 
9
9
  import { LoadEarlierProps } from '../LoadEarlier'
10
10
  import { MessageProps } from '../Message'
11
11
  import { User, IMessage, Reply } from '../types'
12
12
  import { ReanimatedScrollEvent } from 'react-native-reanimated/lib/typescript/hook/commonTypes'
13
+ import { FlatList } from 'react-native-reanimated/lib/typescript/Animated'
13
14
  import { AnimateProps } from 'react-native-reanimated'
14
15
 
15
16
  export type ListViewProps = {
16
17
  onLayout?: (event: LayoutChangeEvent) => void
17
18
  } & object
18
19
 
19
- export type AnimatedList = Component<AnimateProps<FlashListProps<unknown>>, unknown, unknown> & FlashList<FlashListProps<unknown>>
20
+ export type AnimatedList<TMessage> = Component<AnimateProps<FlatListProps<TMessage>>, unknown, unknown> & FlatList<FlatListProps<TMessage>>
20
21
 
21
- export interface MessageContainerProps<TMessage extends IMessage> {
22
- forwardRef?: RefObject<AnimatedList | null>
22
+ export interface MessageContainerProps<TMessage extends IMessage = IMessage> {
23
+ forwardRef?: RefObject<AnimatedList<TMessage>>
23
24
  messages?: TMessage[]
24
25
  isTyping?: boolean
25
26
  user?: User
@@ -140,13 +140,13 @@ export function MessageText<TMessage extends IMessage = IMessage> ({
140
140
  <View
141
141
  style={[
142
142
  styles[position].container,
143
- containerStyle && containerStyle[position],
143
+ containerStyle?.[position],
144
144
  ]}
145
145
  >
146
146
  <ParsedText
147
147
  style={[
148
148
  styles[position].text,
149
- textStyle && textStyle[position],
149
+ textStyle?.[position],
150
150
  customTextStyle,
151
151
  ]}
152
152
  parse={[
package/src/Time.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import * as React from 'react'
1
+ import React from 'react'
2
2
  import { StyleSheet, Text, View, ViewStyle, TextStyle } from 'react-native'
3
3
  import dayjs from 'dayjs'
4
4
 
@@ -14,6 +14,7 @@ const { containerStyle } = StyleSheet.create({
14
14
  marginBottom: 5,
15
15
  },
16
16
  })
17
+
17
18
  const { textStyle } = StyleSheet.create({
18
19
  textStyle: {
19
20
  fontSize: 10,
@@ -58,6 +59,7 @@ export function Time<TMessage extends IMessage = IMessage> ({
58
59
  timeTextStyle,
59
60
  }: TimeProps<TMessage>) {
60
61
  const { getLocale } = useChatContext()
62
+
61
63
  if (currentMessage == null)
62
64
  return null
63
65
 
@@ -65,13 +67,13 @@ export function Time<TMessage extends IMessage = IMessage> ({
65
67
  <View
66
68
  style={[
67
69
  styles[position].container,
68
- containerStyle && containerStyle[position],
70
+ containerStyle?.[position],
69
71
  ]}
70
72
  >
71
73
  <Text
72
74
  style={[
73
75
  styles[position].text,
74
- timeTextStyle && timeTextStyle[position],
76
+ timeTextStyle?.[position],
75
77
  ]}
76
78
  >
77
79
  {dayjs(currentMessage.createdAt).locale(getLocale()).format(timeFormat)}