stream-chat-react-native-core 9.2.0-beta.2 → 9.2.0-beta.4

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 (386) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/components/AttachmentPicker/components/AttachmentPickerContent.js +2 -2
  3. package/lib/commonjs/components/AttachmentPicker/components/AttachmentPickerContent.js.map +1 -1
  4. package/lib/commonjs/components/Channel/Channel.js +10 -1
  5. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  6. package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js +26 -3
  7. package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js.map +1 -1
  8. package/lib/commonjs/components/ChannelList/ChannelList.js +29 -4
  9. package/lib/commonjs/components/ChannelList/ChannelList.js.map +1 -1
  10. package/lib/commonjs/components/ChannelList/hooks/useChannelActions.js +314 -11
  11. package/lib/commonjs/components/ChannelList/hooks/useChannelActions.js.map +1 -1
  12. package/lib/commonjs/components/Message/hooks/useMessageActionHandlers.js +202 -15
  13. package/lib/commonjs/components/Message/hooks/useMessageActionHandlers.js.map +1 -1
  14. package/lib/commonjs/components/MessageInput/MessageComposer.js +1 -1
  15. package/lib/commonjs/components/MessageInput/MessageComposerLeadingView.js +1 -1
  16. package/lib/commonjs/components/MessageInput/MessageComposerLeadingView.js.map +1 -1
  17. package/lib/commonjs/components/MessageInput/MessageInputHeaderView.js +1 -1
  18. package/lib/commonjs/components/MessageInput/MessageInputHeaderView.js.map +1 -1
  19. package/lib/commonjs/components/MessageInput/MessageInputTrailingView.js +1 -1
  20. package/lib/commonjs/components/MessageInput/MessageInputTrailingView.js.map +1 -1
  21. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +7 -13
  22. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
  23. package/lib/commonjs/components/MessageInput/components/AudioRecorder/AudioRecorder.js +27 -6
  24. package/lib/commonjs/components/MessageInput/components/AudioRecorder/AudioRecorder.js.map +1 -1
  25. package/lib/commonjs/components/MessageInput/components/AudioRecorder/AudioRecordingButton.js +29 -9
  26. package/lib/commonjs/components/MessageInput/components/AudioRecorder/AudioRecordingButton.js.map +1 -1
  27. package/lib/commonjs/components/MessageInput/components/OutputButtons/index.js +1 -1
  28. package/lib/commonjs/components/MessageList/MessageFlashList.js +5 -2
  29. package/lib/commonjs/components/MessageList/MessageFlashList.js.map +1 -1
  30. package/lib/commonjs/components/MessageList/MessageList.js +5 -2
  31. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  32. package/lib/commonjs/components/MessageMenu/hooks/useFetchReactions.js +23 -2
  33. package/lib/commonjs/components/MessageMenu/hooks/useFetchReactions.js.map +1 -1
  34. package/lib/commonjs/components/Notifications/Notification.js +230 -0
  35. package/lib/commonjs/components/Notifications/Notification.js.map +1 -0
  36. package/lib/commonjs/components/Notifications/NotificationList.js +120 -0
  37. package/lib/commonjs/components/Notifications/NotificationList.js.map +1 -0
  38. package/lib/commonjs/components/Notifications/NotificationTargetContext.js +45 -0
  39. package/lib/commonjs/components/Notifications/NotificationTargetContext.js.map +1 -0
  40. package/lib/commonjs/components/Notifications/hooks/index.js +59 -0
  41. package/lib/commonjs/components/Notifications/hooks/index.js.map +1 -0
  42. package/lib/commonjs/components/Notifications/hooks/useNotificationApi.js +133 -0
  43. package/lib/commonjs/components/Notifications/hooks/useNotificationApi.js.map +1 -0
  44. package/lib/commonjs/components/Notifications/hooks/useNotificationListController.js +133 -0
  45. package/lib/commonjs/components/Notifications/hooks/useNotificationListController.js.map +1 -0
  46. package/lib/commonjs/components/Notifications/hooks/useNotificationTarget.js +26 -0
  47. package/lib/commonjs/components/Notifications/hooks/useNotificationTarget.js.map +1 -0
  48. package/lib/commonjs/components/Notifications/hooks/useNotifications.js +26 -0
  49. package/lib/commonjs/components/Notifications/hooks/useNotifications.js.map +1 -0
  50. package/lib/commonjs/components/Notifications/hooks/useSystemNotifications.js +22 -0
  51. package/lib/commonjs/components/Notifications/hooks/useSystemNotifications.js.map +1 -0
  52. package/lib/commonjs/components/Notifications/index.js +59 -0
  53. package/lib/commonjs/components/Notifications/index.js.map +1 -0
  54. package/lib/commonjs/components/Notifications/notificationTarget.js +220 -0
  55. package/lib/commonjs/components/Notifications/notificationTarget.js.map +1 -0
  56. package/lib/commonjs/components/Notifications/notificationTranslations.js +137 -0
  57. package/lib/commonjs/components/Notifications/notificationTranslations.js.map +1 -0
  58. package/lib/commonjs/components/Poll/components/PollOption.js +14 -9
  59. package/lib/commonjs/components/Poll/components/PollOption.js.map +1 -1
  60. package/lib/commonjs/components/Poll/hooks/usePollState.js +35 -3
  61. package/lib/commonjs/components/Poll/hooks/usePollState.js.map +1 -1
  62. package/lib/commonjs/components/Thread/Thread.js +19 -11
  63. package/lib/commonjs/components/Thread/Thread.js.map +1 -1
  64. package/lib/commonjs/components/ThreadList/ThreadList.js +30 -9
  65. package/lib/commonjs/components/ThreadList/ThreadList.js.map +1 -1
  66. package/lib/commonjs/components/index.js +11 -0
  67. package/lib/commonjs/components/index.js.map +1 -1
  68. package/lib/commonjs/contexts/componentsContext/defaultComponents.js +4 -0
  69. package/lib/commonjs/contexts/componentsContext/defaultComponents.js.map +1 -1
  70. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +37 -0
  71. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  72. package/lib/commonjs/contexts/themeContext/utils/theme.js +13 -0
  73. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  74. package/lib/commonjs/hooks/index.js +11 -0
  75. package/lib/commonjs/hooks/index.js.map +1 -1
  76. package/lib/commonjs/hooks/useAudioPlayer.js +34 -1
  77. package/lib/commonjs/hooks/useAudioPlayer.js.map +1 -1
  78. package/lib/commonjs/hooks/useInAppNotificationsState.js.map +1 -1
  79. package/lib/commonjs/hooks/useLazyRef.js +13 -0
  80. package/lib/commonjs/hooks/useLazyRef.js.map +1 -0
  81. package/lib/commonjs/i18n/en.json +60 -1
  82. package/lib/commonjs/i18n/es.json +62 -3
  83. package/lib/commonjs/i18n/fr.json +60 -1
  84. package/lib/commonjs/i18n/he.json +60 -1
  85. package/lib/commonjs/i18n/hi.json +60 -1
  86. package/lib/commonjs/i18n/it.json +60 -1
  87. package/lib/commonjs/i18n/ja.json +60 -1
  88. package/lib/commonjs/i18n/ko.json +60 -1
  89. package/lib/commonjs/i18n/nl.json +60 -1
  90. package/lib/commonjs/i18n/pt-br.json +60 -1
  91. package/lib/commonjs/i18n/ru.json +60 -1
  92. package/lib/commonjs/i18n/tr.json +60 -1
  93. package/lib/commonjs/state-store/audio-player-pool.js +1 -0
  94. package/lib/commonjs/state-store/audio-player-pool.js.map +1 -1
  95. package/lib/commonjs/state-store/audio-player.js +92 -13
  96. package/lib/commonjs/state-store/audio-player.js.map +1 -1
  97. package/lib/commonjs/theme/generated/dark/StreamTokens.android.js +16 -16
  98. package/lib/commonjs/theme/generated/dark/StreamTokens.android.js.map +1 -1
  99. package/lib/commonjs/theme/generated/dark/StreamTokens.ios.js +8 -8
  100. package/lib/commonjs/theme/generated/dark/StreamTokens.ios.js.map +1 -1
  101. package/lib/commonjs/theme/generated/dark/StreamTokens.web.js +8 -8
  102. package/lib/commonjs/theme/generated/dark/StreamTokens.web.js.map +1 -1
  103. package/lib/commonjs/theme/generated/light/StreamTokens.android.js +16 -16
  104. package/lib/commonjs/theme/generated/light/StreamTokens.android.js.map +1 -1
  105. package/lib/commonjs/theme/generated/light/StreamTokens.ios.js +8 -8
  106. package/lib/commonjs/theme/generated/light/StreamTokens.ios.js.map +1 -1
  107. package/lib/commonjs/theme/generated/light/StreamTokens.web.js +8 -8
  108. package/lib/commonjs/theme/generated/light/StreamTokens.web.js.map +1 -1
  109. package/lib/commonjs/utils/animations/createBoundedZoomTransition.js +151 -0
  110. package/lib/commonjs/utils/animations/createBoundedZoomTransition.js.map +1 -0
  111. package/lib/commonjs/utils/{transitions.js → animations/transitions.js} +6 -0
  112. package/lib/commonjs/utils/animations/transitions.js.map +1 -0
  113. package/lib/commonjs/version.json +1 -1
  114. package/lib/module/components/AttachmentPicker/components/AttachmentPickerContent.js +2 -2
  115. package/lib/module/components/AttachmentPicker/components/AttachmentPickerContent.js.map +1 -1
  116. package/lib/module/components/Channel/Channel.js +10 -1
  117. package/lib/module/components/Channel/Channel.js.map +1 -1
  118. package/lib/module/components/Channel/hooks/useMessageListPagination.js +26 -3
  119. package/lib/module/components/Channel/hooks/useMessageListPagination.js.map +1 -1
  120. package/lib/module/components/ChannelList/ChannelList.js +29 -4
  121. package/lib/module/components/ChannelList/ChannelList.js.map +1 -1
  122. package/lib/module/components/ChannelList/hooks/useChannelActions.js +314 -11
  123. package/lib/module/components/ChannelList/hooks/useChannelActions.js.map +1 -1
  124. package/lib/module/components/Message/hooks/useMessageActionHandlers.js +202 -15
  125. package/lib/module/components/Message/hooks/useMessageActionHandlers.js.map +1 -1
  126. package/lib/module/components/MessageInput/MessageComposer.js +1 -1
  127. package/lib/module/components/MessageInput/MessageComposerLeadingView.js +1 -1
  128. package/lib/module/components/MessageInput/MessageComposerLeadingView.js.map +1 -1
  129. package/lib/module/components/MessageInput/MessageInputHeaderView.js +1 -1
  130. package/lib/module/components/MessageInput/MessageInputHeaderView.js.map +1 -1
  131. package/lib/module/components/MessageInput/MessageInputTrailingView.js +1 -1
  132. package/lib/module/components/MessageInput/MessageInputTrailingView.js.map +1 -1
  133. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +7 -13
  134. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
  135. package/lib/module/components/MessageInput/components/AudioRecorder/AudioRecorder.js +27 -6
  136. package/lib/module/components/MessageInput/components/AudioRecorder/AudioRecorder.js.map +1 -1
  137. package/lib/module/components/MessageInput/components/AudioRecorder/AudioRecordingButton.js +29 -9
  138. package/lib/module/components/MessageInput/components/AudioRecorder/AudioRecordingButton.js.map +1 -1
  139. package/lib/module/components/MessageInput/components/OutputButtons/index.js +1 -1
  140. package/lib/module/components/MessageList/MessageFlashList.js +5 -2
  141. package/lib/module/components/MessageList/MessageFlashList.js.map +1 -1
  142. package/lib/module/components/MessageList/MessageList.js +5 -2
  143. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  144. package/lib/module/components/MessageMenu/hooks/useFetchReactions.js +23 -2
  145. package/lib/module/components/MessageMenu/hooks/useFetchReactions.js.map +1 -1
  146. package/lib/module/components/Notifications/Notification.js +230 -0
  147. package/lib/module/components/Notifications/Notification.js.map +1 -0
  148. package/lib/module/components/Notifications/NotificationList.js +120 -0
  149. package/lib/module/components/Notifications/NotificationList.js.map +1 -0
  150. package/lib/module/components/Notifications/NotificationTargetContext.js +45 -0
  151. package/lib/module/components/Notifications/NotificationTargetContext.js.map +1 -0
  152. package/lib/module/components/Notifications/hooks/index.js +59 -0
  153. package/lib/module/components/Notifications/hooks/index.js.map +1 -0
  154. package/lib/module/components/Notifications/hooks/useNotificationApi.js +133 -0
  155. package/lib/module/components/Notifications/hooks/useNotificationApi.js.map +1 -0
  156. package/lib/module/components/Notifications/hooks/useNotificationListController.js +133 -0
  157. package/lib/module/components/Notifications/hooks/useNotificationListController.js.map +1 -0
  158. package/lib/module/components/Notifications/hooks/useNotificationTarget.js +26 -0
  159. package/lib/module/components/Notifications/hooks/useNotificationTarget.js.map +1 -0
  160. package/lib/module/components/Notifications/hooks/useNotifications.js +26 -0
  161. package/lib/module/components/Notifications/hooks/useNotifications.js.map +1 -0
  162. package/lib/module/components/Notifications/hooks/useSystemNotifications.js +22 -0
  163. package/lib/module/components/Notifications/hooks/useSystemNotifications.js.map +1 -0
  164. package/lib/module/components/Notifications/index.js +59 -0
  165. package/lib/module/components/Notifications/index.js.map +1 -0
  166. package/lib/module/components/Notifications/notificationTarget.js +220 -0
  167. package/lib/module/components/Notifications/notificationTarget.js.map +1 -0
  168. package/lib/module/components/Notifications/notificationTranslations.js +137 -0
  169. package/lib/module/components/Notifications/notificationTranslations.js.map +1 -0
  170. package/lib/module/components/Poll/components/PollOption.js +14 -9
  171. package/lib/module/components/Poll/components/PollOption.js.map +1 -1
  172. package/lib/module/components/Poll/hooks/usePollState.js +35 -3
  173. package/lib/module/components/Poll/hooks/usePollState.js.map +1 -1
  174. package/lib/module/components/Thread/Thread.js +19 -11
  175. package/lib/module/components/Thread/Thread.js.map +1 -1
  176. package/lib/module/components/ThreadList/ThreadList.js +30 -9
  177. package/lib/module/components/ThreadList/ThreadList.js.map +1 -1
  178. package/lib/module/components/index.js +11 -0
  179. package/lib/module/components/index.js.map +1 -1
  180. package/lib/module/contexts/componentsContext/defaultComponents.js +4 -0
  181. package/lib/module/contexts/componentsContext/defaultComponents.js.map +1 -1
  182. package/lib/module/contexts/messageInputContext/MessageInputContext.js +37 -0
  183. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  184. package/lib/module/contexts/themeContext/utils/theme.js +13 -0
  185. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  186. package/lib/module/hooks/index.js +11 -0
  187. package/lib/module/hooks/index.js.map +1 -1
  188. package/lib/module/hooks/useAudioPlayer.js +34 -1
  189. package/lib/module/hooks/useAudioPlayer.js.map +1 -1
  190. package/lib/module/hooks/useInAppNotificationsState.js.map +1 -1
  191. package/lib/module/hooks/useLazyRef.js +13 -0
  192. package/lib/module/hooks/useLazyRef.js.map +1 -0
  193. package/lib/module/i18n/en.json +60 -1
  194. package/lib/module/i18n/es.json +62 -3
  195. package/lib/module/i18n/fr.json +60 -1
  196. package/lib/module/i18n/he.json +60 -1
  197. package/lib/module/i18n/hi.json +60 -1
  198. package/lib/module/i18n/it.json +60 -1
  199. package/lib/module/i18n/ja.json +60 -1
  200. package/lib/module/i18n/ko.json +60 -1
  201. package/lib/module/i18n/nl.json +60 -1
  202. package/lib/module/i18n/pt-br.json +60 -1
  203. package/lib/module/i18n/ru.json +60 -1
  204. package/lib/module/i18n/tr.json +60 -1
  205. package/lib/module/state-store/audio-player-pool.js +1 -0
  206. package/lib/module/state-store/audio-player-pool.js.map +1 -1
  207. package/lib/module/state-store/audio-player.js +92 -13
  208. package/lib/module/state-store/audio-player.js.map +1 -1
  209. package/lib/module/theme/generated/dark/StreamTokens.android.js +16 -16
  210. package/lib/module/theme/generated/dark/StreamTokens.android.js.map +1 -1
  211. package/lib/module/theme/generated/dark/StreamTokens.ios.js +8 -8
  212. package/lib/module/theme/generated/dark/StreamTokens.ios.js.map +1 -1
  213. package/lib/module/theme/generated/dark/StreamTokens.web.js +8 -8
  214. package/lib/module/theme/generated/dark/StreamTokens.web.js.map +1 -1
  215. package/lib/module/theme/generated/light/StreamTokens.android.js +16 -16
  216. package/lib/module/theme/generated/light/StreamTokens.android.js.map +1 -1
  217. package/lib/module/theme/generated/light/StreamTokens.ios.js +8 -8
  218. package/lib/module/theme/generated/light/StreamTokens.ios.js.map +1 -1
  219. package/lib/module/theme/generated/light/StreamTokens.web.js +8 -8
  220. package/lib/module/theme/generated/light/StreamTokens.web.js.map +1 -1
  221. package/lib/module/utils/animations/createBoundedZoomTransition.js +151 -0
  222. package/lib/module/utils/animations/createBoundedZoomTransition.js.map +1 -0
  223. package/lib/module/utils/{transitions.js → animations/transitions.js} +6 -0
  224. package/lib/module/utils/animations/transitions.js.map +1 -0
  225. package/lib/module/version.json +1 -1
  226. package/lib/typescript/components/AttachmentPicker/components/AttachmentPickerContent.d.ts.map +1 -1
  227. package/lib/typescript/components/Channel/Channel.d.ts +1 -0
  228. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  229. package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts.map +1 -1
  230. package/lib/typescript/components/ChannelList/ChannelList.d.ts +1 -0
  231. package/lib/typescript/components/ChannelList/ChannelList.d.ts.map +1 -1
  232. package/lib/typescript/components/ChannelList/hooks/useChannelActions.d.ts.map +1 -1
  233. package/lib/typescript/components/Message/hooks/useMessageActionHandlers.d.ts.map +1 -1
  234. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.d.ts.map +1 -1
  235. package/lib/typescript/components/MessageInput/components/AudioRecorder/AudioRecorder.d.ts.map +1 -1
  236. package/lib/typescript/components/MessageInput/components/AudioRecorder/AudioRecordingButton.d.ts.map +1 -1
  237. package/lib/typescript/components/MessageList/MessageFlashList.d.ts.map +1 -1
  238. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  239. package/lib/typescript/components/MessageMenu/hooks/useFetchReactions.d.ts.map +1 -1
  240. package/lib/typescript/components/Notifications/Notification.d.ts +31 -0
  241. package/lib/typescript/components/Notifications/Notification.d.ts.map +1 -0
  242. package/lib/typescript/components/Notifications/NotificationList.d.ts +28 -0
  243. package/lib/typescript/components/Notifications/NotificationList.d.ts.map +1 -0
  244. package/lib/typescript/components/Notifications/NotificationTargetContext.d.ts +14 -0
  245. package/lib/typescript/components/Notifications/NotificationTargetContext.d.ts.map +1 -0
  246. package/lib/typescript/components/Notifications/hooks/index.d.ts +6 -0
  247. package/lib/typescript/components/Notifications/hooks/index.d.ts.map +1 -0
  248. package/lib/typescript/components/Notifications/hooks/useNotificationApi.d.ts +48 -0
  249. package/lib/typescript/components/Notifications/hooks/useNotificationApi.d.ts.map +1 -0
  250. package/lib/typescript/components/Notifications/hooks/useNotificationListController.d.ts +14 -0
  251. package/lib/typescript/components/Notifications/hooks/useNotificationListController.d.ts.map +1 -0
  252. package/lib/typescript/components/Notifications/hooks/useNotificationTarget.d.ts +3 -0
  253. package/lib/typescript/components/Notifications/hooks/useNotificationTarget.d.ts.map +1 -0
  254. package/lib/typescript/components/Notifications/hooks/useNotifications.d.ts +14 -0
  255. package/lib/typescript/components/Notifications/hooks/useNotifications.d.ts.map +1 -0
  256. package/lib/typescript/components/Notifications/hooks/useSystemNotifications.d.ts +9 -0
  257. package/lib/typescript/components/Notifications/hooks/useSystemNotifications.d.ts.map +1 -0
  258. package/lib/typescript/components/Notifications/index.d.ts +6 -0
  259. package/lib/typescript/components/Notifications/index.d.ts.map +1 -0
  260. package/lib/typescript/components/Notifications/notificationTarget.d.ts +55 -0
  261. package/lib/typescript/components/Notifications/notificationTarget.d.ts.map +1 -0
  262. package/lib/typescript/components/Notifications/notificationTranslations.d.ts +7 -0
  263. package/lib/typescript/components/Notifications/notificationTranslations.d.ts.map +1 -0
  264. package/lib/typescript/components/Poll/components/PollOption.d.ts.map +1 -1
  265. package/lib/typescript/components/Poll/hooks/usePollState.d.ts.map +1 -1
  266. package/lib/typescript/components/Thread/Thread.d.ts +1 -0
  267. package/lib/typescript/components/Thread/Thread.d.ts.map +1 -1
  268. package/lib/typescript/components/ThreadList/ThreadList.d.ts +3 -1
  269. package/lib/typescript/components/ThreadList/ThreadList.d.ts.map +1 -1
  270. package/lib/typescript/components/index.d.ts +1 -0
  271. package/lib/typescript/components/index.d.ts.map +1 -1
  272. package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts +3 -0
  273. package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts.map +1 -1
  274. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts.map +1 -1
  275. package/lib/typescript/contexts/themeContext/ThemeContext.d.ts +13 -0
  276. package/lib/typescript/contexts/themeContext/ThemeContext.d.ts.map +1 -1
  277. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +13 -0
  278. package/lib/typescript/contexts/themeContext/utils/theme.d.ts.map +1 -1
  279. package/lib/typescript/hooks/index.d.ts +1 -0
  280. package/lib/typescript/hooks/index.d.ts.map +1 -1
  281. package/lib/typescript/hooks/useAudioPlayer.d.ts.map +1 -1
  282. package/lib/typescript/hooks/useInAppNotificationsState.d.ts +6 -0
  283. package/lib/typescript/hooks/useInAppNotificationsState.d.ts.map +1 -1
  284. package/lib/typescript/hooks/useLazyRef.d.ts +7 -0
  285. package/lib/typescript/hooks/useLazyRef.d.ts.map +1 -0
  286. package/lib/typescript/i18n/en.json +60 -1
  287. package/lib/typescript/i18n/es.json +62 -3
  288. package/lib/typescript/i18n/fr.json +60 -1
  289. package/lib/typescript/i18n/he.json +60 -1
  290. package/lib/typescript/i18n/hi.json +60 -1
  291. package/lib/typescript/i18n/it.json +60 -1
  292. package/lib/typescript/i18n/ja.json +60 -1
  293. package/lib/typescript/i18n/ko.json +60 -1
  294. package/lib/typescript/i18n/nl.json +60 -1
  295. package/lib/typescript/i18n/pt-br.json +60 -1
  296. package/lib/typescript/i18n/ru.json +60 -1
  297. package/lib/typescript/i18n/tr.json +60 -1
  298. package/lib/typescript/state-store/audio-player-pool.d.ts.map +1 -1
  299. package/lib/typescript/state-store/audio-player.d.ts +13 -0
  300. package/lib/typescript/state-store/audio-player.d.ts.map +1 -1
  301. package/lib/typescript/utils/animations/createBoundedZoomTransition.d.ts +21 -0
  302. package/lib/typescript/utils/animations/createBoundedZoomTransition.d.ts.map +1 -0
  303. package/lib/typescript/utils/animations/transitions.d.ts +21 -0
  304. package/lib/typescript/utils/animations/transitions.d.ts.map +1 -0
  305. package/lib/typescript/utils/i18n/Streami18n.d.ts +59 -0
  306. package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
  307. package/package.json +2 -2
  308. package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx +3 -3
  309. package/src/components/AttachmentPicker/components/__tests__/AttachmentPickerContent.test.tsx +19 -18
  310. package/src/components/Channel/Channel.tsx +15 -1
  311. package/src/components/Channel/__tests__/useMessageListPagination.test.tsx +34 -2
  312. package/src/components/Channel/hooks/useMessageListPagination.tsx +19 -3
  313. package/src/components/ChannelList/ChannelList.tsx +27 -5
  314. package/src/components/ChannelList/hooks/__tests__/useChannelActions.test.tsx +123 -0
  315. package/src/components/ChannelList/hooks/useChannelActions.ts +181 -12
  316. package/src/components/Message/hooks/__tests__/useMessageActionHandlers.test.tsx +131 -0
  317. package/src/components/Message/hooks/useMessageActionHandlers.ts +133 -23
  318. package/src/components/MessageInput/MessageComposer.tsx +1 -1
  319. package/src/components/MessageInput/MessageComposerLeadingView.tsx +1 -1
  320. package/src/components/MessageInput/MessageInputHeaderView.tsx +1 -1
  321. package/src/components/MessageInput/MessageInputTrailingView.tsx +1 -1
  322. package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx +1 -10
  323. package/src/components/MessageInput/components/AudioRecorder/AudioRecorder.tsx +10 -2
  324. package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx +27 -13
  325. package/src/components/MessageInput/components/OutputButtons/index.tsx +1 -1
  326. package/src/components/MessageList/MessageFlashList.tsx +3 -1
  327. package/src/components/MessageList/MessageList.tsx +3 -1
  328. package/src/components/MessageList/__tests__/MessageList.test.tsx +35 -0
  329. package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.tsx.snap +1 -1
  330. package/src/components/MessageMenu/hooks/useFetchReactions.ts +17 -2
  331. package/src/components/Notifications/Notification.tsx +253 -0
  332. package/src/components/Notifications/NotificationList.tsx +160 -0
  333. package/src/components/Notifications/NotificationTargetContext.tsx +45 -0
  334. package/src/components/Notifications/__tests__/NotificationList.test.tsx +480 -0
  335. package/src/components/Notifications/__tests__/notificationTarget.test.ts +157 -0
  336. package/src/components/Notifications/hooks/__tests__/useNotificationApi.test.tsx +172 -0
  337. package/src/components/Notifications/hooks/__tests__/useNotificationTarget.test.tsx +85 -0
  338. package/src/components/Notifications/hooks/index.ts +5 -0
  339. package/src/components/Notifications/hooks/useNotificationApi.ts +248 -0
  340. package/src/components/Notifications/hooks/useNotificationListController.ts +160 -0
  341. package/src/components/Notifications/hooks/useNotificationTarget.ts +37 -0
  342. package/src/components/Notifications/hooks/useNotifications.ts +43 -0
  343. package/src/components/Notifications/hooks/useSystemNotifications.ts +33 -0
  344. package/src/components/Notifications/index.ts +5 -0
  345. package/src/components/Notifications/notificationTarget.ts +305 -0
  346. package/src/components/Notifications/notificationTranslations.ts +142 -0
  347. package/src/components/Poll/components/PollOption.tsx +10 -6
  348. package/src/components/Poll/hooks/usePollState.ts +26 -2
  349. package/src/components/Thread/Thread.tsx +24 -16
  350. package/src/components/ThreadList/ThreadList.tsx +33 -9
  351. package/src/components/index.ts +2 -0
  352. package/src/contexts/componentsContext/defaultComponents.ts +4 -0
  353. package/src/contexts/messageInputContext/MessageInputContext.tsx +36 -0
  354. package/src/contexts/themeContext/utils/theme.ts +26 -0
  355. package/src/hooks/index.ts +1 -0
  356. package/src/hooks/useAudioPlayer.ts +44 -3
  357. package/src/hooks/useInAppNotificationsState.ts +6 -0
  358. package/src/hooks/useLazyRef.ts +15 -0
  359. package/src/i18n/en.json +60 -1
  360. package/src/i18n/es.json +62 -3
  361. package/src/i18n/fr.json +60 -1
  362. package/src/i18n/he.json +60 -1
  363. package/src/i18n/hi.json +60 -1
  364. package/src/i18n/it.json +60 -1
  365. package/src/i18n/ja.json +60 -1
  366. package/src/i18n/ko.json +60 -1
  367. package/src/i18n/nl.json +60 -1
  368. package/src/i18n/pt-br.json +60 -1
  369. package/src/i18n/ru.json +60 -1
  370. package/src/i18n/tr.json +60 -1
  371. package/src/state-store/__tests__/audio-player.test.ts +45 -0
  372. package/src/state-store/audio-player-pool.ts +1 -0
  373. package/src/state-store/audio-player.ts +108 -16
  374. package/src/theme/generated/dark/StreamTokens.android.ts +16 -16
  375. package/src/theme/generated/dark/StreamTokens.ios.ts +8 -8
  376. package/src/theme/generated/dark/StreamTokens.web.ts +8 -8
  377. package/src/theme/generated/light/StreamTokens.android.ts +16 -16
  378. package/src/theme/generated/light/StreamTokens.ios.ts +8 -8
  379. package/src/theme/generated/light/StreamTokens.web.ts +8 -8
  380. package/src/utils/animations/createBoundedZoomTransition.ts +117 -0
  381. package/src/utils/{transitions.ts → animations/transitions.ts} +6 -0
  382. package/src/version.json +1 -1
  383. package/lib/commonjs/utils/transitions.js.map +0 -1
  384. package/lib/module/utils/transitions.js.map +0 -1
  385. package/lib/typescript/utils/transitions.d.ts +0 -9
  386. package/lib/typescript/utils/transitions.d.ts.map +0 -1
