stream-chat-react 12.7.1 → 12.8.1

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 (49) hide show
  1. package/dist/components/ChannelList/ChannelList.d.ts +1 -1
  2. package/dist/components/ChannelList/ChannelList.js +29 -34
  3. package/dist/components/ChannelList/hooks/index.d.ts +1 -0
  4. package/dist/components/ChannelList/hooks/index.js +1 -0
  5. package/dist/components/ChannelList/hooks/useChannelListShape.d.ts +60 -0
  6. package/dist/components/ChannelList/hooks/useChannelListShape.js +344 -0
  7. package/dist/components/ChannelList/hooks/useChannelMembershipState.d.ts +2 -0
  8. package/dist/components/ChannelList/hooks/useChannelMembershipState.js +15 -0
  9. package/dist/components/ChannelList/utils.d.ts +41 -5
  10. package/dist/components/ChannelList/utils.js +85 -0
  11. package/dist/components/ChannelPreview/ChannelPreviewActionButtons.d.ts +6 -0
  12. package/dist/components/ChannelPreview/ChannelPreviewActionButtons.js +30 -0
  13. package/dist/components/ChannelPreview/ChannelPreviewMessenger.d.ts +2 -2
  14. package/dist/components/ChannelPreview/ChannelPreviewMessenger.js +14 -9
  15. package/dist/components/ChannelPreview/icons.d.ts +6 -0
  16. package/dist/components/ChannelPreview/icons.js +8 -0
  17. package/dist/components/ChannelPreview/index.d.ts +1 -0
  18. package/dist/components/ChannelPreview/index.js +1 -0
  19. package/dist/components/Chat/hooks/useChat.js +1 -1
  20. package/dist/components/Dialog/DialogManager.d.ts +8 -0
  21. package/dist/components/Dialog/DialogManager.js +42 -0
  22. package/dist/components/Dialog/hooks/useDialog.js +5 -1
  23. package/dist/context/ComponentContext.d.ts +3 -1
  24. package/dist/css/v2/index.css +1 -1
  25. package/dist/css/v2/index.layout.css +1 -1
  26. package/dist/experimental/index.browser.cjs +1 -1
  27. package/dist/experimental/index.browser.cjs.map +2 -2
  28. package/dist/experimental/index.node.cjs +1 -1
  29. package/dist/experimental/index.node.cjs.map +2 -2
  30. package/dist/i18n/Streami18n.d.ts +2 -0
  31. package/dist/i18n/de.json +2 -0
  32. package/dist/i18n/en.json +2 -0
  33. package/dist/i18n/es.json +2 -0
  34. package/dist/i18n/fr.json +2 -0
  35. package/dist/i18n/hi.json +2 -0
  36. package/dist/i18n/it.json +2 -0
  37. package/dist/i18n/ja.json +2 -0
  38. package/dist/i18n/ko.json +2 -0
  39. package/dist/i18n/nl.json +2 -0
  40. package/dist/i18n/pt.json +2 -0
  41. package/dist/i18n/ru.json +2 -0
  42. package/dist/i18n/tr.json +2 -0
  43. package/dist/index.browser.cjs +10360 -9733
  44. package/dist/index.browser.cjs.map +4 -4
  45. package/dist/index.node.cjs +10285 -9650
  46. package/dist/index.node.cjs.map +4 -4
  47. package/dist/scss/v2/ChannelPreview/ChannelPreview-layout.scss +24 -0
  48. package/dist/scss/v2/ChannelPreview/ChannelPreview-theme.scss +18 -0
  49. package/package.json +4 -4
@@ -94,4 +94,4 @@ export type ChannelListProps<StreamChatGenerics extends DefaultStreamChatGeneric
94
94
  /**
95
95
  * Renders a preview list of Channels, allowing you to select the Channel you want to open
96
96
  */
97
- export declare const ChannelList: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(props: ChannelListProps<StreamChatGenerics>) => React.JSX.Element;
97
+ export declare const ChannelList: <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(props: ChannelListProps<SCG>) => React.JSX.Element;
@@ -1,20 +1,10 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { ChannelListMessenger } from './ChannelListMessenger';
4
- import { useChannelDeletedListener } from './hooks/useChannelDeletedListener';
5
- import { useChannelHiddenListener } from './hooks/useChannelHiddenListener';
6
- import { useChannelTruncatedListener } from './hooks/useChannelTruncatedListener';
7
- import { useChannelUpdatedListener } from './hooks/useChannelUpdatedListener';
8
- import { useChannelVisibleListener } from './hooks/useChannelVisibleListener';
9
4
  import { useConnectionRecoveredListener } from './hooks/useConnectionRecoveredListener';
