@linktr.ee/messaging-react 3.6.2-rc-1783311170 → 3.6.2-rc-1783311731

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 (29) hide show
  1. package/dist/{Card-DUoHKAuh.js → Card-0wf8lj5n.js} +2 -2
  2. package/dist/{Card-DUoHKAuh.js.map → Card-0wf8lj5n.js.map} +1 -1
  3. package/dist/{Card-CK1jw1VU.cjs → Card-4UyBK3XO.cjs} +2 -2
  4. package/dist/{Card-CK1jw1VU.cjs.map → Card-4UyBK3XO.cjs.map} +1 -1
  5. package/dist/{Card-DQtg7wWP.cjs → Card-6b4Ws9rn.cjs} +2 -2
  6. package/dist/{Card-DQtg7wWP.cjs.map → Card-6b4Ws9rn.cjs.map} +1 -1
  7. package/dist/{Card-CSwQT3wz.cjs → Card-DsQhJjj0.cjs} +2 -2
  8. package/dist/{Card-CSwQT3wz.cjs.map → Card-DsQhJjj0.cjs.map} +1 -1
  9. package/dist/{Card-DnEyeR_d.js → Card-RZ6C_IkE.js} +3 -3
  10. package/dist/{Card-DnEyeR_d.js.map → Card-RZ6C_IkE.js.map} +1 -1
  11. package/dist/{Card-CJh6R76n.js → Card-iGNcxybY.js} +2 -2
  12. package/dist/{Card-CJh6R76n.js.map → Card-iGNcxybY.js.map} +1 -1
  13. package/dist/{LockedThumbnail-C_F2Sw-9.js → LockedThumbnail-Do9udNMm.js} +2 -2
  14. package/dist/{LockedThumbnail-C_F2Sw-9.js.map → LockedThumbnail-Do9udNMm.js.map} +1 -1
  15. package/dist/{LockedThumbnail-BZLpmTpY.cjs → LockedThumbnail-N0pNOO3D.cjs} +2 -2
  16. package/dist/{LockedThumbnail-BZLpmTpY.cjs.map → LockedThumbnail-N0pNOO3D.cjs.map} +1 -1
  17. package/dist/index-B3ZJaqJY.cjs +2 -0
  18. package/dist/index-B3ZJaqJY.cjs.map +1 -0
  19. package/dist/{index-DcEVHNS3.js → index-wFe1UO9t.js} +160 -158
  20. package/dist/index-wFe1UO9t.js.map +1 -0
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.js +1 -1
  23. package/package.json +2 -2
  24. package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +36 -12
  25. package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +46 -27
  26. package/src/components/CustomMessage/index.tsx +0 -7
  27. package/dist/index-DcEVHNS3.js.map +0 -1
  28. package/dist/index-LP0PQJxl.cjs +0 -2
  29. package/dist/index-LP0PQJxl.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-LP0PQJxl.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.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-B3ZJaqJY.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.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-DcEVHNS3.js";
1
+ import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-wFe1UO9t.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "3.6.2-rc-1783311170",
3
+ "version": "3.6.2-rc-1783311731",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@linktr.ee/component-library": "11.8.6",
41
- "@linktr.ee/messaging-core": "2.3.0-rc-1783311170",
41
+ "@linktr.ee/messaging-core": "2.3.0-rc-1783311731",
42
42
  "@phosphor-icons/react": "^2.1.10"
43
43
  },