@@ -0,0 +1,160 @@
1
+ import React, { useMemo } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+
4
+ import Animated from 'react-native-reanimated';
5
+
6
+ import type { Notification as NotificationType } from 'stream-chat';
7
+
8
+ import { useNotificationListController } from './hooks/useNotificationListController';
9
+ import type { NotificationTargetPanel } from './notificationTarget';
10
+
11
+ import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
12
+ import { useTheme } from '../../contexts/themeContext/ThemeContext';
13
+ import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
14
+ import { primitives } from '../../theme';
15
+ import { transitions } from '../../utils/animations/transitions';
16
+
17
+ /** Predicate used to hide notifications from a specific rendered notification list. */
18
+ export type NotificationListFilter = (notification: NotificationType) => boolean;
19
+ /** Direction from which new snackbars animate into the list. */
20
+ export type NotificationListEnterFrom = 'bottom' | 'left' | 'right' | 'top';
21
+ /** Vertical edge used by the absolutely positioned notification host. */
22
+ export type NotificationListVerticalAlignment = 'bottom' | 'top';
23
+
24
+ export type NotificationListProps = {
25
+ /** Extra distance from the bottom edge, useful when floating composers cover the default host. */
26
+ bottomOffset?: number;
27
+ /** Default enter/exit animation direction when notification metadata does not define one. */
28
+ enterFrom?: NotificationListEnterFrom;
29
+ /** Additional per-list filter applied after target filtering. */
30
+ filter?: NotificationListFilter;
31
+ /** Exact notification host id. Use together with `panel` when rendering outside a target provider. */
32
+ hostId?: string;
33
+ /** Target panel consumed by this list. Defaults to the nearest `NotificationTargetProvider`. */
34
+ panel?: NotificationTargetPanel;
35
+ /** Extra distance from the top edge when `verticalAlignment` is `top`. */
36
+ topOffset?: number;
37
+ /** Whether the host is anchored to the top or bottom of its parent. */
38
+ verticalAlignment?: NotificationListVerticalAlignment;
39
+ };
40
+
41
+ const isEnterFrom = (value: unknown): value is NotificationListEnterFrom =>
42
+ value === 'bottom' || value === 'left' || value === 'right' || value === 'top';
43
+
44
+ const getNotificationEnterFrom = (
45
+ notification: NotificationType,
46
+ fallbackEnterFrom: NotificationListEnterFrom,
47
+ ) => {
48
+ const metadataEnterFrom = notification.metadata?.entryDirection;
49
+ if (isEnterFrom(metadataEnterFrom)) return metadataEnterFrom;
50
+
51
+ const originEnterFrom = notification.origin.context?.entryDirection;
52
+ if (isEnterFrom(originEnterFrom)) return originEnterFrom;
53
+
54
+ return fallbackEnterFrom;
55
+ };
56
+
57
+ const getStringValue = (value: unknown) => (typeof value === 'string' ? value : undefined);
58
+
59
+ const getNotificationPresentationKey = (notification: NotificationType) =>
60
+ notification.type ??
61
+ getStringValue(notification.metadata?.dedupeKey) ??
62
+ getStringValue(notification.origin.context?.dedupeKey) ??
63
+ [notification.origin.emitter, notification.severity, notification.message]
64
+ .filter(Boolean)
65
+ .join(':');
66
+
67
+ /** Renders the newest matching client notification as an animated snackbar. */
68
+ export const NotificationList = ({
69
+ bottomOffset,
70
+ enterFrom = 'bottom',
71
+ filter,
72
+ hostId,
73
+ panel,
74
+ topOffset,
75
+ verticalAlignment = 'bottom',
76
+ }: NotificationListProps) => {
77
+ const { Notification: NotificationComponent } = useComponentsContext();
78
+ const styles = useStyles({ bottomOffset, topOffset, verticalAlignment });
79
+ const { t } = useTranslationContext();
80
+ const { dismissNotification, notification } = useNotificationListController({
81
+ filter,
82
+ hostId,
83
+ panel,
84
+ });
85
+
86
+ if (!notification) return null;
87
+
88
+ const notificationEnterFrom = getNotificationEnterFrom(notification, enterFrom);
89
+ const notificationPresentationKey = getNotificationPresentationKey(notification);
90
+
91
+ return (
92
+ <Animated.View
93
+ accessibilityLabel={t('a11y/Notifications')}
94
+ pointerEvents='box-none'
95
+ layout={transitions.layout200}
96
+ style={styles.container}
97
+ testID='notification-list'
98
+ >
99
+ <Animated.View
100
+ entering={transitions.boundedZoomIn200[notificationEnterFrom]}
101
+ exiting={transitions.boundedZoomOut200[notificationEnterFrom]}
102
+ key={notificationPresentationKey}
103
+ style={styles.notificationWrapper}
104
+ testID='notification-list-item'
105
+ >
106
+ <NotificationComponent
107
+ entryDirection={notificationEnterFrom}
108
+ notification={notification}
109
+ onDismiss={dismissNotification}
110
+ showClose={!notification.duration}
111
+ />
112
+ </Animated.View>
113
+ </Animated.View>
114
+ );
115
+ };
116
+
117
+ const useStyles = ({
118
+ bottomOffset,
119
+ topOffset,
120
+ verticalAlignment,
121
+ }: Pick<NotificationListProps, 'bottomOffset' | 'topOffset'> & {
122
+ verticalAlignment: NotificationListVerticalAlignment;
123
+ }) => {
124
+ const {
125
+ theme: {
126
+ notificationList: { container: notificationListContainer },
127
+ },
128
+ } = useTheme();
129
+
130
+ return useMemo(() => {
131
+ const containerAlignmentStyle =
132
+ verticalAlignment === 'bottom'
133
+ ? { bottom: primitives.spacingMd }
134
+ : { top: primitives.spacingMd };
135
+ const containerOffsetStyle =
136
+ verticalAlignment === 'bottom' && typeof bottomOffset === 'number'
137
+ ? { bottom: primitives.spacingMd + bottomOffset }
138
+ : verticalAlignment === 'top' && typeof topOffset === 'number'
139
+ ? { top: primitives.spacingMd + topOffset }
140
+ : undefined;
141
+
142
+ return StyleSheet.create({
143
+ container: {
144
+ alignItems: 'center',
145
+ left: primitives.spacingMd,
146
+ maxHeight: '100%',
147
+ position: 'absolute',
148
+ right: primitives.spacingMd,
149
+ zIndex: 20,
150
+ ...containerAlignmentStyle,
151
+ ...notificationListContainer,
152
+ ...containerOffsetStyle,
153
+ },
154
+ notificationWrapper: {
155
+ alignSelf: 'center',
156
+ maxWidth: '100%',
157
+ },
158
+ });
159
+ }, [bottomOffset, notificationListContainer, topOffset, verticalAlignment]);
160
+ };
@@ -0,0 +1,45 @@
1
+ import React, { createContext, PropsWithChildren, useContext, useMemo } from 'react';
2
+
3
+ import type { NotificationTarget, NotificationTargetPanel } from './notificationTarget';
4
+
5
+ /** Provides the default notification target for SDK actions rendered inside this subtree. */
6
+ export type NotificationTargetProviderProps = PropsWithChildren<NotificationTarget>;
7
+
8
+ const NotificationTargetContext = createContext<NotificationTarget | undefined>(undefined);
9
+
10
+ /** Makes notifications emitted by descendants resolve to a specific panel and host id. */
11
+ export const NotificationTargetProvider = ({
12
+ children,
13
+ hostId,
14
+ panel,
15
+ }: NotificationTargetProviderProps) => {
16
+ const value = useMemo(() => ({ hostId, panel }), [hostId, panel]);
17
+
18
+ return (
19
+ <NotificationTargetContext.Provider value={value}>
20
+ {children}
21
+ </NotificationTargetContext.Provider>
22
+ );
23
+ };
24
+
25
+ /** Returns the nearest notification target, if the caller is rendered inside a target provider. */
26
+ export const useNotificationTargetContext = () => useContext(NotificationTargetContext);
27
+
28
+ /** Resolves an explicit target or falls back to the nearest compatible target provider. */
29
+ export const useResolvedNotificationTarget = ({
30
+ hostId,
31
+ panel,
32
+ }: {
33
+ hostId?: string;
34
+ panel?: NotificationTargetPanel;
35
+ } = {}) => {
36
+ const contextTarget = useNotificationTargetContext();
37
+
38
+ return useMemo(() => {
39
+ if (hostId && panel) return { hostId, panel };
40
+ if (!hostId && !panel) return contextTarget;
41
+ if (panel && contextTarget?.panel === panel) return contextTarget;
42
+
43
+ return undefined;
44
+ }, [contextTarget, hostId, panel]);
45
+ };
@@ -0,0 +1,480 @@
1
+ import React, { PropsWithChildren } from 'react';
2
+
3
+ import { Pressable, Text } from 'react-native';
4
+
5
+ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react-native';
6
+ import { NotificationManager } from 'stream-chat';
7
+
8
+ import { ChannelProvider } from '../../../contexts/channelContext/ChannelContext';
9
+ import { ChatProvider } from '../../../contexts/chatContext/ChatContext';
10
+ import {
11
+ ComponentOverrides,
12
+ WithComponents,
13
+ } from '../../../contexts/componentsContext/ComponentsContext';
14
+ import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
15
+ import {
16
+ DEFAULT_USER_LANGUAGE,
17
+ TranslationProvider,
18
+ } from '../../../contexts/translationContext/TranslationContext';
19
+ import type { TranslationContextValue } from '../../../contexts/translationContext/TranslationContext';
20
+ import { useNotificationApi } from '../hooks/useNotificationApi';
21
+ import { NotificationList } from '../NotificationList';
22
+ import { NotificationTargetProvider } from '../NotificationTargetContext';
23
+
24
+ const t = ((key: string, options?: Record<string, unknown>) => {
25
+ if (options?.reason && key.includes('{{reason}}')) {
26
+ return key.replace('{{reason}}', String(options.reason));
27
+ }
28
+
29
+ return key;
30
+ }) as TranslationContextValue['t'];
31
+
32
+ const createWrapper =
33
+ (manager: NotificationManager, overrides?: ComponentOverrides) =>
34
+ ({ children }: PropsWithChildren) => (
35
+ <ChatProvider value={{ client: { notifications: manager } } as never}>
36
+ <ChannelProvider value={{ channel: { cid: 'messaging:general' } } as never}>
37
+ <TranslationProvider
38
+ value={{
39
+ t,
40
+ tDateTimeParser: (input) => input ?? new Date(),
41
+ userLanguage: DEFAULT_USER_LANGUAGE,
42
+ }}
43
+ >
44
+ <ThemeProvider>
45
+ <NotificationTargetProvider hostId='channel:messaging:general' panel='channel'>
46
+ {overrides ? (
47
+ <WithComponents overrides={overrides}>{children}</WithComponents>
48
+ ) : (
49
+ children
50
+ )}
51
+ </NotificationTargetProvider>
52
+ </ThemeProvider>
53
+ </TranslationProvider>
54
+ </ChannelProvider>
55
+ </ChatProvider>
56
+ );
57
+
58
+ describe('NotificationList', () => {
59
+ it('renders client notifications and starts their timeout once displayed', async () => {
60
+ const manager = new NotificationManager();
61
+ const startTimeoutSpy = jest.spyOn(manager, 'startTimeout').mockImplementation();
62
+
63
+ render(<NotificationList panel='channel' />, { wrapper: createWrapper(manager) });
64
+
65
+ act(() => {
66
+ manager.add({
67
+ message: 'Upload failed',
68
+ options: { severity: 'error', tags: ['target:channel'] },
69
+ origin: { emitter: 'test' },
70
+ });
71
+ });
72
+
73
+ await waitFor(() => expect(screen.getByText('Upload failed')).toBeTruthy());
74
+ expect(screen.getByTestId('notification-icon')).toBeTruthy();
75
+ expect(startTimeoutSpy).toHaveBeenCalledWith(manager.notifications[0].id);
76
+ });
77
+
78
+ it('applies a bottom offset on top of the default bottom inset', async () => {
79
+ const manager = new NotificationManager();
80
+ jest.spyOn(manager, 'startTimeout').mockImplementation();
81
+
82
+ render(<NotificationList bottomOffset={80} panel='channel' />, {
83
+ wrapper: createWrapper(manager),
84
+ });
85
+
86
+ act(() => {
87
+ manager.add({
88
+ message: 'Floating composer notice',
89
+ options: { severity: 'info', tags: ['target:channel'] },
90
+ origin: { emitter: 'test' },
91
+ });
92
+ });
93
+
94
+ await waitFor(() => expect(screen.getByText('Floating composer notice')).toBeTruthy());
95
+ expect(screen.queryByTestId('notification-icon')).toBeNull();
96
+ });
97
+
98
+ it('renders default notifications without an icon when severity is omitted', async () => {
99
+ const manager = new NotificationManager();
100
+ jest.spyOn(manager, 'startTimeout').mockImplementation();
101
+
102
+ render(<NotificationList panel='channel' />, {
103
+ wrapper: createWrapper(manager),
104
+ });
105
+
106
+ act(() => {
107
+ manager.add({
108
+ message: 'Default notice',
109
+ options: { tags: ['target:channel'] },
110
+ origin: { emitter: 'test' },
111
+ });
112
+ });
113
+
114
+ await waitFor(() => expect(screen.getByText('Default notice')).toBeTruthy());
115
+ expect(screen.queryByTestId('notification-icon')).toBeNull();
116
+ });
117
+
118
+ it('applies a top offset on top of the default top inset', async () => {
119
+ const manager = new NotificationManager();
120
+ jest.spyOn(manager, 'startTimeout').mockImplementation();
121
+
122
+ render(<NotificationList panel='channel' topOffset={40} verticalAlignment='top' />, {
123
+ wrapper: createWrapper(manager),
124
+ });
125
+
126
+ act(() => {
127
+ manager.add({
128
+ message: 'Top notice',
129
+ options: { severity: 'info', tags: ['target:channel'] },
130
+ origin: { emitter: 'test' },
131
+ });
132
+ });
133
+
134
+ await waitFor(() => expect(screen.getByText('Top notice')).toBeTruthy());
135
+ });
136
+
137
+ it('does not render system notifications in the snackbar list', () => {
138
+ const manager = new NotificationManager();
139
+ manager.add({
140
+ message: 'System notice',
141
+ options: { severity: 'info', tags: ['system'] },
142
+ origin: { emitter: 'test' },
143
+ });
144
+
145
+ render(<NotificationList />, { wrapper: createWrapper(manager) });
146
+
147
+ expect(screen.queryByTestId('notification-list')).toBeNull();
148
+ });
149
+
150
+ it('removes non-system notifications for its panel on unmount', async () => {
151
+ const manager = new NotificationManager();
152
+ jest.spyOn(manager, 'startTimeout').mockImplementation();
153
+ let channelId = '';
154
+ let threadId = '';
155
+ let systemId = '';
156
+
157
+ const { unmount } = render(<NotificationList panel='channel' />, {
158
+ wrapper: createWrapper(manager),
159
+ });
160
+
161
+ act(() => {
162
+ channelId = manager.add({
163
+ message: 'Channel notice',
164
+ options: { severity: 'info', tags: ['target:channel'] },
165
+ origin: { emitter: 'test' },
166
+ });
167
+ threadId = manager.add({
168
+ message: 'Thread notice',
169
+ options: { severity: 'info', tags: ['target:thread'] },
170
+ origin: { emitter: 'test' },
171
+ });
172
+ systemId = manager.add({
173
+ message: 'System notice',
174
+ options: { severity: 'info', tags: ['system', 'target:channel'] },
175
+ origin: { emitter: 'test' },
176
+ });
177
+ });
178
+
179
+ await waitFor(() => expect(screen.getByText('Channel notice')).toBeTruthy());
180
+
181
+ unmount();
182
+
183
+ expect(manager.notifications.some((notification) => notification.id === channelId)).toBe(false);
184
+ expect(manager.notifications.some((notification) => notification.id === threadId)).toBe(true);
185
+ expect(manager.notifications.some((notification) => notification.id === systemId)).toBe(true);
186
+ });
187
+
188
+ it('dismisses persistent notifications with the close button', async () => {
189
+ const manager = new NotificationManager();
190
+ const id = manager.add({
191
+ message: 'Persistent notice',
192
+ options: { duration: 0, severity: 'warning', tags: ['target:channel'] },
193
+ origin: { emitter: 'test' },
194
+ });
195
+
196
+ render(<NotificationList panel='channel' />, { wrapper: createWrapper(manager) });
197
+
198
+ await waitFor(() => expect(screen.getByText('Persistent notice')).toBeTruthy());
199
+ fireEvent.press(screen.getByTestId('notification-close-button'));
200
+
201
+ await waitFor(() => {
202
+ expect(manager.notifications.some((notification) => notification.id === id)).toBe(false);
203
+ });
204
+ });
205
+
206
+ it('shows the newest notification and removes older matching notifications instead of queueing', async () => {
207
+ const manager = new NotificationManager();
208
+ const startTimeoutSpy = jest.spyOn(manager, 'startTimeout').mockImplementation();
209
+ let firstId = '';
210
+ let threadId = '';
211
+ let secondId = '';
212
+
213
+ render(<NotificationList panel='channel' />, { wrapper: createWrapper(manager) });
214
+
215
+ act(() => {
216
+ firstId = manager.add({
217
+ message: 'First notice',
218
+ options: { severity: 'info', tags: ['target:channel'], type: 'ui:first' },
219
+ origin: { emitter: 'test' },
220
+ });
221
+ threadId = manager.add({
222
+ message: 'Thread notice',
223
+ options: { severity: 'info', tags: ['target:thread'], type: 'ui:thread' },
224
+ origin: { emitter: 'test' },
225
+ });
226
+ secondId = manager.add({
227
+ message: 'Second notice',
228
+ options: { severity: 'warning', tags: ['target:channel'], type: 'ui:second' },
229
+ origin: { emitter: 'test' },
230
+ });
231
+ });
232
+
233
+ await waitFor(() => expect(screen.getByText('Second notice')).toBeTruthy());
234
+ expect(screen.queryByText('First notice')).toBeNull();
235
+
236
+ await waitFor(() => {
237
+ expect(manager.notifications.some((notification) => notification.id === firstId)).toBe(false);
238
+ expect(manager.notifications.some((notification) => notification.id === threadId)).toBe(true);
239
+ expect(manager.notifications.some((notification) => notification.id === secondId)).toBe(true);
240
+ });
241
+ expect(startTimeoutSpy).toHaveBeenCalledWith(secondId);
242
+ });
243
+
244
+ it('keeps a persistent notification visible when a transient notification arrives', async () => {
245
+ const manager = new NotificationManager();
246
+ const startTimeoutSpy = jest.spyOn(manager, 'startTimeout').mockImplementation();
247
+ const retryHandler = jest.fn();
248
+ let persistentId = '';
249
+ let transientId = '';
250
+
251
+ render(<NotificationList panel='channel' />, { wrapper: createWrapper(manager) });
252
+
253
+ act(() => {
254
+ persistentId = manager.add({
255
+ message: 'Retry upload',
256
+ options: {
257
+ actions: [{ handler: retryHandler, label: 'Retry' }],
258
+ duration: 0,
259
+ severity: 'error',
260
+ tags: ['target:channel'],
261
+ type: 'ui:upload:retry',
262
+ },
263
+ origin: { emitter: 'test' },
264
+ });
265
+ transientId = manager.add({
266
+ message: 'Copied',
267
+ options: { severity: 'success', tags: ['target:channel'], type: 'ui:copy' },
268
+ origin: { emitter: 'test' },
269
+ });
270
+ });
271
+
272
+ await waitFor(() => expect(screen.getByText('Retry upload')).toBeTruthy());
273
+ fireEvent.press(screen.getByLabelText('Retry'));
274
+ expect(retryHandler).toHaveBeenCalledTimes(1);
275
+ expect(screen.queryByText('Copied')).toBeNull();
276
+
277
+ await waitFor(() => {
278
+ expect(manager.notifications.some((notification) => notification.id === persistentId)).toBe(
279
+ true,
280
+ );
281
+ expect(manager.notifications.some((notification) => notification.id === transientId)).toBe(
282
+ false,
283
+ );
284
+ });
285
+ expect(startTimeoutSpy).toHaveBeenCalledWith(persistentId);
286
+ expect(startTimeoutSpy).not.toHaveBeenCalledWith(transientId);
287
+ });
288
+
289
+ it('lets a persistent notification replace a transient notification', async () => {
290
+ const manager = new NotificationManager();
291
+ let transientId = '';
292
+ let persistentId = '';
293
+
294
+ render(<NotificationList panel='channel' />, { wrapper: createWrapper(manager) });
295
+
296
+ act(() => {
297
+ transientId = manager.add({
298
+ message: 'Copied',
299
+ options: { severity: 'success', tags: ['target:channel'], type: 'ui:copy' },
300
+ origin: { emitter: 'test' },
301
+ });
302
+ persistentId = manager.add({
303
+ message: 'Retry upload',
304
+ options: {
305
+ actions: [{ handler: jest.fn(), label: 'Retry' }],
306
+ duration: 0,
307
+ severity: 'error',
308
+ tags: ['target:channel'],
309
+ type: 'ui:upload:retry',
310
+ },
311
+ origin: { emitter: 'test' },
312
+ });
313
+ });
314
+
315
+ await waitFor(() => expect(screen.getByText('Retry upload')).toBeTruthy());
316
+ expect(screen.queryByText('Copied')).toBeNull();
317
+
318
+ await waitFor(() => {
319
+ expect(manager.notifications.some((notification) => notification.id === transientId)).toBe(
320
+ false,
321
+ );
322
+ expect(manager.notifications.some((notification) => notification.id === persistentId)).toBe(
323
+ true,
324
+ );
325
+ });
326
+ });
327
+
328
+ it('starts action notification timeouts with a longer LLC duration override', async () => {
329
+ const manager = new NotificationManager();
330
+ const startTimeoutSpy = jest.spyOn(manager, 'startTimeout').mockImplementation();
331
+ let id = '';
332
+
333
+ render(<NotificationList panel='channel' />, { wrapper: createWrapper(manager) });
334
+
335
+ act(() => {
336
+ id = manager.add({
337
+ message: 'Undo delete',
338
+ options: {
339
+ actions: [{ handler: jest.fn(), label: 'Undo' }],
340
+ severity: 'info',
341
+ tags: ['target:channel'],
342
+ type: 'ui:message:delete:undo',
343
+ },
344
+ origin: { emitter: 'test' },
345
+ });
346
+ });
347
+
348
+ await waitFor(() => expect(screen.getByText('Undo delete')).toBeTruthy());
349
+ expect(startTimeoutSpy).toHaveBeenCalledWith(id, 5000);
350
+ });
351
+
352
+ it('updates repeated matching notifications without remounting the snackbar item', async () => {
353
+ const manager = new NotificationManager();
354
+ const startTimeoutSpy = jest.spyOn(manager, 'startTimeout').mockImplementation();
355
+ let mountCount = 0;
356
+ let firstId = '';
357
+ let secondId = '';
358
+
359
+ const Notification: ComponentOverrides['Notification'] = ({ notification }) => {
360
+ React.useEffect(() => {
361
+ mountCount += 1;
362
+ }, []);
363
+
364
+ return <Text>{notification.message}</Text>;
365
+ };
366
+
367
+ render(<NotificationList panel='channel' />, {
368
+ wrapper: createWrapper(manager, { Notification }),
369
+ });
370
+
371
+ act(() => {
372
+ firstId = manager.add({
373
+ message: 'Copied',
374
+ options: { severity: 'success', tags: ['target:channel'], type: 'ui:copy' },
375
+ origin: { emitter: 'test' },
376
+ });
377
+ });
378
+
379
+ await waitFor(() => expect(screen.getByText('Copied')).toBeTruthy());
380
+ expect(mountCount).toBe(1);
381
+
382
+ act(() => {
383
+ secondId = manager.add({
384
+ message: 'Copied again',
385
+ options: { severity: 'success', tags: ['target:channel'], type: 'ui:copy' },
386
+ origin: { emitter: 'test' },
387
+ });
388
+ });
389
+
390
+ await waitFor(() => expect(screen.getByText('Copied again')).toBeTruthy());
391
+ await waitFor(() => {
392
+ expect(manager.notifications.some((notification) => notification.id === firstId)).toBe(false);
393
+ expect(manager.notifications.some((notification) => notification.id === secondId)).toBe(true);
394
+ });
395
+ expect(mountCount).toBe(1);
396
+ expect(startTimeoutSpy).toHaveBeenCalledWith(firstId);
397
+ expect(startTimeoutSpy).toHaveBeenCalledWith(secondId);
398
+ });
399
+
400
+ it('adds an exact target tag to untagged manager notifications from the latest active host', async () => {
401
+ const manager = new NotificationManager();
402
+ jest.spyOn(manager, 'startTimeout').mockImplementation();
403
+ let id = '';
404
+
405
+ render(
406
+ <>
407
+ <NotificationTargetProvider hostId='channel:messaging:first' panel='channel'>
408
+ <NotificationList />
409
+ </NotificationTargetProvider>
410
+ <NotificationTargetProvider hostId='channel:messaging:second' panel='channel'>
411
+ <NotificationList />
412
+ </NotificationTargetProvider>
413
+ </>,
414
+ { wrapper: createWrapper(manager) },
415
+ );
416
+
417
+ act(() => {
418
+ id = manager.add({
419
+ message: 'Untagged notice',
420
+ options: { severity: 'info' },
421
+ origin: { emitter: 'test' },
422
+ });
423
+ });
424
+
425
+ await waitFor(() => expect(screen.getByText('Untagged notice')).toBeTruthy());
426
+ expect(screen.getAllByText('Untagged notice')).toHaveLength(1);
427
+ expect(manager.notifications.find((notification) => notification.id === id)?.tags).toEqual([
428
+ 'target:channel:channel:messaging:second',
429
+ ]);
430
+ });
431
+
432
+ it('adds an exact target tag to action-scoped manager notifications from the triggering host', async () => {
433
+ const manager = new NotificationManager();
434
+ jest.spyOn(manager, 'startTimeout').mockImplementation();
435
+
436
+ const Trigger = () => {
437
+ const { runWithNotificationTarget } = useNotificationApi();
438
+
439
+ return (
440
+ <Pressable
441
+ onPress={() =>
442
+ runWithNotificationTarget(() => {
443
+ manager.add({
444
+ message: 'Action-scoped notice',
445
+ options: { severity: 'info' },
446
+ origin: { emitter: 'Poll' },
447
+ });
448
+ })
449
+ }
450
+ testID='trigger-action-notification'
451
+ >
452
+ <Text>Trigger</Text>
453
+ </Pressable>
454
+ );
455
+ };
456
+
457
+ render(
458
+ <>
459
+ <NotificationTargetProvider hostId='channel:messaging:first' panel='channel'>
460
+ <Trigger />
461
+ <NotificationList />
462
+ </NotificationTargetProvider>
463
+ <NotificationTargetProvider hostId='channel:messaging:second' panel='channel'>
464
+ <NotificationList />
465
+ </NotificationTargetProvider>
466
+ </>,
467
+ { wrapper: createWrapper(manager) },
468
+ );
469
+
470
+ fireEvent.press(screen.getByTestId('trigger-action-notification'));
471
+
472
+ await waitFor(() => expect(screen.getByText('Action-scoped notice')).toBeTruthy());
473
+ const notification = manager.notifications.find(
474
+ (notification) => notification.message === 'Action-scoped notice',
475
+ );
476
+
477
+ expect(screen.getAllByText('Action-scoped notice')).toHaveLength(1);
478
+ expect(notification?.tags).toEqual(['target:channel:channel:messaging:first']);
479
+ });
480
+ });