10
- import { useMessageNewListener } from './hooks/useMessageNewListener';
11
5
  import { useMobileNavigation } from './hooks/useMobileNavigation';
12
- import { useNotificationAddedToChannelListener } from './hooks/useNotificationAddedToChannelListener';
13
- import { useNotificationMessageNewListener } from './hooks/useNotificationMessageNewListener';
14
- import { useNotificationRemovedFromChannelListener } from './hooks/useNotificationRemovedFromChannelListener';
15
6
  import { usePaginatedChannels } from './hooks/usePaginatedChannels';
16
- import { useUserPresenceChangedListener } from './hooks/useUserPresenceChangedListener';
17
- import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUp } from './utils';
7
+ import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUpwards } from './utils';
18
8
  import { Avatar as DefaultAvatar } from '../Avatar';
19
9
  import { ChannelPreview } from '../ChannelPreview/ChannelPreview';
20
10
  import { ChannelSearch as DefaultChannelSearch, } from '../ChannelSearch/ChannelSearch';
@@ -24,11 +14,12 @@ import { LoadMorePaginator } from '../LoadMore/LoadMorePaginator';
24
14
  import { NullComponent } from '../UtilityComponents';
25
15
  import { ChannelListContextProvider } from '../../context';
26
16
  import { useChatContext } from '../../context/ChatContext';
17
+ import { useChannelListShape, usePrepareShapeHandlers } from './hooks/useChannelListShape';
27
18
  const DEFAULT_FILTERS = {};
28
19
  const DEFAULT_OPTIONS = {};
29
20
  const DEFAULT_SORT = {};
