@linktr.ee/messaging-react 1.25.1 → 1.26.1-rc-1776055454

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 (47) hide show
  1. package/dist/Preview-DqAv16NS.js +87 -0
  2. package/dist/Preview-DqAv16NS.js.map +1 -0
  3. package/dist/dash.all.min-Duv4lvGS.js +18858 -0
  4. package/dist/dash.all.min-Duv4lvGS.js.map +1 -0
  5. package/dist/hls-Bogc7CBn.js +21710 -0
  6. package/dist/hls-Bogc7CBn.js.map +1 -0
  7. package/dist/index-Da-xN4Yq.js +16142 -0
  8. package/dist/index-Da-xN4Yq.js.map +1 -0
  9. package/dist/index-Dj9rqWcU.js +69 -0
  10. package/dist/index-Dj9rqWcU.js.map +1 -0
  11. package/dist/index.d.ts +85 -9
  12. package/dist/index.js +1745 -1156
  13. package/dist/index.js.map +1 -1
  14. package/dist/mixin-B6jYfIcp.js +808 -0
  15. package/dist/mixin-B6jYfIcp.js.map +1 -0
  16. package/dist/react-BxlQMOfz.js +419 -0
  17. package/dist/react-BxlQMOfz.js.map +1 -0
  18. package/dist/react-COAP-MIW.js +377 -0
  19. package/dist/react-COAP-MIW.js.map +1 -0
  20. package/dist/react-Cn4WlMcl.js +3108 -0
  21. package/dist/react-Cn4WlMcl.js.map +1 -0
  22. package/dist/react-CwTJArKY.js +459 -0
  23. package/dist/react-CwTJArKY.js.map +1 -0
  24. package/dist/react-DkfS_atT.js +373 -0
  25. package/dist/react-DkfS_atT.js.map +1 -0
  26. package/dist/react-Pea5fum1.js +286 -0
  27. package/dist/react-Pea5fum1.js.map +1 -0
  28. package/dist/react-RiBbsUDd.js +534 -0
  29. package/dist/react-RiBbsUDd.js.map +1 -0
  30. package/dist/react-dS1WBxxz.js +238 -0
  31. package/dist/react-dS1WBxxz.js.map +1 -0
  32. package/package.json +2 -1
  33. package/src/components/ChannelList/index.test.tsx +18 -0
  34. package/src/components/ChannelList/index.tsx +2 -0
  35. package/src/components/ChannelView.test.tsx +50 -1
  36. package/src/components/ChannelView.tsx +13 -3
  37. package/src/components/CustomMessage/CustomMessage.stories.tsx +61 -2
  38. package/src/components/CustomMessage/MessageTag.tsx +5 -0
  39. package/src/components/CustomMessage/index.tsx +46 -4
  40. package/src/components/LockedAttachmentCard/LockedAttachmentCard.stories.tsx +351 -0
  41. package/src/components/LockedAttachmentCard/index.tsx +378 -0
  42. package/src/components/LockedAttachmentCard/mimeType.test.ts +97 -0
  43. package/src/components/LockedAttachmentCard/mimeType.ts +35 -0
  44. package/src/components/MessagingShell/index.tsx +2 -0
  45. package/src/index.ts +4 -0
  46. package/src/stream-custom-data.ts +10 -3
  47. package/src/types.ts +41 -0
