stream-chat-react 12.8.2 → 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.
Files changed (52) hide show
  1. package/dist/components/Attachment/components/WaveProgressBar.js +1 -1
  2. package/dist/components/Attachment/hooks/useAudioController.d.ts +1 -1
  3. package/dist/components/Attachment/hooks/useAudioController.js +1 -1
  4. package/dist/components/AutoCompleteTextarea/Textarea.js +0 -4
  5. package/dist/components/AutoCompleteTextarea/utils.js +1 -5
  6. package/dist/components/Channel/Channel.js +3 -2
  7. package/dist/components/Channel/channelState.d.ts +1 -3
  8. package/dist/components/Channel/channelState.js +1 -1
  9. package/dist/components/Channel/hooks/useIsMounted.d.ts +1 -1
  10. package/dist/components/ChannelList/hooks/useChannelListShape.d.ts +3 -3
  11. package/dist/components/ChannelList/hooks/useChannelListShape.js +54 -47
  12. package/dist/components/ChannelList/hooks/useChannelMembershipState.d.ts +3 -2
  13. package/dist/components/ChannelList/hooks/useChannelMembershipState.js +6 -15
  14. package/dist/components/ChannelList/hooks/useMobileNavigation.d.ts +1 -1
  15. package/dist/components/ChannelList/hooks/usePaginatedChannels.js +1 -1
  16. package/dist/components/ChannelList/hooks/useSelectedChannelState.d.ts +11 -0
  17. package/dist/components/ChannelList/hooks/useSelectedChannelState.js +20 -0
  18. package/dist/components/ChannelList/utils.d.ts +16 -6
  19. package/dist/components/ChannelList/utils.js +44 -18
  20. package/dist/components/ChannelSearch/SearchBar.d.ts +1 -1
  21. package/dist/components/ChannelSearch/SearchInput.d.ts +1 -1
  22. package/dist/components/ChannelSearch/hooks/useChannelSearch.js +1 -1
  23. package/dist/components/Chat/hooks/useChat.js +1 -1
  24. package/dist/components/ChatAutoComplete/ChatAutoComplete.d.ts +2 -0
  25. package/dist/components/ChatAutoComplete/ChatAutoComplete.js +1 -1
  26. package/dist/components/Gallery/BaseImage.d.ts +1 -3
  27. package/dist/components/Gallery/Gallery.js +10 -2
  28. package/dist/components/Gallery/ModalGallery.js +5 -4
  29. package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +4 -4
  30. package/dist/components/MessageActions/hooks/useMessageActionsBoxPopper.d.ts +1 -1
  31. package/dist/components/MessageInput/hooks/useMessageInputText.d.ts +1 -1
  32. package/dist/components/MessageInput/hooks/useMessageInputText.js +2 -2
  33. package/dist/components/MessageInput/hooks/useTimeElapsed.js +1 -1
  34. package/dist/components/MessageList/MessageList.js +0 -1
  35. package/dist/components/MessageList/VirtualizedMessageList.d.ts +1 -1
  36. package/dist/components/MessageList/VirtualizedMessageList.js +2 -2
  37. package/dist/components/MessageList/hooks/MessageList/useMessageListScrollManager.js +1 -1
  38. package/dist/components/MessageList/hooks/VirtualizedMessageList/useMessageSetKey.js +1 -1
  39. package/dist/components/MessageList/hooks/VirtualizedMessageList/useNewMessageNotification.d.ts +1 -1
  40. package/dist/components/MessageList/hooks/VirtualizedMessageList/usePrependMessagesCount.js +2 -2
  41. package/dist/components/MessageList/hooks/VirtualizedMessageList/useScrollToBottomOnNewMessage.js +1 -1
  42. package/dist/components/MessageList/hooks/useMarkRead.d.ts +2 -4
  43. package/dist/components/MessageList/hooks/useMarkRead.js +14 -16
  44. package/dist/context/VirtualizedMessageListContext.d.ts +13 -0
  45. package/dist/context/VirtualizedMessageListContext.js +7 -0
  46. package/dist/experimental/index.browser.cjs.map +2 -2
  47. package/dist/experimental/index.node.cjs.map +2 -2
  48. package/dist/index.browser.cjs +721 -267
  49. package/dist/index.browser.cjs.map +4 -4
  50. package/dist/index.node.cjs +986 -286
  51. package/dist/index.node.cjs.map +4 -4
  52. package/package.json +17 -18