30
21
  const UnMemoizedChannelList = (props) => {
31
- const { additionalChannelSearchProps, Avatar = DefaultAvatar, allowNewMessagesFromUnfilteredChannels, channelRenderFilterFn, ChannelSearch = DefaultChannelSearch, customActiveChannel, customQueryChannels, EmptyStateIndicator = DefaultEmptyStateIndicator, filters, getLatestMessagePreview, LoadingErrorIndicator = NullComponent, LoadingIndicator = LoadingChannels, List = ChannelListMessenger, lockChannelOrder, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, options, Paginator = LoadMorePaginator, Preview, recoveryThrottleIntervalMs, renderChannels, sendChannelsToList = false, setActiveChannelOnMount = true, showChannelSearch = false, sort = DEFAULT_SORT, watchers = {}, } = props;
22
+ const { additionalChannelSearchProps, Avatar = DefaultAvatar, allowNewMessagesFromUnfilteredChannels = true, channelRenderFilterFn, ChannelSearch = DefaultChannelSearch, customActiveChannel, customQueryChannels, EmptyStateIndicator = DefaultEmptyStateIndicator, filters = {}, getLatestMessagePreview, LoadingErrorIndicator = NullComponent, LoadingIndicator = LoadingChannels, List = ChannelListMessenger, lockChannelOrder = false, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, options, Paginator = LoadMorePaginator, Preview, recoveryThrottleIntervalMs, renderChannels, sendChannelsToList = false, setActiveChannelOnMount = true, showChannelSearch = false, sort = DEFAULT_SORT, watchers = {}, } = props;
32
23
  const { channel, channelsQueryState, client, closeMobileNav, customClasses, navOpen = false, setActiveChannel, theme, useImageFlagEmojisOnWindows, } = useChatContext('ChannelList');
33
24
  const channelListRef = useRef(null);
34
25
  const [channelUpdateCount, setChannelUpdateCount] = useState(0);
@@ -42,6 +33,7 @@ const UnMemoizedChannelList = (props) => {
42
33
  return;
43
34
  }
44
35
  if (customActiveChannel) {
36
+ // FIXME: this is wrong...
45
37
  let customActiveChannelObject = channels.find((chan) => chan.id === customActiveChannel);
46
38
  if (!customActiveChannelObject) {
47
39
  //@ts-expect-error
@@ -49,10 +41,10 @@ const UnMemoizedChannelList = (props) => {
49
41
  }
50
42
  if (customActiveChannelObject) {
51
43
  setActiveChannel(customActiveChannelObject, watchers);
52
- const newChannels = moveChannelUp({
53
- activeChannel: customActiveChannelObject,
44
+ const newChannels = moveChannelUpwards({
54
45
  channels,
55
- cid: customActiveChannelObject.cid,
46
+ channelToMove: customActiveChannelObject,
47
+ sort,
56
48
  });
57
49
  setChannels(newChannels);
58
50
  }
@@ -66,16 +58,9 @@ const UnMemoizedChannelList = (props) => {
66
58
  * For some events, inner properties on the channel will update but the shallow comparison will not
67
59
  * force a re-render. Incrementing this dummy variable ensures the channel previews update.
68
60
  */
69
- const forceUpdate = useCallback(() => setChannelUpdateCount((count) => count + 1), [
70
- setChannelUpdateCount,
71
- ]);
61
+ const forceUpdate = useCallback(() => setChannelUpdateCount((count) => count + 1), []);
72
62
  const onSearch = useCallback((event) => {
73
- if (!event.target.value) {
74
- setSearchActive(false);
75
- }
76
- else {
77
- setSearchActive(true);
78
- }
63
+ setSearchActive(!!event.target.value);
79
64
  additionalChannelSearchProps?.onSearch?.(event);
80
65
  // eslint-disable-next-line react-hooks/exhaustive-deps
81
66
  }, []);
@@ -87,17 +72,27 @@ const UnMemoizedChannelList = (props) => {
87
72
  const { channels, hasNextPage, loadNextPage, setChannels } = usePaginatedChannels(client, filters || DEFAULT_FILTERS, sort || DEFAULT_SORT, options || DEFAULT_OPTIONS, activeChannelHandler, recoveryThrottleIntervalMs, customQueryChannels);
88
73
  const loadedChannels = channelRenderFilterFn ? channelRenderFilterFn(channels) : channels;
89
74
  useMobileNavigation(channelListRef, navOpen, closeMobileNav);
90
- useMessageNewListener(setChannels, onMessageNewHandler, lockChannelOrder, allowNewMessagesFromUnfilteredChannels);
91
- useNotificationMessageNewListener(setChannels, onMessageNew, allowNewMessagesFromUnfilteredChannels);
92
- useNotificationAddedToChannelListener(setChannels, onAddedToChannel, allowNewMessagesFromUnfilteredChannels);
93
- useNotificationRemovedFromChannelListener(setChannels, onRemovedFromChannel);
94
- useChannelDeletedListener(setChannels, onChannelDeleted);
95
- useChannelHiddenListener(setChannels, onChannelHidden);
96
- useChannelVisibleListener(setChannels, onChannelVisible);
97
- useChannelTruncatedListener(setChannels, onChannelTruncated, forceUpdate);
98
- useChannelUpdatedListener(setChannels, onChannelUpdated, forceUpdate);
75
+ const { customHandler, defaultHandler } = usePrepareShapeHandlers({
76
+ allowNewMessagesFromUnfilteredChannels,
77
+ filters,
78
+ lockChannelOrder,
79
+ onAddedToChannel,
80
+ onChannelDeleted,
81
+ onChannelHidden,
82
+ onChannelTruncated,
83
+ onChannelUpdated,
84
+ onChannelVisible,
85
+ onMessageNew,
86
+ onMessageNewHandler,
87
+ onRemovedFromChannel,
88
+ setChannels,
89
+ sort,
90
+ // TODO: implement
91
+ // customHandleChannelListShape
92
+ });
93
+ useChannelListShape(customHandler ?? defaultHandler);
94
+ // TODO: maybe move this too
99
95
  useConnectionRecoveredListener(forceUpdate);
100
- useUserPresenceChangedListener(setChannels);
101
96
  useEffect(() => {
102
97
  const handleEvent = (event) => {
103
98
  if (event.cid === channel?.cid) {
@@ -11,3 +11,4 @@ export * from './useNotificationMessageNewListener';
11
11
  export * from './useNotificationRemovedFromChannelListener';
12
12
  export * from './usePaginatedChannels';
13
13
  export * from './useUserPresenceChangedListener';
14
+ export * from './useChannelMembershipState';
@@ -11,3 +11,4 @@ export * from './useNotificationMessageNewListener';
11
11
  export * from './useNotificationRemovedFromChannelListener';
12
12
  export * from './usePaginatedChannels';
13
13
  export * from './useUserPresenceChangedListener';
14
+ export * from './useChannelMembershipState';
@@ -0,0 +1,60 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ import { Channel, Event, ExtendableGenerics } from 'stream-chat';
3
+ import { ChannelListProps } from '../ChannelList';
4
+ type SetChannels<SCG extends ExtendableGenerics> = Dispatch<SetStateAction<Channel<SCG>[]>>;
5
+ type BaseParameters<SCG extends ExtendableGenerics> = {
6
+ event: Event<SCG>;
7
+ setChannels: SetChannels<SCG>;
8
+ };
9
+ type RepeatedParameters<SCG extends ExtendableGenerics> = {
10
+ customHandler?: (setChannels: BaseParameters<SCG>['setChannels'], event: BaseParameters<SCG>['event']) => void;
11
+ };
12
+ type HandleMessageNewParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG> & {
13
+ allowNewMessagesFromUnfilteredChannels: boolean;
14
+ lockChannelOrder: boolean;
15
+ } & Required<Pick<ChannelListProps<SCG>, 'filters' | 'sort'>>;
16
+ type HandleNotificationMessageNewParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG> & {
17
+ allowNewMessagesFromUnfilteredChannels: boolean;
18
+ lockChannelOrder: boolean;
19
+ } & Required<Pick<ChannelListProps<SCG>, 'filters' | 'sort'>>;
20
+ type HandleNotificationRemovedFromChannelParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
21
+ type HandleNotificationAddedToChannelParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG> & {
22
+ allowNewMessagesFromUnfilteredChannels: boolean;
23
+ lockChannelOrder: boolean;
24
+ } & Required<Pick<ChannelListProps<SCG>, 'sort'>>;
25
+ type HandleMemberUpdatedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & {
26
+ lockChannelOrder: boolean;
27
+ } & Required<Pick<ChannelListProps<SCG>, 'sort'>>;
28
+ type HandleChannelDeletedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
29
+ type HandleChannelHiddenParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
30
+ type HandleChannelVisibleParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
31
+ type HandleChannelTruncatedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
32
+ type HandleChannelUpdatedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
33
+ type HandleUserPresenceChangedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG>;
34
+ export declare const useChannelListShapeDefaults: <SCG extends ExtendableGenerics>() => {
35
+ handleChannelDeleted: (p: HandleChannelDeletedParameters<SCG>) => void;
36
+ handleChannelHidden: (p: HandleChannelHiddenParameters<SCG>) => void;
37
+ handleChannelTruncated: ({ customHandler, event, setChannels }: HandleChannelTruncatedParameters<SCG>) => void;
38
+ handleChannelUpdated: ({ customHandler, event, setChannels }: HandleChannelUpdatedParameters<SCG>) => void;
39
+ handleChannelVisible: ({ customHandler, event, setChannels }: HandleChannelVisibleParameters<SCG>) => Promise<void>;
40
+ handleMemberUpdated: ({ event, lockChannelOrder, setChannels, sort }: HandleMemberUpdatedParameters<SCG>) => void;
41
+ handleMessageNew: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, lockChannelOrder, setChannels, sort, }: HandleMessageNewParameters<SCG>) => void;
42
+ handleNotificationAddedToChannel: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, setChannels, }: HandleNotificationAddedToChannelParameters<SCG>) => Promise<void>;
43
+ handleNotificationMessageNew: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, setChannels, sort, }: HandleNotificationMessageNewParameters<SCG>) => Promise<void>;
44
+ handleNotificationRemovedFromChannel: ({ customHandler, event, setChannels, }: HandleNotificationRemovedFromChannelParameters<SCG>) => void;
45
+ handleUserPresenceChanged: ({ event, setChannels }: HandleUserPresenceChangedParameters<SCG>) => void;
46
+ };
47
+ type UseDefaultHandleChannelListShapeParameters<SCG extends ExtendableGenerics> = Required<Pick<ChannelListProps<SCG>, 'allowNewMessagesFromUnfilteredChannels' | 'lockChannelOrder' | 'filters' | 'sort'>> & Pick<ChannelListProps<SCG>, 'onAddedToChannel' | 'onChannelDeleted' | 'onChannelHidden' | 'onChannelTruncated' | 'onChannelUpdated' | 'onChannelVisible' | 'onMessageNew' | 'onMessageNewHandler' | 'onRemovedFromChannel'> & {
48
+ setChannels: SetChannels<SCG>;
49
+ customHandleChannelListShape?: (data: {
50
+ defaults: ReturnType<typeof useChannelListShapeDefaults>;
51
+ event: Event<SCG>;
52
+ setChannels: SetChannels<SCG>;
53
+ }) => void;
54
+ };
55
+ export declare const usePrepareShapeHandlers: <SCG extends ExtendableGenerics>({ allowNewMessagesFromUnfilteredChannels, customHandleChannelListShape, filters, lockChannelOrder, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, setChannels, sort, }: UseDefaultHandleChannelListShapeParameters<SCG>) => {
56
+ customHandler: ((e: Event<SCG>) => void) | null;
57
+ defaultHandler: (e: Event<SCG>) => void;
58
+ };
59
+ export declare const useChannelListShape: <SCG extends ExtendableGenerics>(handler: (e: Event<SCG>) => void) => void;
60
+ export {};
@@ -0,0 +1,344 @@
1
+ // const defaults = useChannelListShapeDefaults();
2
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
3
+ import uniqBy from 'lodash.uniqby';
4
+ import { findLastPinnedChannelIndex, isChannelArchived, isChannelPinned, moveChannelUpwards, shouldConsiderArchivedChannels, shouldConsiderPinnedChannels, } from '../utils';
5
+ import { useChatContext } from '../../../context';
6
+ import { getChannel } from '../../../utils';
7
+ const shared = ({ customHandler, event, setChannels, }) => {
8
+ if (typeof customHandler === 'function') {
9
+ return customHandler(setChannels, event);
10
+ }
11
+ setChannels((channels) => {
12
+ const channelIndex = channels.findIndex((channel) => channel.cid === event.cid);
13
+ if (channelIndex < 0)
14
+ return channels;
15
+ channels.splice(channelIndex, 1);
16
+ return [...channels];
17
+ });
18
+ };
19
+ export const useChannelListShapeDefaults = () => {
20
+ const { client } = useChatContext();
21
+ const handleMessageNew = useCallback(({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, lockChannelOrder, setChannels, sort, }) => {
22
+ if (typeof customHandler === 'function') {
23
+ return customHandler(setChannels, event);
24
+ }
25
+ setChannels((channels) => {
26
+ const targetChannelIndex = channels.findIndex((channel) => channel.cid === event.cid);
27
+ const targetChannelExistsWithinList = targetChannelIndex >= 0;
28
+ const targetChannel = channels[targetChannelIndex];
29
+ const isTargetChannelPinned = isChannelPinned(targetChannel);
30
+ const isTargetChannelArchived = isChannelArchived(targetChannel);
31
+ const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
32
+ const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
33
+ if (
34
+ // target channel is archived
35
+ (isTargetChannelArchived && considerArchivedChannels) ||
36
+ // target channel is pinned
37
+ (isTargetChannelPinned && considerPinnedChannels) ||
38
+ // list order is locked
39
+ lockChannelOrder ||
40
+ // target channel is not within the loaded list and loading from cache is disallowed
41
+ (!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
+ });
56
+ }
57
+ return channels;
58
+ });
59
+ }, [client]);
60
+ const handleNotificationMessageNew = useCallback(async ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, setChannels, sort, }) => {
61
+ if (typeof customHandler === 'function') {
62
+ return customHandler(setChannels, event);
63
+ }
64
+ if (!event.channel) {
65
+ return;
66
+ }
67
+ const channel = await getChannel({
68
+ client,
69
+ id: event.channel.id,
70
+ type: event.channel.type,
71
+ });
72
+ const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
73
+ if (isChannelArchived(channel) && considerArchivedChannels) {
74
+ return;
75
+ }
76
+ if (!allowNewMessagesFromUnfilteredChannels) {
77
+ return;
78
+ }
79
+ setChannels((channels) => moveChannelUpwards({
80
+ channels,
81
+ channelToMove: channel,
82
+ channelToMoveIndexWithinChannels: -1,
83
+ sort,
84
+ }));
85
+ }, [client]);
86
+ const handleNotificationAddedToChannel = useCallback(async ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, setChannels, }) => {
87
+ if (typeof customHandler === 'function') {
88
+ return customHandler(setChannels, event);
89
+ }
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'));
104
+ }
105
+ }, [client]);
106
+ const handleNotificationRemovedFromChannel = useCallback(({ customHandler, event, setChannels, }) => {
107
+ if (typeof customHandler === 'function') {
108
+ return customHandler(setChannels, event);
109
+ }
110
+ setChannels((channels) => channels.filter((channel) => channel.cid !== event.channel?.cid));
111
+ }, []);
112
+ const handleMemberUpdated = useCallback(({ event, lockChannelOrder, setChannels, sort }) => {
113
+ if (!event.member?.user || event.member.user.id !== client.userID || !event.channel_type) {
114
+ return;
115
+ }
116
+ const member = event.member;
117
+ const channelType = event.channel_type;
118
+ const channelId = event.channel_id;
119
+ 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;
122
+ setChannels((currentChannels) => {
123
+ const targetChannel = client.channel(channelType, channelId);
124
+ // assumes that channel instances are not changing
125
+ const targetChannelIndex = currentChannels.indexOf(targetChannel);
126
+ const targetChannelExistsWithinList = targetChannelIndex >= 0;
127
+ // handle pinning
128
+ if (!considerPinnedChannels || lockChannelOrder)
129
+ return currentChannels;
130
+ const newChannels = [...currentChannels];
131
+ if (targetChannelExistsWithinList) {
132
+ newChannels.splice(targetChannelIndex, 1);
133
+ }
134
+ // handle archiving (remove channel)
135
+ if (typeof member.archived_at === 'string') {
136
+ return newChannels;
137
+ }
138
+ let lastPinnedChannelIndex = null;
139
+ // calculate last pinned channel index only if `pinned_at` sort is set to
140
+ // ascending order or if it's in descending order while the pin is being removed, otherwise
141
+ // we move to the top (index 0)
142
+ if (pinnedAtSort === 1 || (pinnedAtSort === -1 && !member.pinned_at)) {
143
+ lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels });
144
+ }
145
+ const newTargetChannelIndex = typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0;
146
+ // skip re-render if the position of the channel does not change
147
+ if (currentChannels[newTargetChannelIndex] === targetChannel) {
148
+ return currentChannels;
149
+ }
150
+ newChannels.splice(newTargetChannelIndex, 0, targetChannel);
151
+ return newChannels;
152
+ });
153
+ }, [client]);
154
+ const handleChannelDeleted = useCallback((p) => shared(p), []);
155
+ const handleChannelHidden = useCallback((p) => shared(p), []);
156
+ const handleChannelVisible = useCallback(async ({ customHandler, event, setChannels }) => {
157
+ if (typeof customHandler === 'function') {
158
+ return customHandler(setChannels, event);
159
+ }
160
+ if (event.type && event.channel_type && event.channel_id) {
161
+ const channel = await getChannel({
162
+ client,
163
+ id: event.channel_id,
164
+ type: event.channel_type,
165
+ });
166
+ setChannels((channels) => uniqBy([channel, ...channels], 'cid'));
167
+ }
168
+ }, [client]);
169
+ const handleChannelTruncated = useCallback(({ customHandler, event, setChannels }) => {
170
+ if (typeof customHandler === 'function') {
171
+ return customHandler(setChannels, event);
172
+ }
173
+ // TODO: not sure whether this is needed
174
+ setChannels((channels) => [...channels]);
175
+ // if (forceUpdate) {
176
+ // forceUpdate();
177
+ // }
178
+ }, []);
179
+ const handleChannelUpdated = useCallback(({ customHandler, event, setChannels }) => {
180
+ if (typeof customHandler === 'function') {
181
+ return customHandler(setChannels, event);
182
+ }
183
+ setChannels((channels) => {
184
+ const channelIndex = channels.findIndex((channel) => channel.cid === event.channel?.cid);
185
+ if (channelIndex > -1 && event.channel) {
186
+ const newChannels = channels;
187
+ newChannels[channelIndex].data = {
188
+ ...event.channel,
189
+ hidden: event.channel?.hidden ?? newChannels[channelIndex].data?.hidden,
190
+ own_capabilities: event.channel?.own_capabilities ?? newChannels[channelIndex].data?.own_capabilities,
191
+ };
192
+ return [...newChannels];
193
+ }
194
+ return channels;
195
+ });
196
+ // if (forceUpdate) {
197
+ // forceUpdate();
198
+ // }
199
+ }, []);
200
+ const handleUserPresenceChanged = useCallback(({ event, setChannels }) => {
201
+ setChannels((channels) => {
202
+ const newChannels = channels.map((channel) => {
203
+ if (!event.user?.id || !channel.state.members[event.user.id]) {
204
+ return channel;
205
+ }
206
+ // FIXME: oh no...
207
+ const newChannel = channel;
208
+ newChannel.state.members[event.user.id].user = event.user;
209
+ return newChannel;
210
+ });
211
+ return newChannels;
212
+ });
213
+ }, []);
214
+ return useMemo(() => ({
215
+ handleChannelDeleted,
216
+ handleChannelHidden,
217
+ handleChannelTruncated,
218
+ handleChannelUpdated,
219
+ handleChannelVisible,
220
+ handleMemberUpdated,
221
+ handleMessageNew,
222
+ handleNotificationAddedToChannel,
223
+ handleNotificationMessageNew,
224
+ handleNotificationRemovedFromChannel,
225
+ handleUserPresenceChanged,
226
+ }), [
227
+ handleChannelDeleted,
228
+ handleChannelHidden,
229
+ handleChannelTruncated,
230
+ handleChannelUpdated,
231
+ handleChannelVisible,
232
+ handleMemberUpdated,
233
+ handleMessageNew,
234
+ handleNotificationAddedToChannel,
235
+ handleNotificationMessageNew,
236
+ handleNotificationRemovedFromChannel,
237
+ handleUserPresenceChanged,
238
+ ]);
239
+ };
240
+ export const usePrepareShapeHandlers = ({ allowNewMessagesFromUnfilteredChannels, customHandleChannelListShape, filters, lockChannelOrder, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, setChannels, sort, }) => {
241
+ const defaults = useChannelListShapeDefaults();
242
+ const defaultHandleChannelListShapeRef = useRef();
243
+ const customHandleChannelListShapeRef = useRef();
244
+ customHandleChannelListShapeRef.current = (event) => {
245
+ // @ts-expect-error can't use ReturnType<typeof useChannelListShapeDefaults<SCG>> until we upgrade prettier to at least v2.7.0
246
+ customHandleChannelListShape?.({ defaults, event, setChannels });
247
+ };
248
+ defaultHandleChannelListShapeRef.current = (event) => {
249
+ switch (event.type) {
250
+ case 'message.new':
251
+ defaults.handleMessageNew({
252
+ allowNewMessagesFromUnfilteredChannels,
253
+ customHandler: onMessageNewHandler,
254
+ event,
255
+ filters,
256
+ lockChannelOrder,
257
+ setChannels,
258
+ sort,
259
+ });
260
+ break;
261
+ case 'notification.message_new':
262
+ defaults.handleNotificationMessageNew({
263
+ allowNewMessagesFromUnfilteredChannels,
264
+ customHandler: onMessageNew,
265
+ event,
266
+ filters,
267
+ lockChannelOrder,
268
+ setChannels,
269
+ sort,
270
+ });
271
+ break;
272
+ case 'notification.added_to_channel':
273
+ defaults.handleNotificationAddedToChannel({
274
+ allowNewMessagesFromUnfilteredChannels,
275
+ customHandler: onAddedToChannel,
276
+ event,
277
+ lockChannelOrder,
278
+ setChannels,
279
+ sort,
280
+ });
281
+ break;
282
+ case 'notification.removed_from_channel':
283
+ defaults.handleNotificationRemovedFromChannel({
284
+ customHandler: onRemovedFromChannel,
285
+ event,
286
+ setChannels,
287
+ });
288
+ break;
289
+ case 'channel.deleted':
290
+ defaults.handleChannelDeleted({
291
+ customHandler: onChannelDeleted,
292
+ event,
293
+ setChannels,
294
+ });
295
+ break;
296
+ case 'channel.hidden':
297
+ defaults.handleChannelHidden({ customHandler: onChannelHidden, event, setChannels });
298
+ break;
299
+ case 'channel.visible':
300
+ defaults.handleChannelVisible({ customHandler: onChannelVisible, event, setChannels });
301
+ break;
302
+ case 'channel.truncated':
303
+ defaults.handleChannelTruncated({ customHandler: onChannelTruncated, event, setChannels });
304
+ break;
305
+ case 'channel.updated':
306
+ defaults.handleChannelUpdated({ customHandler: onChannelUpdated, event, setChannels });
307
+ break;
308
+ case 'user.presence.changed':
309
+ defaults.handleUserPresenceChanged({ event, setChannels });
310
+ break;
311
+ case 'member.updated':
312
+ defaults.handleMemberUpdated({
313
+ event,
314
+ lockChannelOrder,
315
+ setChannels,
316
+ sort,
317
+ });
318
+ break;
319
+ default:
320
+ break;
321
+ }
322
+ };
323
+ const defaultFn = useCallback((e) => {
324
+ defaultHandleChannelListShapeRef.current?.(e);
325
+ }, []);
326
+ const customFn = useMemo(() => {
327
+ if (!customHandleChannelListShape)
328
+ return null;
329
+ return (e) => {
330
+ customHandleChannelListShapeRef.current?.(e);
331
+ };
332
+ }, [customHandleChannelListShape]);
333
+ return {
334
+ customHandler: customFn,
335
+ defaultHandler: defaultFn,
336
+ };
337
+ };
338
+ export const useChannelListShape = (handler) => {
339
+ const { client } = useChatContext();
340
+ useEffect(() => {
341
+ const subscription = client.on('all', handler);
342
+ return subscription.unsubscribe;
343
+ }, [client, handler]);
344
+ };
@@ -0,0 +1,2 @@
1
+ import type { Channel, ExtendableGenerics } from 'stream-chat';
2
+ export declare const useChannelMembershipState: <SCG extends ExtendableGenerics>(channel?: Channel<SCG>) => import("stream-chat").ChannelMemberResponse<SCG>;
@@ -0,0 +1,15 @@
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,10 +1,46 @@
1
- import type { Channel } from 'stream-chat';
1
+ import type { Channel, ChannelSort, ExtendableGenerics } from 'stream-chat';
2
2
  import type { DefaultStreamChatGenerics } from '../../types/types';