44
44
  "devDependencies": {
@@ -1,5 +1,9 @@
1
1
  import React from 'react'
2
- import { useMessageContext } from 'stream-chat-react'
2
+ import {
3
+ useChannelStateContext,
4
+ useChatContext,
5
+ useMessageContext,
6
+ } from 'stream-chat-react'
3
7
  import { beforeEach, describe, expect, it, vi } from 'vitest'
4
8
 
5
9
  import { renderWithProviders, screen, within } from '../../test/utils'
@@ -7,13 +11,16 @@ import { renderWithProviders, screen, within } from '../../test/utils'
7
11
  import { SentMessageDeliveryStatus } from './SentMessageDeliveryStatus'
8
12
 
9
13
  // stream-chat-react's `MessageStatus` owns the outer gating (own message, not
10
- // error, not in a thread) and the state machine Stream's tested code. What
11
- // this component owns is the "Delivered" label wired into the terminal slots
12
- // (sent/delivered/read), its "last own message" self-gate, and suppressing
13
- // the sending spinner. Mock `MessageStatus` to render the slots it was handed,
14
- // and `useMessageContext` to feed the label's self-gate.
14
+ // error, not in a thread) and only exposes a terminal state once a message is
15
+ // stored — Stream's tested code. What this component owns is the "Delivered"
16
+ // label wired into the terminal slots (sent/delivered/read), its "last own
17
+ // message" gate derived from live channel state, and suppressing the sending
18
+ // spinner. Mock `MessageStatus` to render the slots it was handed, and the
19
+ // context hooks the label reads.
15
20
  vi.mock('stream-chat-react', () => ({
16
21
  useMessageContext: vi.fn(),
22
+ useChannelStateContext: vi.fn(),
23
+ useChatContext: vi.fn(),
17
24
  MessageStatus: ({
18
25
  MessageSentStatus,
19
26
  MessageDeliveredStatus,
@@ -38,14 +45,24 @@ vi.mock('stream-chat-react', () => ({
38
45
  }))
39
46
 
40
47
  const mockedUseMessageContext = vi.mocked(useMessageContext)
48
+ const mockedUseChannelStateContext = vi.mocked(useChannelStateContext)
49
+ const mockedUseChatContext = vi.mocked(useChatContext)
50
+
51
+ // A run where the viewer ('me') sent the first and last messages, with an
52
+ // incoming message in between — the case that broke the memo-based approach.
53
+ const CHANNEL_MESSAGES = [
54
+ { id: 'own-1', user: { id: 'me' } },
55
+ { id: 'their-1', user: { id: 'them' } },
56
+ { id: 'own-2', user: { id: 'me' } },
57
+ ]
41
58
 
42
59
  const TERMINAL_SLOTS = ['sent-slot', 'delivered-slot', 'read-slot'] as const
43
60
 
44
61
  describe('SentMessageDeliveryStatus', () => {
45
62
  beforeEach(() => {
46
- mockedUseMessageContext.mockReturnValue({
47
- lastOwnMessage: { id: 'message-1' },
48
- message: { id: 'message-1' },
63
+ mockedUseChatContext.mockReturnValue({ client: { user: { id: 'me' } } } as never)
64
+ mockedUseChannelStateContext.mockReturnValue({
65
+ messages: CHANNEL_MESSAGES,
49
66
  } as never)
50
67
  })
51
68
 
@@ -53,6 +70,10 @@ describe('SentMessageDeliveryStatus', () => {
53
70
  // delivered/read fire ahead of sent in receipt channels, so all three
54
71
  // must show the label — never Stream's default delivered icon / read
55
72
  // avatars.
73
+ mockedUseMessageContext.mockReturnValue({
74
+ message: { id: 'own-2' },
75
+ } as never)
76
+
56
77
  renderWithProviders(<SentMessageDeliveryStatus />)
57
78
 
58
79
  for (const slot of TERMINAL_SLOTS) {
@@ -62,10 +83,9 @@ describe('SentMessageDeliveryStatus', () => {
62
83
  }
63
84
  })
64
85
 
65
- it('renders nothing in the terminal slots when it is not the last own message', () => {
86
+ it('renders nothing for an earlier own message even though one is the last own message', () => {
66
87
  mockedUseMessageContext.mockReturnValue({
67
- lastOwnMessage: { id: 'message-2' },
68
- message: { id: 'message-1' },
88
+ message: { id: 'own-1' },
69
89
  } as never)
70
90
 
71
91
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -76,6 +96,10 @@ describe('SentMessageDeliveryStatus', () => {
76
96
  })
77
97
 
78
98
  it('always suppresses the sending spinner', () => {
99
+ mockedUseMessageContext.mockReturnValue({
100
+ message: { id: 'own-2' },
101
+ } as never)
102
+
79
103
  renderWithProviders(<SentMessageDeliveryStatus />)
80
104
 
81
105
  expect(screen.getByTestId('sending-slot')).toBeEmptyDOMElement()
@@ -1,28 +1,54 @@
1
1
  import { ChecksIcon } from '@phosphor-icons/react'
2
- import { MessageStatus, useMessageContext } from 'stream-chat-react'
2
+ import { useMemo } from 'react'
3
+ import {
4
+ MessageStatus,
5
+ useChannelStateContext,
6
+ useChatContext,
7
+ useMessageContext,
8
+ } from 'stream-chat-react'
3
9
 
4
10
  /**
5
11
  * "✓✓ Delivered" label, wired into *every* terminal `MessageStatus` state
6
12
  * (sent / delivered / read).
7
13
  *
8
- * `MessageStatus` picks its `delivered` and `read` branches ahead of `sent`
9
- * whenever Stream has `deliveredTo` / `readBy` for the message — and, unlike
10
- * `sent`, those branches are *not* gated to the last own message. So in a
11
- * channel that has receipts we must (a) replace Stream's default delivered
12
- * icon / read avatars with our label otherwise the required "Delivered"
13
- * label disappears and an out-of-scope read receipt would show — and (b)
14
- * re-apply the "last own message only" gate ourselves, since the
15
- * delivered/read branches would otherwise render on every read own message.
14
+ * Two reasons this is more than a static label:
15
+ *
16
+ * 1. `MessageStatus` picks its `delivered`/`read` branches ahead of `sent`
17
+ * whenever Stream has `deliveredTo`/`readBy`, and unlike `sent` those
18
+ * branches aren't gated to the last own message. So we override all three
19
+ * terminal slots with this same label (never Stream's default delivered
20
+ * icon / read avatars — Read is out of scope) and re-apply the "last own
21
+ * message" gate ourselves.
22
+ *
23
+ * 2. We derive "last own message" from **live channel state**, not the
24
+ * message context's `lastOwnMessage`. Stream's `Message` is memoised by
25
+ * `areMessagePropsEqual`, which does not compare `lastOwnMessage`; a
26
+ * previous own row whose message/grouping props are unchanged (e.g. after
27
+ * an intervening incoming message) never re-renders, so its context
28
+ * `lastOwnMessage` goes stale and the old bubble would keep showing
29
+ * "Delivered" beside the new one. `useChannelStateContext` gets a fresh
30
+ * `messages` array on every new message and, being a context, re-renders
31
+ * this leaf straight through the stale `Message`/`MessageStatus` memos —
32
+ * so the label reliably hops to the newest own message.
16
33
  *
17
34
  * Rendered inside stream's `.str-chat__message-status` span, which supplies
18
35
  * the flex layout; icon and copy are styled via that span in `styles.css`.
19
36
  */
20
37
  const DeliveredStatus = () => {
21
- const { lastOwnMessage, message } = useMessageContext(
22
- 'SentMessageDeliveryStatus'
23
- )
38
+ const { message } = useMessageContext('SentMessageDeliveryStatus')
39
+ const { messages } = useChannelStateContext('SentMessageDeliveryStatus')
40
+ const { client } = useChatContext('SentMessageDeliveryStatus')
24
41
 
25
- if (lastOwnMessage?.id !== message.id) {
42
+ const ownUserId = client.user?.id
43
+ const lastOwnMessageId = useMemo(() => {
44
+ if (!ownUserId || !messages) return undefined
45
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
46
+ if (messages[i].user?.id === ownUserId) return messages[i].id
47
+ }
48
+ return undefined
49
+ }, [messages, ownUserId])
50
+
51
+ if (message.id !== lastOwnMessageId) {
26
52
  return null
27
53
  }
28
54
 
@@ -42,20 +68,13 @@ const NoStatus = () => null
42
68
  * a right-aligned "✓✓ Delivered" that appears once Stream confirms the
43
69
  * message is stored and updates live.
44
70
  *
45
- * Built on stream-chat-react's `MessageStatus`, which owns the gating we want
46
- * — own message, not an error, and not inside a thread — plus the
47
- * sending/sent/delivered/read state machine.
48
- *
49
- * Scope is "Delivered" only (no Sending / Read). Stream's `sent` state maps
50
- * to "Delivered" for a backend-confirmed (`status === 'received'`) last own
51
- * message; its `delivered`/`read` states (only populated in channels with
52
- * per-recipient receipts) also render the same "Delivered" label via
53
- * {@link DeliveredStatus} so we never surface a read receipt. The sending
54
- * spinner is suppressed.
55
- *
56
- * `DeliveredStatus` reads `lastOwnMessage` from the message context, so
57
- * `CustomMessage`'s memo comparator must re-render when it changes (see
58
- * `index.tsx`) for the label to hop to a newer message.
71
+ * Built on stream-chat-react's `MessageStatus`, which owns the gating we keep
72
+ * — own message, not an error, not inside a thread — and only surfaces a
73
+ * terminal (sending/sent/delivered/read) state once the message is stored.
74
+ * Scope is "Delivered" only: the sending spinner is suppressed and all
75
+ * terminal states render {@link DeliveredStatus}, which supplies the label
76
+ * and the last-own-message gate (see there for why the gate is computed from
77
+ * live channel state rather than the context's `lastOwnMessage`).
59
78
  */
60
79
  export const SentMessageDeliveryStatus = () => (
61
80
  <MessageStatus
@@ -418,13 +418,6 @@ const MemoizedCustomMessage = React.memo(
418
418
  (prev, next) => {
419
419
  if (prev.chatbotVotingEnabled !== next.chatbotVotingEnabled) return false
420
420
  if (prev.viewerLanguage !== next.viewerLanguage) return false
421
- // `lastOwnMessage` drives the "Delivered" indicator, which
422
- // `SentMessageDeliveryStatus` derives from stream's `MessageStatus`
423
- // (reading it off the message context inside this memoised subtree).
424
- // `areMessageUIPropsEqual` doesn't compare it, so without this a stale
425
- // "Delivered" would linger on the previous bubble after a newer message
426
- // is sent — re-render when it changes.
427
- if (prev.lastOwnMessage?.id !== next.lastOwnMessage?.id) return false
428
421
  return areMessageUIPropsEqual(prev, next)
429
422
  }
430
423
  ) as typeof CustomMessageWithContext