stream-chat-react-native-core 9.3.1-beta.6 → 9.3.1-beta.8

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 (329) hide show
  1. package/lib/commonjs/a11y/hooks/useAnnounceOnShow.js +27 -0
  2. package/lib/commonjs/a11y/hooks/useAnnounceOnShow.js.map +1 -0
  3. package/lib/commonjs/a11y/index.js +11 -0
  4. package/lib/commonjs/a11y/index.js.map +1 -1
  5. package/lib/commonjs/components/Accessibility/CompositeAccessibilityProbe.js +29 -0
  6. package/lib/commonjs/components/Accessibility/CompositeAccessibilityProbe.js.map +1 -0
  7. package/lib/commonjs/components/Accessibility/HiddenA11yText.js +30 -0
  8. package/lib/commonjs/components/Accessibility/HiddenA11yText.js.map +1 -0
  9. package/lib/commonjs/components/Accessibility/OverlayA11yShield.js +37 -0
  10. package/lib/commonjs/components/Accessibility/OverlayA11yShield.js.map +1 -0
  11. package/lib/commonjs/components/Accessibility/index.js +22 -0
  12. package/lib/commonjs/components/Accessibility/index.js.map +1 -1
  13. package/lib/commonjs/components/Attachment/Gallery.js +10 -0
  14. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  15. package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +31 -29
  16. package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
  17. package/lib/commonjs/components/ChannelPreview/ChannelPreviewMutedStatus.js +11 -5
  18. package/lib/commonjs/components/ChannelPreview/ChannelPreviewMutedStatus.js.map +1 -1
  19. package/lib/commonjs/components/ChannelPreview/ChannelPreviewStatus.js +18 -2
  20. package/lib/commonjs/components/ChannelPreview/ChannelPreviewStatus.js.map +1 -1
  21. package/lib/commonjs/components/ChannelPreview/ChannelPreviewUnreadCount.js +8 -2
  22. package/lib/commonjs/components/ChannelPreview/ChannelPreviewUnreadCount.js.map +1 -1
  23. package/lib/commonjs/components/ChannelPreview/ChannelPreviewView.js +3 -0
  24. package/lib/commonjs/components/ChannelPreview/ChannelPreviewView.js.map +1 -1
  25. package/lib/commonjs/components/ImageGallery/ImageGallery.js +56 -1
  26. package/lib/commonjs/components/ImageGallery/ImageGallery.js.map +1 -1
  27. package/lib/commonjs/components/Message/MessageItemView/Headers/MessageReminderHeader.js +2 -0
  28. package/lib/commonjs/components/Message/MessageItemView/Headers/MessageReminderHeader.js.map +1 -1
  29. package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js +2 -0
  30. package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
  31. package/lib/commonjs/components/Message/MessageItemView/MessageContent.js +26 -4
  32. package/lib/commonjs/components/Message/MessageItemView/MessageContent.js.map +1 -1
  33. package/lib/commonjs/components/Message/MessageItemView/MessageFooter.js +2 -0
  34. package/lib/commonjs/components/Message/MessageItemView/MessageFooter.js.map +1 -1
  35. package/lib/commonjs/components/Message/MessageItemView/MessageReplies.js +7 -3
  36. package/lib/commonjs/components/Message/MessageItemView/MessageReplies.js.map +1 -1
  37. package/lib/commonjs/components/Message/MessageItemView/MessageRepliesAvatars.js +2 -0
  38. package/lib/commonjs/components/Message/MessageItemView/MessageRepliesAvatars.js.map +1 -1
  39. package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js +35 -30
  40. package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js.map +1 -1
  41. package/lib/commonjs/components/Message/MessageItemView/MessageTextContainer.js +16 -6
  42. package/lib/commonjs/components/Message/MessageItemView/MessageTextContainer.js.map +1 -1
  43. package/lib/commonjs/components/Message/MessageItemView/ReactionList/ReactionListClustered.js +19 -6
  44. package/lib/commonjs/components/Message/MessageItemView/ReactionList/ReactionListClustered.js.map +1 -1
  45. package/lib/commonjs/components/Message/MessageItemView/ReactionList/ReactionListItem.js +10 -1
  46. package/lib/commonjs/components/Message/MessageItemView/ReactionList/ReactionListItem.js.map +1 -1
  47. package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js +6 -1
  48. package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js.map +1 -1
  49. package/lib/commonjs/components/Message/hooks/useMessageActionHandlers.js +8 -0
  50. package/lib/commonjs/components/Message/hooks/useMessageActionHandlers.js.map +1 -1
  51. package/lib/commonjs/components/MessageList/InlineDateSeparator.js +8 -1
  52. package/lib/commonjs/components/MessageList/InlineDateSeparator.js.map +1 -1
  53. package/lib/commonjs/components/MessageMenu/ReactionButton.js +0 -7
  54. package/lib/commonjs/components/MessageMenu/ReactionButton.js.map +1 -1
  55. package/lib/commonjs/components/Poll/Poll.js +2 -17
  56. package/lib/commonjs/components/Poll/Poll.js.map +1 -1
  57. package/lib/commonjs/components/Poll/components/PollOption.js +6 -1
  58. package/lib/commonjs/components/Poll/components/PollOption.js.map +1 -1
  59. package/lib/commonjs/components/Reply/Reply.js +43 -6
  60. package/lib/commonjs/components/Reply/Reply.js.map +1 -1
  61. package/lib/commonjs/components/ui/Avatar/ChannelAvatar.js +17 -11
  62. package/lib/commonjs/components/ui/Avatar/ChannelAvatar.js.map +1 -1
  63. package/lib/commonjs/components/ui/Badge/BadgeNotification.js +3 -1
  64. package/lib/commonjs/components/ui/Badge/BadgeNotification.js.map +1 -1
  65. package/lib/commonjs/contexts/accessibilityContext/AccessibilityContext.js +6 -22
  66. package/lib/commonjs/contexts/accessibilityContext/AccessibilityContext.js.map +1 -1
  67. package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
  68. package/lib/commonjs/contexts/overlayContext/MessageOverlayHostLayer.js +1 -0
  69. package/lib/commonjs/contexts/overlayContext/MessageOverlayHostLayer.js.map +1 -1
  70. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js +4 -1
  71. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js.map +1 -1
  72. package/lib/commonjs/i18n/ar.json +26 -8
  73. package/lib/commonjs/i18n/en.json +23 -5
  74. package/lib/commonjs/i18n/es.json +23 -5
  75. package/lib/commonjs/i18n/fr.json +23 -5
  76. package/lib/commonjs/i18n/he.json +23 -5
  77. package/lib/commonjs/i18n/hi.json +24 -6
  78. package/lib/commonjs/i18n/it.json +23 -5
  79. package/lib/commonjs/i18n/ja.json +23 -5
  80. package/lib/commonjs/i18n/ko.json +23 -5
  81. package/lib/commonjs/i18n/nl.json +24 -6
  82. package/lib/commonjs/i18n/pt-br.json +25 -7
  83. package/lib/commonjs/i18n/ru.json +23 -5
  84. package/lib/commonjs/i18n/tr.json +24 -6
  85. package/lib/commonjs/state-store/image-gallery-state-store.js +13 -0
  86. package/lib/commonjs/state-store/image-gallery-state-store.js.map +1 -1
  87. package/lib/commonjs/utils/i18n/getDateString.js +26 -1
  88. package/lib/commonjs/utils/i18n/getDateString.js.map +1 -1
  89. package/lib/commonjs/version.json +1 -1
  90. package/lib/module/a11y/hooks/useAnnounceOnShow.js +27 -0
  91. package/lib/module/a11y/hooks/useAnnounceOnShow.js.map +1 -0
  92. package/lib/module/a11y/index.js +11 -0
  93. package/lib/module/a11y/index.js.map +1 -1
  94. package/lib/module/components/Accessibility/CompositeAccessibilityProbe.js +29 -0
  95. package/lib/module/components/Accessibility/CompositeAccessibilityProbe.js.map +1 -0
  96. package/lib/module/components/Accessibility/HiddenA11yText.js +30 -0
  97. package/lib/module/components/Accessibility/HiddenA11yText.js.map +1 -0
  98. package/lib/module/components/Accessibility/OverlayA11yShield.js +37 -0
  99. package/lib/module/components/Accessibility/OverlayA11yShield.js.map +1 -0
  100. package/lib/module/components/Accessibility/index.js +22 -0
  101. package/lib/module/components/Accessibility/index.js.map +1 -1
  102. package/lib/module/components/Attachment/Gallery.js +10 -0
  103. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  104. package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +31 -29
  105. package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
  106. package/lib/module/components/ChannelPreview/ChannelPreviewMutedStatus.js +11 -5
  107. package/lib/module/components/ChannelPreview/ChannelPreviewMutedStatus.js.map +1 -1
  108. package/lib/module/components/ChannelPreview/ChannelPreviewStatus.js +18 -2
  109. package/lib/module/components/ChannelPreview/ChannelPreviewStatus.js.map +1 -1
  110. package/lib/module/components/ChannelPreview/ChannelPreviewUnreadCount.js +8 -2
  111. package/lib/module/components/ChannelPreview/ChannelPreviewUnreadCount.js.map +1 -1
  112. package/lib/module/components/ChannelPreview/ChannelPreviewView.js +3 -0
  113. package/lib/module/components/ChannelPreview/ChannelPreviewView.js.map +1 -1
  114. package/lib/module/components/ImageGallery/ImageGallery.js +56 -1
  115. package/lib/module/components/ImageGallery/ImageGallery.js.map +1 -1
  116. package/lib/module/components/Message/MessageItemView/Headers/MessageReminderHeader.js +2 -0
  117. package/lib/module/components/Message/MessageItemView/Headers/MessageReminderHeader.js.map +1 -1
  118. package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js +2 -0
  119. package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
  120. package/lib/module/components/Message/MessageItemView/MessageContent.js +26 -4
  121. package/lib/module/components/Message/MessageItemView/MessageContent.js.map +1 -1
  122. package/lib/module/components/Message/MessageItemView/MessageFooter.js +2 -0
  123. package/lib/module/components/Message/MessageItemView/MessageFooter.js.map +1 -1
  124. package/lib/module/components/Message/MessageItemView/MessageReplies.js +7 -3
  125. package/lib/module/components/Message/MessageItemView/MessageReplies.js.map +1 -1
  126. package/lib/module/components/Message/MessageItemView/MessageRepliesAvatars.js +2 -0
  127. package/lib/module/components/Message/MessageItemView/MessageRepliesAvatars.js.map +1 -1
  128. package/lib/module/components/Message/MessageItemView/MessageStatus.js +35 -30
  129. package/lib/module/components/Message/MessageItemView/MessageStatus.js.map +1 -1
  130. package/lib/module/components/Message/MessageItemView/MessageTextContainer.js +16 -6
  131. package/lib/module/components/Message/MessageItemView/MessageTextContainer.js.map +1 -1
  132. package/lib/module/components/Message/MessageItemView/ReactionList/ReactionListClustered.js +19 -6
  133. package/lib/module/components/Message/MessageItemView/ReactionList/ReactionListClustered.js.map +1 -1
  134. package/lib/module/components/Message/MessageItemView/ReactionList/ReactionListItem.js +10 -1
  135. package/lib/module/components/Message/MessageItemView/ReactionList/ReactionListItem.js.map +1 -1
  136. package/lib/module/components/Message/hooks/useCreateMessageContext.js +6 -1
  137. package/lib/module/components/Message/hooks/useCreateMessageContext.js.map +1 -1
  138. package/lib/module/components/Message/hooks/useMessageActionHandlers.js +8 -0
  139. package/lib/module/components/Message/hooks/useMessageActionHandlers.js.map +1 -1
  140. package/lib/module/components/MessageList/InlineDateSeparator.js +8 -1
  141. package/lib/module/components/MessageList/InlineDateSeparator.js.map +1 -1
  142. package/lib/module/components/MessageMenu/ReactionButton.js +0 -7
  143. package/lib/module/components/MessageMenu/ReactionButton.js.map +1 -1
  144. package/lib/module/components/Poll/Poll.js +2 -17
  145. package/lib/module/components/Poll/Poll.js.map +1 -1
  146. package/lib/module/components/Poll/components/PollOption.js +6 -1
  147. package/lib/module/components/Poll/components/PollOption.js.map +1 -1
  148. package/lib/module/components/Reply/Reply.js +43 -6
  149. package/lib/module/components/Reply/Reply.js.map +1 -1
  150. package/lib/module/components/ui/Avatar/ChannelAvatar.js +17 -11
  151. package/lib/module/components/ui/Avatar/ChannelAvatar.js.map +1 -1
  152. package/lib/module/components/ui/Badge/BadgeNotification.js +3 -1
  153. package/lib/module/components/ui/Badge/BadgeNotification.js.map +1 -1
  154. package/lib/module/contexts/accessibilityContext/AccessibilityContext.js +6 -22
  155. package/lib/module/contexts/accessibilityContext/AccessibilityContext.js.map +1 -1
  156. package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
  157. package/lib/module/contexts/overlayContext/MessageOverlayHostLayer.js +1 -0
  158. package/lib/module/contexts/overlayContext/MessageOverlayHostLayer.js.map +1 -1
  159. package/lib/module/contexts/overlayContext/OverlayProvider.js +4 -1
  160. package/lib/module/contexts/overlayContext/OverlayProvider.js.map +1 -1
  161. package/lib/module/i18n/ar.json +26 -8
  162. package/lib/module/i18n/en.json +23 -5
  163. package/lib/module/i18n/es.json +23 -5
  164. package/lib/module/i18n/fr.json +23 -5
  165. package/lib/module/i18n/he.json +23 -5
  166. package/lib/module/i18n/hi.json +24 -6
  167. package/lib/module/i18n/it.json +23 -5
  168. package/lib/module/i18n/ja.json +23 -5
  169. package/lib/module/i18n/ko.json +23 -5
  170. package/lib/module/i18n/nl.json +24 -6
  171. package/lib/module/i18n/pt-br.json +25 -7
  172. package/lib/module/i18n/ru.json +23 -5
  173. package/lib/module/i18n/tr.json +24 -6
  174. package/lib/module/state-store/image-gallery-state-store.js +13 -0
  175. package/lib/module/state-store/image-gallery-state-store.js.map +1 -1
  176. package/lib/module/utils/i18n/getDateString.js +26 -1
  177. package/lib/module/utils/i18n/getDateString.js.map +1 -1
  178. package/lib/module/version.json +1 -1
  179. package/lib/typescript/a11y/hooks/useAnnounceOnShow.d.ts +21 -0
  180. package/lib/typescript/a11y/hooks/useAnnounceOnShow.d.ts.map +1 -0
  181. package/lib/typescript/a11y/index.d.ts +1 -0
  182. package/lib/typescript/a11y/index.d.ts.map +1 -1
  183. package/lib/typescript/components/Accessibility/CompositeAccessibilityProbe.d.ts +30 -0
  184. package/lib/typescript/components/Accessibility/CompositeAccessibilityProbe.d.ts.map +1 -0
  185. package/lib/typescript/components/Accessibility/HiddenA11yText.d.ts +25 -0
  186. package/lib/typescript/components/Accessibility/HiddenA11yText.d.ts.map +1 -0
  187. package/lib/typescript/components/Accessibility/OverlayA11yShield.d.ts +20 -0
  188. package/lib/typescript/components/Accessibility/OverlayA11yShield.d.ts.map +1 -0
  189. package/lib/typescript/components/Accessibility/index.d.ts +2 -0
  190. package/lib/typescript/components/Accessibility/index.d.ts.map +1 -1
  191. package/lib/typescript/components/Attachment/Gallery.d.ts.map +1 -1
  192. package/lib/typescript/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.d.ts.map +1 -1
  193. package/lib/typescript/components/ChannelPreview/ChannelPreviewMutedStatus.d.ts.map +1 -1
  194. package/lib/typescript/components/ChannelPreview/ChannelPreviewStatus.d.ts.map +1 -1
  195. package/lib/typescript/components/ChannelPreview/ChannelPreviewUnreadCount.d.ts.map +1 -1
  196. package/lib/typescript/components/ChannelPreview/ChannelPreviewView.d.ts.map +1 -1
  197. package/lib/typescript/components/ImageGallery/ImageGallery.d.ts.map +1 -1
  198. package/lib/typescript/components/Message/MessageItemView/Headers/MessageReminderHeader.d.ts.map +1 -1
  199. package/lib/typescript/components/Message/MessageItemView/Headers/SentToChannelHeader.d.ts.map +1 -1
  200. package/lib/typescript/components/Message/MessageItemView/MessageContent.d.ts +1 -1
  201. package/lib/typescript/components/Message/MessageItemView/MessageContent.d.ts.map +1 -1
  202. package/lib/typescript/components/Message/MessageItemView/MessageFooter.d.ts.map +1 -1
  203. package/lib/typescript/components/Message/MessageItemView/MessageReplies.d.ts.map +1 -1
  204. package/lib/typescript/components/Message/MessageItemView/MessageRepliesAvatars.d.ts.map +1 -1
  205. package/lib/typescript/components/Message/MessageItemView/MessageStatus.d.ts.map +1 -1
  206. package/lib/typescript/components/Message/MessageItemView/MessageTextContainer.d.ts +1 -1
  207. package/lib/typescript/components/Message/MessageItemView/MessageTextContainer.d.ts.map +1 -1
  208. package/lib/typescript/components/Message/MessageItemView/ReactionList/ReactionListClustered.d.ts.map +1 -1
  209. package/lib/typescript/components/Message/MessageItemView/ReactionList/ReactionListItem.d.ts.map +1 -1
  210. package/lib/typescript/components/Message/MessageOverlayWrapper.d.ts +1 -1
  211. package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts +1 -1
  212. package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts.map +1 -1
  213. package/lib/typescript/components/Message/hooks/useMessageActionHandlers.d.ts.map +1 -1
  214. package/lib/typescript/components/MessageList/InlineDateSeparator.d.ts.map +1 -1
  215. package/lib/typescript/components/MessageMenu/ReactionButton.d.ts.map +1 -1
  216. package/lib/typescript/components/Poll/Poll.d.ts.map +1 -1
  217. package/lib/typescript/components/Poll/components/PollOption.d.ts.map +1 -1
  218. package/lib/typescript/components/Reply/Reply.d.ts.map +1 -1
  219. package/lib/typescript/components/ui/Avatar/ChannelAvatar.d.ts.map +1 -1
  220. package/lib/typescript/components/ui/Badge/BadgeNotification.d.ts +5 -0
  221. package/lib/typescript/components/ui/Badge/BadgeNotification.d.ts.map +1 -1
  222. package/lib/typescript/contexts/accessibilityContext/AccessibilityContext.d.ts +32 -2
  223. package/lib/typescript/contexts/accessibilityContext/AccessibilityContext.d.ts.map +1 -1
  224. package/lib/typescript/contexts/messageContext/MessageContext.d.ts +8 -0
  225. package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
  226. package/lib/typescript/contexts/overlayContext/MessageOverlayHostLayer.d.ts.map +1 -1
  227. package/lib/typescript/contexts/overlayContext/OverlayProvider.d.ts.map +1 -1
  228. package/lib/typescript/hooks/useTranslatedMessage.d.ts +1 -1
  229. package/lib/typescript/i18n/ar.json +26 -8
  230. package/lib/typescript/i18n/en.json +23 -5
  231. package/lib/typescript/i18n/es.json +23 -5
  232. package/lib/typescript/i18n/fr.json +23 -5
  233. package/lib/typescript/i18n/he.json +23 -5
  234. package/lib/typescript/i18n/hi.json +24 -6
  235. package/lib/typescript/i18n/it.json +23 -5
  236. package/lib/typescript/i18n/ja.json +23 -5
  237. package/lib/typescript/i18n/ko.json +23 -5
  238. package/lib/typescript/i18n/nl.json +24 -6
  239. package/lib/typescript/i18n/pt-br.json +25 -7
  240. package/lib/typescript/i18n/ru.json +23 -5
  241. package/lib/typescript/i18n/tr.json +24 -6
  242. package/lib/typescript/state-store/image-gallery-state-store.d.ts +5 -1
  243. package/lib/typescript/state-store/image-gallery-state-store.d.ts.map +1 -1
  244. package/lib/typescript/utils/i18n/Streami18n.d.ts +22 -4
  245. package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
  246. package/lib/typescript/utils/i18n/getDateString.d.ts +28 -0
  247. package/lib/typescript/utils/i18n/getDateString.d.ts.map +1 -1
  248. package/package.json +1 -1
  249. package/src/a11y/hooks/useAnnounceOnShow.ts +44 -0
  250. package/src/a11y/index.ts +1 -0
  251. package/src/components/Accessibility/CompositeAccessibilityProbe.tsx +48 -0
  252. package/src/components/Accessibility/HiddenA11yText.tsx +49 -0
  253. package/src/components/Accessibility/OverlayA11yShield.tsx +49 -0
  254. package/src/components/Accessibility/__tests__/OverlayA11yShield.test.tsx +83 -0
  255. package/src/components/Accessibility/index.ts +2 -0
  256. package/src/components/Attachment/Gallery.tsx +14 -2
  257. package/src/components/Channel/__tests__/ownCapabilities.test.tsx +25 -2
  258. package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx +19 -16
  259. package/src/components/ChannelPreview/ChannelPreviewMutedStatus.tsx +8 -1
  260. package/src/components/ChannelPreview/ChannelPreviewStatus.tsx +26 -6
  261. package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx +5 -1
  262. package/src/components/ChannelPreview/ChannelPreviewView.tsx +3 -0
  263. package/src/components/ImageGallery/ImageGallery.tsx +82 -4
  264. package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx +13 -3
  265. package/src/components/ImageGallery/__tests__/ImageGalleryAdjustable.test.tsx +141 -0
  266. package/src/components/Message/MessageItemView/Headers/MessageReminderHeader.tsx +7 -1
  267. package/src/components/Message/MessageItemView/Headers/SentToChannelHeader.tsx +7 -1
  268. package/src/components/Message/MessageItemView/MessageContent.tsx +34 -4
  269. package/src/components/Message/MessageItemView/MessageFooter.tsx +6 -1
  270. package/src/components/Message/MessageItemView/MessageReplies.tsx +9 -7
  271. package/src/components/Message/MessageItemView/MessageRepliesAvatars.tsx +5 -1
  272. package/src/components/Message/MessageItemView/MessageStatus.tsx +36 -39
  273. package/src/components/Message/MessageItemView/MessageTextContainer.tsx +27 -4
  274. package/src/components/Message/MessageItemView/ReactionList/ReactionListClustered.tsx +24 -10
  275. package/src/components/Message/MessageItemView/ReactionList/ReactionListItem.tsx +7 -1
  276. package/src/components/Message/MessageItemView/__tests__/Message.test.tsx +10 -3
  277. package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx +10 -2
  278. package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx +10 -7
  279. package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx +1 -1
  280. package/src/components/Message/hooks/useCreateMessageContext.ts +12 -1
  281. package/src/components/Message/hooks/useMessageActionHandlers.ts +7 -0
  282. package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.tsx.snap +3 -0
  283. package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.tsx.snap +2 -0
  284. package/src/components/MessageList/InlineDateSeparator.tsx +10 -3
  285. package/src/components/MessageMenu/ReactionButton.tsx +0 -7
  286. package/src/components/MessageMenu/__tests__/ReactionButton.test.tsx +1 -14
  287. package/src/components/Poll/Poll.tsx +2 -26
  288. package/src/components/Poll/components/PollOption.tsx +7 -1
  289. package/src/components/Reply/Reply.tsx +58 -7
  290. package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap +9 -0
  291. package/src/components/ui/Avatar/ChannelAvatar.tsx +39 -29
  292. package/src/components/ui/Badge/BadgeNotification.tsx +11 -2
  293. package/src/contexts/accessibilityContext/AccessibilityContext.tsx +60 -36
  294. package/src/contexts/messageContext/MessageContext.tsx +8 -0
  295. package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx +5 -1
  296. package/src/contexts/overlayContext/OverlayProvider.tsx +2 -1
  297. package/src/i18n/ar.json +26 -8
  298. package/src/i18n/en.json +23 -5
  299. package/src/i18n/es.json +23 -5
  300. package/src/i18n/fr.json +23 -5
  301. package/src/i18n/he.json +23 -5
  302. package/src/i18n/hi.json +24 -6
  303. package/src/i18n/it.json +23 -5
  304. package/src/i18n/ja.json +23 -5
  305. package/src/i18n/ko.json +23 -5
  306. package/src/i18n/nl.json +24 -6
  307. package/src/i18n/pt-br.json +25 -7
  308. package/src/i18n/ru.json +23 -5
  309. package/src/i18n/tr.json +24 -6
  310. package/src/state-store/__tests__/image-gallery-state-store.test.ts +1 -0
  311. package/src/state-store/image-gallery-state-store.ts +13 -1
  312. package/src/utils/i18n/getDateString.ts +57 -0
  313. package/src/version.json +1 -1
  314. package/lib/commonjs/components/Poll/hooks/usePollAccessibilityActions.js +0 -150
  315. package/lib/commonjs/components/Poll/hooks/usePollAccessibilityActions.js.map +0 -1
  316. package/lib/commonjs/components/Poll/hooks/usePollAccessibilityLabel.js +0 -59
  317. package/lib/commonjs/components/Poll/hooks/usePollAccessibilityLabel.js.map +0 -1
  318. package/lib/module/components/Poll/hooks/usePollAccessibilityActions.js +0 -150
  319. package/lib/module/components/Poll/hooks/usePollAccessibilityActions.js.map +0 -1
  320. package/lib/module/components/Poll/hooks/usePollAccessibilityLabel.js +0 -59
  321. package/lib/module/components/Poll/hooks/usePollAccessibilityLabel.js.map +0 -1
  322. package/lib/typescript/components/Poll/hooks/usePollAccessibilityActions.d.ts +0 -25
  323. package/lib/typescript/components/Poll/hooks/usePollAccessibilityActions.d.ts.map +0 -1
  324. package/lib/typescript/components/Poll/hooks/usePollAccessibilityLabel.d.ts +0 -8
  325. package/lib/typescript/components/Poll/hooks/usePollAccessibilityLabel.d.ts.map +0 -1
  326. package/src/components/Poll/hooks/__tests__/usePollAccessibilityActions.test.tsx +0 -358
  327. package/src/components/Poll/hooks/__tests__/usePollAccessibilityLabel.test.tsx +0 -142
  328. package/src/components/Poll/hooks/usePollAccessibilityActions.ts +0 -191
  329. package/src/components/Poll/hooks/usePollAccessibilityLabel.ts +0 -75
