stream-chat-react 12.9.0 → 12.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Attachment/components/WaveProgressBar.js +1 -1
- package/dist/components/Attachment/hooks/useAudioController.d.ts +1 -1
- package/dist/components/Attachment/hooks/useAudioController.js +1 -1
- package/dist/components/AutoCompleteTextarea/Textarea.js +0 -4
- package/dist/components/AutoCompleteTextarea/utils.js +1 -5
- package/dist/components/Channel/Channel.js +3 -2
- package/dist/components/Channel/channelState.d.ts +1 -3
- package/dist/components/Channel/channelState.js +1 -1
- package/dist/components/Channel/hooks/useIsMounted.d.ts +1 -1
- package/dist/components/ChannelList/hooks/useChannelListShape.js +2 -2
- package/dist/components/ChannelList/hooks/useMobileNavigation.d.ts +1 -1
- package/dist/components/ChannelList/hooks/usePaginatedChannels.js +1 -1
- package/dist/components/ChannelSearch/SearchBar.d.ts +1 -1
- package/dist/components/ChannelSearch/SearchInput.d.ts +1 -1
- package/dist/components/ChannelSearch/hooks/useChannelSearch.js +1 -1
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Gallery/BaseImage.d.ts +1 -3
- package/dist/components/Gallery/Gallery.js +10 -2
- package/dist/components/Gallery/ModalGallery.js +5 -4
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +4 -4
- package/dist/components/MessageActions/hooks/useMessageActionsBoxPopper.d.ts +1 -1
- package/dist/components/MessageInput/hooks/useMessageInputText.d.ts +1 -1
- package/dist/components/MessageInput/hooks/useMessageInputText.js +2 -2
- package/dist/components/MessageInput/hooks/useTimeElapsed.js +1 -1
- package/dist/components/MessageList/MessageList.js +0 -1
- package/dist/components/MessageList/VirtualizedMessageList.d.ts +1 -1
- package/dist/components/MessageList/VirtualizedMessageList.js +2 -2
- package/dist/components/MessageList/hooks/MessageList/useMessageListScrollManager.js +1 -1
- package/dist/components/MessageList/hooks/VirtualizedMessageList/useMessageSetKey.js +1 -1
- package/dist/components/MessageList/hooks/VirtualizedMessageList/useNewMessageNotification.d.ts +1 -1
- package/dist/components/MessageList/hooks/VirtualizedMessageList/usePrependMessagesCount.js +2 -2
- package/dist/components/MessageList/hooks/VirtualizedMessageList/useScrollToBottomOnNewMessage.js +1 -1
- package/dist/components/MessageList/hooks/useMarkRead.d.ts +2 -4
- package/dist/components/MessageList/hooks/useMarkRead.js +14 -16
- package/dist/context/VirtualizedMessageListContext.d.ts +13 -0
- package/dist/context/VirtualizedMessageListContext.js +7 -0
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/index.browser.cjs +572 -178
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +836 -197
- package/dist/index.node.cjs.map +4 -4
- package/package.json +12 -15
|
@@ -7,7 +7,7 @@ export const WaveProgressBar = ({ amplitudesCount = 40, progress = 0, relativeAm
|
|
|
7
7
|
const isDragging = useRef(false);
|
|
8
8
|
const [root, setRoot] = useState(null);
|
|
9
9
|
const [trackAxisX, setTrackAxisX] = useState();
|
|
10
|
-
const lastRootWidth = useRef();
|
|
10
|
+
const lastRootWidth = useRef(undefined);
|
|
11
11
|
const handleDragStart = (e) => {
|
|
12
12
|
e.preventDefault();
|
|
13
13
|
if (!progressIndicator)
|
|
@@ -13,7 +13,7 @@ type AudioControllerParams = {
|
|
|
13
13
|
playbackRates?: number[];
|
|
14
14
|
};
|
|
15
15
|
export declare const useAudioController: ({ durationSeconds, mimeType, playbackRates, }?: AudioControllerParams) => {
|
|
16
|
-
audioRef: import("react").
|
|
16
|
+
audioRef: import("react").RefObject<HTMLAudioElement | null>;
|
|
17
17
|
canPlayRecord: boolean;
|
|
18
18
|
increasePlaybackRate: () => void;
|
|
19
19
|
isPlaying: boolean;
|
|
@@ -13,7 +13,7 @@ export const useAudioController = ({ durationSeconds, mimeType, playbackRates =
|
|
|
13
13
|
const [canPlayRecord, setCanPlayRecord] = useState(true);
|
|
14
14
|
const [secondsElapsed, setSecondsElapsed] = useState(0);
|
|
15
15
|
const [playbackRateIndex, setPlaybackRateIndex] = useState(0);
|
|
16
|
-
const playTimeout = useRef();
|
|
16
|
+
const playTimeout = useRef(undefined);
|
|
17
17
|
const audioRef = useRef(null);
|
|
18
18
|
const registerError = useCallback((e) => {
|
|
19
19
|
logError(e);
|
|
@@ -2,7 +2,6 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import Textarea from 'react-textarea-autosize';
|
|
4
4
|
import getCaretCoordinates from 'textarea-caret';
|
|
5
|
-
import { isValidElementType } from 'react-is';
|
|
6
5
|
import clsx from 'clsx';
|
|
7
6
|
import { List as DefaultSuggestionList } from './List';
|
|
8
7
|
import { DEFAULT_CARET_POSITION, defaultScrollToItem, errorMessage, triggerPropsCheck, } from './utils';
|
|
@@ -242,9 +241,6 @@ export class ReactTextareaAutocomplete extends React.Component {
|
|
|
242
241
|
if (!Array.isArray(data)) {
|
|
243
242
|
throw new Error('Trigger provider has to provide an array!');
|
|
244
243
|
}
|
|
245
|
-
if (!isValidElementType(component)) {
|
|
246
|
-
throw new Error('Component should be defined!');
|
|
247
|
-
}
|
|
248
244
|
// throw away if we resolved old trigger
|
|
249
245
|
if (currentTrigger !== this.state.currentTrigger)
|
|
250
246
|
return;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isValidElementType } from 'react-is';
|
|
2
1
|
export const DEFAULT_CARET_POSITION = 'next';
|
|
3
2
|
export function defaultScrollToItem(container, item) {
|
|
4
3
|
if (!item)
|
|
@@ -26,10 +25,7 @@ export const triggerPropsCheck = ({ trigger }) => {
|
|
|
26
25
|
}
|
|
27
26
|
// $FlowFixMe
|
|
28
27
|
const triggerSetting = settings;
|
|
29
|
-
const { callback,
|
|
30
|
-
if (!isValidElementType(component)) {
|
|
31
|
-
return Error('Invalid prop trigger: component should be defined.');
|
|
32
|
-
}
|
|
28
|
+
const { callback, dataProvider, output } = triggerSetting;
|
|
33
29
|
if (!dataProvider || typeof dataProvider !== 'function') {
|
|
34
30
|
return Error('Invalid prop trigger: dataProvider should be defined.');
|
|
35
31
|
}
|
|
@@ -4,7 +4,7 @@ import defaultsDeep from 'lodash.defaultsdeep';
|
|
|
4
4
|
import throttle from 'lodash.throttle';
|
|
5
5
|
import { nanoid } from 'nanoid';
|
|
6
6
|
import clsx from 'clsx';
|
|
7
|
-
import {
|
|
7
|
+
import { initialState, makeChannelReducer } from './channelState';
|
|
8
8
|
import { useCreateChannelStateContext } from './hooks/useCreateChannelStateContext';
|
|
9
9
|
import { useCreateTypingContext } from './hooks/useCreateTypingContext';
|
|
10
10
|
import { useEditMessageHandler } from './hooks/useEditMessageHandler';
|
|
@@ -63,6 +63,7 @@ const ChannelInner = (props) => {
|
|
|
63
63
|
const [quotedMessage, setQuotedMessage] = useState();
|
|
64
64
|
const [channelUnreadUiState, _setChannelUnreadUiState] = useState();
|
|
65
65
|
const notificationTimeouts = useRef([]);
|
|
66
|
+
const channelReducer = useMemo(() => makeChannelReducer(), []);
|
|
66
67
|
const [state, dispatch] = useReducer(channelReducer,
|
|
67
68
|
// channel.initialized === false if client.channel().query() was not called, e.g. ChannelList is not used
|
|
68
69
|
// => Channel will call channel.watch() in useLayoutEffect => state.loading is used to signal the watch() call state
|
|
@@ -73,7 +74,7 @@ const ChannelInner = (props) => {
|
|
|
73
74
|
});
|
|
74
75
|
const isMounted = useIsMounted();
|
|
75
76
|
const originalTitle = useRef('');
|
|
76
|
-
const lastRead = useRef();
|
|
77
|
+
const lastRead = useRef(undefined);
|
|
77
78
|
const online = useRef(true);
|
|
78
79
|
const channelCapabilitiesArray = channel.data?.own_capabilities;
|
|
79
80
|
const throttledCopyStateFromChannel = throttle(() => dispatch({ channel, type: 'copyStateFromChannelOnEvent' }), 500, {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Reducer } from 'react';
|
|
2
1
|
import type { Channel, MessageResponse, ChannelState as StreamChannelState } from 'stream-chat';
|
|
3
2
|
import type { ChannelState, StreamMessage } from '../../context/ChannelStateContext';
|
|
4
3
|
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
@@ -61,8 +60,7 @@ export type ChannelStateReducerAction<StreamChatGenerics extends DefaultStreamCh
|
|
|
61
60
|
} | {
|
|
62
61
|
type: 'jumpToLatestMessage';
|
|
63
62
|
};
|
|
64
|
-
export
|
|
65
|
-
export declare const channelReducer: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(state: ChannelState<StreamChatGenerics>, action: ChannelStateReducerAction<StreamChatGenerics>) => ChannelState<StreamChatGenerics>;
|
|
63
|
+
export declare const makeChannelReducer: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>() => (state: ChannelState<StreamChatGenerics>, action: ChannelStateReducerAction<StreamChatGenerics>) => ChannelState<StreamChatGenerics>;
|
|
66
64
|
export declare const initialState: {
|
|
67
65
|
error: null;
|
|
68
66
|
hasMore: boolean;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
export declare const useIsMounted: () => import("react").
|
|
2
|
+
export declare const useIsMounted: () => import("react").RefObject<boolean>;
|
|
@@ -245,8 +245,8 @@ export const useChannelListShapeDefaults = () => {
|
|
|
245
245
|
};
|
|
246
246
|
export const usePrepareShapeHandlers = ({ allowNewMessagesFromUnfilteredChannels, customHandleChannelListShape, filters, lockChannelOrder, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, setChannels, sort, }) => {
|
|
247
247
|
const defaults = useChannelListShapeDefaults();
|
|
248
|
-
const defaultHandleChannelListShapeRef = useRef();
|
|
249
|
-
const customHandleChannelListShapeRef = useRef();
|
|
248
|
+
const defaultHandleChannelListShapeRef = useRef(undefined);
|
|
249
|
+
const customHandleChannelListShapeRef = useRef(undefined);
|
|
250
250
|
customHandleChannelListShapeRef.current = (event) => {
|
|
251
251
|
// @ts-expect-error can't use ReturnType<typeof useChannelListShapeDefaults<SCG>> until we upgrade prettier to at least v2.7.0
|
|
252
252
|
customHandleChannelListShape?.({ defaults, event, setChannels });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const useMobileNavigation: (channelListRef: React.RefObject<HTMLDivElement>, navOpen: boolean, closeMobileNav?: () => void) => void;
|
|
1
|
+
export declare const useMobileNavigation: (channelListRef: React.RefObject<HTMLDivElement | null>, navOpen: boolean, closeMobileNav?: () => void) => void;
|
|
@@ -9,7 +9,7 @@ export const usePaginatedChannels = (client, filters, sort, options, activeChann
|
|
|
9
9
|
const { channelsQueryState: { error, setError, setQueryInProgress }, } = useChatContext('usePaginatedChannels');
|
|
10
10
|
const [channels, setChannels] = useState([]);
|
|
11
11
|
const [hasNextPage, setHasNextPage] = useState(true);
|
|
12
|
-
const lastRecoveryTimestamp = useRef();
|
|
12
|
+
const lastRecoveryTimestamp = useRef(undefined);
|
|
13
13
|
const recoveryThrottleInterval = recoveryThrottleIntervalMs < MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS
|
|
14
14
|
? MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS
|
|
15
15
|
: recoveryThrottleIntervalMs ?? RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS;
|
|
@@ -11,7 +11,7 @@ export type SearchBarController = {
|
|
|
11
11
|
/** Flag determining whether the search input is focused */
|
|
12
12
|
inputIsFocused: boolean;
|
|
13
13
|
/** Ref object for the input wrapper in the SearchBar */
|
|
14
|
-
searchBarRef: React.RefObject<HTMLDivElement>;
|
|
14
|
+
searchBarRef: React.RefObject<HTMLDivElement | null>;
|
|
15
15
|
};
|
|
16
16
|
export type AdditionalSearchBarProps = {
|
|
17
17
|
/** Application menu to be displayed when clicked on MenuIcon */
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
export type SearchInputController = {
|
|
3
3
|
/** Clears the channel search state */
|
|
4
4
|
clearState: () => void;
|
|
5
|
-
inputRef: React.RefObject<HTMLInputElement>;
|
|
5
|
+
inputRef: React.RefObject<HTMLInputElement | null>;
|
|
6
6
|
/** Search input change handler */
|
|
7
7
|
onSearch: React.ChangeEventHandler<HTMLInputElement>;
|
|
8
8
|
/** Current search string */
|
|
@@ -9,7 +9,7 @@ export const useChannelSearch = ({ channelType = 'messaging', clearSearchOnClick
|
|
|
9
9
|
const [query, setQuery] = useState('');
|
|
10
10
|
const [results, setResults] = useState([]);
|
|
11
11
|
const [searching, setSearching] = useState(false);
|
|
12
|
-
const searchQueryPromiseInProgress = useRef();
|
|
12
|
+
const searchQueryPromiseInProgress = useRef(undefined);
|
|
13
13
|
const shouldIgnoreQueryResults = useRef(false);
|
|
14
14
|
const inputRef = useRef(null);
|
|
15
15
|
const searchBarRef = useRef(null);
|
|
@@ -28,7 +28,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
28
28
|
if (!userAgent.includes('stream-chat-react')) {
|
|
29
29
|
// result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
|
|
30
30
|
// the upper-case text between double underscores is replaced with the actual semantic version of the library
|
|
31
|
-
client.setUserAgent(`stream-chat-react-12.
|
|
31
|
+
client.setUserAgent(`stream-chat-react-12.10.0-${userAgent}`);
|
|
32
32
|
}
|
|
33
33
|
client.threads.registerSubscriptions();
|
|
34
34
|
client.polls.registerSubscriptions();
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export type BaseImageProps = React.ComponentPropsWithRef<'img'>;
|
|
3
|
-
export declare const BaseImage: React.ForwardRefExoticComponent<Omit<
|
|
4
|
-
ref?: ((instance: HTMLImageElement | null) => void) | React.RefObject<HTMLImageElement> | null | undefined;
|
|
5
|
-
}, "ref"> & React.RefAttributes<HTMLImageElement>>;
|
|
3
|
+
export declare const BaseImage: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref"> & React.RefAttributes<HTMLImageElement>>;
|
|
@@ -29,11 +29,19 @@ const UnMemoizedGallery = (props) => {
|
|
|
29
29
|
images[lastImageIndexInPreview].image_url ||
|
|
30
30
|
images[lastImageIndexInPreview].thumb_url})`,
|
|
31
31
|
...image.style,
|
|
32
|
-
}, ...(innerRefs?.current && {
|
|
32
|
+
}, ...(innerRefs?.current && {
|
|
33
|
+
ref: (r) => {
|
|
34
|
+
innerRefs.current[i] = r;
|
|
35
|
+
},
|
|
36
|
+
}) },
|
|
33
37
|
React.createElement("p", null, t('{{ imageCount }} more', {
|
|
34
38
|
imageCount: images.length - countImagesDisplayedInPreview,
|
|
35
39
|
})))) : (React.createElement("button", { className: 'str-chat__gallery-image', "data-testid": 'gallery-image', key: `gallery-image-${i}`, onClick: () => toggleModal(i) },
|
|
36
|
-
React.createElement(BaseImage, { alt: image?.fallback || imageFallbackTitle, src: sanitizeUrl(image.previewUrl || image.image_url || image.thumb_url), style: image.style, title: image?.fallback || imageFallbackTitle, ...(innerRefs?.current && {
|
|
40
|
+
React.createElement(BaseImage, { alt: image?.fallback || imageFallbackTitle, src: sanitizeUrl(image.previewUrl || image.image_url || image.thumb_url), style: image.style, title: image?.fallback || imageFallbackTitle, ...(innerRefs?.current && {
|
|
41
|
+
ref: (r) => {
|
|
42
|
+
innerRefs.current[i] = r;
|
|
43
|
+
},
|
|
44
|
+
}) }))));
|
|
37
45
|
const className = clsx('str-chat__gallery', {
|
|
38
46
|
'str-chat__gallery--square': images.length > lastImageIndexInPreview,
|
|
39
47
|
'str-chat__gallery-two-rows': images.length > 2,
|
|
@@ -18,8 +18,9 @@ export const ModalGallery = (props) => {
|
|
|
18
18
|
originalAlt: t('User uploaded content'),
|
|
19
19
|
source: imageSrc,
|
|
20
20
|
};
|
|
21
|
-
}),
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
}), [images, t]);
|
|
22
|
+
return (
|
|
23
|
+
// ignore the TS error as react-image-gallery was on @types/react@18 while stream-chat-react being upgraded to React 19 (https://github.com/xiaolin/react-image-gallery/issues/809)
|
|
24
|
+
// @ts-expect-error
|
|
25
|
+
React.createElement(ImageGallery, { items: formattedArray, renderItem: renderItem, showIndex: true, showPlayButton: false, showThumbnails: false, startIndex: index }));
|
|
25
26
|
};
|
|
@@ -26,10 +26,10 @@ export const InfiniteScroll = (props) => {
|
|
|
26
26
|
const loadPreviousPageFn = loadPreviousPage || loadMore;
|
|
27
27
|
const hasNextPageFlag = hasNextPage || hasMoreNewer;
|
|
28
28
|
const hasPreviousPageFlag = hasPreviousPage || hasMore;
|
|
29
|
-
const scrollComponent = useRef();
|
|
30
|
-
const previousOffset = useRef();
|
|
31
|
-
const previousReverseOffset = useRef();
|
|
32
|
-
const scrollListenerRef = useRef();
|
|
29
|
+
const scrollComponent = useRef(undefined);
|
|
30
|
+
const previousOffset = useRef(undefined);
|
|
31
|
+
const previousReverseOffset = useRef(undefined);
|
|
32
|
+
const scrollListenerRef = useRef(undefined);
|
|
33
33
|
scrollListenerRef.current = () => {
|
|
34
34
|
const element = scrollComponent.current;
|
|
35
35
|
if (!element || element.offsetParent === null) {
|
|
@@ -11,7 +11,7 @@ export declare function useMessageActionsBoxPopper<T extends HTMLElement>({ open
|
|
|
11
11
|
[key: string]: string;
|
|
12
12
|
} | undefined;
|
|
13
13
|
};
|
|
14
|
-
popperElementRef: import("react").RefObject<T>;
|
|
14
|
+
popperElementRef: import("react").RefObject<T | null>;
|
|
15
15
|
styles: {
|
|
16
16
|
[key: string]: import("react").CSSProperties;
|
|
17
17
|
};
|
|
@@ -6,5 +6,5 @@ import type { EnrichURLsController } from './useLinkPreviews';
|
|
|
6
6
|
export declare const useMessageInputText: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, V extends CustomTrigger = CustomTrigger>(props: MessageInputProps<StreamChatGenerics, V>, state: MessageInputState<StreamChatGenerics>, dispatch: React.Dispatch<MessageInputReducerAction<StreamChatGenerics>>, findAndEnqueueURLsToEnrich?: EnrichURLsController['findAndEnqueueURLsToEnrich']) => {
|
|
7
7
|
handleChange: import("react").ChangeEventHandler<HTMLTextAreaElement>;
|
|
8
8
|
insertText: (textToInsert: string) => void;
|
|
9
|
-
textareaRef: import("react").
|
|
9
|
+
textareaRef: import("react").RefObject<HTMLTextAreaElement | undefined>;
|
|
10
10
|
};
|
|
@@ -5,7 +5,7 @@ export const useMessageInputText = (props, state, dispatch, findAndEnqueueURLsTo
|
|
|
5
5
|
const { channel } = useChannelStateContext('useMessageInputText');
|
|
6
6
|
const { additionalTextareaProps, focus, parent, publishTypingEvent = true } = props;
|
|
7
7
|
const { text } = state;
|
|
8
|
-
const textareaRef = useRef();
|
|
8
|
+
const textareaRef = useRef(undefined);
|
|
9
9
|
// Focus
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
if (focus && textareaRef.current) {
|
|
@@ -13,7 +13,7 @@ export const useMessageInputText = (props, state, dispatch, findAndEnqueueURLsTo
|
|
|
13
13
|
}
|
|
14
14
|
}, [focus]);
|
|
15
15
|
// Text + cursor position
|
|
16
|
-
const newCursorPosition = useRef();
|
|
16
|
+
const newCursorPosition = useRef(undefined);
|
|
17
17
|
const insertText = useCallback((textToInsert) => {
|
|
18
18
|
const { maxLength } = additionalTextareaProps || {};
|
|
19
19
|
if (!textareaRef.current) {
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
// todo: provide start timestamp
|
|
3
3
|
export const useTimeElapsed = ({ startOnMount } = {}) => {
|
|
4
4
|
const [secondsElapsed, setSecondsElapsed] = useState(0);
|
|
5
|
-
const updateInterval = useRef();
|
|
5
|
+
const updateInterval = useRef(undefined);
|
|
6
6
|
const startCounter = useCallback(() => {
|
|
7
7
|
if (updateInterval.current)
|
|
8
8
|
return;
|
|
@@ -42,7 +42,6 @@ const MessageListWithContext = (props) => {
|
|
|
42
42
|
useMarkRead({
|
|
43
43
|
isMessageListScrolledToBottom,
|
|
44
44
|
messageListIsThread: threadList,
|
|
45
|
-
unreadCount: channelUnreadUiState?.unread_messages ?? 0,
|
|
46
45
|
wasMarkedUnread: !!channelUnreadUiState?.first_unread_message_id,
|
|
47
46
|
});
|
|
48
47
|
const { messageGroupStyles, messages: enrichedMessages } = useEnrichedMessages({
|
|
@@ -25,7 +25,7 @@ export type VirtuosoContext<StreamChatGenerics extends DefaultStreamChatGenerics
|
|
|
25
25
|
/** The original message list enriched with date separators, omitted deleted messages or giphy previews. */
|
|
26
26
|
processedMessages: StreamMessage<StreamChatGenerics>[];
|
|
27
27
|
/** Instance of VirtuosoHandle object providing the API to navigate in the virtualized list by various scroll actions. */
|
|
28
|
-
virtuosoRef: RefObject<VirtuosoHandle>;
|
|
28
|
+
virtuosoRef: RefObject<VirtuosoHandle | null>;
|
|
29
29
|
/** Message id which was marked as unread. ALl the messages following this message are considered unrea. */
|
|
30
30
|
firstUnreadMessageId?: string;
|
|
31
31
|
lastReadDate?: Date;
|
|
@@ -19,6 +19,7 @@ import { useChannelActionContext, } from '../../context/ChannelActionContext';
|
|
|
19
19
|
import { useChannelStateContext, } from '../../context/ChannelStateContext';
|
|
20
20
|
import { useChatContext } from '../../context/ChatContext';
|
|
21
21
|
import { useComponentContext } from '../../context/ComponentContext';
|
|
22
|
+
import { VirtualizedMessageListContextProvider } from '../../context/VirtualizedMessageListContext';
|
|
22
23
|
import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';
|
|
23
24
|
function captureResizeObserverExceededError(e) {
|
|
24
25
|
if (e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
|
|
@@ -122,7 +123,6 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
122
123
|
useMarkRead({
|
|
123
124
|
isMessageListScrolledToBottom,
|
|
124
125
|
messageListIsThread: !!threadList,
|
|
125
|
-
unreadCount: channelUnreadUiState?.unread_messages ?? 0,
|
|
126
126
|
wasMarkedUnread: !!channelUnreadUiState?.first_unread_message_id,
|
|
127
127
|
});
|
|
128
128
|
const scrollToBottom = useCallback(async () => {
|
|
@@ -192,7 +192,7 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
192
192
|
const dialogManagerId = threadList
|
|
193
193
|
? 'virtualized-message-list-dialog-manager-thread'
|
|
194
194
|
: 'virtualized-message-list-dialog-manager';
|
|
195
|
-
return (React.createElement(
|
|
195
|
+
return (React.createElement(VirtualizedMessageListContextProvider, { value: { scrollToBottom } },
|
|
196
196
|
React.createElement(MessageListMainPanel, null,
|
|
197
197
|
React.createElement(DialogManagerProvider, { id: dialogManagerId },
|
|
198
198
|
!threadList && showUnreadMessagesNotification && (React.createElement(UnreadMessagesNotification, { unreadCount: channelUnreadUiState?.unread_messages })),
|
|
@@ -8,7 +8,7 @@ export function useMessageListScrollManager(params) {
|
|
|
8
8
|
offsetHeight: 0,
|
|
9
9
|
scrollHeight: 0,
|
|
10
10
|
});
|
|
11
|
-
const messages = useRef();
|
|
11
|
+
const messages = useRef(undefined);
|
|
12
12
|
const scrollTop = useRef(0);
|
|
13
13
|
useLayoutEffect(() => {
|
|
14
14
|
const prevMeasures = measures.current;
|
|
@@ -4,7 +4,7 @@ export const useMessageSetKey = ({ messages, }) => {
|
|
|
4
4
|
* Logic to update the key of the virtuoso component when the list jumps to a new location.
|
|
5
5
|
*/
|
|
6
6
|
const [messageSetKey, setMessageSetKey] = useState(+new Date());
|
|
7
|
-
const firstMessageId = useRef();
|
|
7
|
+
const firstMessageId = useRef(undefined);
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
const continuousSet = messages?.find((message) => message.id === firstMessageId.current);
|
|
10
10
|
if (!continuousSet) {
|
package/dist/components/MessageList/hooks/VirtualizedMessageList/useNewMessageNotification.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { StreamMessage } from '../../../../context/ChannelStateContext';
|
|
3
3
|
import type { DefaultStreamChatGenerics } from '../../../../types/types';
|
|
4
4
|
export declare function useNewMessageNotification<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(messages: StreamMessage<StreamChatGenerics>[], currentUserId: string | undefined, hasMoreNewer?: boolean): {
|
|
5
|
-
atBottom: import("react").
|
|
5
|
+
atBottom: import("react").RefObject<boolean>;
|
|
6
6
|
isMessageListScrolledToBottom: boolean;
|
|
7
7
|
newMessagesNotification: boolean;
|
|
8
8
|
setIsMessageListScrolledToBottom: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
@@ -5,8 +5,8 @@ const STATUSES_EXCLUDED_FROM_PREPEND = {
|
|
|
5
5
|
};
|
|
6
6
|
export function usePrependedMessagesCount(messages, hasDateSeparator) {
|
|
7
7
|
const firstRealMessageIndex = hasDateSeparator ? 1 : 0;
|
|
8
|
-
const firstMessageOnFirstLoadedPage = useRef();
|
|
9
|
-
const previousFirstMessageOnFirstLoadedPage = useRef();
|
|
8
|
+
const firstMessageOnFirstLoadedPage = useRef(undefined);
|
|
9
|
+
const previousFirstMessageOnFirstLoadedPage = useRef(undefined);
|
|
10
10
|
const previousNumItemsPrepended = useRef(0);
|
|
11
11
|
const numItemsPrepended = useMemo(() => {
|
|
12
12
|
if (!messages || !messages.length) {
|
package/dist/components/MessageList/hooks/VirtualizedMessageList/useScrollToBottomOnNewMessage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
export const useScrollToBottomOnNewMessage = ({ messages, scrollToBottom, scrollToLatestMessageOnFocus, }) => {
|
|
3
3
|
const [newMessagesReceivedInBackground, setNewMessagesReceivedInBackground] = useState(false);
|
|
4
|
-
const scrollToBottomIfConfigured = useRef();
|
|
4
|
+
const scrollToBottomIfConfigured = useRef(undefined);
|
|
5
5
|
scrollToBottomIfConfigured.current = (event) => {
|
|
6
6
|
if (!scrollToLatestMessageOnFocus ||
|
|
7
7
|
!newMessagesReceivedInBackground ||
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { DefaultStreamChatGenerics } from '../../../types';
|
|
1
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
2
2
|
type UseMarkReadParams = {
|
|
3
3
|
isMessageListScrolledToBottom: boolean;
|
|
4
4
|
messageListIsThread: boolean;
|
|
5
|
-
unreadCount: number;
|
|
6
5
|
wasMarkedUnread?: boolean;
|
|
7
6
|
};
|
|
8
7
|
/**
|
|
@@ -12,8 +11,7 @@ type UseMarkReadParams = {
|
|
|
12
11
|
* 3. the channel was not marked unread by the user
|
|
13
12
|
* @param isMessageListScrolledToBottom
|
|
14
13
|
* @param messageListIsThread
|
|
15
|
-
* @param unreadCount
|
|
16
14
|
* @param wasChannelMarkedUnread
|
|
17
15
|
*/
|
|
18
|
-
export declare const useMarkRead: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ isMessageListScrolledToBottom, messageListIsThread,
|
|
16
|
+
export declare const useMarkRead: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ isMessageListScrolledToBottom, messageListIsThread, wasMarkedUnread, }: UseMarkReadParams) => void;
|
|
19
17
|
export {};
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { useEffect
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
import { useChannelActionContext, useChannelStateContext, useChatContext, } from '../../../context';
|
|
3
|
+
const hasReadLastMessage = (channel, userId) => {
|
|
4
|
+
const latestMessageIdInChannel = channel.state.latestMessages.slice(-1)[0]?.id;
|
|
5
|
+
const lastReadMessageIdServer = channel.state.read[userId]?.last_read_message_id;
|
|
6
|
+
return latestMessageIdInChannel === lastReadMessageIdServer;
|
|
7
|
+
};
|
|
3
8
|
/**
|
|
4
9
|
* Takes care of marking a channel read. The channel is read only if all the following applies:
|
|
5
10
|
* 1. the message list is not rendered in a thread
|
|
@@ -7,29 +12,25 @@ import { useChannelActionContext, useChannelStateContext, useChatContext, } from
|
|
|
7
12
|
* 3. the channel was not marked unread by the user
|
|
8
13
|
* @param isMessageListScrolledToBottom
|
|
9
14
|
* @param messageListIsThread
|
|
10
|
-
* @param unreadCount
|
|
11
15
|
* @param wasChannelMarkedUnread
|
|
12
16
|
*/
|
|
13
|
-
export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread,
|
|
17
|
+
export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread, wasMarkedUnread, }) => {
|
|
14
18
|
const { client } = useChatContext('useMarkRead');
|
|
15
19
|
const { markRead, setChannelUnreadUiState } = useChannelActionContext('useMarkRead');
|
|
16
20
|
const { channel } = useChannelStateContext('useMarkRead');
|
|
17
|
-
const previousRenderMessageListScrolledToBottom = useRef(isMessageListScrolledToBottom);
|
|
18
21
|
useEffect(() => {
|
|
19
|
-
const shouldMarkRead = (
|
|
22
|
+
const shouldMarkRead = () => !document.hidden &&
|
|
20
23
|
!wasMarkedUnread &&
|
|
21
24
|
!messageListIsThread &&
|
|
22
25
|
isMessageListScrolledToBottom &&
|
|
23
|
-
|
|
26
|
+
client.user?.id &&
|
|
27
|
+
!hasReadLastMessage(channel, client.user.id);
|
|
24
28
|
const onVisibilityChange = () => {
|
|
25
|
-
if (shouldMarkRead(
|
|
29
|
+
if (shouldMarkRead())
|
|
26
30
|
markRead();
|
|
27
31
|
};
|
|
28
32
|
const handleMessageNew = (event) => {
|
|
29
|
-
const isOwnMessage = event.user?.id && event.user.id === client.user?.id;
|
|
30
33
|
const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel;
|
|
31
|
-
if (isOwnMessage)
|
|
32
|
-
return;
|
|
33
34
|
if (!isMessageListScrolledToBottom || wasMarkedUnread || document.hidden) {
|
|
34
35
|
setChannelUnreadUiState((prev) => {
|
|
35
36
|
const previousUnreadCount = prev?.unread_messages ?? 0;
|
|
@@ -44,17 +45,15 @@ export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread
|
|
|
44
45
|
};
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
|
-
else if (mainChannelUpdated && shouldMarkRead(
|
|
48
|
+
else if (mainChannelUpdated && shouldMarkRead()) {
|
|
48
49
|
markRead();
|
|
49
50
|
}
|
|
50
51
|
};
|
|
51
52
|
channel.on('message.new', handleMessageNew);
|
|
52
53
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
53
|
-
|
|
54
|
-
isMessageListScrolledToBottom;
|
|
55
|
-
if (hasScrolledToBottom && shouldMarkRead(channel.countUnread()))
|
|
54
|
+
if (shouldMarkRead()) {
|
|
56
55
|
markRead();
|
|
57
|
-
|
|
56
|
+
}
|
|
58
57
|
return () => {
|
|
59
58
|
channel.off('message.new', handleMessageNew);
|
|
60
59
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
@@ -66,7 +65,6 @@ export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread
|
|
|
66
65
|
markRead,
|
|
67
66
|
messageListIsThread,
|
|
68
67
|
setChannelUnreadUiState,
|
|
69
|
-
unreadCount,
|
|
70
68
|
wasMarkedUnread,
|
|
71
69
|
]);
|
|
72
70
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
|
2
|
+
export type VirtualizedMessageListContextValue = {
|
|
3
|
+
/** Function that scrolls the list to the bottom. */
|
|
4
|
+
scrollToBottom: () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare const VirtualizedMessageListContext: React.Context<VirtualizedMessageListContextValue | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* Context provider for components rendered within the `VirtualizedMessageList`
|
|
9
|
+
*/
|
|
10
|
+
export declare const VirtualizedMessageListContextProvider: ({ children, value, }: PropsWithChildren<{
|
|
11
|
+
value: VirtualizedMessageListContextValue;
|
|
12
|
+
}>) => React.JSX.Element;
|
|
13
|
+
export declare const useVirtualizedMessageListContext: () => VirtualizedMessageListContextValue;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
export const VirtualizedMessageListContext = createContext(undefined);
|
|
3
|
+
/**
|
|
4
|
+
* Context provider for components rendered within the `VirtualizedMessageList`
|
|
5
|
+
*/
|
|
6
|
+
export const VirtualizedMessageListContextProvider = ({ children, value, }) => (React.createElement(VirtualizedMessageListContext.Provider, { value: value }, children));
|
|
7
|
+
export const useVirtualizedMessageListContext = () => useContext(VirtualizedMessageListContext);
|