@@ -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").MutableRefObject<HTMLAudioElement | null>;
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, component, dataProvider, output } = triggerSetting;
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 { channelReducer, initialState } from './channelState';
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 type ChannelStateReducer<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Reducer<ChannelState<StreamChatGenerics>, ChannelStateReducerAction<StreamChatGenerics>>;
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,4 +1,4 @@
1
- export const channelReducer = (state, action) => {
1
+ export const makeChannelReducer = () => (state, action) => {
2
2
  switch (action.type) {
3
3
  case 'closeThread': {
4
4
  return {
@@ -1,2 +1,2 @@
1
1
  /// <reference types="react" />
2
- export declare const useIsMounted: () => import("react").MutableRefObject<boolean>;
2
+ export declare const useIsMounted: () => import("react").RefObject<boolean>;
@@ -24,7 +24,7 @@ type HandleNotificationAddedToChannelParameters<SCG extends ExtendableGenerics>
24
24
  } & Required<Pick<ChannelListProps<SCG>, 'sort'>>;
25
25
  type HandleMemberUpdatedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & {
26
26
  lockChannelOrder: boolean;
27
- } & Required<Pick<ChannelListProps<SCG>, 'sort'>>;
27
+ } & Required<Pick<ChannelListProps<SCG>, 'sort' | 'filters'>>;
28
28
  type HandleChannelDeletedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
29
29
  type HandleChannelHiddenParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
30
30
  type HandleChannelVisibleParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
@@ -37,9 +37,9 @@ export declare const useChannelListShapeDefaults: <SCG extends ExtendableGeneric
37
37
  handleChannelTruncated: ({ customHandler, event, setChannels }: HandleChannelTruncatedParameters<SCG>) => void;
38
38
  handleChannelUpdated: ({ customHandler, event, setChannels }: HandleChannelUpdatedParameters<SCG>) => void;
39
39
  handleChannelVisible: ({ customHandler, event, setChannels }: HandleChannelVisibleParameters<SCG>) => Promise<void>;
40
- handleMemberUpdated: ({ event, lockChannelOrder, setChannels, sort }: HandleMemberUpdatedParameters<SCG>) => void;
40
+ handleMemberUpdated: ({ event, filters, lockChannelOrder, setChannels, sort, }: HandleMemberUpdatedParameters<SCG>) => void;
41
41
  handleMessageNew: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, lockChannelOrder, setChannels, sort, }: HandleMessageNewParameters<SCG>) => void;
42
- handleNotificationAddedToChannel: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, setChannels, }: HandleNotificationAddedToChannelParameters<SCG>) => Promise<void>;
42
+ handleNotificationAddedToChannel: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, setChannels, sort, }: HandleNotificationAddedToChannelParameters<SCG>) => Promise<void>;
43
43
  handleNotificationMessageNew: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, setChannels, sort, }: HandleNotificationMessageNewParameters<SCG>) => Promise<void>;
44
44
  handleNotificationRemovedFromChannel: ({ customHandler, event, setChannels, }: HandleNotificationRemovedFromChannelParameters<SCG>) => void;
45
45
  handleUserPresenceChanged: ({ event, setChannels }: HandleUserPresenceChangedParameters<SCG>) => void;
@@ -1,7 +1,7 @@
1
1
  // const defaults = useChannelListShapeDefaults();
2
2
  import { useCallback, useEffect, useMemo, useRef } from 'react';
3
3
  import uniqBy from 'lodash.uniqby';
4
- import { findLastPinnedChannelIndex, isChannelArchived, isChannelPinned, moveChannelUpwards, shouldConsiderArchivedChannels, shouldConsiderPinnedChannels, } from '../utils';
4
+ import { extractSortValue, findLastPinnedChannelIndex, isChannelArchived, isChannelPinned, moveChannelUpwards, shouldConsiderArchivedChannels, shouldConsiderPinnedChannels, } from '../utils';
5
5
  import { useChatContext } from '../../../context';