3
+ import type { ChannelListProps } from './ChannelList';
3
4
  export declare const MAX_QUERY_CHANNELS_LIMIT = 30;
4
- type MoveChannelUpParams<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
5
- channels: Array<Channel<StreamChatGenerics>>;
5
+ type MoveChannelUpParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
6
+ channels: Array<Channel<SCG>>;
6
7
  cid: string;
7
- activeChannel?: Channel<StreamChatGenerics>;
8
+ activeChannel?: Channel<SCG>;
8
9
  };
9
- export declare const moveChannelUp: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ activeChannel, channels, cid, }: MoveChannelUpParams<StreamChatGenerics>) => Channel<StreamChatGenerics>[];
10
+ /**
11
+ * @deprecated
12
+ */
13
+ export declare const moveChannelUp: <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ activeChannel, channels, cid, }: MoveChannelUpParams<SCG>) => Channel<SCG>[];
14
+ /**
15
+ * Expects channel array sorted by `{ pinned_at: -1 }`.
16
+ *
17
+ * TODO: add support for the `{ pinned_at: 1 }`
18
+ */
19
+ export declare function findLastPinnedChannelIndex<SCG extends ExtendableGenerics>({ channels, }: {
20
+ channels: Channel<SCG>[];
21
+ }): number | null;
22
+ type MoveChannelUpwardsParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
23
+ channels: Array<Channel<SCG>>;
24
+ channelToMove: Channel<SCG>;
25
+ sort: ChannelSort<SCG>;
26
+ /**
27
+ * If the index of the channel within `channels` list which is being moved upwards
28
+ * (`channelToMove`) is known, you can supply it to skip extra calculation.
29
+ */
30
+ channelToMoveIndexWithinChannels?: number;
31
+ };
32
+ /**
33
+ * This function should not be used to move pinned already channels.
34
+ */
35
+ export declare const moveChannelUpwards: <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ channels, channelToMove, channelToMoveIndexWithinChannels, sort, }: MoveChannelUpwardsParams<SCG>) => Channel<SCG>[];
36
+ /**
37
+ * Returns true only if `{ pinned_at: -1 }` or `{ pinned_at: 1 }` option is first within the `sort` array.
38
+ */
39
+ export declare const shouldConsiderPinnedChannels: <SCG extends ExtendableGenerics>(sort: ChannelListProps<SCG>['sort']) => boolean;
40
+ /**
41
+ * Returns `true` only if `archived` property is set to `false` within `filters`.
42
+ */
43
+ export declare const shouldConsiderArchivedChannels: <SCG extends ExtendableGenerics>(filters: ChannelListProps<SCG>['filters']) => boolean;
44
+ export declare const isChannelPinned: <SCG extends ExtendableGenerics>(channel: Channel<SCG>) => boolean;
45
+ export declare const isChannelArchived: <SCG extends ExtendableGenerics>(channel: Channel<SCG>) => boolean;
10
46
  export {};