stream-chat-react-native-core 9.3.1-beta.7 → 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.
- package/lib/commonjs/components/Accessibility/OverlayA11yShield.js +37 -0
- package/lib/commonjs/components/Accessibility/OverlayA11yShield.js.map +1 -0
- package/lib/commonjs/components/Attachment/Gallery.js +10 -0
- package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewStatus.js +12 -3
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewStatus.js.map +1 -1
- package/lib/commonjs/components/ImageGallery/ImageGallery.js +56 -1
- package/lib/commonjs/components/ImageGallery/ImageGallery.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/Headers/MessageReminderHeader.js +2 -0
- package/lib/commonjs/components/Message/MessageItemView/Headers/MessageReminderHeader.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js +2 -0
- package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/MessageContent.js +26 -5
- package/lib/commonjs/components/Message/MessageItemView/MessageContent.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/MessageReplies.js +7 -3
- package/lib/commonjs/components/Message/MessageItemView/MessageReplies.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/MessageRepliesAvatars.js +2 -0
- package/lib/commonjs/components/Message/MessageItemView/MessageRepliesAvatars.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/MessageTextContainer.js +16 -6
- package/lib/commonjs/components/Message/MessageItemView/MessageTextContainer.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/ReactionList/ReactionListItem.js +4 -0
- package/lib/commonjs/components/Message/MessageItemView/ReactionList/ReactionListItem.js.map +1 -1
- package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js +6 -1
- package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js.map +1 -1
- package/lib/commonjs/components/MessageList/InlineDateSeparator.js +8 -1
- package/lib/commonjs/components/MessageList/InlineDateSeparator.js.map +1 -1
- package/lib/commonjs/components/MessageMenu/ReactionButton.js +0 -7
- package/lib/commonjs/components/MessageMenu/ReactionButton.js.map +1 -1
- package/lib/commonjs/components/Poll/Poll.js +2 -0
- package/lib/commonjs/components/Poll/Poll.js.map +1 -1
- package/lib/commonjs/components/Poll/components/PollOption.js +6 -1
- package/lib/commonjs/components/Poll/components/PollOption.js.map +1 -1
- package/lib/commonjs/contexts/accessibilityContext/AccessibilityContext.js +6 -22
- package/lib/commonjs/contexts/accessibilityContext/AccessibilityContext.js.map +1 -1
- package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
- package/lib/commonjs/contexts/overlayContext/MessageOverlayHostLayer.js +1 -0
- package/lib/commonjs/contexts/overlayContext/MessageOverlayHostLayer.js.map +1 -1
- package/lib/commonjs/contexts/overlayContext/OverlayProvider.js +4 -1
- package/lib/commonjs/contexts/overlayContext/OverlayProvider.js.map +1 -1
- package/lib/commonjs/i18n/ar.json +7 -1
- package/lib/commonjs/i18n/en.json +7 -1
- package/lib/commonjs/i18n/es.json +7 -1
- package/lib/commonjs/i18n/fr.json +7 -1
- package/lib/commonjs/i18n/he.json +7 -1
- package/lib/commonjs/i18n/hi.json +7 -1
- package/lib/commonjs/i18n/it.json +7 -1
- package/lib/commonjs/i18n/ja.json +7 -1
- package/lib/commonjs/i18n/ko.json +7 -1
- package/lib/commonjs/i18n/nl.json +7 -1
- package/lib/commonjs/i18n/pt-br.json +7 -1
- package/lib/commonjs/i18n/ru.json +7 -1
- package/lib/commonjs/i18n/tr.json +7 -1
- package/lib/commonjs/state-store/image-gallery-state-store.js +13 -0
- package/lib/commonjs/state-store/image-gallery-state-store.js.map +1 -1
- package/lib/commonjs/utils/i18n/getDateString.js +26 -1
- package/lib/commonjs/utils/i18n/getDateString.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Accessibility/OverlayA11yShield.js +37 -0
- package/lib/module/components/Accessibility/OverlayA11yShield.js.map +1 -0
- package/lib/module/components/Attachment/Gallery.js +10 -0
- package/lib/module/components/Attachment/Gallery.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelPreviewStatus.js +12 -3
- package/lib/module/components/ChannelPreview/ChannelPreviewStatus.js.map +1 -1
- package/lib/module/components/ImageGallery/ImageGallery.js +56 -1
- package/lib/module/components/ImageGallery/ImageGallery.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/Headers/MessageReminderHeader.js +2 -0
- package/lib/module/components/Message/MessageItemView/Headers/MessageReminderHeader.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js +2 -0
- package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/MessageContent.js +26 -5
- package/lib/module/components/Message/MessageItemView/MessageContent.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/MessageReplies.js +7 -3
- package/lib/module/components/Message/MessageItemView/MessageReplies.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/MessageRepliesAvatars.js +2 -0
- package/lib/module/components/Message/MessageItemView/MessageRepliesAvatars.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/MessageTextContainer.js +16 -6
- package/lib/module/components/Message/MessageItemView/MessageTextContainer.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/ReactionList/ReactionListItem.js +4 -0
- package/lib/module/components/Message/MessageItemView/ReactionList/ReactionListItem.js.map +1 -1
- package/lib/module/components/Message/hooks/useCreateMessageContext.js +6 -1
- package/lib/module/components/Message/hooks/useCreateMessageContext.js.map +1 -1
- package/lib/module/components/MessageList/InlineDateSeparator.js +8 -1
- package/lib/module/components/MessageList/InlineDateSeparator.js.map +1 -1
- package/lib/module/components/MessageMenu/ReactionButton.js +0 -7
- package/lib/module/components/MessageMenu/ReactionButton.js.map +1 -1
- package/lib/module/components/Poll/Poll.js +2 -0
- package/lib/module/components/Poll/Poll.js.map +1 -1
- package/lib/module/components/Poll/components/PollOption.js +6 -1
- package/lib/module/components/Poll/components/PollOption.js.map +1 -1
- package/lib/module/contexts/accessibilityContext/AccessibilityContext.js +6 -22
- package/lib/module/contexts/accessibilityContext/AccessibilityContext.js.map +1 -1
- package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
- package/lib/module/contexts/overlayContext/MessageOverlayHostLayer.js +1 -0
- package/lib/module/contexts/overlayContext/MessageOverlayHostLayer.js.map +1 -1
- package/lib/module/contexts/overlayContext/OverlayProvider.js +4 -1
- package/lib/module/contexts/overlayContext/OverlayProvider.js.map +1 -1
- package/lib/module/i18n/ar.json +7 -1
- package/lib/module/i18n/en.json +7 -1
- package/lib/module/i18n/es.json +7 -1
- package/lib/module/i18n/fr.json +7 -1
- package/lib/module/i18n/he.json +7 -1
- package/lib/module/i18n/hi.json +7 -1
- package/lib/module/i18n/it.json +7 -1
- package/lib/module/i18n/ja.json +7 -1
- package/lib/module/i18n/ko.json +7 -1
- package/lib/module/i18n/nl.json +7 -1
- package/lib/module/i18n/pt-br.json +7 -1
- package/lib/module/i18n/ru.json +7 -1
- package/lib/module/i18n/tr.json +7 -1
- package/lib/module/state-store/image-gallery-state-store.js +13 -0
- package/lib/module/state-store/image-gallery-state-store.js.map +1 -1
- package/lib/module/utils/i18n/getDateString.js +26 -1
- package/lib/module/utils/i18n/getDateString.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Accessibility/OverlayA11yShield.d.ts +20 -0
- package/lib/typescript/components/Accessibility/OverlayA11yShield.d.ts.map +1 -0
- package/lib/typescript/components/Attachment/Gallery.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/ChannelPreviewStatus.d.ts.map +1 -1
- package/lib/typescript/components/ImageGallery/ImageGallery.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/Headers/MessageReminderHeader.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/Headers/SentToChannelHeader.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageContent.d.ts +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageContent.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageReplies.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageRepliesAvatars.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageTextContainer.d.ts +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageTextContainer.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/ReactionList/ReactionListItem.d.ts.map +1 -1
- package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts +1 -1
- package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/InlineDateSeparator.d.ts.map +1 -1
- package/lib/typescript/components/MessageMenu/ReactionButton.d.ts.map +1 -1
- package/lib/typescript/components/Poll/components/PollOption.d.ts.map +1 -1
- package/lib/typescript/contexts/accessibilityContext/AccessibilityContext.d.ts +32 -2
- package/lib/typescript/contexts/accessibilityContext/AccessibilityContext.d.ts.map +1 -1
- package/lib/typescript/contexts/messageContext/MessageContext.d.ts +8 -0
- package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
- package/lib/typescript/contexts/overlayContext/MessageOverlayHostLayer.d.ts.map +1 -1
- package/lib/typescript/contexts/overlayContext/OverlayProvider.d.ts.map +1 -1
- package/lib/typescript/i18n/ar.json +7 -1
- package/lib/typescript/i18n/en.json +7 -1
- package/lib/typescript/i18n/es.json +7 -1
- package/lib/typescript/i18n/fr.json +7 -1
- package/lib/typescript/i18n/he.json +7 -1
- package/lib/typescript/i18n/hi.json +7 -1
- package/lib/typescript/i18n/it.json +7 -1
- package/lib/typescript/i18n/ja.json +7 -1
- package/lib/typescript/i18n/ko.json +7 -1
- package/lib/typescript/i18n/nl.json +7 -1
- package/lib/typescript/i18n/pt-br.json +7 -1
- package/lib/typescript/i18n/ru.json +7 -1
- package/lib/typescript/i18n/tr.json +7 -1
- package/lib/typescript/state-store/image-gallery-state-store.d.ts +5 -1
- package/lib/typescript/state-store/image-gallery-state-store.d.ts.map +1 -1
- package/lib/typescript/utils/i18n/Streami18n.d.ts +6 -0
- package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
- package/lib/typescript/utils/i18n/getDateString.d.ts +28 -0
- package/lib/typescript/utils/i18n/getDateString.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Accessibility/OverlayA11yShield.tsx +49 -0
- package/src/components/Accessibility/__tests__/OverlayA11yShield.test.tsx +83 -0
- package/src/components/Attachment/Gallery.tsx +14 -2
- package/src/components/Channel/__tests__/ownCapabilities.test.tsx +25 -2
- package/src/components/ChannelPreview/ChannelPreviewStatus.tsx +17 -3
- package/src/components/ImageGallery/ImageGallery.tsx +82 -4
- package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx +13 -3
- package/src/components/ImageGallery/__tests__/ImageGalleryAdjustable.test.tsx +141 -0
- package/src/components/Message/MessageItemView/Headers/MessageReminderHeader.tsx +7 -1
- package/src/components/Message/MessageItemView/Headers/SentToChannelHeader.tsx +7 -1
- package/src/components/Message/MessageItemView/MessageContent.tsx +34 -15
- package/src/components/Message/MessageItemView/MessageReplies.tsx +9 -7
- package/src/components/Message/MessageItemView/MessageRepliesAvatars.tsx +5 -1
- package/src/components/Message/MessageItemView/MessageTextContainer.tsx +27 -4
- package/src/components/Message/MessageItemView/ReactionList/ReactionListItem.tsx +2 -0
- package/src/components/Message/MessageItemView/__tests__/Message.test.tsx +10 -3
- package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx +10 -2
- package/src/components/Message/hooks/useCreateMessageContext.ts +12 -1
- package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.tsx.snap +3 -0
- package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.tsx.snap +2 -0
- package/src/components/MessageList/InlineDateSeparator.tsx +10 -3
- package/src/components/MessageMenu/ReactionButton.tsx +0 -7
- package/src/components/MessageMenu/__tests__/ReactionButton.test.tsx +1 -14
- package/src/components/Poll/Poll.tsx +1 -1
- package/src/components/Poll/components/PollOption.tsx +7 -1
- package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap +1 -0
- package/src/contexts/accessibilityContext/AccessibilityContext.tsx +60 -36
- package/src/contexts/messageContext/MessageContext.tsx +8 -0
- package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx +5 -1
- package/src/contexts/overlayContext/OverlayProvider.tsx +2 -1
- package/src/i18n/ar.json +7 -1
- package/src/i18n/en.json +7 -1
- package/src/i18n/es.json +7 -1
- package/src/i18n/fr.json +7 -1
- package/src/i18n/he.json +7 -1
- package/src/i18n/hi.json +7 -1
- package/src/i18n/it.json +7 -1
- package/src/i18n/ja.json +7 -1
- package/src/i18n/ko.json +7 -1
- package/src/i18n/nl.json +7 -1
- package/src/i18n/pt-br.json +7 -1
- package/src/i18n/ru.json +7 -1
- package/src/i18n/tr.json +7 -1
- package/src/state-store/__tests__/image-gallery-state-store.test.ts +1 -0
- package/src/state-store/image-gallery-state-store.ts +13 -1
- package/src/utils/i18n/getDateString.ts +57 -0
- package/src/version.json +1 -1
|
@@ -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,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 {
|
|
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(() => {
|
|
@@ -9,7 +9,7 @@ import { useTheme } from '../../contexts/themeContext/ThemeContext';
|
|
|
9
9
|
import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
|
|
10
10
|
|
|
11
11
|
import { primitives } from '../../theme';
|
|
12
|
-
import { getDateString } from '../../utils/i18n/getDateString';
|
|
12
|
+
import { getDateString, getDateStringForA11y } from '../../utils/i18n/getDateString';
|
|
13
13
|
|
|
14
14
|
export type ChannelPreviewStatusProps = Pick<
|
|
15
15
|
ChannelPreviewViewPropsWithContext,
|
|
@@ -19,7 +19,7 @@ export type ChannelPreviewStatusProps = Pick<
|
|
|
19
19
|
|
|
20
20
|
export const ChannelPreviewStatus = (props: ChannelPreviewStatusProps) => {
|
|
21
21
|
const { formatLatestMessageDate, lastMessage } = props;
|
|
22
|
-
const { t, tDateTimeParser } = useTranslationContext();
|
|
22
|
+
const { t, tDateTimeParser, userLanguage } = useTranslationContext();
|
|
23
23
|
const styles = useStyles();
|
|
24
24
|
|
|
25
25
|
const created_at = lastMessage?.created_at;
|
|
@@ -36,11 +36,25 @@ export const ChannelPreviewStatus = (props: ChannelPreviewStatusProps) => {
|
|
|
36
36
|
[created_at, t, tDateTimeParser],
|
|
37
37
|
);
|
|
38
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
|
+
|
|
39
50
|
const visibleDate =
|
|
40
51
|
formatLatestMessageDate && latestMessageDate
|
|
41
52
|
? formatLatestMessageDate(latestMessageDate).toString()
|
|
42
53
|
: formattedDate;
|
|
43
|
-
const labelParams = useMemo(
|
|
54
|
+
const labelParams = useMemo(
|
|
55
|
+
() => ({ date: a11yDate ?? visibleDate ?? '' }),
|
|
56
|
+
[a11yDate, visibleDate],
|
|
57
|
+
);
|
|
44
58
|
const accessibilityLabel = useA11yLabel('a11y/Last message {{date}}', labelParams);
|
|
45
59
|
|
|
46
60
|
return (
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import {
|
|
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
|
-
|
|
355
|
+
accessibilityViewIsModal
|
|
286
356
|
pointerEvents={'auto'}
|
|
287
357
|
style={[StyleSheet.absoluteFill, showScreenStyle]}
|
|
288
358
|
>
|
|
289
|
-
<Animated.View
|
|
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
|
-
|
|
103
|
-
|
|
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(
|
|
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
|
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
4
|
+
|
|
5
|
+
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react-native';
|
|
6
|
+
|
|
7
|
+
import { LocalMessage } from 'stream-chat';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
ImageGalleryContext,
|
|
11
|
+
ImageGalleryContextValue,
|
|
12
|
+
} from '../../../contexts/imageGalleryContext/ImageGalleryContext';
|
|
13
|
+
import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider';
|
|
14
|
+
import {
|
|
15
|
+
generateImageAttachment,
|
|
16
|
+
generateVideoAttachment,
|
|
17
|
+
} from '../../../mock-builders/generator/attachment';
|
|
18
|
+
import { generateMessage } from '../../../mock-builders/generator/message';
|
|
19
|
+
import { ImageGalleryStateStore } from '../../../state-store/image-gallery-state-store';
|
|
20
|
+
import { ImageGallery } from '../ImageGallery';
|
|
21
|
+
|
|
22
|
+
jest.mock('../../../native.ts', () => {
|
|
23
|
+
const { View } = require('react-native');
|
|
24
|
+
return {
|
|
25
|
+
isFileSystemAvailable: jest.fn(() => true),
|
|
26
|
+
isImageMediaLibraryAvailable: jest.fn(() => true),
|
|
27
|
+
isShareImageAvailable: jest.fn(() => true),
|
|
28
|
+
isVideoPlayerAvailable: jest.fn(() => true),
|
|
29
|
+
NativeHandlers: { Video: View },
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
type HarnessProps = {
|
|
34
|
+
message: LocalMessage;
|
|
35
|
+
accessibilityEnabled?: boolean;
|
|
36
|
+
store: ImageGalleryStateStore;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const Harness = ({ accessibilityEnabled = true, message, store }: HarnessProps) => {
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const unsubscribe = store.registerSubscriptions();
|
|
42
|
+
return () => unsubscribe();
|
|
43
|
+
}, [store]);
|
|
44
|
+
|
|
45
|
+
const { attachments } = message;
|
|
46
|
+
store.openImageGallery({
|
|
47
|
+
messages: [message],
|
|
48
|
+
selectedAttachmentUrl: attachments?.[0]?.asset_url || attachments?.[0]?.image_url || '',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<OverlayProvider
|
|
53
|
+
accessibility={{ enabled: accessibilityEnabled }}
|
|
54
|
+
value={{ overlayOpacity: { value: 1 } as SharedValue<number> }}
|
|
55
|
+
>
|
|
56
|
+
<ImageGalleryContext.Provider
|
|
57
|
+
value={{ imageGalleryStateStore: store } as unknown as ImageGalleryContextValue}
|
|
58
|
+
>
|
|
59
|
+
<ImageGallery />
|
|
60
|
+
</ImageGalleryContext.Provider>
|
|
61
|
+
</OverlayProvider>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const findGalleryRoot = () =>
|
|
66
|
+
screen.getByLabelText('Image Gallery', { includeHiddenElements: true });
|
|
67
|
+
|
|
68
|
+
const fireAccessibilityAction = (actionName: 'increment' | 'decrement') => {
|
|
69
|
+
fireEvent(findGalleryRoot(), 'accessibilityAction', { nativeEvent: { actionName } });
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const renderWithAssets = (assetsCount: number, accessibilityEnabled = true) => {
|
|
73
|
+
const attachments = [
|
|
74
|
+
...Array.from({ length: Math.max(assetsCount - 1, 0) }, () => generateImageAttachment()),
|
|
75
|
+
...(assetsCount > 0 ? [generateVideoAttachment({ type: 'video' })] : []),
|
|
76
|
+
];
|
|
77
|
+
const message = generateMessage({ attachments });
|
|
78
|
+
const store = new ImageGalleryStateStore();
|
|
79
|
+
render(<Harness accessibilityEnabled={accessibilityEnabled} message={message} store={store} />);
|
|
80
|
+
return { store };
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
describe('ImageGallery adjustable cycling', () => {
|
|
84
|
+
it('marks the root as adjustable with the position value when a11y is enabled and there is more than one asset', async () => {
|
|
85
|
+
renderWithAssets(3);
|
|
86
|
+
|
|
87
|
+
await waitFor(() => {
|
|
88
|
+
const root = findGalleryRoot();
|
|
89
|
+
expect(root.props.accessibilityRole).toBe('adjustable');
|
|
90
|
+
expect(root.props.accessibilityValue).toEqual({ text: '1 of 3' });
|
|
91
|
+
expect(root.props.accessibilityActions).toEqual([
|
|
92
|
+
{ name: 'increment' },
|
|
93
|
+
{ name: 'decrement' },
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('does not apply the adjustable role when accessibility is disabled', async () => {
|
|
99
|
+
renderWithAssets(3, false);
|
|
100
|
+
|
|
101
|
+
await waitFor(() => {
|
|
102
|
+
const root = findGalleryRoot();
|
|
103
|
+
expect(root.props.accessibilityRole).toBeUndefined();
|
|
104
|
+
expect(root.props.accessibilityActions).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('moves to the next asset on increment and clamps at the last asset', async () => {
|
|
109
|
+
const { store } = renderWithAssets(3);
|
|
110
|
+
|
|
111
|
+
await waitFor(() => expect(findGalleryRoot().props.accessibilityRole).toBe('adjustable'));
|
|
112
|
+
|
|
113
|
+
act(() => fireAccessibilityAction('increment'));
|
|
114
|
+
expect(store.state.getLatestValue().currentIndex).toBe(1);
|
|
115
|
+
|
|
116
|
+
act(() => fireAccessibilityAction('increment'));
|
|
117
|
+
expect(store.state.getLatestValue().currentIndex).toBe(2);
|
|
118
|
+
|
|
119
|
+
act(() => fireAccessibilityAction('increment'));
|
|
120
|
+
expect(store.state.getLatestValue().currentIndex).toBe(2);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('moves to the previous asset on decrement and clamps at the first asset', async () => {
|
|
124
|
+
const { store } = renderWithAssets(3);
|
|
125
|
+
|
|
126
|
+
await waitFor(() => expect(findGalleryRoot().props.accessibilityRole).toBe('adjustable'));
|
|
127
|
+
|
|
128
|
+
act(() => fireAccessibilityAction('increment'));
|
|
129
|
+
act(() => fireAccessibilityAction('increment'));
|
|
130
|
+
expect(store.state.getLatestValue().currentIndex).toBe(2);
|
|
131
|
+
|
|
132
|
+
act(() => fireAccessibilityAction('decrement'));
|
|
133
|
+
expect(store.state.getLatestValue().currentIndex).toBe(1);
|
|
134
|
+
|
|
135
|
+
act(() => fireAccessibilityAction('decrement'));
|
|
136
|
+
expect(store.state.getLatestValue().currentIndex).toBe(0);
|
|
137
|
+
|
|
138
|
+
act(() => fireAccessibilityAction('decrement'));
|
|
139
|
+
expect(store.state.getLatestValue().currentIndex).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -33,7 +33,13 @@ const MessageReminderHeaderWithContext = (props: MessageReminderHeaderPropsWithC
|
|
|
33
33
|
<Text style={styles.label}>
|
|
34
34
|
{isReminderTimeLeft ? t('Reminder set') : t('Reminder overdue')}
|
|
35
35
|
</Text>
|
|
36
|
-
<Text
|
|
36
|
+
<Text
|
|
37
|
+
accessibilityElementsHidden
|
|
38
|
+
importantForAccessibility='no-hide-descendants'
|
|
39
|
+
style={styles.dot}
|
|
40
|
+
>
|
|
41
|
+
·
|
|
42
|
+
</Text>
|
|
37
43
|
<Text style={styles.time}>
|
|
38
44
|
{t('duration/Message reminder', {
|
|
39
45
|
milliseconds: timeLeftMs,
|
|
@@ -43,7 +43,13 @@ const SentToChannelHeaderWithContext = (props: SentToChannelHeaderPropsWithConte
|
|
|
43
43
|
</Text>
|
|
44
44
|
{showViewText ? (
|
|
45
45
|
<>
|
|
46
|
-
<Text
|
|
46
|
+
<Text
|
|
47
|
+
accessibilityElementsHidden
|
|
48
|
+
importantForAccessibility='no-hide-descendants'
|
|
49
|
+
style={styles.dot}
|
|
50
|
+
>
|
|
51
|
+
·
|
|
52
|
+
</Text>
|
|
47
53
|
<Pressable onPress={onPress}>
|
|
48
54
|
<Text style={styles.link}>{t('View')}</Text>
|
|
49
55
|
</Pressable>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
|
-
import { ColorValue, Pressable, StyleSheet, View, ViewStyle } from 'react-native';
|
|
2
|
+
import { ColorValue, Platform, Pressable, StyleSheet, View, ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { MessageTextContainer } from './MessageTextContainer';
|
|
5
5
|
|
|
@@ -55,6 +55,7 @@ export type MessageContentPropsWithContext = Pick<
|
|
|
55
55
|
| 'alignment'
|
|
56
56
|
| 'goToMessage'
|
|
57
57
|
| 'groupStyles'
|
|
58
|
+
| 'hasInteractiveAccessibilityContent'
|
|
58
59
|
| 'isMyMessage'
|
|
59
60
|
| 'message'
|
|
60
61
|
| 'messageContentOrder'
|
|
@@ -111,6 +112,7 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
|
|
|
111
112
|
enableMessageGroupingByUser,
|
|
112
113
|
groupStyles,
|
|
113
114
|
goToMessage,
|
|
115
|
+
hasInteractiveAccessibilityContent,
|
|
114
116
|
isMessageAIGenerated,
|
|
115
117
|
isMyMessage,
|
|
116
118
|
isVeryLastMessage,
|
|
@@ -129,6 +131,10 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
|
|
|
129
131
|
} = props;
|
|
130
132
|
const { client } = useChatContext();
|
|
131
133
|
const accessibilityHint = useA11yLabel('a11y/Double tap and hold to activate contextual menu');
|
|
134
|
+
const a11ySenderLabel = useA11yLabel(
|
|
135
|
+
isMyMessage ? 'a11y/Message from you' : 'a11y/Message from {{sender}}',
|
|
136
|
+
isMyMessage ? undefined : { sender: message.user?.name || message.user?.id || '' },
|
|
137
|
+
);
|
|
132
138
|
const {
|
|
133
139
|
Attachment,
|
|
134
140
|
FileAttachmentGroup,
|
|
@@ -317,22 +323,18 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
|
|
|
317
323
|
)}
|
|
318
324
|
</>
|
|
319
325
|
);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
message.poll_id ||
|
|
327
|
-
message.quoted_message ||
|
|
328
|
-
message.attachments?.length ||
|
|
329
|
-
message.shared_location
|
|
330
|
-
);
|
|
326
|
+
const a11yPressableLabel = useMemo(() => {
|
|
327
|
+
if (!a11ySenderLabel) return undefined;
|
|
328
|
+
return message.text && !hasInteractiveAccessibilityContent
|
|
329
|
+
? `${a11ySenderLabel}. ${message.text}`
|
|
330
|
+
: a11ySenderLabel;
|
|
331
|
+
}, [a11ySenderLabel, hasInteractiveAccessibilityContent, message.text]);
|
|
331
332
|
|
|
332
333
|
return (
|
|
333
334
|
<Pressable
|
|
335
|
+
accessibilityLabel={a11yPressableLabel}
|
|
334
336
|
accessibilityHint={accessibilityHint}
|
|
335
|
-
accessible={
|
|
337
|
+
accessible={hasInteractiveAccessibilityContent ? false : undefined}
|
|
336
338
|
disabled={preventPress}
|
|
337
339
|
onLongPress={(event) => {
|
|
338
340
|
if (onLongPress) {
|
|
@@ -382,6 +384,15 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
|
|
|
382
384
|
]}
|
|
383
385
|
testID='message-content-wrapper'
|
|
384
386
|
>
|
|
387
|
+
{a11ySenderLabel && Platform.OS !== 'android' && hasInteractiveAccessibilityContent ? (
|
|
388
|
+
<View
|
|
389
|
+
accessibilityLabel={a11ySenderLabel}
|
|
390
|
+
accessibilityHint={accessibilityHint}
|
|
391
|
+
accessible
|
|
392
|
+
pointerEvents='none'
|
|
393
|
+
style={StyleSheet.absoluteFill}
|
|
394
|
+
/>
|
|
395
|
+
) : null}
|
|
385
396
|
{MessageContentTopView ? <MessageContentTopView /> : null}
|
|
386
397
|
{hasContentSideViews ? (
|
|
387
398
|
<View
|
|
@@ -414,6 +425,7 @@ const areEqual = (
|
|
|
414
425
|
preventPress: prevPreventPress,
|
|
415
426
|
goToMessage: prevGoToMessage,
|
|
416
427
|
groupStyles: prevGroupStyles,
|
|
428
|
+
hasInteractiveAccessibilityContent: prevHasInteractiveAccessibilityContent,
|
|
417
429
|
isAttachmentEqual,
|
|
418
430
|
message: prevMessage,
|
|
419
431
|
messageContentOrder: prevMessageContentOrder,
|
|
@@ -427,6 +439,7 @@ const areEqual = (
|
|
|
427
439
|
preventPress: nextPreventPress,
|
|
428
440
|
goToMessage: nextGoToMessage,
|
|
429
441
|
groupStyles: nextGroupStyles,
|
|
442
|
+
hasInteractiveAccessibilityContent: nextHasInteractiveAccessibilityContent,
|
|
430
443
|
message: nextMessage,
|
|
431
444
|
messageContentOrder: nextMessageContentOrder,
|
|
432
445
|
myMessageTheme: nextMyMessageTheme,
|
|
@@ -434,6 +447,10 @@ const areEqual = (
|
|
|
434
447
|
t: nextT,
|
|
435
448
|
} = nextProps;
|
|
436
449
|
|
|
450
|
+
if (prevHasInteractiveAccessibilityContent !== nextHasInteractiveAccessibilityContent) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
|
|
437
454
|
if (prevBackgroundColor !== nextBackgroundColor) {
|
|
438
455
|
return false;
|
|
439
456
|
}
|
|
@@ -569,8 +586,11 @@ export type MessageContentProps = Partial<MessageContentPropsWithContext>;
|
|
|
569
586
|
export const MessageContent = (props: MessageContentProps) => {
|
|
570
587
|
const {
|
|
571
588
|
alignment,
|
|
589
|
+
files,
|
|
572
590
|
goToMessage,
|
|
573
591
|
groupStyles,
|
|
592
|
+
hasInteractiveAccessibilityContent,
|
|
593
|
+
images,
|
|
574
594
|
isMessageAIGenerated,
|
|
575
595
|
isMyMessage,
|
|
576
596
|
message,
|
|
@@ -581,8 +601,6 @@ export const MessageContent = (props: MessageContentProps) => {
|
|
|
581
601
|
otherAttachments,
|
|
582
602
|
preventPress,
|
|
583
603
|
threadList,
|
|
584
|
-
files,
|
|
585
|
-
images,
|
|
586
604
|
videos,
|
|
587
605
|
} = useMessageContext();
|
|
588
606
|
const {
|
|
@@ -637,6 +655,7 @@ export const MessageContent = (props: MessageContentProps) => {
|
|
|
637
655
|
enableMessageGroupingByUser,
|
|
638
656
|
goToMessage,
|
|
639
657
|
groupStyles,
|
|
658
|
+
hasInteractiveAccessibilityContent,
|
|
640
659
|
isAttachmentEqual,
|
|
641
660
|
isMessageAIGenerated,
|
|
642
661
|
isMyMessage,
|