6
6
  import { getChannel } from '../../../utils';
7
7
  const shared = ({ customHandler, event, setChannels, }) => {
@@ -22,39 +22,37 @@ export const useChannelListShapeDefaults = () => {
22
22
  if (typeof customHandler === 'function') {
23
23
  return customHandler(setChannels, event);
24
24
  }
25
- setChannels((channels) => {
26
- const targetChannelIndex = channels.findIndex((channel) => channel.cid === event.cid);
25
+ const channelType = event.channel_type;
26
+ const channelId = event.channel_id;
27
+ if (!channelType || !channelId)
28
+ return;
29
+ setChannels((currentChannels) => {
30
+ const targetChannel = client.channel(channelType, channelId);
31
+ const targetChannelIndex = currentChannels.indexOf(targetChannel);
27
32
  const targetChannelExistsWithinList = targetChannelIndex >= 0;
28
- const targetChannel = channels[targetChannelIndex];
29
33
  const isTargetChannelPinned = isChannelPinned(targetChannel);
30
34
  const isTargetChannelArchived = isChannelArchived(targetChannel);
31
35
  const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
32
36
  const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
33
37
  if (
34
- // target channel is archived
35
- (isTargetChannelArchived && considerArchivedChannels) ||
36
- // target channel is pinned
37
- (isTargetChannelPinned && considerPinnedChannels) ||
38
+ // filter is defined, target channel is archived and filter option is set to false
39
+ (considerArchivedChannels && isTargetChannelArchived && !filters.archived) ||
40
+ // filter is defined, target channel isn't archived and filter option is set to true
41
+ (considerArchivedChannels && !isTargetChannelArchived && filters.archived) ||
42
+ // sort option is defined, target channel is pinned
43
+ (considerPinnedChannels && isTargetChannelPinned) ||
38
44
  // list order is locked
39
45
  lockChannelOrder ||
40
46
  // target channel is not within the loaded list and loading from cache is disallowed
41
47
  (!targetChannelExistsWithinList && !allowNewMessagesFromUnfilteredChannels)) {
42
- return channels;
43
- }
44
- // we either have the channel to move or we pull it from the cache (or instantiate) if it's allowed
45
- const channelToMove = channels[targetChannelIndex] ??
46
- (allowNewMessagesFromUnfilteredChannels && event.channel_type
47
- ? client.channel(event.channel_type, event.channel_id)
48
- : null);
49
- if (channelToMove) {
50
- return moveChannelUpwards({
51
- channels,
52
- channelToMove,
53
- channelToMoveIndexWithinChannels: targetChannelIndex,
54
- sort,
55
- });
48
+ return currentChannels;
56
49
  }
57
- return channels;
50
+ return moveChannelUpwards({
51
+ channels: currentChannels,
52
+ channelToMove: targetChannel,
53
+ channelToMoveIndexWithinChannels: targetChannelIndex,
54
+ sort,
55
+ });
58
56
  });
59
57
  }, [client]);
60
58
  const handleNotificationMessageNew = useCallback(async ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, setChannels, sort, }) => {
@@ -70,7 +68,7 @@ export const useChannelListShapeDefaults = () => {
70
68
  type: event.channel.type,
71
69
  });
72
70
  const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
73
- if (isChannelArchived(channel) && considerArchivedChannels) {
71
+ if (isChannelArchived(channel) && considerArchivedChannels && !filters.archived) {
74
72
  return;
75
73
  }
76
74
  if (!allowNewMessagesFromUnfilteredChannels) {
@@ -83,25 +81,31 @@ export const useChannelListShapeDefaults = () => {
83
81
  sort,
84
82
  }));
85
83
  }, [client]);