@@ -0,0 +1,69 @@
1
+ var o = Object.defineProperty;
2
+ var g = (n, a, c) => a in n ? o(n, a, { enumerable: !0, configurable: !0, writable: !0, value: c }) : n[a] = c;
3
+ var l = (n, a, c) => g(n, typeof a != "symbol" ? a + "" : a, c);
4
+ function R(n) {
5
+ return class extends n {
6
+ constructor() {
7
+ super(...arguments);
8
+ l(this, "_playedRanges", []);
9
+ l(this, "_currentPlayedRange", null);
10
+ l(this, "_RANGE_EPSILON", 0.5);
11
+ l(this, "_seeking", !1);
12
+ }
13
+ connectedCallback() {
14
+ super.connectedCallback && super.connectedCallback(), this.addEventListener("play", () => this.onPlaybackStart({ time: this.currentTime })), this.addEventListener("pause", () => this.onPlaybackStop({ time: this.currentTime })), this.addEventListener("ended", () => this.onPlaybackStop({ time: this.currentTime })), this.addEventListener("seeking", () => this.onSeeking()), this.addEventListener("seeked", () => this.onSeeked({ time: this.currentTime }));
15
+ }
16
+ onPlaybackStart(e = {}) {
17
+ this._seeking = !1;
18
+ const { time: t } = e, s = typeof t == "number" ? t : this.currentTime;
19
+ this._currentPlayedRange || (this._currentPlayedRange = { start: s, end: s });
20
+ }
21
+ onSeeking() {
22
+ this._seeking = !0, this._commitCurrentRange();
23
+ }
24
+ onSeeked(e = {}) {
25
+ this._seeking = !1;
26
+ const { time: t } = e, s = typeof t == "number" ? t : this.currentTime;
27
+ this._currentPlayedRange = { start: s, end: s };
28
+ }
29
+ onPlaybackStop(e = {}) {
30
+ const { time: t } = e, s = typeof t == "number" ? t : this.currentTime;
31
+ this._commitCurrentRange(s);
32
+ }
33
+ _commitCurrentRange(e) {
34
+ if (!this._currentPlayedRange) return;
35
+ typeof e == "number" && (this._currentPlayedRange.end = e);
36
+ const { start: t, end: s } = this._currentPlayedRange;
37
+ this._currentPlayedRange = null, this.addPlayedRange(t, s);
38
+ }
39
+ addPlayedRange(e, t) {
40
+ if (e >= t) return;
41
+ const s = this._RANGE_EPSILON, h = [...this._playedRanges, { start: e, end: t }];
42
+ h.sort((i, r) => i.start - r.start);
43
+ const d = [];
44
+ for (const i of h) {
45
+ if (!d.length) {
46
+ d.push({ ...i });
47
+ continue;
48
+ }
49
+ const r = d[d.length - 1];
50
+ i.start <= r.end + s ? (r.start = Math.min(r.start, i.start), r.end = Math.max(r.end, i.end)) : d.push({ ...i });
51
+ }
52
+ this._playedRanges = d;
53
+ }
54
+ get played() {
55
+ const e = this.currentTime;
56
+ return !this.paused && !this._currentPlayedRange && typeof e == "number" && (this._currentPlayedRange = { start: e, end: e }), this._currentPlayedRange && typeof e == "number" && (e > this._currentPlayedRange.end && (this._currentPlayedRange.end = e), this.addPlayedRange(this._currentPlayedRange.start, this._currentPlayedRange.end)), this._playedRanges.length ? u(this._playedRanges.map((t) => [t.start, t.end])) : u([[0, 0]]);
57
+ }
58
+ };
59
+ }
60
+ function u(n) {
61
+ return Object.defineProperties(n, {
62
+ start: { value: (a) => n[a][0] },
63
+ end: { value: (a) => n[a][1] }
64
+ }), n;
65
+ }
66
+ export {
67
+ R as M
68
+ };
69
+ //# sourceMappingURL=index-Dj9rqWcU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-Dj9rqWcU.js","sources":["../../../node_modules/media-played-ranges-mixin/dist/index.js"],"sourcesContent":["function MediaPlayedRangesMixin(Base) {\n return class MediaPlayedRanges extends Base {\n _playedRanges = [];\n _currentPlayedRange = null;\n _RANGE_EPSILON = 0.5;\n _seeking = false;\n connectedCallback() {\n if (super.connectedCallback) super.connectedCallback();\n this.addEventListener(\"play\", () => this.onPlaybackStart({ time: this.currentTime }));\n this.addEventListener(\"pause\", () => this.onPlaybackStop({ time: this.currentTime }));\n this.addEventListener(\"ended\", () => this.onPlaybackStop({ time: this.currentTime }));\n this.addEventListener(\"seeking\", () => this.onSeeking());\n this.addEventListener(\"seeked\", () => this.onSeeked({ time: this.currentTime }));\n }\n onPlaybackStart(param = {}) {\n this._seeking = false;\n const { time } = param;\n const t = typeof time === \"number\" ? time : this.currentTime;\n if (!this._currentPlayedRange) {\n this._currentPlayedRange = { start: t, end: t };\n }\n }\n onSeeking() {\n this._seeking = true;\n this._commitCurrentRange();\n }\n onSeeked(param = {}) {\n this._seeking = false;\n const { time } = param;\n const t = typeof time === \"number\" ? time : this.currentTime;\n this._currentPlayedRange = { start: t, end: t };\n }\n onPlaybackStop(param = {}) {\n const { time } = param;\n const t = typeof time === \"number\" ? time : this.currentTime;\n this._commitCurrentRange(t);\n }\n _commitCurrentRange(time) {\n if (!this._currentPlayedRange) return;\n if (typeof time === \"number\") {\n this._currentPlayedRange.end = time;\n }\n const { start, end } = this._currentPlayedRange;\n this._currentPlayedRange = null;\n this.addPlayedRange(start, end);\n }\n addPlayedRange(start, end) {\n if (start >= end) return;\n const EPS = this._RANGE_EPSILON;\n const allRanges = [...this._playedRanges, { start, end }];\n allRanges.sort((a, b) => a.start - b.start);\n const merged = [];\n for (const range of allRanges) {\n if (!merged.length) {\n merged.push({ ...range });\n continue;\n }\n const last = merged[merged.length - 1];\n if (range.start <= last.end + EPS) {\n last.start = Math.min(last.start, range.start);\n last.end = Math.max(last.end, range.end);\n } else {\n merged.push({ ...range });\n }\n }\n this._playedRanges = merged;\n }\n get played() {\n const time = this.currentTime;\n if (!this.paused && !this._currentPlayedRange && typeof time === \"number\") {\n this._currentPlayedRange = { start: time, end: time };\n }\n if (this._currentPlayedRange && typeof time === \"number\") {\n if (time > this._currentPlayedRange.end) {\n this._currentPlayedRange.end = time;\n }\n this.addPlayedRange(this._currentPlayedRange.start, this._currentPlayedRange.end);\n }\n if (!this._playedRanges.length) {\n return createTimeRangesObj([[0, 0]]);\n }\n return createTimeRangesObj(this._playedRanges.map((r) => [r.start, r.end]));\n }\n };\n}\nfunction createTimeRangesObj(ranges) {\n Object.defineProperties(ranges, {\n start: { value: (i) => ranges[i][0] },\n end: { value: (i) => ranges[i][1] }\n });\n return ranges;\n}\nexport {\n MediaPlayedRangesMixin\n};\n"],"names":["MediaPlayedRangesMixin","Base","__publicField","param","time","t","start","end","EPS","allRanges","a","b","merged","range","last","createTimeRangesObj","r","ranges","i"],"mappings":"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","x_google_ignoreList":[0]}
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ export declare interface ActionButtonProps extends default_2.ButtonHTMLAttribute
20
20
 
