react-native-gifted-chat 2.7.2 → 2.8.0

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 (35) hide show
  1. package/README.md +6 -6
  2. package/lib/Day/index.d.ts +1 -1
  3. package/lib/Day/index.js +2 -2
  4. package/lib/Day/index.js.map +1 -1
  5. package/lib/GiftedChat/index.d.ts +1 -1
  6. package/lib/GiftedChat/index.js +6 -3
  7. package/lib/GiftedChat/index.js.map +1 -1
  8. package/lib/GiftedChat/types.d.ts +4 -4
  9. package/lib/GiftedChatContext.d.ts +1 -2
  10. package/lib/GiftedChatContext.js +3 -3
  11. package/lib/GiftedChatContext.js.map +1 -1
  12. package/lib/InputToolbar.js +1 -0
  13. package/lib/InputToolbar.js.map +1 -1
  14. package/lib/MessageContainer/components/Item/index.d.ts +2 -1
  15. package/lib/MessageContainer/components/Item/index.js +2 -5
  16. package/lib/MessageContainer/components/Item/index.js.map +1 -1
  17. package/lib/MessageContainer/components/Item/types.d.ts +4 -5
  18. package/lib/MessageContainer/index.js +45 -29
  19. package/lib/MessageContainer/index.js.map +1 -1
  20. package/lib/MessageContainer/types.d.ts +5 -5
  21. package/lib/Time.d.ts +1 -1
  22. package/lib/Time.js +3 -3
  23. package/lib/Time.js.map +1 -1
  24. package/package.json +1 -3
  25. package/src/Day/index.tsx +2 -2
  26. package/src/GiftedChat/index.tsx +13 -10
  27. package/src/GiftedChat/types.ts +4 -4
  28. package/src/GiftedChatContext.ts +3 -3
  29. package/src/InputToolbar.tsx +1 -0
  30. package/src/MessageContainer/components/Item/index.tsx +4 -16
  31. package/src/MessageContainer/components/Item/types.ts +4 -5
  32. package/src/MessageContainer/index.tsx +67 -37
  33. package/src/MessageContainer/types.ts +5 -4
  34. package/src/Time.tsx +5 -3
  35. package/src/__tests__/__snapshots__/MessageContainer.test.tsx.snap +77 -279
@@ -56,13 +56,18 @@ import styles from './styles'
56
56
  dayjs.extend(localizedFormat)
57
57
 
