react-native-gifted-chat 2.7.3 → 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 +1 -2
  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 +7 -9
  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,7 +56,7 @@ 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 = [],
@@ -94,9 +94,9 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
94
94
  const actionSheetRef = useRef<ActionSheetProviderRef>(null)
95
95
 
96
96
  const messageContainerRef = useMemo(
97
- () => props.messageContainerRef || createRef<AnimatedList>(),
97
+ () => props.messageContainerRef || createRef<AnimatedList<TMessage>>(),
98
98
  [props.messageContainerRef]
99
- )
99
+ ) as RefObject<AnimatedList<TMessage>>
100
100
 
101
101
  const textInputRef = useMemo(
102
102
  () => props.textInputRef || createRef<TextInput>(),
@@ -205,9 +205,9 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
205
205
 
206
206
  const { messagesContainerStyle, ...messagesContainerProps } = props
207
207
 
208
- const fragment = (
208
+ return (
209
209
  <View style={[stylesCommon.fill, messagesContainerStyle]}>
210
- <MessageContainer
210
+ <MessageContainer<TMessage>
211
211
  {...messagesContainerProps}
212
212
  invertibleScrollViewProps={{
213
213
  inverted,
@@ -220,8 +220,6 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
220
220
  {renderChatFooter?.()}
221
221
  </View>
222
222
  )
223
-
224
- return fragment
225
223
  }, [
226
224
  isInitialized,
227
225
  isTyping,
@@ -449,10 +447,10 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
449
447
  )
450
448
  }
451
449
 
452
- function GiftedChatWrapper (props: GiftedChatProps) {
450
+ function GiftedChatWrapper<TMessage extends IMessage = IMessage> (props: GiftedChatProps<TMessage>) {
453
451
  return (
454
452
  <KeyboardProvider>
455
- <GiftedChat {...props} />
453
+ <GiftedChat<TMessage> {...props} />
456
454
  </KeyboardProvider>
457
455
  )
458
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)}