86
- const handleNotificationAddedToChannel = useCallback(async ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, setChannels, }) => {
84
+ const handleNotificationAddedToChannel = useCallback(async ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, setChannels, sort, }) => {
87
85
  if (typeof customHandler === 'function') {
88
86
  return customHandler(setChannels, event);
89
87
  }
90
- if (allowNewMessagesFromUnfilteredChannels && event.channel?.type) {
91
- const channel = await getChannel({
92
- client,
93
- id: event.channel.id,
94
- members: event.channel.members?.reduce((acc, { user, user_id }) => {
95
- const userId = user_id || user?.id;
96
- if (userId) {
97
- acc.push(userId);
98
- }
99
- return acc;
100
- }, []),
101
- type: event.channel.type,
102
- });
103
- setChannels((channels) => uniqBy([channel, ...channels], 'cid'));
88
+ if (!event.channel || !allowNewMessagesFromUnfilteredChannels) {
89
+ return;
104
90
  }
91
+ const channel = await getChannel({
92
+ client,
93
+ id: event.channel.id,
94
+ members: event.channel.members?.reduce((newMembers, { user, user_id }) => {
95
+ const userId = user_id || user?.id;
96
+ if (userId)
97
+ newMembers.push(userId);
98
+ return newMembers;
99
+ }, []),
100
+ type: event.channel.type,
101
+ });
102
+ // membership has been reset (target channel shouldn't be pinned nor archived)
103
+ setChannels((channels) => moveChannelUpwards({
104
+ channels,
105
+ channelToMove: channel,
106
+ channelToMoveIndexWithinChannels: -1,
107
+ sort,
108
+ }));
105
109
  }, [client]);
