@liveblocks/react-ui 3.1.3 → 3.2.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.
Files changed (71) hide show
  1. package/dist/_private/index.d.cts +272 -37
  2. package/dist/_private/index.d.ts +272 -37
  3. package/dist/components/AiChat.cjs +213 -62
  4. package/dist/components/AiChat.cjs.map +1 -1
  5. package/dist/components/AiChat.js +215 -64
  6. package/dist/components/AiChat.js.map +1 -1
  7. package/dist/components/Composer.cjs +2 -0
  8. package/dist/components/Composer.cjs.map +1 -1
  9. package/dist/components/Composer.js +2 -0
  10. package/dist/components/Composer.js.map +1 -1
  11. package/dist/components/Thread.cjs +2 -0
  12. package/dist/components/Thread.cjs.map +1 -1
  13. package/dist/components/Thread.js +2 -0
  14. package/dist/components/Thread.js.map +1 -1
  15. package/dist/components/internal/AiChatAssistantMessage.cjs +38 -22
  16. package/dist/components/internal/AiChatAssistantMessage.cjs.map +1 -1
  17. package/dist/components/internal/AiChatAssistantMessage.js +38 -22
  18. package/dist/components/internal/AiChatAssistantMessage.js.map +1 -1
  19. package/dist/components/internal/AiChatUserMessage.cjs +2 -1
  20. package/dist/components/internal/AiChatUserMessage.cjs.map +1 -1
  21. package/dist/components/internal/AiChatUserMessage.js +2 -1
  22. package/dist/components/internal/AiChatUserMessage.js.map +1 -1
  23. package/dist/components/internal/Prose.cjs +15 -7
  24. package/dist/components/internal/Prose.cjs.map +1 -1
  25. package/dist/components/internal/Prose.js +16 -8
  26. package/dist/components/internal/Prose.js.map +1 -1
  27. package/dist/index.d.cts +277 -4
  28. package/dist/index.d.ts +277 -4
  29. package/dist/primitives/AiMessage/index.cjs +3 -2
  30. package/dist/primitives/AiMessage/index.cjs.map +1 -1
  31. package/dist/primitives/AiMessage/index.js +3 -2
  32. package/dist/primitives/AiMessage/index.js.map +1 -1
  33. package/dist/primitives/AiMessage/tool-invocation.cjs +6 -3
  34. package/dist/primitives/AiMessage/tool-invocation.cjs.map +1 -1
  35. package/dist/primitives/AiMessage/tool-invocation.js +6 -3
  36. package/dist/primitives/AiMessage/tool-invocation.js.map +1 -1
  37. package/dist/primitives/Composer/index.cjs +5 -2
  38. package/dist/primitives/Composer/index.cjs.map +1 -1
  39. package/dist/primitives/Composer/index.js +5 -2
  40. package/dist/primitives/Composer/index.js.map +1 -1
  41. package/dist/primitives/Composer/utils.cjs +1 -2
  42. package/dist/primitives/Composer/utils.cjs.map +1 -1
  43. package/dist/primitives/Composer/utils.js +1 -2
  44. package/dist/primitives/Composer/utils.js.map +1 -1
  45. package/dist/primitives/Markdown.cjs +267 -336
  46. package/dist/primitives/Markdown.cjs.map +1 -1
  47. package/dist/primitives/Markdown.js +269 -338
  48. package/dist/primitives/Markdown.js.map +1 -1
  49. package/dist/primitives/index.d.cts +4 -0
  50. package/dist/primitives/index.d.ts +4 -0
  51. package/dist/utils/use-observable.cjs +2 -2
  52. package/dist/utils/use-observable.cjs.map +1 -1
  53. package/dist/utils/use-observable.js +1 -1
  54. package/dist/utils/use-observable.js.map +1 -1
  55. package/dist/utils/use-visible.cjs +8 -6
  56. package/dist/utils/use-visible.cjs.map +1 -1
  57. package/dist/utils/use-visible.js +7 -5
  58. package/dist/utils/use-visible.js.map +1 -1
  59. package/dist/version.cjs +1 -1
  60. package/dist/version.js +1 -1
  61. package/package.json +4 -4
  62. package/src/styles/constants.css +2 -2
  63. package/src/styles/index.css +14 -1
  64. package/styles/dark/attributes.css +1 -1
  65. package/styles/dark/media-query.css +1 -1
  66. package/styles.css +1 -1
  67. package/styles.css.map +1 -1
  68. package/dist/utils/use-latest.cjs +0 -14
  69. package/dist/utils/use-latest.cjs.map +0 -1
  70. package/dist/utils/use-latest.js +0 -12
  71. package/dist/utils/use-latest.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.js","sources":["../../src/components/Composer.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentAttachment,\n CommentMixedAttachment,\n DM,\n} from \"@liveblocks/core\";\nimport { assertNever, Permission } from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport {\n useCreateRoomComment,\n useCreateRoomThread,\n useEditRoomComment,\n useLayoutEffect,\n useResolveMentionSuggestions,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport type {\n ComponentPropsWithoutRef,\n ComponentType,\n FocusEvent,\n FormEvent,\n ForwardedRef,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\n\nimport { useLiveblocksUiConfig } from \"../config\";\nimport { FLOATING_ELEMENT_SIDE_OFFSET, MENTION_CHARACTER } from \"../constants\";\nimport { AttachmentIcon } from \"../icons/Attachment\";\nimport { BoldIcon } from \"../icons/Bold\";\nimport { CodeIcon } from \"../icons/Code\";\nimport { EmojiIcon } from \"../icons/Emoji\";\nimport { ItalicIcon } from \"../icons/Italic\";\nimport { MentionIcon } from \"../icons/Mention\";\nimport { SendIcon } from \"../icons/Send\";\nimport { StrikethroughIcon } from \"../icons/Strikethrough\";\nimport type { ComposerOverrides, GlobalOverrides } from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport {\n useComposer,\n useComposerAttachmentsContext,\n useComposerEditorContext,\n} from \"../primitives/Composer/contexts\";\nimport type {\n ComposerEditorComponents,\n ComposerEditorLinkProps,\n ComposerEditorMentionProps,\n ComposerEditorMentionSuggestionsProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerMarkToggleProps,\n ComposerSubmitComment,\n} from \"../primitives/Composer/types\";\nimport { useComposerAttachmentsDropArea } from \"../primitives/Composer/utils\";\nimport type { ComposerBodyMark } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\nimport { FileAttachment } from \"./internal/Attachment\";\nimport { Attribution } from \"./internal/Attribution\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport type { EmojiPickerProps } from \"./internal/EmojiPicker\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\ninterface EditorActionProps extends ComponentPropsWithoutRef<\"button\"> {\n label: string;\n tooltipLabel?: string;\n}\n\ninterface EmojiEditorActionProps extends EditorActionProps {\n onPickerOpenChange?: EmojiPickerProps[\"onOpenChange\"];\n}\n\ninterface MarkToggleProps extends ComposerMarkToggleProps {\n icon?: ReactNode;\n shortcut?: string;\n}\n\ntype ComposerCreateThreadProps<M extends BaseMetadata> = {\n threadId?: never;\n commentId?: never;\n\n /**\n * The metadata of the thread to create.\n */\n metadata?: M;\n};\n\ntype ComposerCreateCommentProps = {\n /**\n * The ID of the thread to reply to.\n */\n threadId: string;\n commentId?: never;\n metadata?: never;\n};\n\ntype ComposerEditCommentProps = {\n /**\n * The ID of the thread to edit a comment in.\n */\n threadId: string;\n\n /**\n * The ID of the comment to edit.\n */\n commentId: string;\n metadata?: never;\n};\n\nexport type ComposerProps<M extends BaseMetadata = DM> = Omit<\n ComponentPropsWithoutRef<\"form\">,\n \"defaultValue\"\n> &\n (\n | ComposerCreateThreadProps<M>\n | ComposerCreateCommentProps\n | ComposerEditCommentProps\n ) & {\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n comment: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => Promise<void> | void;\n\n /**\n * The composer's initial value.\n */\n defaultValue?: ComposerEditorProps[\"defaultValue\"];\n\n /**\n * The composer's initial attachments.\n */\n defaultAttachments?: CommentAttachment[];\n\n /**\n * Whether the composer is collapsed. Setting a value will make the composer controlled.\n */\n collapsed?: boolean;\n\n /**\n * The event handler called when the collapsed state of the composer changes.\n */\n onCollapsedChange?: (collapsed: boolean) => void;\n\n /**\n * Whether the composer is initially collapsed. Setting a value will make the composer uncontrolled.\n */\n defaultCollapsed?: boolean;\n\n /**\n * Whether to show and allow adding attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to show formatting controls (e.g. a floating toolbar with formatting toggles when selecting text)\n */\n showFormattingControls?: boolean;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: ComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: ComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n showAttribution?: boolean;\n\n /**\n * @internal\n */\n roomId?: string;\n };\n\ninterface ComposerEditorContainerProps\n extends Pick<\n ComposerProps,\n | \"defaultValue\"\n | \"showAttachments\"\n | \"showFormattingControls\"\n | \"showAttribution\"\n | \"overrides\"\n | \"actions\"\n | \"autoFocus\"\n | \"disabled\"\n > {\n isCollapsed: boolean | undefined;\n onEmptyChange: (isEmpty: boolean) => void;\n hasResolveMentionSuggestions: boolean;\n onEmojiPickerOpenChange: (isOpen: boolean) => void;\n onEditorClick: (event: MouseEvent<HTMLDivElement>) => void;\n}\n\nfunction ComposerInsertMentionEditorAction({\n label,\n tooltipLabel,\n className,\n onClick,\n ...props\n}: EditorActionProps) {\n const { createMention } = useComposer();\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n createMention();\n }\n },\n [createMention, onClick]\n );\n\n return (\n <Tooltip content={tooltipLabel ?? label}>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={handleClick}\n aria-label={label}\n icon={<MentionIcon />}\n {...props}\n />\n </Tooltip>\n );\n}\n\nfunction ComposerInsertEmojiEditorAction({\n label,\n tooltipLabel,\n onPickerOpenChange,\n className,\n ...props\n}: EmojiEditorActionProps) {\n const { insertText } = useComposer();\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <EmojiPicker onEmojiSelect={insertText} onOpenChange={onPickerOpenChange}>\n <Tooltip content={tooltipLabel ?? label}>\n <EmojiPickerTrigger asChild>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n icon={<EmojiIcon />}\n {...props}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n );\n}\n\nfunction ComposerAttachFilesEditorAction({\n label,\n tooltipLabel,\n className,\n ...props\n}: EditorActionProps) {\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <Tooltip content={tooltipLabel ?? label}>\n <ComposerPrimitive.AttachFiles asChild>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n icon={<AttachmentIcon />}\n {...props}\n />\n </ComposerPrimitive.AttachFiles>\n </Tooltip>\n );\n}\n\nfunction ComposerMention({ mention }: ComposerEditorMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return (\n <ComposerPrimitive.Mention className=\"lb-composer-mention\">\n {MENTION_CHARACTER}\n <User userId={mention.id} />\n </ComposerPrimitive.Mention>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n}\n\nfunction ComposerMentionSuggestions({\n mentions,\n}: ComposerEditorMentionSuggestionsProps) {\n return mentions.length > 0 ? (\n <ComposerPrimitive.Suggestions className=\"lb-root lb-portal lb-elevation lb-composer-suggestions lb-composer-mention-suggestions\">\n <ComposerPrimitive.SuggestionsList className=\"lb-composer-suggestions-list lb-composer-mention-suggestions-list\">\n {mentions.map((mention) => {\n switch (mention.kind) {\n case \"user\":\n return (\n <ComposerPrimitive.SuggestionsListItem\n key={mention.id}\n className=\"lb-composer-suggestions-list-item lb-composer-mention-suggestion\"\n value={mention.id}\n >\n <Avatar\n userId={mention.id}\n className=\"lb-composer-mention-suggestion-avatar\"\n />\n <User\n userId={mention.id}\n className=\"lb-composer-mention-suggestion-user\"\n />\n </ComposerPrimitive.SuggestionsListItem>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n })}\n </ComposerPrimitive.SuggestionsList>\n </ComposerPrimitive.Suggestions>\n ) : null;\n}\n\nfunction MarkToggle({\n mark,\n icon,\n shortcut,\n children,\n ...props\n}: MarkToggleProps) {\n const $ = useOverrides();\n const label = useMemo(() => {\n return $.COMPOSER_TOGGLE_MARK(mark);\n }, [$, mark]);\n\n return (\n <ShortcutTooltip\n content={label}\n shortcut={shortcut}\n sideOffset={FLOATING_ELEMENT_SIDE_OFFSET + 2}\n >\n <ComposerPrimitive.MarkToggle mark={mark} asChild {...props}>\n <Button aria-label={label} variant=\"toolbar\" icon={icon}>\n {children}\n </Button>\n </ComposerPrimitive.MarkToggle>\n </ShortcutTooltip>\n );\n}\n\ntype MarkToggles = {\n [K in ComposerBodyMark]: ComponentType<PropsWithChildren>;\n};\n\nconst markToggles: MarkToggles = {\n bold: () => <MarkToggle mark=\"bold\" shortcut=\"Mod-B\" icon={<BoldIcon />} />,\n italic: () => (\n <MarkToggle mark=\"italic\" shortcut=\"Mod-I\" icon={<ItalicIcon />} />\n ),\n strikethrough: () => (\n <MarkToggle\n mark=\"strikethrough\"\n shortcut=\"Mod-Shift-S\"\n icon={<StrikethroughIcon />}\n />\n ),\n code: () => <MarkToggle mark=\"code\" shortcut=\"Mod-E\" icon={<CodeIcon />} />,\n};\n\nconst markTogglesList = Object.entries(markToggles).map(([mark, Toggle]) => (\n <Toggle key={mark} />\n));\n\nfunction ComposerFloatingToolbar() {\n return (\n <ComposerPrimitive.FloatingToolbar className=\"lb-root lb-portal lb-elevation lb-composer-floating-toolbar\">\n {markTogglesList}\n </ComposerPrimitive.FloatingToolbar>\n );\n}\n\nfunction ComposerLink({ href, children }: ComposerEditorLinkProps) {\n return (\n <ComposerPrimitive.Link href={href} className=\"lb-composer-link\">\n {children}\n </ComposerPrimitive.Link>\n );\n}\n\ninterface ComposerAttachmentsProps extends ComponentPropsWithoutRef<\"div\"> {\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n}\n\ninterface ComposerFileAttachmentProps extends ComponentPropsWithoutRef<\"div\"> {\n attachment: CommentMixedAttachment;\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n}\n\nfunction ComposerFileAttachment({\n attachment,\n className,\n overrides,\n ...props\n}: ComposerFileAttachmentProps) {\n const { removeAttachment } = useComposer();\n const { roomId } = useComposerEditorContext();\n\n const handleDeleteClick = useCallback(() => {\n removeAttachment(attachment.id);\n }, [attachment.id, removeAttachment]);\n\n return (\n <FileAttachment\n className={cn(\"lb-composer-attachment\", className)}\n {...props}\n attachment={attachment}\n onDeleteClick={handleDeleteClick}\n preventFocusOnDelete\n overrides={overrides}\n roomId={roomId}\n />\n );\n}\n\nfunction ComposerAttachments({\n overrides,\n className,\n ...props\n}: ComposerAttachmentsProps) {\n const { attachments } = useComposer();\n\n if (attachments.length === 0) {\n return null;\n }\n\n return (\n <div className={cn(\"lb-composer-attachments\", className)} {...props}>\n <div className=\"lb-attachments\">\n {attachments.map((attachment) => {\n return (\n <ComposerFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n />\n );\n })}\n </div>\n </div>\n );\n}\n\nconst editorRequiredComponents: ComposerEditorComponents = {\n Mention: ComposerMention,\n MentionSuggestions: ComposerMentionSuggestions,\n Link: ComposerLink,\n};\n\nfunction ComposerEditorContainer({\n showAttachments = true,\n showFormattingControls = true,\n showAttribution,\n defaultValue,\n isCollapsed,\n overrides,\n actions,\n autoFocus,\n disabled,\n hasResolveMentionSuggestions,\n onEmojiPickerOpenChange,\n onEmptyChange,\n onEditorClick,\n}: ComposerEditorContainerProps) {\n const { isEmpty } = useComposer();\n const { hasMaxAttachments } = useComposerAttachmentsContext();\n const $ = useOverrides(overrides);\n const components = useMemo(() => {\n return {\n ...editorRequiredComponents,\n FloatingToolbar: showFormattingControls\n ? ComposerFloatingToolbar\n : undefined,\n };\n }, [showFormattingControls]);\n\n const [isDraggingOver, dropAreaProps] = useComposerAttachmentsDropArea({\n disabled: disabled || hasMaxAttachments,\n });\n\n useLayoutEffect(() => {\n onEmptyChange(isEmpty);\n }, [isEmpty, onEmptyChange]);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <div className=\"lb-composer-editor-container\" {...dropAreaProps}>\n <ComposerPrimitive.Editor\n className=\"lb-composer-editor\"\n onClick={onEditorClick}\n placeholder={$.COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n autoFocus={autoFocus}\n components={components}\n disabled={disabled}\n dir={$.dir}\n />\n {showAttachments && <ComposerAttachments overrides={overrides} />}\n {(!isCollapsed || isDraggingOver) && (\n <div className=\"lb-composer-footer\">\n <div className=\"lb-composer-editor-actions\">\n {hasResolveMentionSuggestions && (\n <ComposerInsertMentionEditorAction\n label={$.COMPOSER_INSERT_MENTION}\n disabled={disabled}\n />\n )}\n <ComposerInsertEmojiEditorAction\n label={$.COMPOSER_INSERT_EMOJI}\n onPickerOpenChange={onEmojiPickerOpenChange}\n disabled={disabled}\n />\n {showAttachments && (\n <ComposerAttachFilesEditorAction\n label={$.COMPOSER_ATTACH_FILES}\n disabled={disabled}\n />\n )}\n </div>\n {showAttribution && <Attribution />}\n <div className=\"lb-composer-actions\">\n {actions ?? (\n <>\n <ShortcutTooltip content={$.COMPOSER_SEND} shortcut=\"Enter\">\n <ComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-composer-action\"\n variant=\"primary\"\n aria-label={$.COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n )}\n </div>\n </div>\n )}\n {showAttachments && isDraggingOver && (\n <div className=\"lb-composer-attachments-drop-area\">\n <div className=\"lb-composer-attachments-drop-area-label\">\n <AttachmentIcon />\n {$.COMPOSER_ATTACH_FILES}\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport const ComposerRoomIdContext = createContext<string | null>(null);\n\n/**\n * Displays a composer to create comments.\n *\n * @example\n * <Composer />\n */\nexport const Composer = forwardRef(\n <M extends BaseMetadata = DM>(\n {\n threadId,\n commentId,\n metadata,\n defaultValue,\n defaultAttachments,\n onComposerSubmit,\n collapsed: controlledCollapsed,\n defaultCollapsed,\n onCollapsedChange: controlledOnCollapsedChange,\n overrides,\n actions,\n onBlur,\n className,\n onFocus,\n autoFocus,\n disabled,\n showAttachments = true,\n showFormattingControls = true,\n showAttribution,\n roomId: _roomId,\n ...props\n }: ComposerProps<M>,\n forwardedRef: ForwardedRef<HTMLFormElement>\n ) => {\n const room = useRoom({ allowOutsideRoom: true });\n\n const roomId = _roomId !== undefined ? _roomId : room?.id;\n if (roomId === undefined) {\n throw new Error(\n \"Composer must be a descendant of RoomProvider component\"\n );\n }\n\n const createThread = useCreateRoomThread(roomId);\n const createComment = useCreateRoomComment(roomId);\n const editComment = useEditRoomComment(roomId);\n const { preventUnsavedComposerChanges } = useLiveblocksUiConfig();\n const hasResolveMentionSuggestions =\n useResolveMentionSuggestions() !== undefined;\n const isEmptyRef = useRef(true);\n const isEmojiPickerOpenRef = useRef(false);\n const $ = useOverrides(overrides);\n const [isCollapsed, onCollapsedChange] = useControllableState(\n defaultCollapsed ?? false,\n controlledCollapsed,\n controlledOnCollapsedChange\n );\n\n const canCommentFallback = useSyncExternalStore(\n useCallback(\n (callback) => {\n if (room === null) return () => {};\n return room.events.self.subscribeOnce(callback);\n },\n [room]\n ),\n useCallback(() => {\n return room?.getSelf()?.canComment ?? true;\n }, [room]),\n useCallback(() => true, [])\n );\n\n const permissions = useRoomPermissions(roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : canCommentFallback;\n\n const setEmptyRef = useCallback((isEmpty: boolean) => {\n isEmptyRef.current = isEmpty;\n }, []);\n\n const setEmojiPickerOpenRef = useCallback((isEmojiPickerOpen: boolean) => {\n isEmojiPickerOpenRef.current = isEmojiPickerOpen;\n }, []);\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLFormElement>) => {\n onFocus?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n if (isEmptyRef.current && canComment) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, onFocus, canComment]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLFormElement>) => {\n onBlur?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n const isOutside = !event.currentTarget.contains(\n event.relatedTarget ?? document.activeElement\n );\n\n if (isOutside && isEmptyRef.current && !isEmojiPickerOpenRef.current) {\n onCollapsedChange?.(true);\n }\n },\n [onBlur, onCollapsedChange]\n );\n\n const handleEditorClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n event.stopPropagation();\n\n if (isEmptyRef.current && canComment) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, canComment]\n );\n\n const handleComposerSubmit = useCallback(\n (comment: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(comment, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n\n if (commentId && threadId) {\n editComment({\n commentId,\n threadId,\n body: comment.body,\n attachments: comment.attachments,\n });\n } else if (threadId) {\n createComment({\n threadId,\n body: comment.body,\n attachments: comment.attachments,\n });\n } else {\n createThread({\n body: comment.body,\n metadata: metadata ?? {},\n attachments: comment.attachments,\n });\n }\n },\n [\n commentId,\n createComment,\n createThread,\n editComment,\n metadata,\n onComposerSubmit,\n threadId,\n ]\n );\n\n return (\n <TooltipProvider>\n <ComposerPrimitive.Form\n onComposerSubmit={handleComposerSubmit}\n className={cn(\"lb-root lb-composer lb-composer-form\", className)}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n data-collapsed={isCollapsed ? \"\" : undefined}\n onFocus={handleFocus}\n onBlur={handleBlur}\n disabled={disabled || !canComment}\n defaultAttachments={defaultAttachments}\n pasteFilesAsAttachments={showAttachments}\n preventUnsavedChanges={preventUnsavedComposerChanges}\n roomId={roomId}\n >\n <ComposerEditorContainer\n defaultValue={defaultValue}\n actions={actions}\n overrides={overrides}\n isCollapsed={isCollapsed}\n showAttachments={showAttachments}\n showAttribution={showAttribution}\n showFormattingControls={showFormattingControls}\n hasResolveMentionSuggestions={hasResolveMentionSuggestions}\n onEmptyChange={setEmptyRef}\n onEmojiPickerOpenChange={setEmojiPickerOpenRef}\n onEditorClick={handleEditorClick}\n autoFocus={autoFocus}\n disabled={disabled}\n />\n </ComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = DM>(\n props: ComposerProps<M> & RefAttributes<HTMLFormElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoOA;AAA2C;AACzC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AAAoB;AAEhB;AAEA;AACE;AACA;AAAc;AAChB;AACF;AACuB;AAGzB;AACG;AAAiC;AAC/B;AACqD;AACrC;AACN;AACG;AACO;AACf;AACN;AAGN;AAEA;AAAyC;AACvC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAA2B;AAA0B;AACnD;AAAiC;AAC/B;AAA0B;AACxB;AACqD;AACrC;AACN;AACG;AACK;AACb;AACN;AACF;AACF;AAGN;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAiC;AAC/B;AAAqC;AACnC;AACqD;AACrC;AACN;AACG;AACU;AAClB;AACN;AACF;AAGN;AAEA;AACE;AAAsB;AAElB;AACG;AAAoC;AAClC;AAAA;AACA;AAAqB;AAAI;AAAA;AAC5B;AAIF;AAAyD;AAE/D;AAEA;AAAoC;AAEpC;AACE;AACG;AAAwC;AACtC;AAA4C;AAEzC;AAAsB;AAElB;AACG;AAEW;AACK;AAEf;AAAC;AACiB;AACN;AACZ;AACC;AACiB;AACN;AACZ;AAAA;AACF;AAIF;AAAyD;AAC7D;AACD;AACH;AAGN;AAEA;AAAoB;AAClB;AACA;AACA;AACA;AAEF;AACE;AACA;AACE;AAAkC;AAGpC;AACG;AACU;AACT;AAC2C;AAE1C;AAA6B;AAAmB;AAAK;AACnD;AAAmB;AAAe;AAAU;AAC1C;AACH;AACF;AAGN;AAMA;AAAiC;AAClB;AAAgB;AAAgB;AAAwB;AAAI;AAEtE;AAAgB;AAAkB;AAA0B;AAAI;AAGhE;AACM;AACI;AACgB;AAC3B;AAEW;AAAgB;AAAgB;AAAwB;AACvE;AAEA;AAIA;AACE;AACG;AAA4C;AAC1C;AAGP;AAEA;AACE;AACG;AAAuB;AAAsB;AAC3C;AAGP;AAWA;AAAgC;AAC9B;AACA;AACA;AAEF;AACE;AACA;AAEA;AACE;AAA8B;AAGhC;AACG;AACkD;AAC7C;AACJ;AACe;AACK;AACpB;AACA;AAGN;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAO;AAGT;AACG;AAAsD;AAAO;AAC3D;AAAc;AAEX;AACG;AAEC;AACA;AACF;AAEH;AACH;AAGN;AAEA;AAA2D;AAChD;AACW;AAEtB;AAEA;AAAiC;AACb;AACO;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AACA;AACE;AAAO;AACF;AAGC;AACN;AAGF;AAAuE;AAC/C;AAGxB;AACE;AAAqB;AAGvB;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAc;AAAmC;AAChD;AAAC;AACW;AACD;AACM;AACf;AACA;AACA;AACA;AACO;AACT;AACqB;AAAoB;AAAsB;AAE5D;AAAc;AACb;AAAC;AAAc;AACZ;AACE;AACU;AACT;AACF;AAED;AACU;AACW;AACpB;AACF;AAEG;AACU;AACT;AACF;AAAA;AAEJ;AACiC;AAChC;AAAc;AAEX;AACG;AAA2B;AAAwB;AACjD;AAAgC;AAC9B;AACgB;AACN;AACC;AACF;AACM;AACE;AAClB;AACF;AACF;AACF;AAEJ;AAAA;AACF;AAGC;AAAc;AACZ;AAAc;AACb;AAAgB;AACb;AAAA;AACL;AACF;AAAA;AAIR;AAEa;AAQN;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACkB;AACO;AACzB;AACQ;AACL;AAIL;AAEA;AACA;AACE;AAAU;AACR;AACF;AAGF;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AACnB;AACpB;AACA;AAGF;AAA2B;AACzB;AAEI;AAAmB;AAAa;AAChC;AAA8C;AAChD;AACK;AACP;AAEE;AAAsC;AAC/B;AACiB;AAG5B;AACA;AAMA;AACE;AAAqB;AAGvB;AACE;AAA+B;AAGjC;AAAoB;AAEhB;AAEA;AACE;AAAA;AAGF;AACE;AAAyB;AAC3B;AACF;AACuC;AAGzC;AAAmB;AAEf;AAEA;AACE;AAAA;AAGF;AAAuC;AACL;AAGlC;AACE;AAAwB;AAC1B;AACF;AAC0B;AAG5B;AAA0B;AAEtB;AAEA;AACE;AAAyB;AAC3B;AACF;AAC8B;AAGhC;AAA6B;AAEzB;AAEA;AACE;AAAA;AAGF;AAEA;AACE;AAAY;AACV;AACA;AACc;AACO;AACtB;AAED;AAAc;AACZ;AACc;AACO;AACtB;AAED;AAAa;AACG;AACS;AACF;AACtB;AACH;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AACG;AACE;AACmB;AAC6C;AACxD;AACH;AACC;AAC8B;AAC1B;AACD;AACe;AACvB;AACyB;AACF;AACvB;AAEC;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACU;AACV;AACf;AACA;AACF;AACF;AACF;AAGN;;"}
