@factorialco/f0-react 4.40.1 → 4.41.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.
@@ -3702,6 +3702,8 @@ declare const defaultTranslations: {
3702
3702
  readonly noResults: "No chats found";
3703
3703
  readonly backToLatest: "Jump to latest";
3704
3704
  readonly muted: "Muted";
3705
+ readonly mute: "Mute";
3706
+ readonly unmute: "Unmute";
3705
3707
  readonly attachFile: "Attach file";
3706
3708
  readonly addEmoji: "Add emoji";
3707
3709
  readonly recordAudio: "Record audio";
@@ -3728,6 +3730,13 @@ declare const defaultTranslations: {
3728
3730
  readonly twoTyping: "{{first}} and {{second}} are writing…";
3729
3731
  readonly severalTyping: "Several people are writing…";
3730
3732
  readonly deletedMessage: "Message deleted";
3733
+ readonly location: "Location";
3734
+ readonly voiceNote: "Voice note";
3735
+ readonly sendVoiceNote: "Send voice note";
3736
+ readonly sendingVoiceNote: "Sending voice note…";
3737
+ readonly sending: "Sending…";
3738
+ readonly notSent: "Not sent";
3739
+ readonly retry: "Retry";
3731
3740
  readonly moreActions: "Message actions";
3732
3741
  readonly options: "Options";
3733
3742
  readonly pin: "Pin";
@@ -3766,6 +3775,22 @@ declare const defaultTranslations: {
3766
3775
  };
3767
3776
  readonly scrollToBottom: "Scroll to bottom";
3768
3777
  readonly newMessages: "New messages";
3778
+ readonly system: {
3779
+ readonly memberAdded: {
3780
+ readonly one: "{{members}} was added to the group";
3781
+ readonly other: "{{members}} were added to the group";
3782
+ };
3783
+ readonly memberRemoved: {
3784
+ readonly one: "{{members}} was removed from the group";
3785
+ readonly other: "{{members}} were removed from the group";
3786
+ };
3787
+ readonly memberLeft: {
3788
+ readonly one: "{{members}} left the group";
3789
+ readonly other: "{{members}} left the group";
3790
+ };
3791
+ readonly membersWithLast: "{{names}} and {{last}}";
3792
+ readonly membersWithMore: "{{names}} and {{count}} more";
3793
+ };
3769
3794
  readonly unreadCount: {
3770
3795
  readonly one: "{{count}} unread";
3771
3796
  readonly other: "{{count}} unread";
@@ -5020,7 +5045,32 @@ export declare interface F0CardHorizontalProps {
5020
5045
  */
5021
5046
  export declare const F0Chat: (props: F0ChatProps) => ReactNode;
5022
5047
 
5023
- export declare type F0ChatAttachment = F0ChatImageAttachment | F0ChatFileAttachment;
5048
+ export declare type F0ChatAttachment = F0ChatImageAttachment | F0ChatFileAttachment | F0ChatLocationAttachment | F0ChatVoiceAttachment;
5049
+
5050
+ /**
5051
+ * Per-channel permissions. Everything is optional and defaults to today's
5052
+ * behavior, so hosts only express what their transport restricts (frozen /
5053
+ * read-only channels, moderation roles…):
5054
+ * - `canSend` (default true): false hides the composer entirely.
5055
+ * - `canReact` (default true): false hides the quick-reaction row, the emoji
5056
+ * pickers and disables toggling existing reaction pills.
5057
+ * - `canUpload` (default: whether `uploadFiles` exists): false disables the
5058
+ * attach button, drag & drop and voice notes even when `uploadFiles` exists.
5059
+ * - `canEditMessage` (default: own message within {@link F0ChatRuntime.editWindowMs}):
5060
+ * overrides the edit policy per message. Structural gates still apply (the
5061
+ * host must provide `editMessage`; deleted messages and voice notes are
5062
+ * never editable).
5063
+ * - `canDeleteMessage` (default: own message): overrides the delete policy per
5064
+ * message (e.g. moderators deleting others' messages). Failed local echoes
5065
+ * are always discardable — they don't exist server-side.
5066
+ */
5067
+ export declare type F0ChatCapabilities = {
5068
+ canSend?: boolean;
5069
+ canReact?: boolean;
5070
+ canUpload?: boolean;
5071
+ canEditMessage?: (message: F0ChatMessage) => boolean;
5072
+ canDeleteMessage?: (message: F0ChatMessage) => boolean;
5073
+ };
5024
5074
 
5025
5075
  /** The conversation currently shown in the panel (header + behaviour differs by type). */
5026
5076
  export declare type F0ChatChannel = {
@@ -5082,6 +5132,30 @@ export declare type F0ChatFileAttachment = {
5082
5132
  progress?: number;
5083
5133
  };
5084
5134
 
5135
+ /**
5136
+ * A host-provided header action (the only built-in one is Search). Pin/mute,
5137
+ * edit-group, leave… are all expressed through this, so each channel can offer
5138
+ * exactly the actions the current user's PERMISSIONS allow — pass different
5139
+ * arrays per channel (or the function form of `headerActions`), and `[]` for a
5140
+ * channel where the user can do nothing but search.
5141
+ */
5142
+ export declare type F0ChatHeaderAction = {
5143
+ id: string;
5144
+ /** Already-localized label. For toggles (mute/unmute) the host rebuilds the
5145
+ * array per render with the current label — labels are plain strings. */
5146
+ label: string;
5147
+ icon?: IconType;
5148
+ /** The host decides what happens: call a runtime method (togglePin,
5149
+ * toggleMute), open its own modal, navigate… */
5150
+ onClick: (channel: F0ChatChannel) => void;
5151
+ /** Where the action renders: inside the ellipsis overflow menu (default) or
5152
+ * as its own icon button next to it. Inline requires `icon` — an inline
5153
+ * action without one falls back to the menu. */
5154
+ placement?: "menu" | "inline";
5155
+ /** Restrict the action to a channel type. Omit for both. */
5156
+ channelTypes?: F0ChatChannelType[];
5157
+ };
5158
+
5085
5159
  export declare type F0ChatImageAttachment = {
5086
5160
  kind: "image";
5087
5161
  url: string;
@@ -5092,6 +5166,36 @@ export declare type F0ChatImageAttachment = {
5092
5166
  height?: number;
5093
5167
  };
5094
5168
 
5169
+ /** Anything that can appear in the transcript, oldest → newest. */
5170
+ export declare type F0ChatItem = F0ChatMessage | F0ChatSystemMessage;
5171
+
5172
+ /**
5173
+ * Open Graph preview of a URL in the body (WhatsApp-style card above the text).
5174
+ * The host provides scraped metadata — F0 never fetches the URL itself
5175
+ * (factorial → Stream's URL enrichment attachments, `og_scrape_url`).
5176
+ */
5177
+ export declare type F0ChatLinkPreview = {
5178
+ /** The link the card opens (the scraped page). */
5179
+ url: string;
5180
+ title?: string;
5181
+ description?: string;
5182
+ /** Preview image (Open Graph `og:image`). */
5183
+ imageUrl?: string;
5184
+ };
5185
+
5186
+ /**
5187
+ * A shared (static) location. Rendered as a map preview card that opens the
5188
+ * point in Google Maps; the host maps it to its transport's shape (factorial →
5189
+ * a Stream custom attachment `{ type: "location", latitude, longitude }`).
5190
+ */
5191
+ export declare type F0ChatLocationAttachment = {
5192
+ kind: "location";
5193
+ latitude: number;
5194
+ longitude: number;
5195
+ /** Optional place label shown under the map. */
5196
+ name?: string;
5197
+ };
5198
+
5095
5199
  /**
5096
5200
  * A person mentioned in a message. `id` matches an {@link F0ChatUser.id}; `name`
5097
5201
  * is the display name as it appears in the body (e.g. "Ana García"), used to
@@ -5110,6 +5214,11 @@ export declare type F0ChatMention = {
5110
5214
  };
5111
5215
 
5112
5216
  export declare type F0ChatMessage = {
5217
+ /**
5218
+ * Discriminant against {@link F0ChatSystemMessage}. Optional — an absent
5219
+ * `type` means "message", so existing literals keep compiling.
5220
+ */
5221
+ type?: "message";
5113
5222
  id: string;
5114
5223
  author: F0ChatUser;
5115
5224
  body: string;
@@ -5117,6 +5226,12 @@ export declare type F0ChatMessage = {
5117
5226
  createdAt: string;
5118
5227
  isMine: boolean;
5119
5228
  status?: F0ChatMessageStatus;
5229
+ /**
5230
+ * Why the send failed (host-provided, human-readable — e.g. "Message too
5231
+ * long"). Shown alongside the failed indicator's tooltip so the user knows
5232
+ * whether a retry can help. Only meaningful with `status: "failed"`.
5233
+ */
5234
+ failureReason?: string;
5120
5235
  /**
5121
5236
  * When the message was read (DM read receipt), ISO. Approximated from the
5122
5237
  * counterpart's per-channel last-read pointer — Stream has no per-message
@@ -5125,6 +5240,13 @@ export declare type F0ChatMessage = {
5125
5240
  readAt?: string;
5126
5241
  reactions?: F0ChatReaction[];
5127
5242
  attachments?: F0ChatAttachment[];
5243
+ /**
5244
+ * Preview cards for the URLs in the body (host-scraped metadata only; when
5245
+ * omitted, links render as plain auto-linked text). A single preview shows a
5246
+ * full card with its image; several stack as compact title/host rows
5247
+ * (Slack-style unfurls).
5248
+ */
5249
+ linkPreviews?: F0ChatLinkPreview[];
5128
5250
  replyTo?: F0ChatMessageReply;
5129
5251
  /**
5130
5252
  * People mentioned in this message (groups only). Drives the `@name` chip
@@ -5166,8 +5288,16 @@ export declare type F0ChatMessageReply = {
5166
5288
  attachments?: F0ChatAttachment[];
5167
5289
  };
5168
5290
 
5169
- /** iMessage-style delivery state — only meaningful for messages I sent. */
5170
- export declare type F0ChatMessageStatus = "sending" | "sent" | "read" | "failed";
5291
+ /**
5292
+ * iMessage-style delivery state only meaningful for messages I sent.
5293
+ * `sending` renders a delayed clock beside the bubble (only if the send takes
5294
+ * >500ms, so healthy networks never flash it); `failed` dims the bubble and
5295
+ * shows a tappable critical alert whose menu is reduced to Retry / Delete.
5296
+ * `delivered` (reached the counterpart's device, not read yet) is for backends
5297
+ * that distinguish it — Stream doesn't, so the factorial adapter never emits it
5298
+ * and those messages go straight from `sent` to `read`.
5299
+ */
5300
+ export declare type F0ChatMessageStatus = "sending" | "sent" | "delivered" | "read" | "failed";
5171
5301
 
5172
5302
  export declare type F0ChatProps = {
5173
5303
  /** Whether the hosting panel is in fullscreen (controls the header toggle icon). */
@@ -5176,6 +5306,14 @@ export declare type F0ChatProps = {
5176
5306
  onToggleFullscreen?: () => void;
5177
5307
  /** Close the hosting panel. Hidden when omitted. */
5178
5308
  onClose?: () => void;
5309
+ /**
5310
+ * Host-provided header actions (pin, mute, edit group…). Search is the only
5311
+ * built-in one. The function form receives the current channel so each
5312
+ * channel offers exactly what the user's PERMISSIONS allow — return `[]`
5313
+ * where they can do nothing but search. For toggles (mute/unmute) rebuild
5314
+ * the array per render with the current label/icon.
5315
+ */
5316
+ headerActions?: F0ChatHeaderAction[] | ((channel: F0ChatChannel) => F0ChatHeaderAction[]);
5179
5317
  };
5180
5318
 
5181
5319
  /**
@@ -5203,8 +5341,8 @@ export declare type F0ChatRuntime = {
5203
5341
  currentUserId: string;
5204
5342
  channel: F0ChatChannel;
5205
5343
  status: F0ChatStatus;
5206
- /** Oldest → newest. */
5207
- messages: F0ChatMessage[];
5344
+ /** Oldest → newest. May interleave system items (membership events). */
5345
+ messages: F0ChatItem[];
5208
5346
  /** Users currently typing (excluding me). */
5209
5347
  typingUsers: F0ChatUser[];
5210
5348
  hasMoreOlder: boolean;
@@ -5223,17 +5361,57 @@ export declare type F0ChatRuntime = {
5223
5361
  unreadCount: number;
5224
5362
  /** Id of the first unread message — where the "new messages" divider goes. */
5225
5363
  firstUnreadId: string | null;
5226
- sendMessage: (input: F0ChatSendInput) => void;
5227
- retryMessage: (id: string) => void;
5364
+ /**
5365
+ * Send a message. F0 fires-and-forgets; the OPTIMISTIC LIFECYCLE is the
5366
+ * host's contract (this is what makes any backend feel instant):
5367
+ *
5368
+ * 1. Generate the message id CLIENT-SIDE and synchronously insert a local
5369
+ * echo into `messages` with `status: "sending"` — the bubble must appear
5370
+ * in the same render, not after the server acks.
5371
+ * 2. Reconcile by id: when the server echo arrives, replace the local one
5372
+ * (same id → same bubble, no flicker) and advance `status`.
5373
+ * 3. On failure, flip the echo to `status: "failed"` (optionally with
5374
+ * `failureReason`) and keep it in `messages` — F0 renders the retry /
5375
+ * discard affordances.
5376
+ * 4. `retryMessage` re-sends with the SAME id so the server can dedupe when
5377
+ * the original send actually landed (timeouts lie on bad networks).
5378
+ *
5379
+ * factorial → Stream: `channel.state.addMessageSorted` + client-generated
5380
+ * UUID + id-idempotent `sendMessage`.
5381
+ */
5382
+ sendMessage: (input: F0ChatSendInput) => void | Promise<void>;
5383
+ /**
5384
+ * Re-send a message whose `status` is `"failed"`, reusing the SAME message
5385
+ * id so the transport can dedupe if the original send actually reached the
5386
+ * server (factorial → Stream is idempotent on client-generated message ids).
5387
+ * Flips the message back to `"sending"`.
5388
+ */
5389
+ retryMessage: (id: string) => void | Promise<void>;
5228
5390
  loadOlder: () => void;
5229
- toggleReaction: (messageId: string, emoji: string) => void;
5230
- deleteMessage: (id: string) => void;
5391
+ toggleReaction: (messageId: string, emoji: string) => void | Promise<void>;
5392
+ /**
5393
+ * Delete a message that exists server-side (soft delete → tombstone, or hard
5394
+ * delete → removed from `messages`).
5395
+ *
5396
+ * When `deleteFailedMessage` is not provided this is ALSO called for failed
5397
+ * local echoes, and the host must special-case them: discard the local echo
5398
+ * only — no server call, the message doesn't exist server-side (factorial →
5399
+ * `channel.state.removeMessage`). Prefer providing `deleteFailedMessage` so
5400
+ * the two semantics stay explicit.
5401
+ */
5402
+ deleteMessage: (id: string) => void | Promise<void>;
5403
+ /**
5404
+ * Discard a `"failed"` local echo (never delivered — a purely local
5405
+ * operation, no server call). When omitted, F0 falls back to
5406
+ * `deleteMessage`, which then must handle the failed case itself.
5407
+ */
5408
+ deleteFailedMessage?: (id: string) => void | Promise<void>;
5231
5409
  /**
5232
5410
  * Edit an existing message (text, mentions, attachments). Omit to disable
5233
5411
  * editing — the "Edit" action then never shows. factorial →
5234
5412
  * `client.partialUpdateMessage`.
5235
5413
  */
5236
- editMessage?: (id: string, input: F0ChatEditInput) => void;
5414
+ editMessage?: (id: string, input: F0ChatEditInput) => void | Promise<void>;
5237
5415
  /**
5238
5416
  * How long after sending a message stays editable (ms). The "Edit" action is
5239
5417
  * hidden once a message is older than this. Omit for no limit (editable
@@ -5242,6 +5420,15 @@ export declare type F0ChatRuntime = {
5242
5420
  editWindowMs?: number;
5243
5421
  /** Called as the user types so the runtime can emit typing.start/stop. */
5244
5422
  onInputActivity: () => void;
5423
+ /**
5424
+ * Emit typing.stop immediately — the composer calls it on send, when the
5425
+ * text is cleared and on unmount, so the counterpart's dots drop the very
5426
+ * moment typing actually stopped. Hosts whose transport auto-expires typing
5427
+ * (Stream's `keystroke()` does after a few seconds) can omit it and rely on
5428
+ * the timeout; transports without auto-expiry need it (factorial →
5429
+ * `channel.stopTyping()`).
5430
+ */
5431
+ stopTyping?: () => void | Promise<void>;
5245
5432
  uploadFiles?: (files: File[]) => Promise<F0ChatAttachment[]>;
5246
5433
  /**
5247
5434
  * Max files attachable at once. When a selection/drop would exceed it, the
@@ -5255,7 +5442,24 @@ export declare type F0ChatRuntime = {
5255
5442
  * (the Stream adapter omits it, so the mic button stays hidden there).
5256
5443
  */
5257
5444
  transcribe?: TranscribeFn;
5258
- markRead?: () => void;
5445
+ /**
5446
+ * Mark the conversation read. `untilMessageId` supports partial reads
5447
+ * ("read up to this message") for backends that track them; F0 currently
5448
+ * always calls it without arguments (read everything), so simple hosts can
5449
+ * ignore the parameter.
5450
+ */
5451
+ markRead?: (untilMessageId?: string) => void | Promise<void>;
5452
+ /**
5453
+ * Per-channel permissions (frozen / read-only channels, moderation…). Omit
5454
+ * for the default policy — see {@link F0ChatCapabilities}.
5455
+ */
5456
+ capabilities?: F0ChatCapabilities;
5457
+ /**
5458
+ * Retry after a load failure — wired to the Retry button in the error state.
5459
+ * Omit to render the error message without an action (previous behavior).
5460
+ * factorial → re-run `channel.watch()`.
5461
+ */
5462
+ reconnect?: () => void | Promise<void>;
5259
5463
  /**
5260
5464
  * Search the conversation's members for the `@`-mention popover, returning
5261
5465
  * matches for `query` (empty string → the full member list). Provide it
@@ -5267,10 +5471,20 @@ export declare type F0ChatRuntime = {
5267
5471
  searchMembers?: (query: string) => Promise<F0ChatUser[]>;
5268
5472
  /**
5269
5473
  * Toggle the conversation's pinned (favourite) state for the current user.
5270
- * Drives the header "Pin / Unpin" action; omit to hide it. factorial →
5474
+ * Transport capability only the header no longer auto-renders a Pin
5475
+ * action; the host surfaces one via {@link F0ChatHeaderAction} (`onClick:
5476
+ * () => runtime.togglePin()`) where its permissions allow. factorial →
5271
5477
  * `channel.pin()` / `channel.unpin()`.
5272
5478
  */
5273
- togglePin?: () => void;
5479
+ togglePin?: () => void | Promise<void>;
5480
+ /**
5481
+ * Toggle the conversation's muted state for the current user. Transport
5482
+ * capability only — the header no longer auto-renders a Mute action; the
5483
+ * host surfaces one via {@link F0ChatHeaderAction} (the header still shows
5484
+ * the `channel.muted` status icon either way). factorial →
5485
+ * `channel.mute()` / `channel.unmute()`.
5486
+ */
5487
+ toggleMute?: () => void | Promise<void>;
5274
5488
  /**
5275
5489
  * Full-text search within this conversation, returning matches oldest→newest.
5276
5490
  * Omit to fall back to a client-side substring search over the loaded
@@ -5304,7 +5518,61 @@ export declare type F0ChatSendInput = {
5304
5518
  mentionedEveryone?: boolean;
5305
5519
  };
5306
5520
 
5307
- export declare type F0ChatStatus = "connecting" | "ready" | "error";
5521
+ /**
5522
+ * Conversation lifecycle as the host reports it. `connecting` (first load)
5523
+ * shows the skeleton and `error` the error state (with a Retry button when
5524
+ * {@link F0ChatRuntime.reconnect} is provided). `reconnecting` / `offline` are
5525
+ * for hosts with a live/cached transport: F0 renders the transcript exactly
5526
+ * like `ready` — deliberately NO banner (per-message sending/failed states
5527
+ * already communicate connectivity, WhatsApp-style) — falling back to the
5528
+ * skeleton only when there are no messages to show yet. Hosts with a simple
5529
+ * request/response lifecycle can keep using the original three states.
5530
+ */
5531
+ export declare type F0ChatStatus = "connecting" | "ready" | "reconnecting" | "offline" | "error";
5532
+
5533
+ /**
5534
+ * Membership / lifecycle events rendered as a centered system row. Closed
5535
+ * union — the host maps unknown transport event kinds to a body-only system
5536
+ * message (the plain-text fallback). Room to grow: "channel.renamed", ….
5537
+ */
5538
+ export declare type F0ChatSystemEvent = "member.added" | "member.removed" | "member.left";
5539
+
5540
+ /**
5541
+ * A transcript item that is ABOUT the conversation, not from a person: no
5542
+ * author, no isMine, no reactions/replies/status — by construction. Rendered
5543
+ * as a centered row (like the date separator). factorial → a Stream message
5544
+ * with `type: "system"`; `system` comes from its custom fields, `body` from
5545
+ * its free-form `text`.
5546
+ */
5547
+ export declare type F0ChatSystemMessage = {
5548
+ type: "system";
5549
+ id: string;
5550
+ /** ISO timestamp — participates in day separators and ordering. */
5551
+ createdAt: string;
5552
+ /** Structured payload → avatar-tag sentence. Omit to render `body` as-is. */
5553
+ system?: F0ChatSystemPayload;
5554
+ /** Plain-text fallback (e.g. GetStream's free-form system `text`), shown
5555
+ * centered when `system` is absent or the event kind is unknown. */
5556
+ body?: string;
5557
+ };
5558
+
5559
+ /** Structured system payload → rendered as a sentence with inline person tags. */
5560
+ export declare type F0ChatSystemPayload = {
5561
+ event: F0ChatSystemEvent;
5562
+ /**
5563
+ * The people the event is about. One message can carry several — the host /
5564
+ * adapter coalesces bursts into one item (Slack-style "Ana, Luis and 2
5565
+ * more") by REPLACING the previous item with an updated `members` array
5566
+ * (same id); coalescing never happens in the view layer.
5567
+ */
5568
+ members: F0ChatUser[];
5569
+ /** How many more people beyond `members` (host truncation) — added to the
5570
+ * "+N" overflow tag on top of the visual max. */
5571
+ remainingCount?: number;
5572
+ /** Who performed the action (the admin who added/removed), when known.
5573
+ * Not rendered today; reserved for "added by X" templates. */
5574
+ actor?: F0ChatUser;
5575
+ };
5308
5576
 
5309
5577
  /** A participant in a conversation. */
5310
5578
  export declare type F0ChatUser = {
@@ -5317,6 +5585,20 @@ export declare type F0ChatUser = {
5317
5585
  profileHref?: string;
5318
5586
  };
5319
5587
 
5588
+ /**
5589
+ * A voice note: recorded in the composer (mic button) and rendered as an audio
5590
+ * player with playback-speed control. factorial → a Stream attachment
5591
+ * `{ type: "voice_recording", asset_url, duration }`.
5592
+ */
5593
+ export declare type F0ChatVoiceAttachment = {
5594
+ kind: "voice";
5595
+ url: string;
5596
+ /** Recording length in seconds (shown before playback starts). */
5597
+ durationSeconds?: number;
5598
+ mimeType?: string;
5599
+ name?: string;
5600
+ };
5601
+
5320
5602
  export declare type F0FileAction = {
5321
5603
  icon?: IconType;
5322
5604
  label: string;
@@ -6983,6 +7265,10 @@ declare const internalAvatarSizes: readonly ["xsmall", "small", "medium", "large
6983
7265
 
6984
7266
  declare const internalAvatarTypes: readonly ["base", "rounded"];
6985
7267
 
7268
+ export declare const isSystemMessage: (item: F0ChatItem) => item is F0ChatSystemMessage;
7269
+
7270
+ export declare const isUserMessage: (item: F0ChatItem) => item is F0ChatMessage;
7271
+
6986
7272
  export declare function Item({ item, counter, isActive, collapsible, isExpanded, onToggleExpanded, sortable, children, onDragOver, onDragLeave, onDrop, canDropInside, currentParentId, justDropped, }: TOCItemProps): JSX_2.Element;
6987
7273
 
6988
7274
  export declare type ItemActionsDefinition<T extends RecordType> = (item: T) => ActionDefinition[] | undefined;