@@ -0,0 +1,49 @@
1
+ import React, { PropsWithChildren } from 'react';
2
+ import { Platform, StyleSheet, View } from 'react-native';
3
+
4
+ import { useOverlayContext } from '../../contexts/overlayContext/OverlayContext';
5
+ import { useStateStore } from '../../hooks';
6
+ import { overlayStore } from '../../state-store/message-overlay-store';
7
+
8
+ const messageOverlayActiveSelector = (state: { id: string | undefined }) => ({
9
+ isMessageOverlayActive: state.id !== undefined,
10
+ });
11
+
12
+ /**
13
+ * Android only accessibility focus trap for the OverlayProvider's children
14
+ * tree. iOS handles modal focus traps natively via `accessibilityViewIsModal`
15
+ * on each overlay's root, but Android has no equivalent prop - the only
16
+ * JS side mechanism is to mark siblings as `'no-hide-descendants'`.
17
+ *
18
+ * The shield wraps `{children}` in a single View whose `importantForAccessibility`
19
+ * flips to `'no-hide-descendants'` whenever any focus trapping overlay is
20
+ * active (the full screen image/video gallery or the message context menu).
21
+ * When closed, the wrapper is a transparent passthrough.
22
+ *
23
+ * In terms of rerendering, only the wrapper View commits new props on overlay state
24
+ * transitions. The `{children}` element reference is stable across renders,
25
+ * so React reconciliation does not rerender any component below the wrapper.
26
+ *
27
+ * On iOS the wrapper is skipped entirely.
28
+ */
29
+ export function OverlayA11yShield({ children }: PropsWithChildren) {
30
+ const { overlay } = useOverlayContext();
31
+ const { isMessageOverlayActive } = useStateStore(overlayStore, messageOverlayActiveSelector);
32
+
33
+ if (Platform.OS !== 'android') {
34
+ return <>{children}</>;
35
+ }
36
+
37
+ const isAnyOverlayActive = overlay === 'gallery' || isMessageOverlayActive;
38
+
39
+ return (
40
+ <View
41
+ accessibilityElementsHidden={isAnyOverlayActive}
42
+ importantForAccessibility={isAnyOverlayActive ? 'no-hide-descendants' : 'auto'}
43
+ style={StyleSheet.absoluteFill}
44
+ testID='overlay-a11y-shield'
45
+ >
46
+ {children}
47
+ </View>
48
+ );
49
+ }
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { Platform, Text } from 'react-native';
3
+
4
+ import { act, render, screen } from '@testing-library/react-native';
5
+
6
+ import { OverlayContext } from '../../../contexts/overlayContext/OverlayContext';
7
+ import { overlayStore } from '../../../state-store/message-overlay-store';
8
+ import { OverlayA11yShield } from '../OverlayA11yShield';
9
+
10
+ const setPlatform = (os: typeof Platform.OS) => {
11
+ Object.defineProperty(Platform, 'OS', { configurable: true, get: () => os });
12
+ };
13
+
14
+ const renderShield = (overlay: 'none' | 'gallery' = 'none') =>
15
+ render(
16
+ <OverlayContext.Provider
17
+ value={
18
+ {
19
+ overlay,
20
+ setOverlay: () => undefined,
21
+ style: undefined,
22
+ } as never
23
+ }
24
+ >
25
+ <OverlayA11yShield>
26
+ <Text testID='child'>child</Text>
27
+ </OverlayA11yShield>
28
+ </OverlayContext.Provider>,
29
+ );
30
+
31
+ // The wrapper sets `accessibilityElementsHidden` / `importantForAccessibility`
32
+ // when an overlay is active — RTL v13 filters those elements out of a11y queries
33
+ // by default. `includeHiddenElements: true` keeps them queryable for assertion.
34
+ const wrapper = () => screen.queryByTestId('overlay-a11y-shield', { includeHiddenElements: true });
35
+
36
+ describe('OverlayA11yShield', () => {
37
+ const originalOS = Platform.OS;
38
+ afterAll(() => setPlatform(originalOS));
39
+
40
+ beforeEach(() => {
41
+ act(() => {
42
+ overlayStore.partialNext({ closing: false, id: undefined, messageId: undefined });
43
+ });
44
+ });
45
+
46
+ describe('on Android', () => {
47
+ beforeAll(() => setPlatform('android'));
48
+
49
+ it('renders children inside the wrapper', () => {
50
+ renderShield();
51
+ expect(screen.getByTestId('child')).toBeTruthy();
52
+ expect(wrapper()).toBeTruthy();
53
+ });
54
+
55
+ it('does not hide descendants when no overlay is active', () => {
56
+ renderShield('none');
57
+ expect(wrapper()?.props.importantForAccessibility).toBe('auto');
58
+ });
59
+
60
+ it('hides descendants when the gallery overlay is active', () => {
61
+ renderShield('gallery');
62
+ expect(wrapper()?.props.importantForAccessibility).toBe('no-hide-descendants');
63
+ });
64
+
65
+ it('hides descendants when the message overlay opens', () => {
66
+ renderShield('none');
67
+ act(() => {
68
+ overlayStore.partialNext({ id: 'msg-1' });
69
+ });
70
+ expect(wrapper()?.props.importantForAccessibility).toBe('no-hide-descendants');
71
+ });
72
+ });
73
+
74
+ describe('on iOS', () => {
75
+ beforeAll(() => setPlatform('ios'));
76
+
77
+ it('renders children without a wrapper', () => {
78
+ renderShield();
79
+ expect(screen.getByTestId('child')).toBeTruthy();
80
+ expect(wrapper()).toBeNull();
81
+ });
82
+ });
83
+ });
@@ -1,3 +1,5 @@
1
+ export * from './CompositeAccessibilityProbe';
2
+ export * from './HiddenA11yText';
1
3
  export * from './NotificationAnnouncer';