1
+ {"version":3,"file":"Composer.js","sources":["../../src/components/Composer.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentAttachment,\n CommentMixedAttachment,\n DM,\n} from \"@liveblocks/core\";\nimport { assertNever, Permission } from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport {\n useCreateRoomComment,\n useCreateRoomThread,\n useEditRoomComment,\n useLayoutEffect,\n useResolveMentionSuggestions,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport type {\n ComponentPropsWithoutRef,\n ComponentType,\n FocusEvent,\n FormEvent,\n ForwardedRef,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\n\nimport { useLiveblocksUiConfig } from \"../config\";\nimport { FLOATING_ELEMENT_SIDE_OFFSET, MENTION_CHARACTER } from \"../constants\";\nimport { AttachmentIcon } from \"../icons/Attachment\";\nimport { BoldIcon } from \"../icons/Bold\";\nimport { CodeIcon } from \"../icons/Code\";\nimport { EmojiIcon } from \"../icons/Emoji\";\nimport { ItalicIcon } from \"../icons/Italic\";\nimport { MentionIcon } from \"../icons/Mention\";\nimport { SendIcon } from \"../icons/Send\";\nimport { StrikethroughIcon } from \"../icons/Strikethrough\";\nimport type { ComposerOverrides, GlobalOverrides } from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport {\n useComposer,\n useComposerAttachmentsContext,\n useComposerEditorContext,\n} from \"../primitives/Composer/contexts\";\nimport type {\n ComposerEditorComponents,\n ComposerEditorLinkProps,\n ComposerEditorMentionProps,\n ComposerEditorMentionSuggestionsProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerMarkToggleProps,\n ComposerSubmitComment,\n} from \"../primitives/Composer/types\";\nimport { useComposerAttachmentsDropArea } from \"../primitives/Composer/utils\";\nimport type { ComposerBodyMark } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\nimport { FileAttachment } from \"./internal/Attachment\";\nimport { Attribution } from \"./internal/Attribution\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport type { EmojiPickerProps } from \"./internal/EmojiPicker\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\ninterface EditorActionProps extends ComponentPropsWithoutRef<\"button\"> {\n label: string;\n tooltipLabel?: string;\n}\n\ninterface EmojiEditorActionProps extends EditorActionProps {\n onPickerOpenChange?: EmojiPickerProps[\"onOpenChange\"];\n}\n\ninterface MarkToggleProps extends ComposerMarkToggleProps {\n icon?: ReactNode;\n shortcut?: string;\n}\n\ntype ComposerCreateThreadProps<M extends BaseMetadata> = {\n threadId?: never;\n commentId?: never;\n\n /**\n * The metadata of the thread to create.\n */\n metadata?: M;\n};\n\ntype ComposerCreateCommentProps = {\n /**\n * The ID of the thread to reply to.\n */\n threadId: string;\n commentId?: never;\n metadata?: never;\n};\n\ntype ComposerEditCommentProps = {\n /**\n * The ID of the thread to edit a comment in.\n */\n threadId: string;\n\n /**\n * The ID of the comment to edit.\n */\n commentId: string;\n metadata?: never;\n};\n\nexport type ComposerProps<M extends BaseMetadata = DM> = Omit<\n ComponentPropsWithoutRef<\"form\">,\n \"defaultValue\"\n> &\n (\n | ComposerCreateThreadProps<M>\n | ComposerCreateCommentProps\n | ComposerEditCommentProps\n ) & {\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n comment: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => Promise<void> | void;\n\n /**\n * The composer's initial value.\n */\n defaultValue?: ComposerEditorProps[\"defaultValue\"];\n\n /**\n * The composer's initial attachments.\n */\n defaultAttachments?: CommentAttachment[];\n\n /**\n * Whether the composer is collapsed. Setting a value will make the composer controlled.\n */\n collapsed?: boolean;\n\n /**\n * The event handler called when the collapsed state of the composer changes.\n */\n onCollapsedChange?: (collapsed: boolean) => void;\n\n /**\n * Whether the composer is initially collapsed. Setting a value will make the composer uncontrolled.\n */\n defaultCollapsed?: boolean;\n\n /**\n * Whether to show and allow adding attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to show formatting controls (e.g. a floating toolbar with formatting toggles when selecting text)\n */\n showFormattingControls?: boolean;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: ComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: ComposerEditorProps[\"autoFocus\"];\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurOnSubmit?: boolean;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n showAttribution?: boolean;\n\n /**\n * @internal\n */\n roomId?: string;\n };\n\ninterface ComposerEditorContainerProps\n extends Pick<\n ComposerProps,\n | \"defaultValue\"\n | \"showAttachments\"\n | \"showFormattingControls\"\n | \"showAttribution\"\n | \"overrides\"\n | \"actions\"\n | \"autoFocus\"\n | \"disabled\"\n > {\n isCollapsed: boolean | undefined;\n onEmptyChange: (isEmpty: boolean) => void;\n hasResolveMentionSuggestions: boolean;\n onEmojiPickerOpenChange: (isOpen: boolean) => void;\n onEditorClick: (event: MouseEvent<HTMLDivElement>) => void;\n}\n\nfunction ComposerInsertMentionEditorAction({\n label,\n tooltipLabel,\n className,\n onClick,\n ...props\n}: EditorActionProps) {\n const { createMention } = useComposer();\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n createMention();\n }\n },\n [createMention, onClick]\n );\n\n return (\n <Tooltip content={tooltipLabel ?? label}>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={handleClick}\n aria-label={label}\n icon={<MentionIcon />}\n {...props}\n />\n </Tooltip>\n );\n}\n\nfunction ComposerInsertEmojiEditorAction({\n label,\n tooltipLabel,\n onPickerOpenChange,\n className,\n ...props\n}: EmojiEditorActionProps) {\n const { insertText } = useComposer();\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <EmojiPicker onEmojiSelect={insertText} onOpenChange={onPickerOpenChange}>\n <Tooltip content={tooltipLabel ?? label}>\n <EmojiPickerTrigger asChild>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n icon={<EmojiIcon />}\n {...props}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n );\n}\n\nfunction ComposerAttachFilesEditorAction({\n label,\n tooltipLabel,\n className,\n ...props\n}: EditorActionProps) {\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <Tooltip content={tooltipLabel ?? label}>\n <ComposerPrimitive.AttachFiles asChild>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n icon={<AttachmentIcon />}\n {...props}\n />\n </ComposerPrimitive.AttachFiles>\n </Tooltip>\n );\n}\n\nfunction ComposerMention({ mention }: ComposerEditorMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return (\n <ComposerPrimitive.Mention className=\"lb-composer-mention\">\n {MENTION_CHARACTER}\n <User userId={mention.id} />\n </ComposerPrimitive.Mention>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n}\n\nfunction ComposerMentionSuggestions({\n mentions,\n}: ComposerEditorMentionSuggestionsProps) {\n return mentions.length > 0 ? (\n <ComposerPrimitive.Suggestions className=\"lb-root lb-portal lb-elevation lb-composer-suggestions lb-composer-mention-suggestions\">\n <ComposerPrimitive.SuggestionsList className=\"lb-composer-suggestions-list lb-composer-mention-suggestions-list\">\n {mentions.map((mention) => {\n switch (mention.kind) {\n case \"user\":\n return (\n <ComposerPrimitive.SuggestionsListItem\n key={mention.id}\n className=\"lb-composer-suggestions-list-item lb-composer-mention-suggestion\"\n value={mention.id}\n >\n <Avatar\n userId={mention.id}\n className=\"lb-composer-mention-suggestion-avatar\"\n />\n <User\n userId={mention.id}\n className=\"lb-composer-mention-suggestion-user\"\n />\n </ComposerPrimitive.SuggestionsListItem>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n })}\n </ComposerPrimitive.SuggestionsList>\n </ComposerPrimitive.Suggestions>\n ) : null;\n}\n\nfunction MarkToggle({\n mark,\n icon,\n shortcut,\n children,\n ...props\n}: MarkToggleProps) {\n const $ = useOverrides();\n const label = useMemo(() => {\n return $.COMPOSER_TOGGLE_MARK(mark);\n }, [$, mark]);\n\n return (\n <ShortcutTooltip\n content={label}\n shortcut={shortcut}\n sideOffset={FLOATING_ELEMENT_SIDE_OFFSET + 2}\n >\n <ComposerPrimitive.MarkToggle mark={mark} asChild {...props}>\n <Button aria-label={label} variant=\"toolbar\" icon={icon}>\n {children}\n </Button>\n </ComposerPrimitive.MarkToggle>\n </ShortcutTooltip>\n );\n}\n\ntype MarkToggles = {\n [K in ComposerBodyMark]: ComponentType<PropsWithChildren>;\n};\n\nconst markToggles: MarkToggles = {\n bold: () => <MarkToggle mark=\"bold\" shortcut=\"Mod-B\" icon={<BoldIcon />} />,\n italic: () => (\n <MarkToggle mark=\"italic\" shortcut=\"Mod-I\" icon={<ItalicIcon />} />\n ),\n strikethrough: () => (\n <MarkToggle\n mark=\"strikethrough\"\n shortcut=\"Mod-Shift-S\"\n icon={<StrikethroughIcon />}\n />\n ),\n code: () => <MarkToggle mark=\"code\" shortcut=\"Mod-E\" icon={<CodeIcon />} />,\n};\n\nconst markTogglesList = Object.entries(markToggles).map(([mark, Toggle]) => (\n <Toggle key={mark} />\n));\n\nfunction ComposerFloatingToolbar() {\n return (\n <ComposerPrimitive.FloatingToolbar className=\"lb-root lb-portal lb-elevation lb-composer-floating-toolbar\">\n {markTogglesList}\n </ComposerPrimitive.FloatingToolbar>\n );\n}\n\nfunction ComposerLink({ href, children }: ComposerEditorLinkProps) {\n return (\n <ComposerPrimitive.Link href={href} className=\"lb-composer-link\">\n {children}\n </ComposerPrimitive.Link>\n );\n}\n\ninterface ComposerAttachmentsProps extends ComponentPropsWithoutRef<\"div\"> {\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n}\n\ninterface ComposerFileAttachmentProps extends ComponentPropsWithoutRef<\"div\"> {\n attachment: CommentMixedAttachment;\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n}\n\nfunction ComposerFileAttachment({\n attachment,\n className,\n overrides,\n ...props\n}: ComposerFileAttachmentProps) {\n const { removeAttachment } = useComposer();\n const { roomId } = useComposerEditorContext();\n\n const handleDeleteClick = useCallback(() => {\n removeAttachment(attachment.id);\n }, [attachment.id, removeAttachment]);\n\n return (\n <FileAttachment\n className={cn(\"lb-composer-attachment\", className)}\n {...props}\n attachment={attachment}\n onDeleteClick={handleDeleteClick}\n preventFocusOnDelete\n overrides={overrides}\n roomId={roomId}\n />\n );\n}\n\nfunction ComposerAttachments({\n overrides,\n className,\n ...props\n}: ComposerAttachmentsProps) {\n const { attachments } = useComposer();\n\n if (attachments.length === 0) {\n return null;\n }\n\n return (\n <div className={cn(\"lb-composer-attachments\", className)} {...props}>\n <div className=\"lb-attachments\">\n {attachments.map((attachment) => {\n return (\n <ComposerFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n />\n );\n })}\n </div>\n </div>\n );\n}\n\nconst editorRequiredComponents: ComposerEditorComponents = {\n Mention: ComposerMention,\n MentionSuggestions: ComposerMentionSuggestions,\n Link: ComposerLink,\n};\n\nfunction ComposerEditorContainer({\n showAttachments = true,\n showFormattingControls = true,\n showAttribution,\n defaultValue,\n isCollapsed,\n overrides,\n actions,\n autoFocus,\n disabled,\n hasResolveMentionSuggestions,\n onEmojiPickerOpenChange,\n onEmptyChange,\n onEditorClick,\n}: ComposerEditorContainerProps) {\n const { isEmpty } = useComposer();\n const { hasMaxAttachments } = useComposerAttachmentsContext();\n const $ = useOverrides(overrides);\n const components = useMemo(() => {\n return {\n ...editorRequiredComponents,\n FloatingToolbar: showFormattingControls\n ? ComposerFloatingToolbar\n : undefined,\n };\n }, [showFormattingControls]);\n\n const [isDraggingOver, dropAreaProps] = useComposerAttachmentsDropArea({\n disabled: disabled || hasMaxAttachments,\n });\n\n useLayoutEffect(() => {\n onEmptyChange(isEmpty);\n }, [isEmpty, onEmptyChange]);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <div className=\"lb-composer-editor-container\" {...dropAreaProps}>\n <ComposerPrimitive.Editor\n className=\"lb-composer-editor\"\n onClick={onEditorClick}\n placeholder={$.COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n autoFocus={autoFocus}\n components={components}\n disabled={disabled}\n dir={$.dir}\n />\n {showAttachments && <ComposerAttachments overrides={overrides} />}\n {(!isCollapsed || isDraggingOver) && (\n <div className=\"lb-composer-footer\">\n <div className=\"lb-composer-editor-actions\">\n {hasResolveMentionSuggestions && (\n <ComposerInsertMentionEditorAction\n label={$.COMPOSER_INSERT_MENTION}\n disabled={disabled}\n />\n )}\n <ComposerInsertEmojiEditorAction\n label={$.COMPOSER_INSERT_EMOJI}\n onPickerOpenChange={onEmojiPickerOpenChange}\n disabled={disabled}\n />\n {showAttachments && (\n <ComposerAttachFilesEditorAction\n label={$.COMPOSER_ATTACH_FILES}\n disabled={disabled}\n />\n )}\n </div>\n {showAttribution && <Attribution />}\n <div className=\"lb-composer-actions\">\n {actions ?? (\n <>\n <ShortcutTooltip content={$.COMPOSER_SEND} shortcut=\"Enter\">\n <ComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-composer-action\"\n variant=\"primary\"\n aria-label={$.COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n )}\n </div>\n </div>\n )}\n {showAttachments && isDraggingOver && (\n <div className=\"lb-composer-attachments-drop-area\">\n <div className=\"lb-composer-attachments-drop-area-label\">\n <AttachmentIcon />\n {$.COMPOSER_ATTACH_FILES}\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport const ComposerRoomIdContext = createContext<string | null>(null);\n\n/**\n * Displays a composer to create comments.\n *\n * @example\n * <Composer />\n */\nexport const Composer = forwardRef(\n <M extends BaseMetadata = DM>(\n {\n threadId,\n commentId,\n metadata,\n defaultValue,\n defaultAttachments,\n onComposerSubmit,\n collapsed: controlledCollapsed,\n defaultCollapsed,\n onCollapsedChange: controlledOnCollapsedChange,\n overrides,\n actions,\n onBlur,\n className,\n onFocus,\n autoFocus,\n disabled,\n blurOnSubmit = true,\n showAttachments = true,\n showFormattingControls = true,\n showAttribution,\n roomId: _roomId,\n ...props\n }: ComposerProps<M>,\n forwardedRef: ForwardedRef<HTMLFormElement>\n ) => {\n const room = useRoom({ allowOutsideRoom: true });\n\n const roomId = _roomId !== undefined ? _roomId : room?.id;\n if (roomId === undefined) {\n throw new Error(\n \"Composer must be a descendant of RoomProvider component\"\n );\n }\n\n const createThread = useCreateRoomThread(roomId);\n const createComment = useCreateRoomComment(roomId);\n const editComment = useEditRoomComment(roomId);\n const { preventUnsavedComposerChanges } = useLiveblocksUiConfig();\n const hasResolveMentionSuggestions =\n useResolveMentionSuggestions() !== undefined;\n const isEmptyRef = useRef(true);\n const isEmojiPickerOpenRef = useRef(false);\n const $ = useOverrides(overrides);\n const [isCollapsed, onCollapsedChange] = useControllableState(\n defaultCollapsed ?? false,\n controlledCollapsed,\n controlledOnCollapsedChange\n );\n\n const canCommentFallback = useSyncExternalStore(\n useCallback(\n (callback) => {\n if (room === null) return () => {};\n return room.events.self.subscribeOnce(callback);\n },\n [room]\n ),\n useCallback(() => {\n return room?.getSelf()?.canComment ?? true;\n }, [room]),\n useCallback(() => true, [])\n );\n\n const permissions = useRoomPermissions(roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : canCommentFallback;\n\n const setEmptyRef = useCallback((isEmpty: boolean) => {\n isEmptyRef.current = isEmpty;\n }, []);\n\n const setEmojiPickerOpenRef = useCallback((isEmojiPickerOpen: boolean) => {\n isEmojiPickerOpenRef.current = isEmojiPickerOpen;\n }, []);\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLFormElement>) => {\n onFocus?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n if (isEmptyRef.current && canComment) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, onFocus, canComment]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLFormElement>) => {\n onBlur?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n const isOutside = !event.currentTarget.contains(\n event.relatedTarget ?? document.activeElement\n );\n\n if (isOutside && isEmptyRef.current && !isEmojiPickerOpenRef.current) {\n onCollapsedChange?.(true);\n }\n },\n [onBlur, onCollapsedChange]\n );\n\n const handleEditorClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n event.stopPropagation();\n\n if (isEmptyRef.current && canComment) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, canComment]\n );\n\n const handleComposerSubmit = useCallback(\n (comment: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(comment, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n\n if (commentId && threadId) {\n editComment({\n commentId,\n threadId,\n body: comment.body,\n attachments: comment.attachments,\n });\n } else if (threadId) {\n createComment({\n threadId,\n body: comment.body,\n attachments: comment.attachments,\n });\n } else {\n createThread({\n body: comment.body,\n metadata: metadata ?? {},\n attachments: comment.attachments,\n });\n }\n },\n [\n commentId,\n createComment,\n createThread,\n editComment,\n metadata,\n onComposerSubmit,\n threadId,\n ]\n );\n\n return (\n <TooltipProvider>\n <ComposerPrimitive.Form\n onComposerSubmit={handleComposerSubmit}\n className={cn(\"lb-root lb-composer lb-composer-form\", className)}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n data-collapsed={isCollapsed ? \"\" : undefined}\n onFocus={handleFocus}\n onBlur={handleBlur}\n disabled={disabled || !canComment}\n defaultAttachments={defaultAttachments}\n pasteFilesAsAttachments={showAttachments}\n preventUnsavedChanges={preventUnsavedComposerChanges}\n blurOnSubmit={blurOnSubmit}\n roomId={roomId}\n >\n <ComposerEditorContainer\n defaultValue={defaultValue}\n actions={actions}\n overrides={overrides}\n isCollapsed={isCollapsed}\n showAttachments={showAttachments}\n showAttribution={showAttribution}\n showFormattingControls={showFormattingControls}\n hasResolveMentionSuggestions={hasResolveMentionSuggestions}\n onEmptyChange={setEmptyRef}\n onEmojiPickerOpenChange={setEmojiPickerOpenRef}\n onEditorClick={handleEditorClick}\n autoFocus={autoFocus}\n disabled={disabled}\n />\n </ComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = DM>(\n props: ComposerProps<M> & RefAttributes<HTMLFormElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyOA;AAA2C;AACzC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AAAoB;AAEhB;AAEA;AACE;AACA;AAAc;AAChB;AACF;AACuB;AAGzB;AACG;AAAiC;AAC/B;AACqD;AACrC;AACN;AACG;AACO;AACf;AACN;AAGN;AAEA;AAAyC;AACvC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAA2B;AAA0B;AACnD;AAAiC;AAC/B;AAA0B;AACxB;AACqD;AACrC;AACN;AACG;AACK;AACb;AACN;AACF;AACF;AAGN;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAiC;AAC/B;AAAqC;AACnC;AACqD;AACrC;AACN;AACG;AACU;AAClB;AACN;AACF;AAGN;AAEA;AACE;AAAsB;AAElB;AACG;AAAoC;AAClC;AAAA;AACA;AAAqB;AAAI;AAAA;AAC5B;AAIF;AAAyD;AAE/D;AAEA;AAAoC;AAEpC;AACE;AACG;AAAwC;AACtC;AAA4C;AAEzC;AAAsB;AAElB;AACG;AAEW;AACK;AAEf;AAAC;AACiB;AACN;AACZ;AACC;AACiB;AACN;AACZ;AAAA;AACF;AAIF;AAAyD;AAC7D;AACD;AACH;AAGN;AAEA;AAAoB;AAClB;AACA;AACA;AACA;AAEF;AACE;AACA;AACE;AAAkC;AAGpC;AACG;AACU;AACT;AAC2C;AAE1C;AAA6B;AAAmB;AAAK;AACnD;AAAmB;AAAe;AAAU;AAC1C;AACH;AACF;AAGN;AAMA;AAAiC;AAClB;AAAgB;AAAgB;AAAwB;AAAI;AAEtE;AAAgB;AAAkB;AAA0B;AAAI;AAGhE;AACM;AACI;AACgB;AAC3B;AAEW;AAAgB;AAAgB;AAAwB;AACvE;AAEA;AAIA;AACE;AACG;AAA4C;AAC1C;AAGP;AAEA;AACE;AACG;AAAuB;AAAsB;AAC3C;AAGP;AAWA;AAAgC;AAC9B;AACA;AACA;AAEF;AACE;AACA;AAEA;AACE;AAA8B;AAGhC;AACG;AACkD;AAC7C;AACJ;AACe;AACK;AACpB;AACA;AAGN;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAO;AAGT;AACG;AAAsD;AAAO;AAC3D;AAAc;AAEX;AACG;AAEC;AACA;AACF;AAEH;AACH;AAGN;AAEA;AAA2D;AAChD;AACW;AAEtB;AAEA;AAAiC;AACb;AACO;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AACA;AACE;AAAO;AACF;AAGC;AACN;AAGF;AAAuE;AAC/C;AAGxB;AACE;AAAqB;AAGvB;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAc;AAAmC;AAChD;AAAC;AACW;AACD;AACM;AACf;AACA;AACA;AACA;AACO;AACT;AACqB;AAAoB;AAAsB;AAE5D;AAAc;AACb;AAAC;AAAc;AACZ;AACE;AACU;AACT;AACF;AAED;AACU;AACW;AACpB;AACF;AAEG;AACU;AACT;AACF;AAAA;AAEJ;AACiC;AAChC;AAAc;AAEX;AACG;AAA2B;AAAwB;AACjD;AAAgC;AAC9B;AACgB;AACN;AACC;AACF;AACM;AACE;AAClB;AACF;AACF;AACF;AAEJ;AAAA;AACF;AAGC;AAAc;AACZ;AAAc;AACb;AAAgB;AACb;AAAA;AACL;AACF;AAAA;AAIR;AAEa;AAQN;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACG;AACO;AACzB;AACQ;AACL;AAIL;AAEA;AACA;AACE;AAAU;AACR;AACF;AAGF;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AACnB;AACpB;AACA;AAGF;AAA2B;AACzB;AAEI;AAAmB;AAAa;AAChC;AAA8C;AAChD;AACK;AACP;AAEE;AAAsC;AAC/B;AACiB;AAG5B;AACA;AAMA;AACE;AAAqB;AAGvB;AACE;AAA+B;AAGjC;AAAoB;AAEhB;AAEA;AACE;AAAA;AAGF;AACE;AAAyB;AAC3B;AACF;AACuC;AAGzC;AAAmB;AAEf;AAEA;AACE;AAAA;AAGF;AAAuC;AACL;AAGlC;AACE;AAAwB;AAC1B;AACF;AAC0B;AAG5B;AAA0B;AAEtB;AAEA;AACE;AAAyB;AAC3B;AACF;AAC8B;AAGhC;AAA6B;AAEzB;AAEA;AACE;AAAA;AAGF;AAEA;AACE;AAAY;AACV;AACA;AACc;AACO;AACtB;AAED;AAAc;AACZ;AACc;AACO;AACtB;AAED;AAAa;AACG;AACS;AACF;AACtB;AACH;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AACG;AACE;AACmB;AAC6C;AACxD;AACH;AACC;AAC8B;AAC1B;AACD;AACe;AACvB;AACyB;AACF;AACvB;AACA;AAEC;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACU;AACV;AACf;AACA;AACF;AACF;AACF;AAGN;;"}
@@ -60,6 +60,7 @@ const Thread = react.forwardRef(
60
60
  onMentionClick,
61
61
  onAttachmentClick,
62
62
  onComposerSubmit,
63
+ blurComposerOnSubmit,
63
64
  overrides: overrides$1,
64
65
  components,
65
66
  className,
@@ -233,6 +234,7 @@ const Thread = react.forwardRef(
233
234
  showAttachments,
234
235
  showFormattingControls: showComposerFormattingControls,
235
236
  onComposerSubmit,
237
+ blurOnSubmit: blurComposerOnSubmit,
236
238
  overrides: {
237
239
  COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,
238
240
  COMPOSER_SEND: $.THREAD_COMPOSER_SEND,
@@ -1 +1 @@
1
- {"version":3,"file":"Thread.cjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DM,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { DropdownItem } from \"./internal/Dropdown\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<M extends BaseMetadata = DM>\n extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<M>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<M>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <M extends BaseMetadata = DM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n\n const children = (\n <Comment\n key={comment.id}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n autoMarkReadThreadId={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n additionalActionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n additionalActions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n additionalDropdownItemsBefore={\n isFirstComment ? (\n <DropdownItem\n onSelect={handleSubscribeChange}\n onClick={stopPropagation}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </DropdownItem>\n ) : null\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = DM>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsKO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AACG;AACE;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAAC;AAAc;AAEX;AACA;AAGA;AACG;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AACiB;AACC;AACV;AAEN;AACW;AACD;AAID;AAMa;AAGV;AACb;AACF;AAEA;AAID;AACW;AACD;AAKK;AAMR;AAEN;AAKV;AAGG;AACC;AAAC;AACW;AACI;AAEb;AAAe;AACd;AAAC;AAAwB;AAAqC;AAC3D;AAAA;AACL;AACF;AACC;AAAA;AAGH;AAEH;AACH;AAEG;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACW;AACe;AACP;AACd;AACL;AACe;AACjB;AAAA;AAEJ;AACF;AAGN;;"}
1
+ {"version":3,"file":"Thread.cjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DM,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { DropdownItem } from \"./internal/Dropdown\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<M extends BaseMetadata = DM>\n extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<M>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<M>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <M extends BaseMetadata = DM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n\n const children = (\n <Comment\n key={comment.id}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n autoMarkReadThreadId={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n additionalActionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n additionalActions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n additionalDropdownItemsBefore={\n isFirstComment ? (\n <DropdownItem\n onSelect={handleSubscribeChange}\n onClick={stopPropagation}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </DropdownItem>\n ) : null\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = DM>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2KO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AACG;AACE;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAAC;AAAc;AAEX;AACA;AAGA;AACG;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AACiB;AACC;AACV;AAEN;AACW;AACD;AAID;AAMa;AAGV;AACb;AACF;AAEA;AAID;AACW;AACD;AAKK;AAMR;AAEN;AAKV;AAGG;AACC;AAAC;AACW;AACI;AAEb;AAAe;AACd;AAAC;AAAwB;AAAqC;AAC3D;AAAA;AACL;AACF;AACC;AAAA;AAGH;AAEH;AACH;AAEG;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AACjB;AAAA;AAEJ;AACF;AAGN;;"}
@@ -39,6 +39,7 @@ const Thread = forwardRef(
39
39
  onMentionClick,
40
40
  onAttachmentClick,
41
41
  onComposerSubmit,
42
+ blurComposerOnSubmit,
42
43
  overrides,
43
44
  components,
44
45
  className,
@@ -212,6 +213,7 @@ const Thread = forwardRef(
212
213
  showAttachments,
213
214
  showFormattingControls: showComposerFormattingControls,
214
215
  onComposerSubmit,
216
+ blurOnSubmit: blurComposerOnSubmit,
215
217
  overrides: {
216
218
  COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,
217
219
  COMPOSER_SEND: $.THREAD_COMPOSER_SEND,
@@ -1 +1 @@
1
- {"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DM,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { DropdownItem } from \"./internal/Dropdown\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<M extends BaseMetadata = DM>\n extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<M>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<M>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <M extends BaseMetadata = DM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n\n const children = (\n <Comment\n key={comment.id}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n autoMarkReadThreadId={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n additionalActionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n additionalActions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n additionalDropdownItemsBefore={\n isFirstComment ? (\n <DropdownItem\n onSelect={handleSubscribeChange}\n onClick={stopPropagation}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </DropdownItem>\n ) : null\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = DM>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsKO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AACG;AACE;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAAC;AAAc;AAEX;AACA;AAGA;AACG;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AACiB;AACC;AACV;AAEN;AACW;AACD;AAID;AAMa;AAGV;AACb;AACF;AAEA;AAID;AACW;AACD;AAKK;AAMR;AAEN;AAKV;AAGG;AACC;AAAC;AACW;AACI;AAEb;AAAe;AACd;AAAC;AAAwB;AAAqC;AAC3D;AAAA;AACL;AACF;AACC;AAAA;AAGH;AAEH;AACH;AAEG;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACW;AACe;AACP;AACd;AACL;AACe;AACjB;AAAA;AAEJ;AACF;AAGN;;"}
1
+ {"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DM,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { DropdownItem } from \"./internal/Dropdown\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<M extends BaseMetadata = DM>\n extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<M>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<M>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <M extends BaseMetadata = DM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n\n const children = (\n <Comment\n key={comment.id}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n autoMarkReadThreadId={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n additionalActionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n additionalActions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n additionalDropdownItemsBefore={\n isFirstComment ? (\n <DropdownItem\n onSelect={handleSubscribeChange}\n onClick={stopPropagation}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </DropdownItem>\n ) : null\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = DM>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA2KO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AACG;AACE;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAAC;AAAc;AAEX;AACA;AAGA;AACG;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AACiB;AACC;AACV;AAEN;AACW;AACD;AAID;AAMa;AAGV;AACb;AACF;AAEA;AAID;AACW;AACD;AAKK;AAMR;AAEN;AAKV;AAGG;AACC;AAAC;AACW;AACI;AAEb;AAAe;AACd;AAAC;AAAwB;AAAqC;AAC3D;AAAA;AACL;AACF;AACC;AAAA;AAGH;AAEH;AACH;AAEG;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AACjB;AAAA;AAEJ;AACF;AAGN;;"}
@@ -2,7 +2,6 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var react = require('react');
5
- var components = require('../../components.cjs');
6
5
  var ChevronRight = require('../../icons/ChevronRight.cjs');
7
6
  var Warning = require('../../icons/Warning.cjs');
8
7
  var overrides = require('../../overrides.cjs');
@@ -15,7 +14,7 @@ var Prose = require('./Prose.cjs');
15
14
 
16
15
  const AiChatAssistantMessage = react.memo(
17
16
  react.forwardRef(
18
- ({ message, className, overrides: overrides$1, components: components$1, ...props }, forwardedRef) => {
17
+ ({ message, className, overrides: overrides$1, components, copilotId, ...props }, forwardedRef) => {
19
18
  const $ = overrides.useOverrides(overrides$1);
20
19
  let children = null;
21
20
  if (message.deletedAt !== void 0) {
@@ -31,23 +30,31 @@ const AiChatAssistantMessage = react.memo(
31
30
  });
32
31
  } else {
33
32
  children = /* @__PURE__ */ jsxRuntime.jsx(AssistantMessageContent, {
34
- message
33
+ message,
34
+ components,
35
+ copilotId
35
36
  });
36
37
  }
37
38
  } else if (message.status === "completed") {
38
39
  children = /* @__PURE__ */ jsxRuntime.jsx(AssistantMessageContent, {
39
- message
40
+ message,
41
+ components,
42
+ copilotId
40
43
  });
41
44
  } else if (message.status === "failed") {
42
45
  if (message.errorReason === "Aborted by user") {
43
46
  children = /* @__PURE__ */ jsxRuntime.jsx(AssistantMessageContent, {
44
- message
47
+ message,
48
+ components,
49
+ copilotId
45
50
  });
46
51
  } else {
47
52
  children = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
48
53
  children: [
49
54
  /* @__PURE__ */ jsxRuntime.jsx(AssistantMessageContent, {
50
- message
55
+ message,
56
+ components,
57
+ copilotId
51
58
  }),
52
59
  /* @__PURE__ */ jsxRuntime.jsxs("div", {
53
60
  className: "lb-ai-chat-message-error",
@@ -72,36 +79,42 @@ const AiChatAssistantMessage = react.memo(
72
79
  ref: forwardedRef,
73
80
  children: /* @__PURE__ */ jsxRuntime.jsx(overrides.OverridesProvider, {
74
81
  overrides: overrides$1,
75
- children: /* @__PURE__ */ jsxRuntime.jsx(components.ComponentsProvider, {
76
- components: components$1,
77
- children
78
- })
82
+ children
79
83
  })
80
84
  });
81
85
  }
82
86
  )
83
87
  );
84
- function AssistantMessageContent({ message }) {
88
+ function AssistantMessageContent({
89
+ message,
90
+ components,
91
+ copilotId
92
+ }) {
85
93
  return /* @__PURE__ */ jsxRuntime.jsx(index.Content, {
86
94
  message,
87
95
  components: {
88
- TextPart,
89
- ReasoningPart,
96
+ TextPart: (props) => /* @__PURE__ */ jsxRuntime.jsx(TextPart, {
97
+ ...props,
98
+ components
99
+ }),
100
+ ReasoningPart: (props) => /* @__PURE__ */ jsxRuntime.jsx(ReasoningPart, {
101
+ ...props,
102
+ components
103
+ }),
90
104
  ToolInvocationPart
91
105
  },
106
+ copilotId,
92
107
  className: "lb-ai-chat-message-content"
93
108
  });
94
109
  }
95
- function TextPart({ part }) {
110
+ function TextPart({ part, components }) {
96
111
  return /* @__PURE__ */ jsxRuntime.jsx(Prose.Prose, {
97
112
  content: part.text,
98
- className: "lb-ai-chat-message-text"
113
+ className: "lb-ai-chat-message-text",
114
+ components
99
115
  });
100
116
  }
101
- function ReasoningPart({
102
- part,
103
- isStreaming
104
- }) {
117
+ function ReasoningPart({ part, isStreaming, components }) {
105
118
  const [isOpen, setIsOpen] = react.useState(isStreaming);
106
119
  const $ = overrides.useOverrides();
107
120
  react.useEffect(() => {
@@ -130,7 +143,8 @@ function ReasoningPart({
130
143
  /* @__PURE__ */ jsxRuntime.jsx(index$1.Content, {
131
144
  className: "lb-collapsible-content",
132
145
  children: /* @__PURE__ */ jsxRuntime.jsx(Prose.Prose, {
133
- content: part.text
146
+ content: part.text,
147
+ components
134
148
  })
135
149
  })
136
150
  ]
@@ -138,7 +152,8 @@ function ReasoningPart({
138
152
  }
139
153
  function ToolInvocationPart({
140
154
  part,
141
- message
155
+ message,
156
+ copilotId
142
157
  }) {
143
158
  return /* @__PURE__ */ jsxRuntime.jsx("div", {
144
159
  className: "lb-ai-chat-message-tool-invocation",
@@ -163,7 +178,8 @@ function ToolInvocationPart({
163
178
  }),
164
179
  children: /* @__PURE__ */ jsxRuntime.jsx(toolInvocation.AiMessageToolInvocation, {
165
180
  part,
166
- message
181
+ message,
182
+ copilotId
167
183
  })
168
184
  })
169
185
  });
@@ -1 +1 @@
1
- {"version":3,"file":"AiChatAssistantMessage.cjs","sources":["../../../src/components/internal/AiChatAssistantMessage.tsx"],"sourcesContent":["import type { AiAssistantMessage, WithNavigation } from \"@liveblocks/core\";\nimport {\n type ComponentProps,\n forwardRef,\n memo,\n type ReactNode,\n useEffect,\n useState,\n} from \"react\";\n\nimport { ComponentsProvider, type GlobalComponents } from \"../../components\";\nimport { ChevronRightIcon } from \"../../icons/ChevronRight\";\nimport { WarningIcon } from \"../../icons/Warning\";\nimport {\n type AiChatMessageOverrides,\n type GlobalOverrides,\n OverridesProvider,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiMessage from \"../../primitives/AiMessage\";\nimport { AiMessageToolInvocation } from \"../../primitives/AiMessage/tool-invocation\";\nimport type {\n AiMessageContentReasoningPartProps,\n AiMessageContentTextPartProps,\n AiMessageContentToolInvocationPartProps,\n} from \"../../primitives/AiMessage/types\";\nimport * as Collapsible from \"../../primitives/Collapsible\";\nimport { cn } from \"../../utils/cn\";\nimport { ErrorBoundary } from \"../../utils/ErrorBoundary\";\nimport { Prose } from \"./Prose\";\n\ntype UiAssistantMessage = WithNavigation<AiAssistantMessage>;\n\n/* -------------------------------------------------------------------------------------------------\n * AiChatAssistantMessage\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiChatAssistantMessageProps extends ComponentProps<\"div\"> {\n /**\n * The message to display.\n */\n message: UiAssistantMessage;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiChatMessageOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\nexport const AiChatAssistantMessage = memo(\n forwardRef<HTMLDivElement, AiChatAssistantMessageProps>(\n ({ message, className, overrides, components, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n\n let children: ReactNode = null;\n\n if (message.deletedAt !== undefined) {\n children = (\n <div className=\"lb-ai-chat-message-deleted\">\n {$.AI_CHAT_MESSAGE_DELETED}\n </div>\n );\n } else if (\n message.status === \"generating\" ||\n message.status === \"awaiting-tool\"\n ) {\n if (message.contentSoFar.length === 0) {\n children = (\n <div className=\"lb-ai-chat-message-thinking lb-ai-chat-pending\">\n {$.AI_CHAT_MESSAGE_THINKING}\n </div>\n );\n } else {\n children = <AssistantMessageContent message={message} />;\n }\n } else if (message.status === \"completed\") {\n children = <AssistantMessageContent message={message} />;\n } else if (message.status === \"failed\") {\n // Do not include the error message if the user aborted the request.\n if (message.errorReason === \"Aborted by user\") {\n children = <AssistantMessageContent message={message} />;\n } else {\n children = (\n <>\n <AssistantMessageContent message={message} />\n\n <div className=\"lb-ai-chat-message-error\">\n <span className=\"lb-icon-container\">\n <WarningIcon />\n </span>\n {message.errorReason}\n </div>\n </>\n );\n }\n }\n\n return (\n <div\n className={cn(\n \"lb-ai-chat-message lb-ai-chat-assistant-message\",\n className\n )}\n {...props}\n ref={forwardedRef}\n >\n <OverridesProvider overrides={overrides}>\n <ComponentsProvider components={components}>\n {children}\n </ComponentsProvider>\n </OverridesProvider>\n </div>\n );\n }\n )\n);\n\nfunction AssistantMessageContent({ message }: { message: UiAssistantMessage }) {\n return (\n <AiMessage.Content\n message={message}\n components={{\n TextPart,\n ReasoningPart,\n ToolInvocationPart,\n }}\n className=\"lb-ai-chat-message-content\"\n />\n );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * TextPart\n * -----------------------------------------------------------------------------------------------*/\nfunction TextPart({ part }: AiMessageContentTextPartProps) {\n return <Prose content={part.text} className=\"lb-ai-chat-message-text\" />;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * ReasoningPart\n * -----------------------------------------------------------------------------------------------*/\nfunction ReasoningPart({\n part,\n isStreaming,\n}: AiMessageContentReasoningPartProps) {\n // Start collapsed if reasoning is already done.\n const [isOpen, setIsOpen] = useState(isStreaming);\n const $ = useOverrides();\n\n // Auto-collapse when reasoning is done, while still allowing the user to\n // open/collapse it manually during and after it's done.\n useEffect(() => {\n if (!isStreaming) {\n setIsOpen(false);\n }\n }, [isStreaming]);\n\n return (\n <Collapsible.Root\n className=\"lb-collapsible lb-ai-chat-message-reasoning\"\n open={isOpen}\n onOpenChange={setIsOpen}\n >\n <Collapsible.Trigger\n className={cn(\n \"lb-collapsible-trigger\",\n isStreaming && \"lb-ai-chat-pending\"\n )}\n >\n {/* TODO: Show duration as \"Reasoned for x seconds\"? */}\n {$.AI_CHAT_MESSAGE_REASONING(isStreaming)}\n <span className=\"lb-collapsible-chevron lb-icon-container\">\n <ChevronRightIcon />\n </span>\n </Collapsible.Trigger>\n\n <Collapsible.Content className=\"lb-collapsible-content\">\n <Prose content={part.text} />\n </Collapsible.Content>\n </Collapsible.Root>\n );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * ToolInvocationPart\n * -----------------------------------------------------------------------------------------------*/\nfunction ToolInvocationPart({\n part,\n message,\n}: AiMessageContentToolInvocationPartProps) {\n return (\n <div className=\"lb-ai-chat-message-tool-invocation\">\n <ErrorBoundary\n fallback={\n <div className=\"lb-ai-chat-message-error\">\n <span className=\"lb-icon-container\">\n <WarningIcon />\n </span>\n <p>\n Failed to render tool call result for <code>{part.name}</code>.\n See console for details.\n </p>\n </div>\n }\n >\n <AiMessageToolInvocation part={part} message={message} />\n </ErrorBoundary>\n </div>\n );\n}\n"],"names":["memo","forwardRef","overrides","components","useOverrides","jsx","jsxs","Fragment","WarningIcon","cn","OverridesProvider","ComponentsProvider","AiMessage.Content","Prose","useState","useEffect","Collapsible.Root","Collapsible.Trigger","ChevronRightIcon","Collapsible.Content","ErrorBoundary","AiMessageToolInvocation"],"mappings":"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"}
1
+ {"version":3,"file":"AiChatAssistantMessage.cjs","sources":["../../../src/components/internal/AiChatAssistantMessage.tsx"],"sourcesContent":["import type { AiAssistantMessage, WithNavigation } from \"@liveblocks/core\";\nimport {\n type ComponentProps,\n forwardRef,\n memo,\n type ReactNode,\n useEffect,\n useState,\n} from \"react\";\n\nimport { type GlobalComponents } from \"../../components\";\nimport { ChevronRightIcon } from \"../../icons/ChevronRight\";\nimport { WarningIcon } from \"../../icons/Warning\";\nimport {\n type AiChatMessageOverrides,\n type GlobalOverrides,\n OverridesProvider,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiMessage from \"../../primitives/AiMessage\";\nimport { AiMessageToolInvocation } from \"../../primitives/AiMessage/tool-invocation\";\nimport type {\n AiMessageContentReasoningPartProps,\n AiMessageContentTextPartProps,\n AiMessageContentToolInvocationPartProps,\n} from \"../../primitives/AiMessage/types\";\nimport * as Collapsible from \"../../primitives/Collapsible\";\nimport type { MarkdownComponents } from \"../../primitives/Markdown\";\nimport { cn } from \"../../utils/cn\";\nimport { ErrorBoundary } from \"../../utils/ErrorBoundary\";\nimport { Prose } from \"./Prose\";\n\ntype UiAssistantMessage = WithNavigation<AiAssistantMessage>;\n\ntype AiChatAssistantMessageComponents = {\n /**\n * The components used to render Markdown content.\n */\n markdown?: Partial<MarkdownComponents>;\n};\n\n/* -------------------------------------------------------------------------------------------------\n * AiChatAssistantMessage\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiChatAssistantMessageProps extends ComponentProps<\"div\"> {\n /**\n * The message to display.\n */\n message: UiAssistantMessage;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiChatMessageOverrides>;\n\n /**\n * @internal\n * The id of the copilot to use to set tool call result.\n */\n copilotId?: string;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatAssistantMessageComponents>;\n}\n\ninterface TextPartProps extends AiMessageContentTextPartProps {\n components?: Partial<GlobalComponents & AiChatAssistantMessageComponents>;\n}\n\ninterface ReasoningPartProps extends AiMessageContentReasoningPartProps {\n components?: Partial<GlobalComponents & AiChatAssistantMessageComponents>;\n}\n\nexport const AiChatAssistantMessage = memo(\n forwardRef<HTMLDivElement, AiChatAssistantMessageProps>(\n (\n { message, className, overrides, components, copilotId, ...props },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n\n let children: ReactNode = null;\n\n if (message.deletedAt !== undefined) {\n children = (\n <div className=\"lb-ai-chat-message-deleted\">\n {$.AI_CHAT_MESSAGE_DELETED}\n </div>\n );\n } else if (\n message.status === \"generating\" ||\n message.status === \"awaiting-tool\"\n ) {\n if (message.contentSoFar.length === 0) {\n children = (\n <div className=\"lb-ai-chat-message-thinking lb-ai-chat-pending\">\n {$.AI_CHAT_MESSAGE_THINKING}\n </div>\n );\n } else {\n children = (\n <AssistantMessageContent\n message={message}\n components={components}\n copilotId={copilotId}\n />\n );\n }\n } else if (message.status === \"completed\") {\n children = (\n <AssistantMessageContent\n message={message}\n components={components}\n copilotId={copilotId}\n />\n );\n } else if (message.status === \"failed\") {\n // Do not include the error message if the user aborted the request.\n if (message.errorReason === \"Aborted by user\") {\n children = (\n <AssistantMessageContent\n message={message}\n components={components}\n copilotId={copilotId}\n />\n );\n } else {\n children = (\n <>\n <AssistantMessageContent\n message={message}\n components={components}\n copilotId={copilotId}\n />\n\n <div className=\"lb-ai-chat-message-error\">\n <span className=\"lb-icon-container\">\n <WarningIcon />\n </span>\n {message.errorReason}\n </div>\n </>\n );\n }\n }\n\n return (\n <div\n className={cn(\n \"lb-ai-chat-message lb-ai-chat-assistant-message\",\n className\n )}\n {...props}\n ref={forwardedRef}\n >\n <OverridesProvider overrides={overrides}>\n {children}\n </OverridesProvider>\n </div>\n );\n }\n )\n);\n\nfunction AssistantMessageContent({\n message,\n components,\n copilotId,\n}: {\n message: UiAssistantMessage;\n components?: Partial<GlobalComponents & AiChatAssistantMessageComponents>;\n copilotId?: string;\n}) {\n return (\n <AiMessage.Content\n message={message}\n components={{\n TextPart: (props) => <TextPart {...props} components={components} />,\n ReasoningPart: (props) => (\n <ReasoningPart {...props} components={components} />\n ),\n ToolInvocationPart,\n }}\n copilotId={copilotId}\n className=\"lb-ai-chat-message-content\"\n />\n );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * TextPart\n * -----------------------------------------------------------------------------------------------*/\nfunction TextPart({ part, components }: TextPartProps) {\n return (\n <Prose\n content={part.text}\n className=\"lb-ai-chat-message-text\"\n components={components}\n />\n );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * ReasoningPart\n * -----------------------------------------------------------------------------------------------*/\nfunction ReasoningPart({ part, isStreaming, components }: ReasoningPartProps) {\n // Start collapsed if reasoning is already done.\n const [isOpen, setIsOpen] = useState(isStreaming);\n const $ = useOverrides();\n\n // Auto-collapse when reasoning is done, while still allowing the user to\n // open/collapse it manually during and after it's done.\n useEffect(() => {\n if (!isStreaming) {\n setIsOpen(false);\n }\n }, [isStreaming]);\n\n return (\n <Collapsible.Root\n className=\"lb-collapsible lb-ai-chat-message-reasoning\"\n open={isOpen}\n onOpenChange={setIsOpen}\n >\n <Collapsible.Trigger\n className={cn(\n \"lb-collapsible-trigger\",\n isStreaming && \"lb-ai-chat-pending\"\n )}\n >\n {/* TODO: Show duration as \"Reasoned for x seconds\"? */}\n {$.AI_CHAT_MESSAGE_REASONING(isStreaming)}\n <span className=\"lb-collapsible-chevron lb-icon-container\">\n <ChevronRightIcon />\n </span>\n </Collapsible.Trigger>\n\n <Collapsible.Content className=\"lb-collapsible-content\">\n <Prose content={part.text} components={components} />\n </Collapsible.Content>\n </Collapsible.Root>\n );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * ToolInvocationPart\n * -----------------------------------------------------------------------------------------------*/\nfunction ToolInvocationPart({\n part,\n message,\n copilotId,\n}: AiMessageContentToolInvocationPartProps) {\n return (\n <div className=\"lb-ai-chat-message-tool-invocation\">\n <ErrorBoundary\n fallback={\n <div className=\"lb-ai-chat-message-error\">\n <span className=\"lb-icon-container\">\n <WarningIcon />\n </span>\n <p>\n Failed to render tool call result for <code>{part.name}</code>.\n See console for details.\n </p>\n </div>\n }\n >\n <AiMessageToolInvocation\n part={part}\n message={message}\n copilotId={copilotId}\n />\n </ErrorBoundary>\n </div>\n );\n}\n"],"names":["memo","forwardRef","overrides","useOverrides","jsx","jsxs","Fragment","WarningIcon","cn","OverridesProvider","AiMessage.Content","Prose","useState","useEffect","Collapsible.Root","Collapsible.Trigger","ChevronRightIcon","Collapsible.Content","ErrorBoundary","AiMessageToolInvocation"],"mappings":"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"}