21
21
  declare type AgeSafetySystemType = 'SYSTEM_AGE_SAFETY_BLOCKED';
22
22
 
23
+ export declare type AttachmentSourceType = 'image' | 'audio' | 'video' | 'document';
24
+
23
25
  /**
24
26
  * Avatar component that displays a user image or colored initial fallback
25
27
  */
@@ -77,6 +79,20 @@ export declare interface ChannelListProps {
77
79
  * channel belongs in the current list before inserting it.
78
80
  */
79
81
  onAddedToChannel?: ChannelListProps_2['onAddedToChannel'];
82
+ /**
83
+ * Client-side filter applied before rendering the channel list.
84
+ * Websocket events can add channels to the list that bypass server-side
85
+ * query filters. Use this to enforce visibility rules that can't be
86
+ * automatically derived from the filters prop (e.g. $or conditions).
87
+ *
88
+ * @example
89
+ * // Hide channels where the visitor hasn't sent a message yet,
90
+ * // but keep legacy channels that predate the has_visitor_message field
91
+ * channelRenderFilterFn={(channels) =>
92
+ * channels.filter(ch => ch.data?.has_visitor_message !== false)
93
+ * }
94
+ */
95
+ channelRenderFilterFn?: (channels: Channel[]) => Channel[];
80
96
  /**
81
97
  * Sort order for the channel list query.
82
98
  * Defaults to `{ last_message_at: -1 }` (most recent first).
@@ -95,7 +111,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
95
111
  * Props that MessagingShell passes through to ChannelView.
96
112
  * ChannelViewProps is the source of truth for these props.
97
113
  */
98
- declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage'>;
114
+ declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'onAttachmentUnlock' | 'onAttachmentDownload'>;
99
115
 
100
116
  /**
101
117
  * ChannelView component props
@@ -200,6 +216,17 @@ export declare interface ChannelViewProps {
200
216
  * )}
201
217
  */
202
218
  renderMessage?: (messageNode: React.ReactElement, message: LocalMessage) => React.ReactNode;
219
+ /**
220
+ * Called when the visitor clicks Unlock on a locked attachment message.
221
+ * Receives the message and channel. Show checkout, confirm payment, fetch
222
+ * the unlocked URL. `attachment_source` must NOT be stored on the Stream message metadata.
223
+ * The card shows a loading state for the full duration of the promise.
224
+ */
225
+ onAttachmentUnlock?: (message: LocalMessage, channel: Channel) => Promise<string>;
226
+ /**
227
+ * Called when the visitor clicks Download on an unlocked attachment message.
228
+ */
229
+ onAttachmentDownload?: (message: LocalMessage, channel: Channel) => void;
203
230
  }
