stream-chat-react-native-core 9.0.1-beta.1 → 9.0.1-beta.3
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/Channel/Channel.js +2 -0
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js +3 -1
- package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
- package/lib/commonjs/components/Message/Message.js +99 -74
- package/lib/commonjs/components/Message/Message.js.map +1 -1
- package/lib/commonjs/components/Message/MessageOverlayWrapper.js +64 -0
- package/lib/commonjs/components/Message/MessageOverlayWrapper.js.map +1 -0
- package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js +5 -1
- package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js.map +1 -1
- package/lib/commonjs/components/Message/hooks/useShouldUseOverlayStyles.js +8 -1
- package/lib/commonjs/components/Message/hooks/useShouldUseOverlayStyles.js.map +1 -1
- package/lib/commonjs/components/Message/messageOverlayConstants.js +6 -0
- package/lib/commonjs/components/Message/messageOverlayConstants.js.map +1 -0
- package/lib/commonjs/components/index.js +11 -11
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/contexts/componentsContext/defaultComponents.js +0 -2
- package/lib/commonjs/contexts/componentsContext/defaultComponents.js.map +1 -1
- package/lib/commonjs/contexts/messageContext/MessageContext.js +32 -1
- package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
- package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
- package/lib/commonjs/hooks/index.js +4 -4
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Channel/Channel.js +2 -0
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/components/Channel/hooks/useCreateMessagesContext.js +3 -1
- package/lib/module/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
- package/lib/module/components/Message/Message.js +99 -74
- package/lib/module/components/Message/Message.js.map +1 -1
- package/lib/module/components/Message/MessageOverlayWrapper.js +64 -0
- package/lib/module/components/Message/MessageOverlayWrapper.js.map +1 -0
- package/lib/module/components/Message/hooks/useCreateMessageContext.js +5 -1
- package/lib/module/components/Message/hooks/useCreateMessageContext.js.map +1 -1
- package/lib/module/components/Message/hooks/useShouldUseOverlayStyles.js +8 -1
- package/lib/module/components/Message/hooks/useShouldUseOverlayStyles.js.map +1 -1
- package/lib/module/components/Message/messageOverlayConstants.js +6 -0
- package/lib/module/components/Message/messageOverlayConstants.js.map +1 -0
- package/lib/module/components/index.js +11 -11
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/contexts/componentsContext/defaultComponents.js +0 -2
- package/lib/module/contexts/componentsContext/defaultComponents.js.map +1 -1
- package/lib/module/contexts/messageContext/MessageContext.js +32 -1
- package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
- package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
- package/lib/module/hooks/index.js +4 -4
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
- package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts +1 -1
- package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts.map +1 -1
- package/lib/typescript/components/Message/Message.d.ts +1 -1
- package/lib/typescript/components/Message/Message.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageOverlayWrapper.d.ts +18 -0
- package/lib/typescript/components/Message/MessageOverlayWrapper.d.ts.map +1 -0
- 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/Message/hooks/useShouldUseOverlayStyles.d.ts.map +1 -1
- package/lib/typescript/components/Message/messageOverlayConstants.d.ts +2 -0
- package/lib/typescript/components/Message/messageOverlayConstants.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +1 -1
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts +0 -1
- package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts.map +1 -1
- package/lib/typescript/contexts/messageContext/MessageContext.d.ts +26 -0
- package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
- package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +5 -0
- package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +1 -1
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Channel/Channel.tsx +3 -0
- package/src/components/Channel/hooks/useCreateMessagesContext.ts +3 -0
- package/src/components/Message/Message.tsx +109 -77
- package/src/components/Message/MessageItemView/__tests__/Message.test.js +87 -7
- package/src/components/Message/MessageOverlayWrapper.tsx +81 -0
- package/src/components/Message/hooks/__tests__/useShouldUseOverlayStyles.test.tsx +17 -2
- package/src/components/Message/hooks/useCreateMessageContext.ts +5 -0
- package/src/components/Message/hooks/useShouldUseOverlayStyles.ts +15 -2
- package/src/components/Message/messageOverlayConstants.ts +1 -0
- package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +12 -4
- package/src/components/index.ts +1 -1
- package/src/contexts/componentsContext/defaultComponents.ts +0 -2
- package/src/contexts/messageContext/MessageContext.tsx +44 -0
- package/src/contexts/messagesContext/MessagesContext.tsx +5 -0
- package/src/hooks/index.ts +1 -1
- package/src/version.json +1 -1
- package/lib/commonjs/components/MessageMenu/MessageMenu.js +0 -29
- package/lib/commonjs/components/MessageMenu/MessageMenu.js.map +0 -1
- package/lib/commonjs/hooks/useAudioPlayerControl.js +0 -43
- package/lib/commonjs/hooks/useAudioPlayerControl.js.map +0 -1
- package/lib/module/components/MessageMenu/MessageMenu.js +0 -29
- package/lib/module/components/MessageMenu/MessageMenu.js.map +0 -1
- package/lib/module/hooks/useAudioPlayerControl.js +0 -43
- package/lib/module/hooks/useAudioPlayerControl.js.map +0 -1
- package/lib/typescript/components/MessageMenu/MessageMenu.d.ts +0 -40
- package/lib/typescript/components/MessageMenu/MessageMenu.d.ts.map +0 -1
- package/lib/typescript/hooks/useAudioPlayerControl.d.ts +0 -18
- package/lib/typescript/hooks/useAudioPlayerControl.d.ts.map +0 -1
- package/src/components/MessageMenu/MessageMenu.tsx +0 -107
- package/src/hooks/useAudioPlayerControl.ts +0 -59
|
@@ -19,6 +19,8 @@ import { useMessageActions } from './hooks/useMessageActions';
|
|
|
19
19
|
import { useMessageDeliveredData } from './hooks/useMessageDeliveryData';
|
|
20
20
|
import { useMessageReadData } from './hooks/useMessageReadData';
|
|
21
21
|
import { useProcessReactions } from './hooks/useProcessReactions';
|
|
22
|
+
import { DEFAULT_MESSAGE_OVERLAY_TARGET_ID } from './messageOverlayConstants';
|
|
23
|
+
import { MessageOverlayWrapper } from './MessageOverlayWrapper';
|
|
22
24
|
import { measureInWindow } from './utils/measureInWindow';
|
|
23
25
|
import { messageActions as defaultMessageActions } from './utils/messageActions';
|
|
24
26
|
|
|
@@ -36,7 +38,11 @@ import {
|
|
|
36
38
|
MessageComposerAPIContextValue,
|
|
37
39
|
useMessageComposerAPIContext,
|
|
38
40
|
} from '../../contexts/messageComposerContext/MessageComposerAPIContext';
|
|
39
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
MessageContextValue,
|
|
43
|
+
MessageOverlayRuntimeProvider,
|
|
44
|
+
MessageProvider,
|
|
45
|
+
} from '../../contexts/messageContext/MessageContext';
|
|
40
46
|
import { useMessageListItemContext } from '../../contexts/messageListItemContext/MessageListItemContext';
|
|
41
47
|
import {
|
|
42
48
|
MessagesContextValue,
|
|
@@ -207,6 +213,7 @@ export type MessagePropsWithContext = Pick<
|
|
|
207
213
|
| 'handleBlockUser'
|
|
208
214
|
| 'isAttachmentEqual'
|
|
209
215
|
| 'messageActions'
|
|
216
|
+
| 'messageOverlayTargetId'
|
|
210
217
|
| 'messageContentOrder'
|
|
211
218
|
| 'onLongPressMessage'
|
|
212
219
|
| 'onPressInMessage'
|
|
@@ -278,6 +285,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
|
|
|
278
285
|
members,
|
|
279
286
|
message,
|
|
280
287
|
messageActions: messageActionsProp = defaultMessageActions,
|
|
288
|
+
messageOverlayTargetId = DEFAULT_MESSAGE_OVERLAY_TARGET_ID,
|
|
281
289
|
messageContentOrder: messageContentOrderProp,
|
|
282
290
|
messagesContext,
|
|
283
291
|
onLongPressMessage: onLongPressMessageProp,
|
|
@@ -323,11 +331,28 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
|
|
|
323
331
|
const rectRef = useRef<Rect>(undefined);
|
|
324
332
|
const bubbleRect = useRef<Rect>(undefined);
|
|
325
333
|
const contextMenuAnchorRef = useRef<View>(null);
|
|
334
|
+
const messageOverlayTargetsRef = useRef<Record<string, View | null>>({});
|
|
335
|
+
const registerMessageOverlayTarget = useStableCallback(
|
|
336
|
+
({ id, view }: { id: string; view: View | null }) => {
|
|
337
|
+
messageOverlayTargetsRef.current[id] = view;
|
|
338
|
+
},
|
|
339
|
+
);
|
|
340
|
+
const unregisterMessageOverlayTarget = useStableCallback((id: string) => {
|
|
341
|
+
delete messageOverlayTargetsRef.current[id];
|
|
342
|
+
});
|
|
326
343
|
|
|
327
344
|
const showMessageOverlay = useStableCallback(async () => {
|
|
328
345
|
dismissKeyboard();
|
|
329
346
|
try {
|
|
330
|
-
const
|
|
347
|
+
const activeTargetView = messageOverlayTargetsRef.current[messageOverlayTargetId];
|
|
348
|
+
|
|
349
|
+
if (!activeTargetView) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
`No message overlay target is registered for target id "${messageOverlayTargetId}".`,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const layout = await measureInWindow({ current: activeTargetView }, insets);
|
|
331
356
|
const bubbleLayout = await measureInWindow(contextMenuAnchorRef, insets).catch(() => layout);
|
|
332
357
|
|
|
333
358
|
rectRef.current = layout;
|
|
@@ -655,8 +680,6 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
|
|
|
655
680
|
unpinMessage: handleTogglePinMessage,
|
|
656
681
|
};
|
|
657
682
|
|
|
658
|
-
const messageWrapperRef = useRef<View>(null);
|
|
659
|
-
|
|
660
683
|
const onLongPress = () => {
|
|
661
684
|
setNativeScrollability(false);
|
|
662
685
|
if (hasAttachmentActions || isBlockedMessage(message) || !enableLongPress) {
|
|
@@ -771,6 +794,8 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
|
|
|
771
794
|
onThreadSelect,
|
|
772
795
|
otherAttachments: attachments.other,
|
|
773
796
|
preventPress: overlayActive ? true : preventPress,
|
|
797
|
+
registerMessageOverlayTarget,
|
|
798
|
+
unregisterMessageOverlayTarget,
|
|
774
799
|
reactions,
|
|
775
800
|
readBy,
|
|
776
801
|
setQuotedMessage,
|
|
@@ -781,6 +806,14 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
|
|
|
781
806
|
threadList,
|
|
782
807
|
videos: attachments.videos,
|
|
783
808
|
});
|
|
809
|
+
const messageOverlayRuntimeContext = useMemo(
|
|
810
|
+
() => ({
|
|
811
|
+
overlayTargetRectRef: rectRef,
|
|
812
|
+
messageOverlayTargetId,
|
|
813
|
+
overlayActive,
|
|
814
|
+
}),
|
|
815
|
+
[messageOverlayTargetId, overlayActive],
|
|
816
|
+
);
|
|
784
817
|
|
|
785
818
|
const prevActive = useRef<boolean>(overlayActive);
|
|
786
819
|
|
|
@@ -817,82 +850,74 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
|
|
|
817
850
|
|
|
818
851
|
return (
|
|
819
852
|
<MessageProvider value={messageContext}>
|
|
820
|
-
<
|
|
821
|
-
{
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
dismissOverlay={dismissOverlay}
|
|
849
|
-
handleReaction={ownCapabilities.sendReaction ? handleReaction : undefined}
|
|
850
|
-
/>
|
|
851
|
-
</View>
|
|
852
|
-
) : null}
|
|
853
|
-
</Portal>
|
|
854
|
-
<Portal
|
|
855
|
-
hostName={overlayActive ? 'message-overlay' : undefined}
|
|
856
|
-
style={overlayActive && rect ? { width: rect.w } : undefined}
|
|
857
|
-
>
|
|
858
|
-
<View ref={messageWrapperRef}>
|
|
853
|
+
<MessageOverlayRuntimeProvider value={messageOverlayRuntimeContext}>
|
|
854
|
+
<View style={[style, styles.wrapper]} testID='message-wrapper'>
|
|
855
|
+
{/*TODO: V9: Find a way to separate these in a dedicated file*/}
|
|
856
|
+
<Portal hostName={overlayActive && rect ? 'top-item' : undefined}>
|
|
857
|
+
{overlayActive && rect && overlayItemsAnchorRect ? (
|
|
858
|
+
<View
|
|
859
|
+
onLayout={(e) => {
|
|
860
|
+
const { width: w, height: h } = e.nativeEvent.layout;
|
|
861
|
+
|
|
862
|
+
setOverlayTopH({
|
|
863
|
+
h,
|
|
864
|
+
w,
|
|
865
|
+
x:
|
|
866
|
+
overlayItemAlignment === 'right'
|
|
867
|
+
? overlayItemsAnchorRect.x + overlayItemsAnchorRect.w - w
|
|
868
|
+
: overlayItemsAnchorRect.x,
|
|
869
|
+
y: rect.y - h,
|
|
870
|
+
});
|
|
871
|
+
}}
|
|
872
|
+
>
|
|
873
|
+
<MessageReactionPicker
|
|
874
|
+
dismissOverlay={dismissOverlay}
|
|
875
|
+
handleReaction={ownCapabilities.sendReaction ? handleReaction : undefined}
|
|
876
|
+
/>
|
|
877
|
+
</View>
|
|
878
|
+
) : null}
|
|
879
|
+
</Portal>
|
|
880
|
+
<MessageOverlayWrapper targetId={DEFAULT_MESSAGE_OVERLAY_TARGET_ID}>
|
|
859
881
|
<MessageItemView />
|
|
860
|
-
</
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
height={424}
|
|
868
|
-
>
|
|
869
|
-
<MessageUserReactions message={message} selectedReaction={selectedReaction} />
|
|
870
|
-
</BottomSheetModal>
|
|
871
|
-
) : null}
|
|
872
|
-
<Portal hostName={overlayActive && rect ? 'bottom-item' : undefined}>
|
|
873
|
-
{overlayActive && rect && overlayItemsAnchorRect ? (
|
|
874
|
-
<View
|
|
875
|
-
onLayout={(e) => {
|
|
876
|
-
const { width: w, height: h } = e.nativeEvent.layout;
|
|
877
|
-
setOverlayBottomH({
|
|
878
|
-
h,
|
|
879
|
-
w,
|
|
880
|
-
x:
|
|
881
|
-
overlayItemAlignment === 'right'
|
|
882
|
-
? overlayItemsAnchorRect.x + overlayItemsAnchorRect.w - w
|
|
883
|
-
: overlayItemsAnchorRect.x,
|
|
884
|
-
y: rect.y + rect.h,
|
|
885
|
-
});
|
|
886
|
-
}}
|
|
882
|
+
</MessageOverlayWrapper>
|
|
883
|
+
{showMessageReactions ? (
|
|
884
|
+
<BottomSheetModal
|
|
885
|
+
lazy={true}
|
|
886
|
+
onClose={() => setShowMessageReactions(false)}
|
|
887
|
+
visible={showMessageReactions}
|
|
888
|
+
height={424}
|
|
887
889
|
>
|
|
888
|
-
<
|
|
889
|
-
</
|
|
890
|
+
<MessageUserReactions message={message} selectedReaction={selectedReaction} />
|
|
891
|
+
</BottomSheetModal>
|
|
890
892
|
) : null}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
893
|
+
<Portal hostName={overlayActive && rect ? 'bottom-item' : undefined}>
|
|
894
|
+
{overlayActive && rect && overlayItemsAnchorRect ? (
|
|
895
|
+
<View
|
|
896
|
+
onLayout={(e) => {
|
|
897
|
+
const { width: w, height: h } = e.nativeEvent.layout;
|
|
898
|
+
setOverlayBottomH({
|
|
899
|
+
h,
|
|
900
|
+
w,
|
|
901
|
+
x:
|
|
902
|
+
overlayItemAlignment === 'right'
|
|
903
|
+
? overlayItemsAnchorRect.x + overlayItemsAnchorRect.w - w
|
|
904
|
+
: overlayItemsAnchorRect.x,
|
|
905
|
+
y: rect.y + rect.h,
|
|
906
|
+
});
|
|
907
|
+
}}
|
|
908
|
+
>
|
|
909
|
+
<MessageActionList
|
|
910
|
+
dismissOverlay={dismissOverlay}
|
|
911
|
+
messageActions={messageActions}
|
|
912
|
+
/>
|
|
913
|
+
</View>
|
|
914
|
+
) : null}
|
|
915
|
+
</Portal>
|
|
916
|
+
{isBounceDialogOpen ? (
|
|
917
|
+
<MessageBounce setIsBounceDialogOpen={setIsBounceDialogOpen} />
|
|
918
|
+
) : null}
|
|
919
|
+
</View>
|
|
920
|
+
</MessageOverlayRuntimeProvider>
|
|
896
921
|
</MessageProvider>
|
|
897
922
|
);
|
|
898
923
|
};
|
|
@@ -905,6 +930,7 @@ const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWit
|
|
|
905
930
|
groupStyles: prevGroupStyles,
|
|
906
931
|
isAttachmentEqual,
|
|
907
932
|
isTargetedMessage: prevIsTargetedMessage,
|
|
933
|
+
messageOverlayTargetId: prevMessageOverlayTargetId,
|
|
908
934
|
members: prevMembers,
|
|
909
935
|
message: prevMessage,
|
|
910
936
|
messagesContext: prevMessagesContext,
|
|
@@ -918,6 +944,7 @@ const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWit
|
|
|
918
944
|
goToMessage: nextGoToMessage,
|
|
919
945
|
groupStyles: nextGroupStyles,
|
|
920
946
|
isTargetedMessage: nextIsTargetedMessage,
|
|
947
|
+
messageOverlayTargetId: nextMessageOverlayTargetId,
|
|
921
948
|
members: nextMembers,
|
|
922
949
|
message: nextMessage,
|
|
923
950
|
messagesContext: nextMessagesContext,
|
|
@@ -958,6 +985,11 @@ const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWit
|
|
|
958
985
|
return false;
|
|
959
986
|
}
|
|
960
987
|
|
|
988
|
+
const messageOverlayTargetIdEqual = prevMessageOverlayTargetId === nextMessageOverlayTargetId;
|
|
989
|
+
if (!messageOverlayTargetIdEqual) {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
|
|
961
993
|
const messageEqual = checkMessageEquality(prevMessage, nextMessage);
|
|
962
994
|
|
|
963
995
|
if (!messageEqual) {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
+
import { Pressable, Text, View } from 'react-native';
|
|
3
4
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
4
5
|
|
|
5
6
|
import { cleanup, fireEvent, render, waitFor } from '@testing-library/react-native';
|
|
6
7
|
|
|
8
|
+
import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext';
|
|
9
|
+
import { useMessageContext } from '../../../../contexts/messageContext/MessageContext';
|
|
10
|
+
import { MessageListItemProvider } from '../../../../contexts/messageListItemContext/MessageListItemContext';
|
|
7
11
|
import { OverlayProvider } from '../../../../contexts/overlayContext/OverlayProvider';
|
|
8
12
|
import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel';
|
|
9
13
|
|
|
@@ -16,7 +20,38 @@ import { getTestClientWithUser } from '../../../../mock-builders/mock';
|
|
|
16
20
|
import { Channel } from '../../../Channel/Channel';
|
|
17
21
|
import { Chat } from '../../../Chat/Chat';
|
|
18
22
|
import { MessageComposer } from '../../../MessageInput/MessageComposer';
|
|
23
|
+
import { useShouldUseOverlayStyles } from '../../hooks/useShouldUseOverlayStyles';
|
|
19
24
|
import { Message } from '../../Message';
|
|
25
|
+
import { MessageOverlayWrapper } from '../../MessageOverlayWrapper';
|
|
26
|
+
|
|
27
|
+
const OverlayStateText = ({ label }) => {
|
|
28
|
+
const shouldUseOverlayStyles = useShouldUseOverlayStyles();
|
|
29
|
+
|
|
30
|
+
return <Text>{`${label}:${shouldUseOverlayStyles ? 'overlay' : 'normal'}`}</Text>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const OverlayTrigger = () => {
|
|
34
|
+
const { onLongPress } = useMessageContext();
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Pressable
|
|
38
|
+
onLongPress={() => onLongPress({ emitter: 'message' })}
|
|
39
|
+
testID='custom-overlay-trigger'
|
|
40
|
+
>
|
|
41
|
+
<Text>Open overlay</Text>
|
|
42
|
+
</Pressable>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const CustomMessageItemView = () => (
|
|
47
|
+
<View testID='custom-message-item-view'>
|
|
48
|
+
<OverlayStateText label='outside' />
|
|
49
|
+
<MessageOverlayWrapper targetId='custom-overlay-target' testID='custom-overlay-target'>
|
|
50
|
+
<OverlayStateText label='inside' />
|
|
51
|
+
<OverlayTrigger />
|
|
52
|
+
</MessageOverlayWrapper>
|
|
53
|
+
</View>
|
|
54
|
+
);
|
|
20
55
|
|
|
21
56
|
describe('Message', () => {
|
|
22
57
|
let channel;
|
|
@@ -37,16 +72,34 @@ describe('Message', () => {
|
|
|
37
72
|
useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
|
|
38
73
|
channel = chatClient.channel('messaging', mockedChannel.id);
|
|
39
74
|
|
|
40
|
-
renderMessage = (options) =>
|
|
75
|
+
renderMessage = (options, channelProps, componentOverrides) =>
|
|
41
76
|
render(
|
|
42
77
|
<SafeAreaProvider>
|
|
43
78
|
<OverlayProvider>
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
<MessageListItemProvider
|
|
80
|
+
value={{
|
|
81
|
+
goToMessage: jest.fn(),
|
|
82
|
+
modifiedTheme: {},
|
|
83
|
+
onThreadSelect: jest.fn(),
|
|
84
|
+
setNativeScrollability: jest.fn(),
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<Chat client={chatClient}>
|
|
88
|
+
{componentOverrides ? (
|
|
89
|
+
<WithComponents overrides={componentOverrides}>
|
|
90
|
+
<Channel channel={channel} {...channelProps}>
|
|
91
|
+
<Message groupStyles={['bottom']} {...options} />
|
|
92
|
+
<MessageComposer />
|
|
93
|
+
</Channel>
|
|
94
|
+
</WithComponents>
|
|
95
|
+
) : (
|
|
96
|
+
<Channel channel={channel} {...channelProps}>
|
|
97
|
+
<Message groupStyles={['bottom']} {...options} />
|
|
98
|
+
<MessageComposer />
|
|
99
|
+
</Channel>
|
|
100
|
+
)}
|
|
101
|
+
</Chat>
|
|
102
|
+
</MessageListItemProvider>
|
|
50
103
|
</OverlayProvider>
|
|
51
104
|
</SafeAreaProvider>,
|
|
52
105
|
);
|
|
@@ -88,4 +141,31 @@ describe('Message', () => {
|
|
|
88
141
|
expect(onLongPressMessage).toHaveBeenCalledTimes(1);
|
|
89
142
|
});
|
|
90
143
|
});
|
|
144
|
+
|
|
145
|
+
it('teleports a custom overlay target without applying overlay styles to siblings', async () => {
|
|
146
|
+
const message = generateMessage({ user });
|
|
147
|
+
const { getByTestId, getByText } = renderMessage(
|
|
148
|
+
{ message },
|
|
149
|
+
{
|
|
150
|
+
messageOverlayTargetId: 'custom-overlay-target',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
MessageItemView: CustomMessageItemView,
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
await waitFor(() => {
|
|
158
|
+
expect(getByTestId('custom-message-item-view')).toBeTruthy();
|
|
159
|
+
expect(getByText('outside:normal')).toBeTruthy();
|
|
160
|
+
expect(getByText('inside:normal')).toBeTruthy();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
fireEvent(getByTestId('custom-overlay-trigger'), 'longPress');
|
|
164
|
+
|
|
165
|
+
await waitFor(() => {
|
|
166
|
+
expect(getByText('outside:normal')).toBeTruthy();
|
|
167
|
+
expect(getByText('inside:overlay')).toBeTruthy();
|
|
168
|
+
expect(getByTestId('custom-overlay-target-placeholder')).toBeTruthy();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
91
171
|
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React, { PropsWithChildren, useCallback, useEffect } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Portal } from 'react-native-teleport';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
MessageOverlayTargetProvider,
|
|
8
|
+
useMessageContext,
|
|
9
|
+
useMessageOverlayRuntimeContext,
|
|
10
|
+
} from '../../contexts/messageContext/MessageContext';
|
|
11
|
+
|
|
12
|
+
export type MessageOverlayWrapperProps = PropsWithChildren<{
|
|
13
|
+
/**
|
|
14
|
+
* Stable identifier for this overlay target. Must match `messageOverlayTargetId`
|
|
15
|
+
* when this subtree should be teleported into the overlay.
|
|
16
|
+
*/
|
|
17
|
+
targetId: string;
|
|
18
|
+
/**
|
|
19
|
+
* Optional test id attached to the wrapped target container.
|
|
20
|
+
*/
|
|
21
|
+
testID?: string;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Wraps the primary message overlay target so the active message can be teleported
|
|
26
|
+
* into the overlay host while a placeholder preserves its original layout space.
|
|
27
|
+
*/
|
|
28
|
+
export const MessageOverlayWrapper = ({
|
|
29
|
+
children,
|
|
30
|
+
targetId,
|
|
31
|
+
testID,
|
|
32
|
+
}: MessageOverlayWrapperProps) => {
|
|
33
|
+
const { registerMessageOverlayTarget, unregisterMessageOverlayTarget } = useMessageContext();
|
|
34
|
+
const { messageOverlayTargetId, overlayActive, overlayTargetRectRef } =
|
|
35
|
+
useMessageOverlayRuntimeContext();
|
|
36
|
+
const isActiveTarget = messageOverlayTargetId === targetId;
|
|
37
|
+
const placeholderLayout = overlayTargetRectRef.current;
|
|
38
|
+
|
|
39
|
+
const handleTargetRef = useCallback(
|
|
40
|
+
(view: View | null) => {
|
|
41
|
+
registerMessageOverlayTarget({
|
|
42
|
+
id: targetId,
|
|
43
|
+
view,
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
[registerMessageOverlayTarget, targetId],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
useEffect(
|
|
50
|
+
() => () => {
|
|
51
|
+
unregisterMessageOverlayTarget(targetId);
|
|
52
|
+
},
|
|
53
|
+
[targetId, unregisterMessageOverlayTarget],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (!isActiveTarget) {
|
|
57
|
+
return children;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<Portal hostName={overlayActive ? 'message-overlay' : undefined}>
|
|
63
|
+
<View collapsable={false} ref={handleTargetRef} testID={testID}>
|
|
64
|
+
<MessageOverlayTargetProvider value={isActiveTarget}>
|
|
65
|
+
{children}
|
|
66
|
+
</MessageOverlayTargetProvider>
|
|
67
|
+
</View>
|
|
68
|
+
</Portal>
|
|
69
|
+
{overlayActive ? (
|
|
70
|
+
<View
|
|
71
|
+
pointerEvents='none'
|
|
72
|
+
style={{
|
|
73
|
+
height: placeholderLayout?.h ?? 0,
|
|
74
|
+
width: placeholderLayout?.w && placeholderLayout.w > 0 ? placeholderLayout.w : '100%',
|
|
75
|
+
}}
|
|
76
|
+
testID={testID ? `${testID}-placeholder` : 'message-overlay-wrapper-placeholder'}
|
|
77
|
+
/>
|
|
78
|
+
) : null}
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -4,10 +4,12 @@ import { act, cleanup, renderHook } from '@testing-library/react-native';
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
MessageContextValue,
|
|
7
|
+
MessageOverlayRuntimeProvider,
|
|
7
8
|
MessageProvider,
|
|
8
9
|
} from '../../../../contexts/messageContext/MessageContext';
|
|
9
10
|
import { generateMessage } from '../../../../mock-builders/generator/message';
|
|
10
11
|
import { finalizeCloseOverlay, openOverlay, overlayStore } from '../../../../state-store';
|
|
12
|
+
import { DEFAULT_MESSAGE_OVERLAY_TARGET_ID } from '../../messageOverlayConstants';
|
|
11
13
|
|
|
12
14
|
import { useShouldUseOverlayStyles } from '../useShouldUseOverlayStyles';
|
|
13
15
|
|
|
@@ -22,6 +24,7 @@ const createMessageContextValue = (overrides: Partial<MessageContextValue>): Mes
|
|
|
22
24
|
groupStyles: [],
|
|
23
25
|
handleAction: jest.fn(),
|
|
24
26
|
handleToggleReaction: jest.fn(),
|
|
27
|
+
hasAttachmentActions: false,
|
|
25
28
|
hasReactions: false,
|
|
26
29
|
images: [],
|
|
27
30
|
isMessageAIGenerated: jest.fn(),
|
|
@@ -29,6 +32,7 @@ const createMessageContextValue = (overrides: Partial<MessageContextValue>): Mes
|
|
|
29
32
|
lastGroupMessage: false,
|
|
30
33
|
members: {},
|
|
31
34
|
message: generateMessage({ id: 'shared-message-id' }),
|
|
35
|
+
contextMenuAnchorRef: { current: null },
|
|
32
36
|
messageContentOrder: [],
|
|
33
37
|
messageHasOnlySingleAttachment: false,
|
|
34
38
|
messageOverlayId: 'message-overlay-default',
|
|
@@ -38,6 +42,7 @@ const createMessageContextValue = (overrides: Partial<MessageContextValue>): Mes
|
|
|
38
42
|
onPress: jest.fn(),
|
|
39
43
|
onPressIn: null,
|
|
40
44
|
otherAttachments: [],
|
|
45
|
+
registerMessageOverlayTarget: jest.fn(),
|
|
41
46
|
reactions: [],
|
|
42
47
|
readBy: false,
|
|
43
48
|
setQuotedMessage: jest.fn(),
|
|
@@ -46,13 +51,23 @@ const createMessageContextValue = (overrides: Partial<MessageContextValue>): Mes
|
|
|
46
51
|
showReactionsOverlay: jest.fn(),
|
|
47
52
|
showMessageStatus: false,
|
|
48
53
|
threadList: false,
|
|
54
|
+
unregisterMessageOverlayTarget: jest.fn(),
|
|
49
55
|
videos: [],
|
|
50
56
|
...overrides,
|
|
51
57
|
}) as MessageContextValue;
|
|
52
58
|
|
|
53
|
-
const createWrapper = (
|
|
59
|
+
const createWrapper = (
|
|
60
|
+
value: MessageContextValue,
|
|
61
|
+
runtimeValue = {
|
|
62
|
+
overlayTargetRectRef: { current: undefined },
|
|
63
|
+
messageOverlayTargetId: DEFAULT_MESSAGE_OVERLAY_TARGET_ID,
|
|
64
|
+
overlayActive: false,
|
|
65
|
+
},
|
|
66
|
+
) => {
|
|
54
67
|
const Wrapper = ({ children }: PropsWithChildren) => (
|
|
55
|
-
<MessageProvider value={value}>
|
|
68
|
+
<MessageProvider value={value}>
|
|
69
|
+
<MessageOverlayRuntimeProvider value={runtimeValue}>{children}</MessageOverlayRuntimeProvider>
|
|
70
|
+
</MessageProvider>
|
|
56
71
|
);
|
|
57
72
|
|
|
58
73
|
return Wrapper;
|
|
@@ -47,6 +47,8 @@ export const useCreateMessageContext = ({
|
|
|
47
47
|
onThreadSelect,
|
|
48
48
|
otherAttachments,
|
|
49
49
|
preventPress,
|
|
50
|
+
registerMessageOverlayTarget,
|
|
51
|
+
unregisterMessageOverlayTarget,
|
|
50
52
|
reactions,
|
|
51
53
|
readBy,
|
|
52
54
|
showAvatar,
|
|
@@ -102,6 +104,8 @@ export const useCreateMessageContext = ({
|
|
|
102
104
|
onThreadSelect,
|
|
103
105
|
otherAttachments,
|
|
104
106
|
preventPress,
|
|
107
|
+
registerMessageOverlayTarget,
|
|
108
|
+
unregisterMessageOverlayTarget,
|
|
105
109
|
reactions,
|
|
106
110
|
readBy,
|
|
107
111
|
setQuotedMessage,
|
|
@@ -134,6 +138,7 @@ export const useCreateMessageContext = ({
|
|
|
134
138
|
showMessageStatus,
|
|
135
139
|
threadList,
|
|
136
140
|
preventPress,
|
|
141
|
+
unregisterMessageOverlayTarget,
|
|
137
142
|
],
|
|
138
143
|
);
|
|
139
144
|
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useMessageContext,
|
|
3
|
+
useMessageOverlayRuntimeContext,
|
|
4
|
+
useMessageOverlayTargetContext,
|
|
5
|
+
} from '../../../contexts';
|
|
2
6
|
import { useIsOverlayActive } from '../../../state-store';
|
|
7
|
+
import { DEFAULT_MESSAGE_OVERLAY_TARGET_ID } from '../messageOverlayConstants';
|
|
3
8
|
|
|
4
9
|
export const useShouldUseOverlayStyles = () => {
|
|
5
10
|
const { messageOverlayId } = useMessageContext();
|
|
11
|
+
const { messageOverlayTargetId } = useMessageOverlayRuntimeContext();
|
|
12
|
+
const isWithinMessageOverlayTarget = useMessageOverlayTargetContext();
|
|
6
13
|
const { active, closing } = useIsOverlayActive(messageOverlayId);
|
|
7
14
|
|
|
8
|
-
|
|
15
|
+
if (!active || closing) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return messageOverlayTargetId === DEFAULT_MESSAGE_OVERLAY_TARGET_ID
|
|
20
|
+
? true
|
|
21
|
+
: isWithinMessageOverlayTarget;
|
|
9
22
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_MESSAGE_OVERLAY_TARGET_ID = '@stream-io/message-root';
|
|
@@ -368,7 +368,9 @@ exports[`Thread should match thread snapshot 1`] = `
|
|
|
368
368
|
>
|
|
369
369
|
<View />
|
|
370
370
|
<View>
|
|
371
|
-
<View
|
|
371
|
+
<View
|
|
372
|
+
collapsable={false}
|
|
373
|
+
>
|
|
372
374
|
<View
|
|
373
375
|
collapsable={false}
|
|
374
376
|
hitSlop={
|
|
@@ -707,7 +709,9 @@ exports[`Thread should match thread snapshot 1`] = `
|
|
|
707
709
|
>
|
|
708
710
|
<View />
|
|
709
711
|
<View>
|
|
710
|
-
<View
|
|
712
|
+
<View
|
|
713
|
+
collapsable={false}
|
|
714
|
+
>
|
|
711
715
|
<View
|
|
712
716
|
collapsable={false}
|
|
713
717
|
hitSlop={
|
|
@@ -1079,7 +1083,9 @@ exports[`Thread should match thread snapshot 1`] = `
|
|
|
1079
1083
|
>
|
|
1080
1084
|
<View />
|
|
1081
1085
|
<View>
|
|
1082
|
-
<View
|
|
1086
|
+
<View
|
|
1087
|
+
collapsable={false}
|
|
1088
|
+
>
|
|
1083
1089
|
<View
|
|
1084
1090
|
collapsable={false}
|
|
1085
1091
|
hitSlop={
|
|
@@ -1408,7 +1414,9 @@ exports[`Thread should match thread snapshot 1`] = `
|
|
|
1408
1414
|
>
|
|
1409
1415
|
<View />
|
|
1410
1416
|
<View>
|
|
1411
|
-
<View
|
|
1417
|
+
<View
|
|
1418
|
+
collapsable={false}
|
|
1419
|
+
>
|
|
1412
1420
|
<View
|
|
1413
1421
|
collapsable={false}
|
|
1414
1422
|
hitSlop={
|
package/src/components/index.ts
CHANGED
|
@@ -87,6 +87,7 @@ export * from './Message/hooks/useStreamingMessage';
|
|
|
87
87
|
export * from './Message/hooks/useMessageDeliveryData';
|
|
88
88
|
export * from './Message/hooks/useMessageReadData';
|
|
89
89
|
export * from './Message/Message';
|
|
90
|
+
export * from './Message/MessageOverlayWrapper';
|
|
90
91
|
export * from './Message/MessageItemView/MessageAuthor';
|
|
91
92
|
export * from './Message/MessageItemView/MessageBounce';
|
|
92
93
|
export * from './Message/MessageItemView/MessageBlocked';
|
|
@@ -163,7 +164,6 @@ export * from './MessageList/hooks/useMessageGroupStyles';
|
|
|
163
164
|
|
|
164
165
|
export * from './MessageMenu/MessageActionList';
|
|
165
166
|
export * from './MessageMenu/MessageActionListItem';
|
|
166
|
-
export * from './MessageMenu/MessageMenu';
|
|
167
167
|
export * from './MessageMenu/MessageUserReactions';
|
|
168
168
|
export * from './MessageMenu/MessageUserReactionsAvatar';
|
|
169
169
|
export * from './MessageMenu/MessageReactionPicker';
|
|
@@ -123,7 +123,6 @@ import { TypingIndicatorContainer } from '../../components/MessageList/TypingInd
|
|
|
123
123
|
import { UnreadMessagesNotification } from '../../components/MessageList/UnreadMessagesNotification';
|
|
124
124
|
import { MessageActionList } from '../../components/MessageMenu/MessageActionList';
|
|
125
125
|
import { MessageActionListItem } from '../../components/MessageMenu/MessageActionListItem';
|
|
126
|
-
import { MessageMenu } from '../../components/MessageMenu/MessageMenu';
|
|
127
126
|
import { MessageReactionPicker } from '../../components/MessageMenu/MessageReactionPicker';
|
|
128
127
|
import { MessageUserReactions } from '../../components/MessageMenu/MessageUserReactions';
|
|
129
128
|
import { MessageUserReactionsAvatar } from '../../components/MessageMenu/MessageUserReactionsAvatar';
|
|
@@ -226,7 +225,6 @@ const components = {
|
|
|
226
225
|
MessageInputTrailingView,
|
|
227
226
|
MessageItemView,
|
|
228
227
|
MessageList,
|
|
229
|
-
MessageMenu,
|
|
230
228
|
MessagePinnedHeader,
|
|
231
229
|
MessageReactionPicker,
|
|
232
230
|
MessageReminderHeader,
|