@linktr.ee/messaging-react 3.1.3 → 3.1.4-rc-1780636753
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/README.md +18 -1
- package/dist/{Card-DvoK42pX.cjs → Card-1U2tLPcp.cjs} +2 -2
- package/dist/{Card-DvoK42pX.cjs.map → Card-1U2tLPcp.cjs.map} +1 -1
- package/dist/{Card-RHd97_iq.js → Card-B9atg4sP.js} +2 -2
- package/dist/{Card-RHd97_iq.js.map → Card-B9atg4sP.js.map} +1 -1
- package/dist/{Card-CqeFyk7l.cjs → Card-BkgsPkp4.cjs} +2 -2
- package/dist/{Card-CqeFyk7l.cjs.map → Card-BkgsPkp4.cjs.map} +1 -1
- package/dist/{Card-Ctub3AU9.js → Card-BwFdJXYm.js} +2 -2
- package/dist/{Card-Ctub3AU9.js.map → Card-BwFdJXYm.js.map} +1 -1
- package/dist/{Card-BHF-XfHG.cjs → Card-D_XOj1eE.cjs} +2 -2
- package/dist/{Card-BHF-XfHG.cjs.map → Card-D_XOj1eE.cjs.map} +1 -1
- package/dist/{Card-VEde2Hfe.js → Card-jyXjZZ0u.js} +3 -3
- package/dist/{Card-VEde2Hfe.js.map → Card-jyXjZZ0u.js.map} +1 -1
- package/dist/{LockedThumbnail-BADzjHNM.js → LockedThumbnail-Dwt_goCX.js} +2 -2
- package/dist/{LockedThumbnail-BADzjHNM.js.map → LockedThumbnail-Dwt_goCX.js.map} +1 -1
- package/dist/{LockedThumbnail-BPMP5yZP.cjs → LockedThumbnail-oxtdpgut.cjs} +2 -2
- package/dist/{LockedThumbnail-BPMP5yZP.cjs.map → LockedThumbnail-oxtdpgut.cjs.map} +1 -1
- package/dist/{index-CnOvDQIp.js → index-CO975B6P.js} +908 -868
- package/dist/{index-CnOvDQIp.js.map → index-CO975B6P.js.map} +1 -1
- package/dist/index-D4Dse1Lu.cjs +2 -0
- package/dist/{index-BQf4WkPC.cjs.map → index-D4Dse1Lu.cjs.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +38 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/ChannelList/index.test.tsx +151 -3
- package/src/components/ChannelList/index.tsx +72 -4
- package/src/index.ts +1 -0
- package/src/types.ts +38 -0
- package/dist/index-BQf4WkPC.cjs +0 -2
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-D4Dse1Lu.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -95,6 +95,36 @@ export declare const ChannelEmptyState: default_2.FC;
|
|
|
95
95
|
*/
|
|
96
96
|
export declare const ChannelList: default_2.NamedExoticComponent<ChannelListProps>;
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Derived state reported by the mounted ChannelList instance.
|
|
100
|
+
*
|
|
101
|
+
* This reflects the currently loaded list data only. It does not guarantee
|
|
102
|
+
* complete server-side truth for channels outside the mounted list's current
|
|
103
|
+
* query window or cache.
|
|
104
|
+
*/
|
|
105
|
+
export declare interface ChannelListDerivedState {
|
|
106
|
+
/**
|
|
107
|
+
* Whether the mounted list has completed its first channel-resolution pass.
|
|
108
|
+
*/
|
|
109
|
+
isInitialLoadSettled: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Visible channels after `channelRenderFilterFn` is applied.
|
|
112
|
+
*/
|
|
113
|
+
channels: Channel[];
|
|
114
|
+
/**
|
|
115
|
+
* Raw channels received from Stream before client-side filtering.
|
|
116
|
+
*/
|
|
117
|
+
rawChannels: Channel[];
|
|
118
|
+
/**
|
|
119
|
+
* Convenience flag for empty-state consumers. Mirrors `channels.length > 0`.
|
|
120
|
+
*/
|
|
121
|
+
hasChannels: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Number of visible channels with at least one unread message.
|
|
124
|
+
*/
|
|
125
|
+
unreadCount: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
98
128
|
/**
|
|
99
129
|
* ChannelList component props
|
|
100
130
|
*/
|
|
@@ -137,6 +167,14 @@ export declare interface ChannelListProps {
|
|
|
137
167
|
* Falls back to message.text when no matching translation exists.
|
|
138
168
|
*/
|
|
139
169
|
viewerLanguage?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Reports derived state from the mounted Stream ChannelList instance.
|
|
172
|
+
*
|
|
173
|
+
* This callback is driven by currently loaded list data only. Use it for
|
|
174
|
+
* cache/list-derived UI state such as empty-state confirmation or visible
|
|
175
|
+
* unread badges; do not assume it represents complete server-side truth.
|
|
176
|
+
*/
|
|
177
|
+
onStateChange?: (state: ChannelListDerivedState) => void;
|
|
140
178
|
}
|
|
141
179
|
|
|
142
180
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, b as t, C as i, c as n, d as o, e as m, F as g, f as l, L as r, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as v, o as A, p as C, q as F, s as k, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B } from "./index-
|
|
1
|
+
import { a as e, b as t, C as i, c as n, d as o, e as m, F as g, f as l, L as r, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as v, o as A, p as C, q as F, s as k, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B } from "./index-CO975B6P.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { act, waitFor } from '@testing-library/react'
|
|
1
2
|
import React from 'react'
|
|
2
3
|
import type { Channel } from 'stream-chat'
|
|
3
4
|
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
@@ -56,7 +57,7 @@ describe('ChannelList', () => {
|
|
|
56
57
|
})
|
|
57
58
|
})
|
|
58
59
|
|
|
59
|
-
it('wraps channelRenderFilterFn to restore pending messages and delegates to consumer filter', () => {
|
|
60
|
+
it('wraps channelRenderFilterFn to restore pending messages and delegates to consumer filter', async () => {
|
|
60
61
|
const filterFn = vi.fn((channels: Channel[]) => channels)
|
|
61
62
|
|
|
62
63
|
renderWithProviders(
|
|
@@ -77,9 +78,156 @@ describe('ChannelList', () => {
|
|
|
77
78
|
|
|
78
79
|
// When the wrapper is called, it should delegate to the consumer's filter
|
|
79
80
|
const mockChannels = [
|
|
80
|
-
{
|
|
81
|
+
{
|
|
82
|
+
cid: 'ch-1',
|
|
83
|
+
countUnread: () => 0,
|
|
84
|
+
state: { pending_messages: [], addMessageSorted: vi.fn() },
|
|
85
|
+
},
|
|
81
86
|
]
|
|
82
|
-
|
|
87
|
+
await act(async () => {
|
|
88
|
+
streamProps.channelRenderFilterFn!(mockChannels)
|
|
89
|
+
await Promise.resolve()
|
|
90
|
+
})
|
|
83
91
|
expect(filterFn).toHaveBeenCalledWith(mockChannels)
|
|
84
92
|
})
|
|
93
|
+
|
|
94
|
+
it('notifies hosts with derived visible-channel state', async () => {
|
|
95
|
+
const onStateChange = vi.fn()
|
|
96
|
+
const visibleChannel = {
|
|
97
|
+
cid: 'ch-visible',
|
|
98
|
+
countUnread: () => 2,
|
|
99
|
+
state: { pending_messages: [], addMessageSorted: vi.fn() },
|
|
100
|
+
}
|
|
101
|
+
const hiddenChannel = {
|
|
102
|
+
cid: 'ch-hidden',
|
|
103
|
+
countUnread: () => 7,
|
|
104
|
+
state: { pending_messages: [], addMessageSorted: vi.fn() },
|
|
105
|
+
}
|
|
106
|
+
const filterFn = vi.fn((channels: Channel[]) => channels.slice(0, 1))
|
|
107
|
+
|
|
108
|
+
renderWithProviders(
|
|
109
|
+
React.createElement(ChannelList, {
|
|
110
|
+
...defaultProps,
|
|
111
|
+
channelRenderFilterFn: filterFn,
|
|
112
|
+
onStateChange,
|
|
113
|
+
} as never)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const streamProps = streamChannelListMock.mock.calls[0][0] as {
|
|
117
|
+
channelRenderFilterFn?: (channels: Channel[]) => Channel[]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await act(async () => {
|
|
121
|
+
streamProps.channelRenderFilterFn!([
|
|
122
|
+
visibleChannel as unknown as Channel,
|
|
123
|
+
hiddenChannel as unknown as Channel,
|
|
124
|
+
])
|
|
125
|
+
await Promise.resolve()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(onStateChange).toHaveBeenLastCalledWith({
|
|
130
|
+
isInitialLoadSettled: true,
|
|
131
|
+
channels: [visibleChannel],
|
|
132
|
+
rawChannels: [visibleChannel, hiddenChannel],
|
|
133
|
+
hasChannels: true,
|
|
134
|
+
unreadCount: 1,
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('reports a settled empty state when no visible channels remain', async () => {
|
|
140
|
+
const onStateChange = vi.fn()
|
|
141
|
+
const filterFn = vi.fn(() => [])
|
|
142
|
+
const hiddenChannel = {
|
|
143
|
+
cid: 'ch-hidden',
|
|
144
|
+
countUnread: () => 3,
|
|
145
|
+
state: { pending_messages: [], addMessageSorted: vi.fn() },
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
renderWithProviders(
|
|
149
|
+
React.createElement(ChannelList, {
|
|
150
|
+
...defaultProps,
|
|
151
|
+
channelRenderFilterFn: filterFn,
|
|
152
|
+
onStateChange,
|
|
153
|
+
} as never)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
const streamProps = streamChannelListMock.mock.calls[0][0] as {
|
|
157
|
+
channelRenderFilterFn?: (channels: Channel[]) => Channel[]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await act(async () => {
|
|
161
|
+
streamProps.channelRenderFilterFn!([hiddenChannel as unknown as Channel])
|
|
162
|
+
await Promise.resolve()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
await waitFor(() => {
|
|
166
|
+
expect(onStateChange).toHaveBeenLastCalledWith({
|
|
167
|
+
isInitialLoadSettled: true,
|
|
168
|
+
channels: [],
|
|
169
|
+
rawChannels: [hiddenChannel],
|
|
170
|
+
hasChannels: false,
|
|
171
|
+
unreadCount: 0,
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('resets derived state when filters change before the new list resolves', async () => {
|
|
177
|
+
const onStateChange = vi.fn()
|
|
178
|
+
const firstChannel = {
|
|
179
|
+
cid: 'ch-first',
|
|
180
|
+
countUnread: () => 1,
|
|
181
|
+
state: { pending_messages: [], addMessageSorted: vi.fn() },
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const { rerender } = renderWithProviders(
|
|
185
|
+
React.createElement(ChannelList, {
|
|
186
|
+
...defaultProps,
|
|
187
|
+
onStateChange,
|
|
188
|
+
} as never)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
let streamProps = streamChannelListMock.mock.calls.at(-1)?.[0] as {
|
|
192
|
+
channelRenderFilterFn?: (channels: Channel[]) => Channel[]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await act(async () => {
|
|
196
|
+
streamProps.channelRenderFilterFn!([firstChannel as unknown as Channel])
|
|
197
|
+
await Promise.resolve()
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
await waitFor(() => {
|
|
201
|
+
expect(onStateChange).toHaveBeenLastCalledWith({
|
|
202
|
+
isInitialLoadSettled: true,
|
|
203
|
+
channels: [firstChannel],
|
|
204
|
+
rawChannels: [firstChannel],
|
|
205
|
+
hasChannels: true,
|
|
206
|
+
unreadCount: 1,
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
rerender(
|
|
211
|
+
React.createElement(ChannelList, {
|
|
212
|
+
...defaultProps,
|
|
213
|
+
filters: { type: 'messaging', frozen: true },
|
|
214
|
+
onStateChange,
|
|
215
|
+
} as never)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
streamProps = streamChannelListMock.mock.calls.at(-1)?.[0] as {
|
|
219
|
+
channelRenderFilterFn?: (channels: Channel[]) => Channel[]
|
|
220
|
+
}
|
|
221
|
+
expect(typeof streamProps.channelRenderFilterFn).toBe('function')
|
|
222
|
+
|
|
223
|
+
await waitFor(() => {
|
|
224
|
+
expect(onStateChange).toHaveBeenLastCalledWith({
|
|
225
|
+
isInitialLoadSettled: false,
|
|
226
|
+
channels: [],
|
|
227
|
+
rawChannels: [],
|
|
228
|
+
hasChannels: false,
|
|
229
|
+
unreadCount: 0,
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
})
|
|
85
233
|
})
|
|
@@ -5,13 +5,21 @@ import { ChannelList as StreamChannelList } from 'stream-chat-react'
|
|
|
5
5
|
|
|
6
6
|
import { restorePendingMessages } from '../../hooks/useRestorePendingMessages'
|
|
7
7
|
import { useMessagingContext } from '../../providers/MessagingProvider'
|
|
8
|
-
import type { ChannelListProps } from '../../types'
|
|
8
|
+
import type { ChannelListDerivedState, ChannelListProps } from '../../types'
|
|
9
9
|
|
|
10
10
|
import { ChannelListProvider } from './ChannelListContext'
|
|
11
11
|
import CustomChannelPreview from './CustomChannelPreview'
|
|
12
12
|
|
|
13
13
|
const DEFAULT_SORT = { last_message_at: -1 } as const
|
|
14
14
|
|
|
15
|
+
const EMPTY_DERIVED_STATE: ChannelListDerivedState = {
|
|
16
|
+
isInitialLoadSettled: false,
|
|
17
|
+
channels: [],
|
|
18
|
+
rawChannels: [],
|
|
19
|
+
hasChannels: false,
|
|
20
|
+
unreadCount: 0,
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
/**
|
|
16
24
|
* Channel list component with customizable header and actions
|
|
17
25
|
*/
|
|
@@ -27,6 +35,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
27
35
|
customEmptyStateIndicator,
|
|
28
36
|
renderMessagePreview,
|
|
29
37
|
viewerLanguage,
|
|
38
|
+
onStateChange,
|
|
30
39
|
}) => {
|
|
31
40
|
// Track renders
|
|
32
41
|
const renderCountRef = React.useRef(0)
|
|
@@ -34,6 +43,42 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
34
43
|
|
|
35
44
|
// Get debug flag from context
|
|
36
45
|
const { debug = false } = useMessagingContext()
|
|
46
|
+
const [derivedState, setDerivedState] = React.useState<ChannelListDerivedState>(
|
|
47
|
+
EMPTY_DERIVED_STATE
|
|
48
|
+
)
|
|
49
|
+
const listIdentityKey = React.useMemo(
|
|
50
|
+
() => `${JSON.stringify(filters)}:${JSON.stringify(sort)}`,
|
|
51
|
+
[filters, sort]
|
|
52
|
+
)
|
|
53
|
+
const pendingDerivedStateRef = React.useRef<ChannelListDerivedState>(
|
|
54
|
+
EMPTY_DERIVED_STATE
|
|
55
|
+
)
|
|
56
|
+
const publishScheduledRef = React.useRef(false)
|
|
57
|
+
const listVersionRef = React.useRef(0)
|
|
58
|
+
|
|
59
|
+
const publishDerivedState = React.useCallback(
|
|
60
|
+
(nextState: ChannelListDerivedState) => {
|
|
61
|
+
const publishVersion = listVersionRef.current
|
|
62
|
+
pendingDerivedStateRef.current = nextState
|
|
63
|
+
if (publishScheduledRef.current) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
publishScheduledRef.current = true
|
|
68
|
+
queueMicrotask(() => {
|
|
69
|
+
if (publishVersion !== listVersionRef.current) {
|
|
70
|
+
publishScheduledRef.current = false
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
publishScheduledRef.current = false
|
|
74
|
+
setDerivedState((currentState) => {
|
|
75
|
+
const pendingState = pendingDerivedStateRef.current
|
|
76
|
+
return currentState === pendingState ? currentState : pendingState
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
},
|
|
80
|
+
[]
|
|
81
|
+
)
|
|
37
82
|
|
|
38
83
|
// Wrap channelRenderFilterFn to restore pending messages for all channels
|
|
39
84
|
// as soon as they are loaded, without waiting for the user to click into each one.
|
|
@@ -42,11 +87,23 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
42
87
|
for (const channel of channels) {
|
|
43
88
|
restorePendingMessages(channel)
|
|
44
89
|
}
|
|
45
|
-
|
|
90
|
+
|
|
91
|
+
const visibleChannels = channelRenderFilterFn
|
|
46
92
|
? channelRenderFilterFn(channels)
|
|
47
93
|
: channels
|
|
94
|
+
|
|
95
|
+
publishDerivedState({
|
|
96
|
+
isInitialLoadSettled: true,
|
|
97
|
+
channels: visibleChannels,
|
|
98
|
+
rawChannels: channels,
|
|
99
|
+
hasChannels: visibleChannels.length > 0,
|
|
100
|
+
unreadCount: visibleChannels.filter((channel) => channel.countUnread() > 0)
|
|
101
|
+
.length,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return visibleChannels
|
|
48
105
|
},
|
|
49
|
-
[channelRenderFilterFn]
|
|
106
|
+
[channelRenderFilterFn, publishDerivedState]
|
|
50
107
|
)
|
|
51
108
|
|
|
52
109
|
if (debug) {
|
|
@@ -74,6 +131,17 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
74
131
|
]
|
|
75
132
|
)
|
|
76
133
|
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
listVersionRef.current += 1
|
|
136
|
+
pendingDerivedStateRef.current = EMPTY_DERIVED_STATE
|
|
137
|
+
publishScheduledRef.current = false
|
|
138
|
+
setDerivedState(EMPTY_DERIVED_STATE)
|
|
139
|
+
}, [listIdentityKey])
|
|
140
|
+
|
|
141
|
+
React.useEffect(() => {
|
|
142
|
+
onStateChange?.(derivedState)
|
|
143
|
+
}, [derivedState, onStateChange])
|
|
144
|
+
|
|
77
145
|
return (
|
|
78
146
|
<div
|
|
79
147
|
className={classNames(
|
|
@@ -85,7 +153,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
85
153
|
<div className="flex-1 overflow-hidden min-w-0">
|
|
86
154
|
<ChannelListProvider value={contextValue}>
|
|
87
155
|
<StreamChannelList
|
|
88
|
-
key={
|
|
156
|
+
key={listIdentityKey}
|
|
89
157
|
filters={filters}
|
|
90
158
|
sort={sort}
|
|
91
159
|
options={{ limit: 30 }}
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -52,6 +52,36 @@ export interface MessagingCapabilities {
|
|
|
52
52
|
showDeleteConversation?: boolean
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Derived state reported by the mounted ChannelList instance.
|
|
57
|
+
*
|
|
58
|
+
* This reflects the currently loaded list data only. It does not guarantee
|
|
59
|
+
* complete server-side truth for channels outside the mounted list's current
|
|
60
|
+
* query window or cache.
|
|
61
|
+
*/
|
|
62
|
+
export interface ChannelListDerivedState {
|
|
63
|
+
/**
|
|
64
|
+
* Whether the mounted list has completed its first channel-resolution pass.
|
|
65
|
+
*/
|
|
66
|
+
isInitialLoadSettled: boolean
|
|
67
|
+
/**
|
|
68
|
+
* Visible channels after `channelRenderFilterFn` is applied.
|
|
69
|
+
*/
|
|
70
|
+
channels: Channel[]
|
|
71
|
+
/**
|
|
72
|
+
* Raw channels received from Stream before client-side filtering.
|
|
73
|
+
*/
|
|
74
|
+
rawChannels: Channel[]
|
|
75
|
+
/**
|
|
76
|
+
* Convenience flag for empty-state consumers. Mirrors `channels.length > 0`.
|
|
77
|
+
*/
|
|
78
|
+
hasChannels: boolean
|
|
79
|
+
/**
|
|
80
|
+
* Number of visible channels with at least one unread message.
|
|
81
|
+
*/
|
|
82
|
+
unreadCount: number
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
/**
|
|
56
86
|
* ChannelList component props
|
|
57
87
|
*/
|
|
@@ -97,6 +127,14 @@ export interface ChannelListProps {
|
|
|
97
127
|
* Falls back to message.text when no matching translation exists.
|
|
98
128
|
*/
|
|
99
129
|
viewerLanguage?: string
|
|
130
|
+
/**
|
|
131
|
+
* Reports derived state from the mounted Stream ChannelList instance.
|
|
132
|
+
*
|
|
133
|
+
* This callback is driven by currently loaded list data only. Use it for
|
|
134
|
+
* cache/list-derived UI state such as empty-state confirmation or visible
|
|
135
|
+
* unread badges; do not assume it represents complete server-side truth.
|
|
136
|
+
*/
|
|
137
|
+
onStateChange?: (state: ChannelListDerivedState) => void
|
|
100
138
|
}
|
|
101
139
|
|
|
102
140
|
/**
|