204
231
 
205
232
  declare type DmAgentSystemType = 'SYSTEM_DM_AGENT_PAUSED' | 'SYSTEM_DM_AGENT_RESUMED';
@@ -239,18 +266,50 @@ export declare interface FaqListProps {
239
266
  */
240
267
  export declare const formatRelativeTime: (date: Date) => string;
241
268
 
242
- declare type MessageCustomType = 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT' | AgeSafetySystemType | DmAgentSystemType;
269
+ /** Check if a message is a locked attachment */
270
+ export declare const isAttachmentMessage: (message: LocalMessage) => boolean;
271
+
272
+ export declare const LockedAttachmentCard: default_2.FC<LockedAttachmentCardProps>;
273
+
274
+ export declare interface LockedAttachmentCardProps {
275
+ title: string;
276
+ amountText?: string;
277
+ thumbnail?: string;
278
+ /**
279
+ * The unlocked media URL. When undefined the card is in locked state.
280
+ * Typically undefined while the source is being fetched post-purchase.
281
+ */
282
+ source?: string;
283
+ /** MIME type of the attachment (e.g. 'video/mp4', 'audio/mpeg', 'image/jpeg', 'application/pdf'). Determines the icon and player behaviour. */
284
+ mimeType: string;
285
+ detail?: string;
286
+ /**
287
+ * Called when the visitor clicks Unlock.
288
+ * Omit to hide the Unlock button (e.g. creator-side views).
289
+ */
290
+ onUnlock?: () => void;
291
+ /** Called when the visitor clicks Download on an unlocked card. */
292
+ onDownload?: () => void;
293
+ /** Shows a loading spinner on the unlock button while the source is being fetched. */
294
+ loading?: boolean;
295
+ /**
296
+ * Payment has been completed but the source URL hasn't been resolved yet.
297
+ * Shows a "Preparing…" state instead of the lock overlay.
298
+ */
299
+ isPurchased?: boolean;
300
+ }
301
+
302
+ declare type MessageCustomType = 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT' | 'MESSAGE_ATTACHMENT' | AgeSafetySystemType | DmAgentSystemType;
243
303
 
244
- /**
245
- * Message metadata for paid messaging and chatbot flows.
246
- * Used to identify message types and payment status.
247
- */
248
304
  export declare interface MessageMetadata {
249
305
  custom_type?: MessageCustomType;
250
- amount_text?: string;
251
- payment_status?: string;
252
- payment_intent_id?: string;
253
306
  listing_id?: string;
307
+ amount_text?: string;
308
+ payment_status?: PaymentStatus;
309
+ attachment_title?: string;
310
+ attachment_mime_type?: string;
311
+ attachment_thumbnail?: string;
312
+ attachment_detail?: string;
254
313
  }
255
314
 
256
315
  export declare const MessageVoteButtons: default_2.FC<MessageVoteButtonsProps>;
@@ -346,6 +405,17 @@ export declare interface MessagingShellProps extends ChannelViewPassthroughProps
346
405
  * Filters for channel list. Passed through to StreamChat ChannelList.
347
406
  */
348
407
  filters?: ChannelFilters;
408
+ /**
409
+ * Client-side filter applied before rendering the channel list.
410
+ * Websocket events can add channels to the list that bypass server-side
411
+ * query filters. Use this to enforce visibility rules client-side.
412
+ *
413
+ * @example
414
+ * channelRenderFilterFn={(channels) =>
415
+ * channels.filter(ch => ch.data?.has_visitor_message !== false)
416
+ * }
417
+ */
418
+ channelRenderFilterFn?: (channels: Channel[]) => Channel[];
349
419
  /**
350
420
  * Custom empty state indicator component to render when the channel list is empty.
351
421
  * Useful for showing a custom empty state indicator when the channel list is empty.
@@ -406,6 +476,12 @@ export declare interface ParticipantSource {
406
476
  loading?: boolean;
407
477
  }
408
478
 
479
+ /**
480
+ * Message metadata for paid messaging and chatbot flows.
481
+ * Used to identify message types and payment status.
482
+ */
483
+ declare type PaymentStatus = 'pending' | 'paid' | 'failed' | 'refunded';
484
+
409
485
  /**
410
486
  * Hook that wraps Stream Chat reactions to provide toggle-style
411
487
  * upvote/downvote behavior on a message.