stream-chat-react 14.2.0 → 14.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.mjs CHANGED
@@ -22363,6 +22363,7 @@ const defaultReactionOptions = {
22363
22363
  }
22364
22364
  }
22365
22365
  };
22366
+ const getHasExtendedReactions = (reactionOptions) => !Array.isArray(reactionOptions) && typeof reactionOptions.extended !== "undefined" && Object.keys(reactionOptions.extended).length > 0;
22366
22367
  const stableOwnReactions = [];
22367
22368
  const ReactionSelector = (props) => {
22368
22369
  const {
@@ -22408,7 +22409,7 @@ const ReactionSelector = (props) => {
22408
22409
  })
22409
22410
  );
22410
22411
  }, [reactionOptions]);
22411
- const hasExtendedReactions = !Array.isArray(reactionOptions) && reactionOptions.extended && Object.keys(reactionOptions.extended).length > 0;
22412
+ const hasExtendedReactions = getHasExtendedReactions(reactionOptions);
22412
22413
  return /* @__PURE__ */ jsx(
22413
22414
  "div",
22414
22415
  {
@@ -22639,6 +22640,7 @@ const MessageReactionsDetail = ({
22639
22640
  reactionDetailsSort: contextReactionDetailsSort
22640
22641
  } = useMessageContext(MessageReactionsDetail.name);
22641
22642
  const reactionDetailsSort = propReactionDetailsSort ?? contextReactionDetailsSort ?? defaultReactionDetailsSort;
22643
+ const hasExtendedReactions = getHasExtendedReactions(reactionOptions);
22642
22644
  const {
22643
22645
  isLoading: areReactionsLoading,
22644
22646
  reactions: reactionDetails,
@@ -22685,7 +22687,7 @@ const MessageReactionsDetail = ({
22685
22687
  className: "str-chat__message-reactions-detail__reaction-type-list",
22686
22688
  "data-testid": "reaction-type-list",
22687
22689
  children: [
22688
- /* @__PURE__ */ jsx("li", { className: "str-chat__message-reactions-detail__reaction-type-list-item", children: /* @__PURE__ */ jsx(
22690
+ hasExtendedReactions && /* @__PURE__ */ jsx("li", { className: "str-chat__message-reactions-detail__reaction-type-list-item", children: /* @__PURE__ */ jsx(
22689
22691
  "button",
22690
22692
  {
22691
22693
  "aria-label": t("Add reaction"),
@@ -27275,6 +27277,9 @@ const useConnectionRecoveredListener = (forceUpdate) => {
27275
27277
  };
27276
27278
  const RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS = 5e3;
27277
27279
  const MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS = 2e3;
27280
+ const mapPredefinedFilterSortToChannelSort = (sort) => sort.map(({ direction = 1, field }) => ({
27281
+ [field]: direction
27282
+ }));
27278
27283
  const usePaginatedChannels = (client, filters, sort, options, activeChannelHandler, recoveryThrottleIntervalMs = RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS, customQueryChannels) => {
27279
27284
  const { addNotification } = useNotificationApi();
27280
27285
  const {
@@ -27283,6 +27288,10 @@ const usePaginatedChannels = (client, filters, sort, options, activeChannelHandl
27283
27288
  const { t } = useTranslationContext();
27284
27289
  const [channels, setChannels] = useState([]);
27285
27290
  const [hasNextPage, setHasNextPage] = useState(true);
27291
+ const [responseFilters, setResponseFilters] = useState(
27292
+ void 0
27293
+ );
27294
+ const [responseSort, setResponseSort] = useState(void 0);
27286
27295
  const lastRecoveryTimestamp = useRef(void 0);
27287
27296
  const recoveryThrottleInterval = recoveryThrottleIntervalMs < MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS ? MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS : recoveryThrottleIntervalMs ?? RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS;
27288
27297
  const filterString = useMemo(() => JSON.stringify(filters), [filters]);
@@ -27303,6 +27312,8 @@ const usePaginatedChannels = (client, filters, sort, options, activeChannelHandl
27303
27312
  setChannels,
27304
27313
  setHasNextPage
27305
27314
  });
27315
+ setResponseFilters(void 0);
27316
+ setResponseSort(void 0);
27306
27317
  } else {
27307
27318
  const newOptions = {
27308
27319
  offset,
@@ -27311,13 +27322,22 @@ const usePaginatedChannels = (client, filters, sort, options, activeChannelHandl
27311
27322
  const channelQueryResponse = await client.queryChannels(
27312
27323
  filters,
27313
27324
  sort || {},
27314
- newOptions
27325
+ newOptions,
27326
+ { withResponse: true }
27315
27327
  );
27316
- const newChannels = queryType === "reload" ? channelQueryResponse : uniqBy([...channels, ...channelQueryResponse], "cid");
27328
+ const newChannels = queryType === "reload" ? channelQueryResponse.channels : uniqBy([...channels, ...channelQueryResponse.channels], "cid");
27317
27329
  setChannels(newChannels);
27318
- setHasNextPage(channelQueryResponse.length >= (newOptions.limit ?? 1));
27330
+ setHasNextPage(channelQueryResponse.channels.length >= (newOptions.limit ?? 1));
27331
+ const predefinedFilter = channelQueryResponse.predefined_filter;
27332
+ const nextResponseFilters = predefinedFilter ? predefinedFilter.filter : void 0;
27333
+ const nextResponseSort = predefinedFilter?.sort ? mapPredefinedFilterSortToChannelSort(predefinedFilter.sort) : void 0;
27334
+ setResponseFilters(nextResponseFilters);
27335
+ setResponseSort(nextResponseSort);
27319
27336
  if (!offset && activeChannelHandler) {
27320
- activeChannelHandler(newChannels, setChannels);
27337
+ activeChannelHandler(newChannels, setChannels, {
27338
+ filters: nextResponseFilters ?? filters,
27339
+ sort: nextResponseSort ?? sort
27340
+ });
27321
27341
  }
27322
27342
  }
27323
27343
  } catch (error2) {
@@ -27357,8 +27377,12 @@ const usePaginatedChannels = (client, filters, sort, options, activeChannelHandl
27357
27377
  useEffect(() => {
27358
27378
  queryChannels("reload");
27359
27379
  }, [filterString, sortString]);
27380
+ const effectiveFilters = responseFilters ?? filters;
27381
+ const effectiveSort = responseSort ?? sort;
27360
27382
  return {
27361
27383
  channels,
27384
+ effectiveFilters,
27385
+ effectiveSort,
27362
27386
  hasNextPage,
27363
27387
  loadNextPage,
27364
27388
  setChannels
@@ -28504,7 +28528,7 @@ const UnMemoizedChannelList = (props) => {
28504
28528
  searchController.state,
28505
28529
  searchControllerStateSelector
28506
28530
  );
28507
- const activeChannelHandler = async (channels2, setChannels2) => {
28531
+ const activeChannelHandler = async (channels2, setChannels2, effectiveQueryParams) => {
28508
28532
  if (!channels2.length) {
28509
28533
  return;
28510
28534
  }
@@ -28522,7 +28546,7 @@ const UnMemoizedChannelList = (props) => {
28522
28546
  const newChannels = moveChannelUpwards({
28523
28547
  channels: channels2,
28524
28548
  channelToMove: customActiveChannelObject,
28525
- sort
28549
+ sort: effectiveQueryParams.sort
28526
28550
  });
28527
28551
  setChannels2(newChannels);
28528
28552
  return;
@@ -28533,7 +28557,14 @@ const UnMemoizedChannelList = (props) => {
28533
28557
  }
28534
28558
  };
28535
28559
  const forceUpdate = useCallback(() => setChannelUpdateCount((count) => count + 1), []);
28536
- const { channels, hasNextPage, loadNextPage, setChannels } = usePaginatedChannels(
28560
+ const {
28561
+ channels,
28562
+ effectiveFilters,
28563
+ effectiveSort,
28564
+ hasNextPage,
28565
+ loadNextPage,
28566
+ setChannels
28567
+ } = usePaginatedChannels(
28537
28568
  client,
28538
28569
  filters || DEFAULT_FILTERS,
28539
28570
  sort || DEFAULT_SORT,
@@ -28545,7 +28576,11 @@ const UnMemoizedChannelList = (props) => {
28545
28576
  const loadedChannels = channelRenderFilterFn ? channelRenderFilterFn(channels) : channels;
28546
28577
  const { customHandler, defaultHandler } = usePrepareShapeHandlers({
28547
28578
  allowNewMessagesFromUnfilteredChannels,
28548
- filters,
28579
+ // `effectiveFilters`/`effectiveSort` reflect the backend-resolved
28580
+ // `predefined_filter` metadata when `options.predefined_filter` is in use.
28581
+ // For non-predefined queries they fall back to the caller-supplied
28582
+ // `filters`/`sort` props so behavior is unchanged.
28583
+ filters: effectiveFilters,
28549
28584
  lockChannelOrder,
28550
28585
  onAddedToChannel,
28551
28586
  onChannelDeleted,
@@ -28557,7 +28592,7 @@ const UnMemoizedChannelList = (props) => {
28557
28592
  onMessageNewHandler,
28558
28593
  onRemovedFromChannel,
28559
28594
  setChannels,
28560
- sort
28595
+ sort: effectiveSort
28561
28596
  // TODO: implement
28562
28597
  // customHandleChannelListShape
28563
28598
  });
@@ -29989,7 +30024,7 @@ const Notification = forwardRef(
29989
30024
  }
29990
30025
  removeNotification(notification.id);
29991
30026
  };
29992
- const isPersistent = !notification.duration;
30027
+ const isPersistent2 = !notification.duration;
29993
30028
  const severity = notification.severity;
29994
30029
  const livePriority = severity === "error" ? "assertive" : "polite";
29995
30030
  return /* @__PURE__ */ jsxs(
@@ -30035,7 +30070,7 @@ const Notification = forwardRef(
30035
30070
  },
30036
30071
  index
30037
30072
  )) }),
30038
- (showClose || isPersistent) && /* @__PURE__ */ jsx(
30073
+ (showClose || isPersistent2) && /* @__PURE__ */ jsx(
30039
30074
  Button,
30040
30075
  {
30041
30076
  appearance: "ghost",
@@ -30062,6 +30097,7 @@ const ENTER_TRANSLATION = {
30062
30097
  top: { x: "0%", y: "-100%" }
30063
30098
  };
30064
30099
  const EXIT_ANIMATION_MS = 340;
30100
+ const DEFAULT_MIN_DISPLAY_MS = 1e3;
30065
30101
  const isEnterFrom = (value) => value === "bottom" || value === "left" || value === "right" || value === "top";
30066
30102
  const getNotificationEnterFrom = (notification, fallbackEnterFrom) => {
30067
30103
  if (!notification) return fallbackEnterFrom;
@@ -30071,19 +30107,93 @@ const getNotificationEnterFrom = (notification, fallbackEnterFrom) => {
30071
30107
  if (isEnterFrom(originEnterFrom)) return originEnterFrom;
30072
30108
  return fallbackEnterFrom;
30073
30109
  };
30110
+ const isPersistent = (notification) => !notification.duration;
30111
+ const haveSameType = (a, b) => !!a?.type && !!b?.type && a.type === b.type;
30112
+ const pickOldest = (notifications, displayed) => {
30113
+ const excludeId = displayed?.id ?? null;
30114
+ let oldest = null;
30115
+ for (const notification of notifications) {
30116
+ if (notification.id === excludeId) continue;
30117
+ if (!oldest || notification.createdAt < oldest.createdAt) {
30118
+ oldest = notification;
30119
+ }
30120
+ }
30121
+ return oldest;
30122
+ };
30123
+ const pickNewest = (notifications, displayed) => {
30124
+ const excludeId = displayed?.id ?? null;
30125
+ let newest = null;
30126
+ for (const notification of notifications) {
30127
+ if (notification.id === excludeId) continue;
30128
+ if (!newest || notification.createdAt > newest.createdAt) {
30129
+ newest = notification;
30130
+ }
30131
+ }
30132
+ return newest;
30133
+ };
30134
+ const pickNewestPersistent = (notifications, excludeId) => {
30135
+ let newest = null;
30136
+ for (const notification of notifications) {
30137
+ if (notification.id === excludeId) continue;
30138
+ if (!isPersistent(notification)) continue;
30139
+ if (!newest || notification.createdAt > newest.createdAt) {
30140
+ newest = notification;
30141
+ }
30142
+ }
30143
+ return newest;
30144
+ };
30145
+ const pickNewestOfType = (notifications, type, excludeId) => {
30146
+ if (!type) return null;
30147
+ let newest = null;
30148
+ for (const notification of notifications) {
30149
+ if (notification.id === excludeId) continue;
30150
+ if (notification.type !== type) continue;
30151
+ if (!newest || notification.createdAt > newest.createdAt) {
30152
+ newest = notification;
30153
+ }
30154
+ }
30155
+ return newest;
30156
+ };
30157
+ const createDefaultPickNext = (pickFromQueue = pickOldest) => (notifications, displayed) => {
30158
+ if (notifications.length === 0) return null;
30159
+ const newestPersistent = pickNewestPersistent(notifications, null);
30160
+ if (!displayed) {
30161
+ return newestPersistent ?? pickFromQueue(notifications, null);
30162
+ }
30163
+ const displayedInStore = notifications.some(({ id }) => id === displayed.id);
30164
+ if (!displayedInStore) {
30165
+ return newestPersistent ?? pickFromQueue(notifications, null);
30166
+ }
30167
+ if (isPersistent(displayed)) {
30168
+ const newerPersistent = newestPersistent && newestPersistent.id !== displayed.id ? newestPersistent : pickNewestPersistent(notifications, displayed.id);
30169
+ if (newerPersistent && newerPersistent.createdAt > displayed.createdAt) {
30170
+ return newerPersistent;
30171
+ }
30172
+ return displayed;
30173
+ }
30174
+ const sameTypeNewest = pickNewestOfType(notifications, displayed.type, displayed.id);
30175
+ if (sameTypeNewest) return sameTypeNewest;
30176
+ if (newestPersistent) return newestPersistent;
30177
+ return pickFromQueue(notifications, displayed) ?? displayed;
30178
+ };
30179
+ const defaultPickNext = createDefaultPickNext(pickOldest);
30074
30180
  const NotificationList = ({
30075
30181
  className,
30076
30182
  enterFrom = "bottom",
30077
30183
  fallbackPanel,
30078
30184
  filter,
30185
+ minDisplayMs = DEFAULT_MIN_DISPLAY_MS,
30079
30186
  panel,
30187
+ pickNext = defaultPickNext,
30080
30188
  verticalAlignment = "bottom"
30081
30189
  }) => {
30082
30190
  const { Notification: NotificationComponent = Notification } = useComponentContext();
30083
30191
  const { t } = useTranslationContext();
30084
30192
  const { removeNotification, startNotificationTimeout } = useNotificationApi();
30085
30193
  const exitTimeoutRef = useRef(null);
30086
- const latestNotificationRef = useRef(null);
30194
+ const replacementTimeoutRef = useRef(null);
30195
+ const candidateRef = useRef(null);
30196
+ const displayedAtRef = useRef(null);
30087
30197
  const listRef = useRef(null);
30088
30198
  const observedElementRef = useRef(null);
30089
30199
  const startedTimeoutIdsRef = useRef(null);
@@ -30106,7 +30216,6 @@ const NotificationList = ({
30106
30216
  filter: combinedFilter,
30107
30217
  panel
30108
30218
  });
30109
- const nextNotification = notifications[0] ?? null;
30110
30219
  const dismiss = useCallback(
30111
30220
  (id) => {
30112
30221
  startedTimeoutIdsRef.current?.delete(id);
@@ -30122,33 +30231,105 @@ const NotificationList = ({
30122
30231
  }
30123
30232
  });
30124
30233
  }, [notifications]);
30125
- useEffect(() => {
30126
- latestNotificationRef.current = nextNotification;
30127
- }, [nextNotification]);
30234
+ const clearReplacementTimeout = useCallback(() => {
30235
+ if (replacementTimeoutRef.current !== null) {
30236
+ window.clearTimeout(replacementTimeoutRef.current);
30237
+ replacementTimeoutRef.current = null;
30238
+ }
30239
+ }, []);
30128
30240
  useEffect(
30129
30241
  () => () => {
30130
- if (exitTimeoutRef.current) {
30242
+ clearReplacementTimeout();
30243
+ if (exitTimeoutRef.current !== null) {
30131
30244
  window.clearTimeout(exitTimeoutRef.current);
30245
+ exitTimeoutRef.current = null;
30132
30246
  }
30133
30247
  },
30134
- []
30248
+ [clearReplacementTimeout]
30135
30249
  );
30136
30250
  useEffect(() => {
30251
+ candidateRef.current = pickNext(notifications, displayedNotification);
30252
+ }, [displayedNotification, notifications, pickNext]);
30253
+ useEffect(() => {
30254
+ if (transitionState === "exit") return;
30137
30255
  if (!displayedNotification) {
30138
- if (!nextNotification) return;
30139
- setDisplayedNotification(nextNotification);
30140
- setTransitionState("enter");
30256
+ const candidate2 = pickNext(notifications, null);
30257
+ if (candidate2) {
30258
+ displayedAtRef.current = Date.now();
30259
+ setDisplayedNotification(candidate2);
30260
+ setTransitionState("enter");
30261
+ }
30141
30262
  return;
30142
30263
  }
30143
- if (displayedNotification.id === nextNotification?.id) return;
30144
- if (transitionState === "exit") return;
30145
- setTransitionState("exit");
30146
- exitTimeoutRef.current = window.setTimeout(() => {
30147
- setDisplayedNotification(latestNotificationRef.current);
30148
- setTransitionState("enter");
30149
- exitTimeoutRef.current = null;
30150
- }, EXIT_ANIMATION_MS);
30151
- }, [displayedNotification, nextNotification, transitionState]);
30264
+ const candidate = pickNext(notifications, displayedNotification);
30265
+ if (!candidate) {
30266
+ clearReplacementTimeout();
30267
+ setTransitionState("exit");
30268
+ exitTimeoutRef.current = window.setTimeout(() => {
30269
+ exitTimeoutRef.current = null;
30270
+ displayedAtRef.current = null;
30271
+ setDisplayedNotification(null);
30272
+ setTransitionState("enter");
30273
+ }, EXIT_ANIMATION_MS);
30274
+ return;
30275
+ }
30276
+ if (candidate.id === displayedNotification.id) {
30277
+ clearReplacementTimeout();
30278
+ return;
30279
+ }
30280
+ const displayedInStore = notifications.some(
30281
+ ({ id }) => id === displayedNotification.id
30282
+ );
30283
+ const startSwap = () => {
30284
+ replacementTimeoutRef.current = null;
30285
+ setTransitionState("exit");
30286
+ const previousId = displayedNotification.id;
30287
+ const wasInStore = displayedInStore;
30288
+ exitTimeoutRef.current = window.setTimeout(() => {
30289
+ exitTimeoutRef.current = null;
30290
+ if (wasInStore) {
30291
+ startedTimeoutIdsRef.current?.delete(previousId);
30292
+ removeNotification(previousId);
30293
+ }
30294
+ const next = candidateRef.current;
30295
+ if (next && next.id !== previousId) {
30296
+ displayedAtRef.current = Date.now();
30297
+ setDisplayedNotification(next);
30298
+ setTransitionState("enter");
30299
+ } else {
30300
+ displayedAtRef.current = null;
30301
+ setDisplayedNotification(null);
30302
+ setTransitionState("enter");
30303
+ }
30304
+ }, EXIT_ANIMATION_MS);
30305
+ };
30306
+ if (!displayedInStore) {
30307
+ clearReplacementTimeout();
30308
+ startSwap();
30309
+ return;
30310
+ }
30311
+ if (haveSameType(displayedNotification, candidate)) {
30312
+ clearReplacementTimeout();
30313
+ startSwap();
30314
+ return;
30315
+ }
30316
+ const elapsed = displayedAtRef.current ? Date.now() - displayedAtRef.current : 0;
30317
+ const remaining = Math.max(0, minDisplayMs - elapsed);
30318
+ if (remaining === 0) {
30319
+ clearReplacementTimeout();
30320
+ startSwap();
30321
+ } else if (replacementTimeoutRef.current === null) {
30322
+ replacementTimeoutRef.current = window.setTimeout(startSwap, remaining);
30323
+ }
30324
+ }, [
30325
+ clearReplacementTimeout,
30326
+ displayedNotification,
30327
+ minDisplayMs,
30328
+ notifications,
30329
+ pickNext,
30330
+ removeNotification,
30331
+ transitionState
30332
+ ]);
30152
30333
  const notification = displayedNotification;
30153
30334
  const notificationEnterFrom = getNotificationEnterFrom(notification, enterFrom);
30154
30335
  useEffect(() => {
@@ -30630,7 +30811,7 @@ const useChat = ({
30630
30811
  };
30631
30812
  useEffect(() => {
30632
30813
  if (!client) return;
30633
- const version = "14.2.0";
30814
+ const version = "14.3.0";
30634
30815
  const userAgent = client.getUserAgent();
30635
30816
  if (!userAgent.includes("stream-chat-react")) {
30636
30817
  client.setUserAgent(`stream-chat-react-${version}-${userAgent}`);
@@ -31252,6 +31433,7 @@ export {
31252
31433
  areMessagePropsEqual,
31253
31434
  areMessageUIPropsEqual,
31254
31435
  countEmojis,
31436
+ createDefaultPickNext,
31255
31437
  bK as createIcon,
31256
31438
  deTranslations,
31257
31439
  defaultAllowedTagNames,
@@ -31282,6 +31464,7 @@ export {
31282
31464
  getCssDimensionsVariables,
31283
31465
  getGroupChannelDisplayInfo,
31284
31466
  getGroupStyles,
31467
+ getHasExtendedReactions,
31285
31468
  bS as getImages,
31286
31469
  getIsFirstUnreadMessage,
31287
31470
  getLastReceived,
@@ -31349,6 +31532,8 @@ export {
31349
31532
  modalDialogManagerId,
31350
31533
  moveChannelUpwards,
31351
31534
  nlTranslations,
31535
+ pickNewest,
31536
+ pickOldest,
31352
31537
  plusPlusToEmphasis,
31353
31538
  processMessages,
31354
31539
  ptTranslations,