106
110
  const handleNotificationRemovedFromChannel = useCallback(({ customHandler, event, setChannels, }) => {
107
111
  if (typeof customHandler === 'function') {
@@ -109,21 +113,22 @@ export const useChannelListShapeDefaults = () => {
109
113
  }
110
114
  setChannels((channels) => channels.filter((channel) => channel.cid !== event.channel?.cid));
111
115
  }, []);
112
- const handleMemberUpdated = useCallback(({ event, lockChannelOrder, setChannels, sort }) => {
116
+ const handleMemberUpdated = useCallback(({ event, filters, lockChannelOrder, setChannels, sort, }) => {
113
117
  if (!event.member?.user || event.member.user.id !== client.userID || !event.channel_type) {
114
118
  return;
115
119
  }
116
- const member = event.member;
117
120
  const channelType = event.channel_type;
118
121
  const channelId = event.channel_id;
119
122
  const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
120
- // TODO: extract this and consider single property sort object too
121
- const pinnedAtSort = Array.isArray(sort) ? sort[0]?.pinned_at ?? null : null;
123
+ const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
124
+ const pinnedAtSort = extractSortValue({ atIndex: 0, sort, targetKey: 'pinned_at' });
122
125
  setChannels((currentChannels) => {
123
126
  const targetChannel = client.channel(channelType, channelId);
124
127
  // assumes that channel instances are not changing
125
128
  const targetChannelIndex = currentChannels.indexOf(targetChannel);
126
129
  const targetChannelExistsWithinList = targetChannelIndex >= 0;
130
+ const isTargetChannelArchived = isChannelArchived(targetChannel);
131
+ const isTargetChannelPinned = isChannelPinned(targetChannel);
127
132
  // handle pinning
128
133
  if (!considerPinnedChannels || lockChannelOrder)
129
134
  return currentChannels;
@@ -132,14 +137,15 @@ export const useChannelListShapeDefaults = () => {
132
137
  newChannels.splice(targetChannelIndex, 1);
133
138
  }
134
139
  // handle archiving (remove channel)
135
- if (typeof member.archived_at === 'string') {
140
+ if ((considerArchivedChannels && isTargetChannelArchived && !filters.archived) ||
141
+ (considerArchivedChannels && !isTargetChannelArchived && filters.archived)) {
136
142
  return newChannels;
137
143
  }
138
144
  let lastPinnedChannelIndex = null;
139
145
  // calculate last pinned channel index only if `pinned_at` sort is set to
140
146
  // ascending order or if it's in descending order while the pin is being removed, otherwise
141
147
  // we move to the top (index 0)
142
- if (pinnedAtSort === 1 || (pinnedAtSort === -1 && !member.pinned_at)) {
148
+ if (pinnedAtSort === 1 || (pinnedAtSort === -1 && !isTargetChannelPinned)) {
143
149
  lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels });
144
150
  }
145
151
  const newTargetChannelIndex = typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0;
@@ -239,8 +245,8 @@ export const useChannelListShapeDefaults = () => {
239
245
  };
240
246
  export const usePrepareShapeHandlers = ({ allowNewMessagesFromUnfilteredChannels, customHandleChannelListShape, filters, lockChannelOrder, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, setChannels, sort, }) => {
241
247
  const defaults = useChannelListShapeDefaults();
242
- const defaultHandleChannelListShapeRef = useRef();
243
- const customHandleChannelListShapeRef = useRef();
248
+ const defaultHandleChannelListShapeRef = useRef(undefined);
249
+ const customHandleChannelListShapeRef = useRef(undefined);
244
250
  customHandleChannelListShapeRef.current = (event) => {
245
251
  // @ts-expect-error can't use ReturnType<typeof useChannelListShapeDefaults<SCG>> until we upgrade prettier to at least v2.7.0
246
252
  customHandleChannelListShape?.({ defaults, event, setChannels });
@@ -311,6 +317,7 @@ export const usePrepareShapeHandlers = ({ allowNewMessagesFromUnfilteredChannels
311
317
  case 'member.updated':
312
318
  defaults.handleMemberUpdated({
313
319
  event,
320
+ filters,
314
321
  lockChannelOrder,
315
322
  setChannels,
316
323
  sort,
@@ -1,2 +1,3 @@
1
- import type { Channel, ExtendableGenerics } from 'stream-chat';
2
- export declare const useChannelMembershipState: <SCG extends ExtendableGenerics>(channel?: Channel<SCG>) => import("stream-chat").ChannelMemberResponse<SCG>;
1
+ import type { Channel, ChannelMemberResponse, ExtendableGenerics } from 'stream-chat';
2
+ export declare function useChannelMembershipState<SCG extends ExtendableGenerics>(channel: Channel<SCG>): ChannelMemberResponse<SCG>;
3
+ export declare function useChannelMembershipState<SCG extends ExtendableGenerics>(channel?: Channel<SCG> | undefined): ChannelMemberResponse<SCG> | undefined;
@@ -1,15 +1,6 @@
1
- import { useEffect, useState } from 'react';
2
- import { useChatContext } from '../../../context';
3
- export const useChannelMembershipState = (channel) => {
4
- const [membership, setMembership] = useState(channel?.state.membership || {});
5
- const { client } = useChatContext();
6
- useEffect(() => {
7
- if (!channel)
8
- return;
9
- const subscriptions = ['member.updated'].map((v) => client.on(v, () => {
10
- setMembership(channel.state.membership);
11
- }));
12
- return () => subscriptions.forEach((subscription) => subscription.unsubscribe());
13
- }, [client, channel]);
14
- return membership;
15
- };
1
+ import { useSelectedChannelState } from './useSelectedChannelState';
2
+ const selector = (c) => c.state.membership;
3
+ const keys = ['member.updated'];
4
+ export function useChannelMembershipState(channel) {
5
+ return useSelectedChannelState({ channel, selector, stateChangeEventKeys: keys });
6
+ }
@@ -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;
@@ -0,0 +1,11 @@
1
+ import type { Channel, EventTypes, ExtendableGenerics } from 'stream-chat';
2
+ export declare function useSelectedChannelState<SCG extends ExtendableGenerics, O>(_: {
3
+ channel: Channel<SCG>;
4
+ selector: (channel: Channel<SCG>) => O;
5
+ stateChangeEventKeys?: EventTypes[];
6
+ }): O;
7
+ export declare function useSelectedChannelState<SCG extends ExtendableGenerics, O>(_: {
8
+ selector: (channel: Channel<SCG>) => O;
9
+ channel?: Channel<SCG> | undefined;
10
+ stateChangeEventKeys?: EventTypes[];
11
+ }): O | undefined;
@@ -0,0 +1,20 @@
1
+ import { useCallback } from 'react';
2
+ import { useSyncExternalStore } from 'use-sync-external-store/shim';
3
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
4
+ const noop = () => { };
5
+ export function useSelectedChannelState({ channel, stateChangeEventKeys = ['all'], selector, }) {
6
+ const subscribe = useCallback((onStoreChange) => {
7
+ if (!channel)
8
+ return noop;
9
+ const subscriptions = stateChangeEventKeys.map((et) => channel.on(et, () => {
10
+ onStoreChange(selector(channel));
11
+ }));
12
+ return () => subscriptions.forEach((subscription) => subscription.unsubscribe());
13
+ }, [channel, selector, stateChangeEventKeys]);
14
+ const getSnapshot = useCallback(() => {
15
+ if (!channel)
16
+ return undefined;
17
+ return selector(channel);
18
+ }, [channel, selector]);
19
+ return useSyncExternalStore(subscribe, getSnapshot);
20
+ }
@@ -1,4 +1,4 @@
1
- import type { Channel, ChannelSort, ExtendableGenerics } from 'stream-chat';
1
+ import type { Channel, ChannelSort, ChannelSortBase, ExtendableGenerics } from 'stream-chat';
2
2
  import type { DefaultStreamChatGenerics } from '../../types/types';
3
3
  import type { ChannelListProps } from './ChannelList';
4
4
  export declare const MAX_QUERY_CHANNELS_LIMIT = 30;
@@ -29,18 +29,28 @@ type MoveChannelUpwardsParams<SCG extends DefaultStreamChatGenerics = DefaultStr
29
29
  */
30
30
  channelToMoveIndexWithinChannels?: number;
31
31
  };
32
- /**
33
- * This function should not be used to move pinned already channels.
34
- */
35
32
  export declare const moveChannelUpwards: <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ channels, channelToMove, channelToMoveIndexWithinChannels, sort, }: MoveChannelUpwardsParams<SCG>) => Channel<SCG>[];
36
33
  /**
37
- * Returns true only if `{ pinned_at: -1 }` or `{ pinned_at: 1 }` option is first within the `sort` array.
34
+ * Returns `true` only if object with `pinned_at` property is first within the `sort` array
35
+ * or if `pinned_at` key of the `sort` object gets picked first when using `for...in` looping mechanism
36
+ * and value of the `pinned_at` is either `1` or `-1`.
38
37
  */
39
38
  export declare const shouldConsiderPinnedChannels: <SCG extends ExtendableGenerics>(sort: ChannelListProps<SCG>['sort']) => boolean;
39
+ export declare const extractSortValue: <SCG extends ExtendableGenerics>({ atIndex, sort, targetKey, }: {
40
+ atIndex: number;
41
+ targetKey: keyof ChannelSortBase<SCG>;
42
+ sort?: ChannelListProps<SCG>['sort'];
43
+ }) => NonNullable<ChannelSortBase<SCG>["created_at" | "pinned_at" | "updated_at" | keyof SCG["channelType"] | "has_unread" | "last_message_at" | "last_updated" | "member_count" | "unread_count"]> | null;
40
44
  /**
41
- * Returns `true` only if `archived` property is set to `false` within `filters`.
45
+ * Returns `true` only if `archived` property is of type `boolean` within `filters` object.
42
46
  */
43
47
  export declare const shouldConsiderArchivedChannels: <SCG extends ExtendableGenerics>(filters: ChannelListProps<SCG>['filters']) => boolean;
48
+ /**
49
+ * Returns `true` only if `pinned_at` property is of type `string` within `membership` object.
50
+ */
44
51
  export declare const isChannelPinned: <SCG extends ExtendableGenerics>(channel: Channel<SCG>) => boolean;
52
+ /**
53
+ * Returns `true` only if `archived_at` property is of type `string` within `membership` object.
54
+ */
45
55
  export declare const isChannelArchived: <SCG extends ExtendableGenerics>(channel: Channel<SCG>) => boolean;
46
56
  export {};
@@ -31,9 +31,6 @@ export function findLastPinnedChannelIndex({ channels, }) {
31
31
  }
32
32
  return lastPinnedChannelIndex;
33
33
  }
34
- /**
35
- * This function should not be used to move pinned already channels.
36
- */
37
34
  export const moveChannelUpwards = ({ channels, channelToMove, channelToMoveIndexWithinChannels, sort, }) => {
38
35
  // get index of channel to move up
39
36
  const targetChannelIndex = channelToMoveIndexWithinChannels ??
@@ -44,8 +41,10 @@ export const moveChannelUpwards = ({ channels, channelToMove, channelToMoveIndex
44
41
  // receive messages and are not pinned should move upwards but only under the last pinned channel
45
42
  // in the list
46
43
  const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
47
- if (targetChannelAlreadyAtTheTop)
44
+ const isTargetChannelPinned = isChannelPinned(channelToMove);
45
+ if (targetChannelAlreadyAtTheTop || (considerPinnedChannels && isTargetChannelPinned)) {
48
46
  return channels;
47
+ }
49
48
  const newChannels = [...channels];
50
49
  // target channel index is known, remove it from the list
51
50
  if (targetChannelExistsWithinList) {
@@ -62,35 +61,62 @@ export const moveChannelUpwards = ({ channels, channelToMove, channelToMoveIndex
62
61
  return newChannels;
63
62
  };
64
63
  /**
65
- * Returns true only if `{ pinned_at: -1 }` or `{ pinned_at: 1 }` option is first within the `sort` array.
64
+ * Returns `true` only if object with `pinned_at` property is first within the `sort` array
65
+ * or if `pinned_at` key of the `sort` object gets picked first when using `for...in` looping mechanism
66
+ * and value of the `pinned_at` is either `1` or `-1`.
66
67
  */
67
68
  export const shouldConsiderPinnedChannels = (sort) => {
68
- if (!sort)
69
+ const value = extractSortValue({ atIndex: 0, sort, targetKey: 'pinned_at' });
70
+ if (typeof value !== 'number')
69
71
  return false;
70
- if (!Array.isArray(sort))
71
- return false;
72
- const [option] = sort;
73
- if (!option?.pinned_at)
74
- return false;
75
- return Math.abs(option.pinned_at) === 1;
72
+ return Math.abs(value) === 1;
73
+ };
74
+ export const extractSortValue = ({ atIndex, sort, targetKey, }) => {
75
+ if (!sort)
76
+ return null;
77
+ let option = null;
78
+ if (Array.isArray(sort)) {
79
+ option = sort[atIndex] ?? null;
80
+ }
81
+ else {
82
+ let index = 0;
83
+ for (const key in sort) {
84
+ if (index !== atIndex) {
85
+ index++;
86
+ continue;
87
+ }
88
+ if (key !== targetKey) {
89
+ return null;
90
+ }
91
+ option = sort;
92
+ break;
93
+ }
94
+ }
95
+ return option?.[targetKey] ?? null;
76
96
  };
77
97
  /**
78
- * Returns `true` only if `archived` property is set to `false` within `filters`.
98
+ * Returns `true` only if `archived` property is of type `boolean` within `filters` object.
79
99
  */
80
100
  export const shouldConsiderArchivedChannels = (filters) => {
81
101
  if (!filters)
82
102
  return false;
83
- return !filters.archived;
103
+ return typeof filters.archived === 'boolean';
84
104
  };
105
+ /**
106
+ * Returns `true` only if `pinned_at` property is of type `string` within `membership` object.
107
+ */
85
108
  export const isChannelPinned = (channel) => {
86
109
  if (!channel)
87
110
  return false;
88
- const member = channel.state.membership;
89
- return !!member?.pinned_at;
111
+ const membership = channel.state.membership;
112
+ return typeof membership.pinned_at === 'string';
90
113
  };
114
+ /**
115
+ * Returns `true` only if `archived_at` property is of type `string` within `membership` object.
116
+ */
91
117
  export const isChannelArchived = (channel) => {
92
118
  if (!channel)
93
119
  return false;
94
- const member = channel.state.membership;
95
- return !!member?.archived_at;
120
+ const membership = channel.state.membership;
121
+ return typeof membership.archived_at === 'string';
96
122
  };
@@ -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.8.2-${userAgent}`);
31
+ client.setUserAgent(`stream-chat-react-12.10.0-${userAgent}`);
32
32
  }
33
33
  client.threads.registerSubscriptions();
34
34
  client.polls.registerSubscriptions();
@@ -53,6 +53,8 @@ export type SuggestionListProps<StreamChatGenerics extends DefaultStreamChatGene
53
53
  };
54
54
  }>;
55
55
  export type ChatAutoCompleteProps<T extends UnknownType = UnknownType> = {
56
+ /** Override the default disabled state of the underlying `textarea` component. */
57
+ disabled?: boolean;
56
58
  /** Function to override the default submit handler on the underlying `textarea` component */
57
59
  handleSubmit?: (event: React.BaseSyntheticEvent) => void;
58
60
  /** Function to run on blur of the underlying `textarea` component */
@@ -28,6 +28,6 @@ const UnMemoizedChatAutoComplete = (props) => {
28
28
  innerRef.current = ref;
29
29
  }
30
30
  }, [innerRef]);
31
- return (React.createElement(AutoCompleteTextarea, { additionalTextareaProps: messageInput.additionalTextareaProps, "aria-label": cooldownRemaining ? t('Slow Mode ON') : placeholder, className: 'str-chat__textarea__textarea str-chat__message-textarea', closeCommandsList: messageInput.closeCommandsList, closeMentionsList: messageInput.closeMentionsList, containerClassName: 'str-chat__textarea str-chat__message-textarea-react-host', disabled: disabled || !!cooldownRemaining, disableMentions: messageInput.disableMentions, grow: messageInput.grow, handleSubmit: props.handleSubmit || messageInput.handleSubmit, innerRef: updateInnerRef, loadingComponent: LoadingIndicator, maxRows: messageInput.maxRows, minChar: 0, minRows: messageInput.minRows, onBlur: props.onBlur, onChange: props.onChange || messageInput.handleChange, onFocus: props.onFocus, onPaste: props.onPaste || messageInput.onPaste, placeholder: cooldownRemaining ? t('Slow Mode ON') : placeholder, replaceWord: emojiReplace, rows: props.rows || 1, shouldSubmit: messageInput.shouldSubmit, showCommandsList: messageInput.showCommandsList, showMentionsList: messageInput.showMentionsList, SuggestionItem: SuggestionItem, SuggestionList: SuggestionList, trigger: messageInput.autocompleteTriggers || {}, value: props.value || messageInput.text }));
31
+ return (React.createElement(AutoCompleteTextarea, { additionalTextareaProps: messageInput.additionalTextareaProps, "aria-label": cooldownRemaining ? t('Slow Mode ON') : placeholder, className: 'str-chat__textarea__textarea str-chat__message-textarea', closeCommandsList: messageInput.closeCommandsList, closeMentionsList: messageInput.closeMentionsList, containerClassName: 'str-chat__textarea str-chat__message-textarea-react-host', disabled: (props.disabled ?? disabled) || !!cooldownRemaining, disableMentions: messageInput.disableMentions, grow: messageInput.grow, handleSubmit: props.handleSubmit || messageInput.handleSubmit, innerRef: updateInnerRef, loadingComponent: LoadingIndicator, maxRows: messageInput.maxRows, minChar: 0, minRows: messageInput.minRows, onBlur: props.onBlur, onChange: props.onChange || messageInput.handleChange, onFocus: props.onFocus, onPaste: props.onPaste || messageInput.onPaste, placeholder: cooldownRemaining ? t('Slow Mode ON') : placeholder, replaceWord: emojiReplace, rows: props.rows || 1, shouldSubmit: messageInput.shouldSubmit, showCommandsList: messageInput.showCommandsList, showMentionsList: messageInput.showMentionsList, SuggestionItem: SuggestionItem, SuggestionList: SuggestionList, trigger: messageInput.autocompleteTriggers || {}, value: props.value || messageInput.text }));
32
32
  };
33
33
  export const ChatAutoComplete = React.memo(UnMemoizedChatAutoComplete);
@@ -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<Omit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref"> & {
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>>;