58
58
  function GiftedChat<TMessage extends IMessage = IMessage> (
59
- props: GiftedChatProps
59
+ props: GiftedChatProps<TMessage>
60
60
  ) {
61
61
  const {
62
62
  messages = [],
63
63
  initialText = '',
64
64
  isTyping,
65
- messageIdGenerator = () => crypto.randomUUID(),
65
+
66
+ // "random" function from here: https://stackoverflow.com/a/8084248/3452513
67
+ // we do not use uuid since it would add extra native dependency (https://www.npmjs.com/package/react-native-get-random-values)
68
+ // lib's user can decide which algorithm to use and pass it as a prop
69
+ messageIdGenerator = () => (Math.random() + 1).toString(36).substring(7),
70
+
66
71
  user = {},
67
72
  onSend,
68
73
  locale = 'en',
@@ -89,9 +94,9 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
89
94
  const actionSheetRef = useRef<ActionSheetProviderRef>(null)
90
95
 
91
96
  const messageContainerRef = useMemo(
92
- () => props.messageContainerRef || createRef<AnimatedList>(),
97
+ () => props.messageContainerRef || createRef<AnimatedList<TMessage>>(),
93
98
  [props.messageContainerRef]
94
- )
99
+ ) as RefObject<AnimatedList<TMessage>>
95
100
 
96
101
  const textInputRef = useMemo(
97
102
  () => props.textInputRef || createRef<TextInput>(),
@@ -200,9 +205,9 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
200
205
 
201
206
  const { messagesContainerStyle, ...messagesContainerProps } = props
202
207
 
203
- const fragment = (
208
+ return (
204
209
  <View style={[stylesCommon.fill, messagesContainerStyle]}>
205
- <MessageContainer
210
+ <MessageContainer<TMessage>
206
211
  {...messagesContainerProps}
207
212
  invertibleScrollViewProps={{
208
213
  inverted,
@@ -215,8 +220,6 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
215
220
  {renderChatFooter?.()}
216
221
  </View>
217
222
  )
218
-
219
- return fragment
220
223
  }, [
221
224
  isInitialized,
222
225
  isTyping,
@@ -444,10 +447,10 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
444
447
  )
445
448
  }
446
449
 
447
- function GiftedChatWrapper (props: GiftedChatProps) {
450
+ function GiftedChatWrapper<TMessage extends IMessage = IMessage> (props: GiftedChatProps<TMessage>) {
448
451
  return (
449
452
  <KeyboardProvider>
450
- <GiftedChat {...props} />
453
+ <GiftedChat<TMessage> {...props} />
451
454
  </KeyboardProvider>
452
455
  )
453
456
  }
@@ -30,12 +30,12 @@ import { QuickRepliesProps } from '../QuickReplies'
30
30
  import { SendProps } from '../Send'
31
31
  import { SystemMessageProps } from '../SystemMessage'
32
32
  import { TimeProps } from '../Time'
33
- import MessageContainer, { AnimatedList, ListViewProps } from '../MessageContainer'
33
+ import { AnimatedList, ListViewProps, MessageContainerProps } from '../MessageContainer'
34
34
  import Bubble from '../Bubble'
35
35
 
36
- export interface GiftedChatProps<TMessage extends IMessage = IMessage> extends Partial<Omit<typeof MessageContainer<TMessage>, 'isScrollToBottomEnabled'>> {
36
+ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<MessageContainerProps<TMessage>> {
37
37
  /* Message container ref */
38
- messageContainerRef?: RefObject<AnimatedList>
38
+ messageContainerRef?: RefObject<AnimatedList<TMessage>>
39
39
  /* text input ref */
40
40
  textInputRef?: RefObject<TextInput>
41
41
  /* Messages to display */
@@ -171,7 +171,7 @@ export interface GiftedChatProps<TMessage extends IMessage = IMessage> extends P
171
171
  /* Custom time inside a message */
172
172
  renderTime?(props: TimeProps<TMessage>): React.ReactNode
173
173
  /* Custom footer component on the ListView, e.g. 'User is typing...' */
174
- renderFooter?(): React.ReactNode
174
+ renderFooter?(props: MessageContainerProps<TMessage>): React.ReactNode
175
175
  /* Custom component to render in the ListView when messages are empty */
176
176
  renderChatEmpty?(): React.ReactNode
177
177
  /* Custom component to render below the MessageContainer (separate from the ListView) */
@@ -1,4 +1,4 @@
1
- import * as React from 'react'
1
+ import { createContext, useContext } from 'react'
2
2
  import {
3
3
  ActionSheetOptions,
4
4
  } from '@expo/react-native-action-sheet'
@@ -13,11 +13,11 @@ export interface IGiftedChatContext {
13
13
  getLocale(): string
14
14
  }
15
15
 
16
- export const GiftedChatContext = React.createContext<IGiftedChatContext>({
16
+ export const GiftedChatContext = createContext<IGiftedChatContext>({
17
17
  getLocale: () => 'en',
18
18
  actionSheet: () => ({
19
19
  showActionSheetWithOptions: () => {},
20
20
  }),
21
21
  })
22
22
 
23
- export const useChatContext = () => React.useContext(GiftedChatContext)
23
+ export const useChatContext = () => useContext(GiftedChatContext)
@@ -40,6 +40,7 @@ export function InputToolbar<TMessage extends IMessage = IMessage> (
40
40
 
41
41
  const actionsFragment = useMemo(() => {
42
42
  const props = {
43
+ onPressActionButton,
43
44
  options,
44
45
  optionTintColor,
45
46
  icon,
@@ -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,18 @@ 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 (
146
137
  <View key={props.currentMessage._id.toString()}>
147
138
  <Animated.View
148
139
  style={style}
149
140
  onLayout={handleLayoutDayContainer}
150
141
  >
151
- <DayWrapper
152
- {...rest as MessageProps<IMessage>}
153
- ref={handleRef}
154
- />
142
+ <DayWrapper {...rest as MessageProps<TMessage>} />
155
143
  </Animated.View>
156
144
  {
157
145
  renderMessageProp
158
- ? renderMessageProp(rest as MessageProps<IMessage>)
159
- : <Message {...rest as MessageProps<IMessage>} />
146
+ ? renderMessageProp(rest as MessageProps<TMessage>)
147
+ : <Message {...rest as MessageProps<TMessage>} />
160
148
  }
161
149
  </View>
162
150
  )
@@ -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)
@@ -288,6 +265,37 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
288
265
 
289
266
  const keyExtractor = useCallback((item: unknown) => (item as TMessage)._id.toString(), [])
290
267
 
268
+ const renderCell = useCallback((props: CellRendererProps<unknown>) => {
269
+ const handleOnLayout = (event: LayoutChangeEvent) => {
270
+ const prevMessage = messages[props.index + (inverted ? 1 : -1)]
271
+ if (prevMessage && isSameDay(props.item as IMessage, prevMessage))
272
+ return
273
+
274
+ const { y, height } = event.nativeEvent.layout
275
+
276
+ const newValue = {
277
+ y,
278
+ height,
279
+ createdAt: new Date((props.item as IMessage).createdAt).getTime(),
280
+ }
281
+
282
+ daysPositions.modify(value => {
283
+ 'worklet'
284
+
285
+ // @ts-expect-error: https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue#remarks
286
+ value[props.item._id] = newValue
287
+ return value
288
+ })
289
+ }
290
+
291
+ return (
292
+ <Animated.View
293
+ {...props}
294
+ onLayout={handleOnLayout}
295
+ />
296
+ )
297
+ }, [daysPositions, messages, inverted])
298
+
291
299
  const scrollHandler = useAnimatedScrollHandler({
292
300
  onScroll: event => {
293
301
  scrolledY.value = event.contentOffset.y
@@ -296,6 +304,28 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
296
304
  },
297
305
  }, [handleOnScroll])
298
306
 
307
+ // removes unrendered days positions when messages are added/removed
308
+ useEffect(() => {
309
+ Object.keys(daysPositions.value).forEach(key => {
310
+ const messageIndex = messages.findIndex(m => m._id === key)
311
+ let shouldRemove = messageIndex === -1
312
+
313
+ if (!shouldRemove) {
314
+ const prevMessage = messages[messageIndex + (inverted ? 1 : -1)]
315
+ const message = messages[messageIndex]
316
+ shouldRemove = !!prevMessage && isSameDay(message, prevMessage)
317
+ }
318
+
319
+ if (shouldRemove)
320
+ daysPositions.modify(value => {
321
+ 'worklet'
322
+
323
+ delete value[key]
324
+ return value
325
+ })
326
+ })
327
+ }, [messages, daysPositions, inverted])
328
+
299
329
  return (
300
330
  <View
301
331
  style={[
@@ -303,8 +333,8 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
303
333
  alignTop ? styles.containerAlignTop : stylesCommon.fill,
304
334
  ]}
305
335
  >
306
- <AnimatedFlashList
307
- ref={forwardRef}
336
+ <AnimatedFlatList
337
+ ref={forwardRef as React.Ref<FlatList<unknown>>}
308
338
  extraData={[extraData, isTyping]}
309
339
  keyExtractor={keyExtractor}
310
340
  automaticallyAdjustContentInsets={false}
@@ -321,12 +351,12 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
321
351
  inverted ? ListFooterComponent : ListHeaderComponent
322
352
  }
323
353
  onScroll={scrollHandler}
324
- scrollEventThrottle={16}
354
+ scrollEventThrottle={1}
325
355
  onEndReached={onEndReached}
326
356
  onEndReachedThreshold={0.1}
327
- estimatedItemSize={100}
328
357
  {...listViewProps}
329
358
  onLayout={onLayoutList}
359
+ CellRendererComponent={renderCell}
330
360
  />
331
361
  {isScrollToBottomEnabled
332
362
  ? 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
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)}