2
4
  export * from './useAccessibilityAnnouncer';
3
5
  export * from './hooks/useIncomingMessageAnnouncements';
@@ -1,5 +1,5 @@
1
- import React, { useMemo } from 'react';
2
- import { Pressable, StyleSheet, Text, View } from 'react-native';
1
+ import React, { useMemo, useRef } from 'react';
2
+ import { findNodeHandle, Pressable, StyleSheet, Text, View } from 'react-native';
3
3
 
4
4
  import type { Attachment, LocalMessage } from 'stream-chat';
5
5
 
@@ -14,6 +14,7 @@ import {
14
14
 
15
15
  import { openUrlSafely } from './utils/openUrlSafely';
16
16
 
17
+ import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
17
18
  import { useTranslationContext } from '../../contexts';
18
19
  import { useChatConfigContext } from '../../contexts/chatConfigContext/ChatConfigContext';
19
20
  import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
@@ -236,12 +237,19 @@ const GalleryThumbnail = ({
236
237
  } = useTheme();
237
238
  const { t } = useTranslationContext();
238
239
  const styles = useStyles();
240
+ const isVideo = thumbnail.type === FileTypes.Video;
241
+ const thumbnailAccessibilityLabel = useA11yLabel(
242
+ isVideo ? 'a11y/Gallery Video' : 'a11y/Gallery Image',
243
+ );
244
+ const thumbnailAccessibilityHint = useA11yLabel('a11y/Double tap to open');
245
+ const thumbnailRef = useRef<View>(null);
239
246
  const openImageViewer = () => {
240
247
  if (!message) {
241
248
  return;
242
249
  }
243
250
  imageGalleryStateStore.openImageGallery({
244
251
  messages: [message],
252
+ requesterNode: findNodeHandle(thumbnailRef.current),
245
253
  selectedAttachmentUrl: thumbnail.url,
246
254
  });
247
255
  setOverlay('gallery');
@@ -260,8 +268,12 @@ const GalleryThumbnail = ({
260
268
  };
261
269
  return (
262
270
  <Pressable
271
+ accessibilityHint={thumbnailAccessibilityHint}
272
+ accessibilityLabel={thumbnailAccessibilityLabel}
273
+ accessibilityRole='button'
263
274
  disabled={preventPress}
264
275
  key={`gallery-item-${message.id}/${colIndex}/${rowIndex}/${imagesAndVideos.length}`}
276
+ ref={thumbnailRef}
265
277
  onLongPress={(event) => {
266
278
  if (onLongPress) {
267
279
  onLongPress({
@@ -3,7 +3,7 @@ import { FlatList } from 'react-native';
3
3
 
4
4
  import { SafeAreaProvider } from 'react-native-safe-area-context';
5
5
 
6
- import { act, fireEvent, render, waitFor } from '@testing-library/react-native';
6
+ import { act, cleanup, fireEvent, render, waitFor } from '@testing-library/react-native';
7
7
  import type { Channel as ChannelType, LocalMessage, StreamChat } from 'stream-chat';
8
8
 
9
9
  import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider';
@@ -16,6 +16,7 @@ import { generateMessage } from '../../../mock-builders/generator/message';
16
16
  import { generateUser } from '../../../mock-builders/generator/user';
17
17
  import { getTestClientWithUser } from '../../../mock-builders/mock';
18
18
  import { registerNativeHandlers } from '../../../native';
19
+ import { closeOverlay, finalizeCloseOverlay } from '../../../state-store';
19
20
  import { Channel } from '../../Channel/Channel';
20
21
  import { Chat } from '../../Chat/Chat';
21
22
  import { MessageComposer } from '../../MessageInput/MessageComposer';
@@ -49,6 +50,15 @@ describe('Own capabilities', () => {
49
50
  });
50
51
  });
51
52
 
53
+ afterEach(() => {
54
+ cleanup();
55
+ // The overlay state-store is module-level, so an overlay opened by a
56
+ // previous test would still be `active` when the next test mounts.
57
+ // Reset it so each test starts with `isActive=false`.
58
+ closeOverlay();
59
+ finalizeCloseOverlay();
60
+ });
61
+
52
62
  const getComponent = (props: Partial<React.ComponentProps<typeof Channel>> = {}) => (
53
63
  <SafeAreaProvider>
54
64
  <OverlayProvider>
@@ -76,7 +86,20 @@ describe('Own capabilities', () => {
76
86
  targetMessage: LocalMessage,
77
87
  props: Partial<React.ComponentProps<typeof Channel>> = {},
78
88
  ) => {
79
- const { findByTestId, queryByLabelText, queryByText, unmount } = render(getComponent(props));
89
+ const {
90
+ findByTestId,
91
+ queryByLabelText: rawQueryByLabelText,
92
+ queryByText,
93
+ unmount,
94
+ } = render(getComponent(props));
95
+ // After the overlay opens, the host layer's `accessibilityViewIsModal`
96
+ // marks the chat tree as hidden to RNTL. The message action list is
97
+ // Portal-teleported into the overlay but its React parent stays inside
98
+ // the chat, so the visible-elements filter excludes it. The list IS
99
+ // rendered (and visible in production); we pass `includeHiddenElements`
100
+ // to look past the modal-sibling heuristic.
101
+ const queryByLabelText: typeof rawQueryByLabelText = (text, options) =>
102
+ rawQueryByLabelText(text, { includeHiddenElements: true, ...options });
80
103
  await waitFor(() => queryByText(targetMessage.text as string));
81
104
 
82
105
  act(() => {
@@ -13,6 +13,7 @@ import { MessageDeliveryStatus, useMessageDeliveryStatus } from '../../hooks';
13
13
  import { Check, CheckAll, Time } from '../../icons';
14
14
  import { primitives } from '../../theme';
15
15
  import { MessageStatusTypes } from '../../utils/utils';
16
+ import { CompositeAccessibilityProbe } from '../Accessibility/CompositeAccessibilityProbe';
16
17
 
17
18
  export type ChannelMessagePreviewDeliveryStatusProps = Pick<ChannelPreviewProps, 'channel'> & {
18
19
  message: MessageResponse | LocalMessage;
@@ -66,11 +67,11 @@ export const ChannelMessagePreviewDeliveryStatus = ({
66
67
  message.status === MessageStatusTypes.SENDING
67
68
  ? 'a11y/Sending'
68
69
  : message.status === MessageStatusTypes.RECEIVED && status === MessageDeliveryStatus.READ
69
- ? 'a11y/Read'
70
+ ? 'a11y/Read, sent by you'
70
71
  : status === MessageDeliveryStatus.DELIVERED
71
- ? 'a11y/Delivered'
72
+ ? 'a11y/Delivered, sent by you'
72
73
  : status === MessageDeliveryStatus.SENT
73
- ? 'a11y/Sent'
74
+ ? 'a11y/Sent by you'
74
75
  : 'a11y/Sending',
75
76
  );
76
77
 
@@ -83,19 +84,21 @@ export const ChannelMessagePreviewDeliveryStatus = ({
83
84
  }
84
85
 
85
86
  return (
86
- <View accessibilityLabel={statusLabel} accessibilityRole='text' style={styles.container}>
87
- {message.status === MessageStatusTypes.SENDING ? (
88
- <Time stroke={semantics.chatTextTimestamp} height={16} width={16} {...timeIcon} />
89
- ) : message.status === MessageStatusTypes.RECEIVED &&
90
- status === MessageDeliveryStatus.READ ? (
91
- <CheckAll stroke={semantics.accentPrimary} height={16} width={16} {...checkAllIcon} />
92
- ) : status === MessageDeliveryStatus.DELIVERED ? (
93
- <CheckAll stroke={semantics.chatTextTimestamp} height={16} width={16} {...checkAllIcon} />
94
- ) : status === MessageDeliveryStatus.SENT ? (
95
- <Check stroke={semantics.chatTextTimestamp} height={16} width={16} {...checkIcon} />
96
- ) : null}
97
- <Text style={styles.text}>{t('You')}:</Text>
98
- </View>
87
+ <CompositeAccessibilityProbe label={statusLabel}>
88
+ <View style={styles.container}>
89
+ {message.status === MessageStatusTypes.SENDING ? (
90
+ <Time stroke={semantics.chatTextTimestamp} height={16} width={16} {...timeIcon} />
91
+ ) : message.status === MessageStatusTypes.RECEIVED &&
92
+ status === MessageDeliveryStatus.READ ? (
93
+ <CheckAll stroke={semantics.accentPrimary} height={16} width={16} {...checkAllIcon} />
94
+ ) : status === MessageDeliveryStatus.DELIVERED ? (
95
+ <CheckAll stroke={semantics.chatTextTimestamp} height={16} width={16} {...checkAllIcon} />
96
+ ) : status === MessageDeliveryStatus.SENT ? (
97
+ <Check stroke={semantics.chatTextTimestamp} height={16} width={16} {...checkIcon} />
98
+ ) : null}
99
+ <Text style={styles.text}>{t('You')}:</Text>
100
+ </View>
101
+ </CompositeAccessibilityProbe>
99
102
  );
100
103
  };
101
104
 
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
2
 
3
+ import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
3
4
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
4
5
  import { Mute } from '../../icons';
6
+ import { CompositeAccessibilityProbe } from '../Accessibility/CompositeAccessibilityProbe';
5
7
 
6
8
  /**
7
9
  * This UI component displays an avatar for a particular channel.
@@ -13,6 +15,11 @@ export const ChannelPreviewMutedStatus = () => {
13
15
  semantics,
14
16
  },
15
17
  } = useTheme();
18
+ const accessibilityLabel = useA11yLabel('a11y/Muted');
16
19
 
17
- return <Mute height={20} fill={semantics.textTertiary} width={20} {...mutedStatus} />;
20
+ return (
21
+ <CompositeAccessibilityProbe label={accessibilityLabel}>
22
+ <Mute height={20} fill={semantics.textTertiary} width={20} {...mutedStatus} />
23
+ </CompositeAccessibilityProbe>
24
+ );
18
25
  };
@@ -4,11 +4,12 @@ import { StyleSheet, Text } from 'react-native';
4
4
  import type { ChannelPreviewProps } from './ChannelPreview';
5
5
  import type { ChannelPreviewViewPropsWithContext } from './ChannelPreviewView';
6
6
 
7
+ import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
7
8
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
8
9
  import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
9
10
 
10
11
  import { primitives } from '../../theme';
11
- import { getDateString } from '../../utils/i18n/getDateString';
12
+ import { getDateString, getDateStringForA11y } from '../../utils/i18n/getDateString';
12
13
 
13
14
  export type ChannelPreviewStatusProps = Pick<
14
15
  ChannelPreviewViewPropsWithContext,
@@ -18,7 +19,7 @@ export type ChannelPreviewStatusProps = Pick<
18
19
 
19
20
  export const ChannelPreviewStatus = (props: ChannelPreviewStatusProps) => {
20
21
  const { formatLatestMessageDate, lastMessage } = props;
21
- const { t, tDateTimeParser } = useTranslationContext();
22
+ const { t, tDateTimeParser, userLanguage } = useTranslationContext();
22
23
  const styles = useStyles();
23
24
 
24
25
  const created_at = lastMessage?.created_at;
@@ -35,11 +36,30 @@ export const ChannelPreviewStatus = (props: ChannelPreviewStatusProps) => {
35
36
  [created_at, t, tDateTimeParser],
36
37
  );
37
38
 
39
+ const a11yDate = useMemo(
40
+ () =>
41
+ getDateStringForA11y({
42
+ calendarFormatOverrides: { sameDay: 'LT' },
43
+ date: created_at,
44
+ tDateTimeParser,
45
+ userLanguage,
46
+ }),
47
+ [created_at, tDateTimeParser, userLanguage],
48
+ );
49
+
50
+ const visibleDate =
51
+ formatLatestMessageDate && latestMessageDate
52
+ ? formatLatestMessageDate(latestMessageDate).toString()
53
+ : formattedDate;
54
+ const labelParams = useMemo(
55
+ () => ({ date: a11yDate ?? visibleDate ?? '' }),
56
+ [a11yDate, visibleDate],
57
+ );
58
+ const accessibilityLabel = useA11yLabel('a11y/Last message {{date}}', labelParams);
59
+
38
60
  return (
39
- <Text style={styles.date}>
40
- {formatLatestMessageDate && latestMessageDate
41
- ? formatLatestMessageDate(latestMessageDate).toString()
42
- : formattedDate}
61
+ <Text accessibilityLabel={accessibilityLabel} style={styles.date}>
62
+ {visibleDate}
43
63
  </Text>
44
64
  );
45
65
  };
@@ -1,7 +1,8 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
 
3
3
  import { ChannelPreviewProps } from './ChannelPreview';
4
4
 
5
+ import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
5
6
  import type { ChannelsContextValue } from '../../contexts/channelsContext/ChannelsContext';
6
7
  import { BadgeNotification } from '../ui/Badge';
7
8
 
@@ -15,12 +16,15 @@ export type ChannelPreviewUnreadCountProps = Pick<ChannelsContextValue, 'maxUnre
15
16
 
16
17
  export const ChannelPreviewUnreadCount = (props: ChannelPreviewUnreadCountProps) => {
17
18
  const { maxUnreadCount, unread } = props;
19
+ const labelParams = useMemo(() => ({ count: unread ?? 0 }), [unread]);
20
+ const accessibilityLabel = useA11yLabel('a11y/{{count}} unread messages', labelParams);
18
21
  if (!unread) {
19
22
  return null;
20
23
  }
21
24
 
22
25
  return (
23
26
  <BadgeNotification
27
+ accessibilityLabel={accessibilityLabel}
24
28
  count={unread > maxUnreadCount ? maxUnreadCount : unread}
25
29
  size='sm'
26
30
  type='primary'
@@ -5,6 +5,7 @@ import type { ChannelPreviewProps } from './ChannelPreview';
5
5
 
6
6
  import type { LastMessageType } from './hooks/useChannelPreviewData';
7
7
 
8
+ import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
8
9
  import {
9
10
  ChannelsContextValue,
10
11
  useChannelsContext,
@@ -70,6 +71,7 @@ const ChannelPreviewViewWithContext = (props: ChannelPreviewViewPropsWithContext
70
71
  } = useTheme();
71
72
  const styles = useStyles();
72
73
  const swipeRegistry = useSwipeRegistryContext();
74
+ const accessibilityHint = useA11yLabel('a11y/Double tap to open');
73
75
 
74
76
  const onPress = useStableCallback(() => {
75
77
  if (swipeRegistry?.hasOpen()) {
@@ -84,6 +86,7 @@ const ChannelPreviewViewWithContext = (props: ChannelPreviewViewPropsWithContext
84
86
  return (
85
87
  <View style={[styles.wrapper, wrapper]}>
86
88
  <Pressable
89
+ accessibilityHint={accessibilityHint}
87
90
  onPress={onPress}
88
91
  style={({ pressed }) => [
89
92
  styles.container,
@@ -1,5 +1,12 @@
1
- import React, { useCallback, useEffect, useState } from 'react';
2
- import { Image, ImageStyle, StyleSheet, ViewStyle } from 'react-native';
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ AccessibilityInfo,
4
+ Image,
5
+ ImageStyle,
6
+ Platform,
7
+ StyleSheet,
8
+ ViewStyle,
9
+ } from 'react-native';
3
10
  import { Gesture, GestureDetector } from 'react-native-gesture-handler';
4
11
 
5
12
  import Animated, {
@@ -21,6 +28,8 @@ import type {
21
28
 
22
29
  import { useImageGalleryGestures } from './hooks/useImageGalleryGestures';
23
30
 
31
+ import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
32
+ import { useAccessibilityContext } from '../../contexts/accessibilityContext/AccessibilityContext';
24
33
  import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
25
34
  import {
26
35
  ImageGalleryProviderProps,
@@ -280,13 +289,82 @@ export const ImageGalleryWithContext = (props: ImageGalleryWithContextProps) =>
280
289
  setIsGridViewVisible(true);
281
290
  };
282
291
 
292
+ const { enabled: isAccessibilityEnabled } = useAccessibilityContext();
293
+ const assetsCount = assets.length;
294
+ const isAdjustable = isAccessibilityEnabled;
295
+ const accessibilityValueParams = useMemo(
296
+ () => ({ count: assetsCount, position: currentIndex + 1 }),
297
+ [currentIndex, assetsCount],
298
+ );
299
+ const accessibilityValueText = useA11yLabel(
300
+ 'a11y/{{position}} of {{count}}',
301
+ accessibilityValueParams,
302
+ );
303
+ const accessibilityValue = useMemo(
304
+ () => (accessibilityValueText ? { text: accessibilityValueText } : undefined),
305
+ [accessibilityValueText],
306
+ );
307
+ const adjustableActions = useMemo(
308
+ () =>
309
+ isAdjustable ? [{ name: 'increment' as const }, { name: 'decrement' as const }] : undefined,
310
+ [isAdjustable],
311
+ );
312
+
313
+ const onAccessibilityAction = useCallback(
314
+ (event: { nativeEvent: { actionName: string } }) => {
315
+ if (!isAccessibilityEnabled) return;
316
+ const latest = imageGalleryStateStore.state.getLatestValue();
317
+ const latestCount = latest.assets.length;
318
+ const latestIndex = latest.currentIndex;
319
+ if (latestCount <= 1) return;
320
+ if (event.nativeEvent.actionName === 'increment') {
321
+ if (latestIndex < latestCount - 1) {
322
+ imageGalleryStateStore.currentIndex = latestIndex + 1;
323
+ }
324
+ } else if (event.nativeEvent.actionName === 'decrement') {
325
+ if (latestIndex > 0) {
326
+ imageGalleryStateStore.currentIndex = latestIndex - 1;
327
+ }
328
+ }
329
+ },
330
+ [imageGalleryStateStore, isAccessibilityEnabled],
331
+ );
332
+
333
+ useEffect(() => {
334
+ return () => {
335
+ const handle = imageGalleryStateStore.requesterNode;
336
+ if (handle == null) return;
337
+ imageGalleryStateStore.requesterNode = null;
338
+ // Because of the fact that iOS and Android handle supressing
339
+ // the content underneath differently, we have to wait a frame
340
+ // before iOS is allowed to attempt to refocus (it takes a frame
341
+ // for the native a11y tree to become aware that it no longer has
342
+ // an accessibilityViewIsModal sibling).
343
+ if (Platform.OS === 'android') {
344
+ AccessibilityInfo.setAccessibilityFocus(handle);
345
+ } else {
346
+ requestAnimationFrame(() => {
347
+ AccessibilityInfo.setAccessibilityFocus(handle);
348
+ });
349
+ }
350
+ };
351
+ }, [imageGalleryStateStore]);
352
+
283
353
  return (
284
354
  <Animated.View
285
- accessibilityLabel='Image Gallery'
355
+ accessibilityViewIsModal
286
356
  pointerEvents={'auto'}
287
357
  style={[StyleSheet.absoluteFill, showScreenStyle]}
288
358
  >
289
- <Animated.View style={[StyleSheet.absoluteFill, containerBackground]} />
359
+ <Animated.View
360
+ accessible
361
+ accessibilityActions={adjustableActions}
362
+ accessibilityLabel='Image Gallery'
363
+ accessibilityRole={isAdjustable ? 'adjustable' : undefined}
364
+ accessibilityValue={isAdjustable ? accessibilityValue : undefined}
365
+ onAccessibilityAction={isAdjustable ? onAccessibilityAction : undefined}
366
+ style={[StyleSheet.absoluteFill, containerBackground]}
367
+ />
290
368
  <GestureDetector gesture={Gesture.Simultaneous(singleTap, doubleTap, pinch, pan)}>
291
369
  <Animated.View style={StyleSheet.absoluteFill}>
292
370
  <Animated.View
@@ -99,8 +99,16 @@ describe('ImageGallery', () => {
99
99
  );
100
100
 
101
101
  await waitFor(() => {
102
- expect(screen.queryAllByLabelText('Image Item')).toHaveLength(2);
103
- expect(screen.queryAllByLabelText('Image Gallery Video')).toHaveLength(1);
102
+ // The pager subtree is marked `accessibilityElementsHidden` /
103
+ // `importantForAccessibility='no-hide-descendants'` so screen readers
104
+ // can't land on the silent shadow views inside it; the queries here
105
+ // opt back into hidden elements so tests can still find the assets.
106
+ expect(
107
+ screen.queryAllByLabelText('Image Item', { includeHiddenElements: true }),
108
+ ).toHaveLength(2);
109
+ expect(
110
+ screen.queryAllByLabelText('Image Gallery Video', { includeHiddenElements: true }),
111
+ ).toHaveLength(1);
104
112
  });
105
113
  });
106
114
 
@@ -116,7 +124,9 @@ describe('ImageGallery', () => {
116
124
  );
117
125
 
118
126
  await waitFor(() => {
119
- const pagerStyle = StyleSheet.flatten(screen.getByTestId('image-gallery-pager').props.style);
127
+ const pagerStyle = StyleSheet.flatten(
128
+ screen.getByTestId('image-gallery-pager', { includeHiddenElements: true }).props.style,
129
+ );
120
130
  expect(pagerStyle.direction).toBe('ltr');
121
131
  });
122
132
  });