@liveblocks/react-ui 2.7.2 → 2.8.0-beta2

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 (126) hide show
  1. package/dist/_private/index.d.mts +5 -3
  2. package/dist/_private/index.d.ts +5 -3
  3. package/dist/_private/index.js +4 -2
  4. package/dist/_private/index.js.map +1 -1
  5. package/dist/_private/index.mjs +2 -1
  6. package/dist/_private/index.mjs.map +1 -1
  7. package/dist/components/Comment.js +106 -3
  8. package/dist/components/Comment.js.map +1 -1
  9. package/dist/components/Comment.mjs +107 -5
  10. package/dist/components/Comment.mjs.map +1 -1
  11. package/dist/components/Composer.js +216 -103
  12. package/dist/components/Composer.js.map +1 -1
  13. package/dist/components/Composer.mjs +220 -107
  14. package/dist/components/Composer.mjs.map +1 -1
  15. package/dist/components/HistoryVersionSummary.js +5 -0
  16. package/dist/components/HistoryVersionSummary.js.map +1 -1
  17. package/dist/components/HistoryVersionSummary.mjs +5 -0
  18. package/dist/components/HistoryVersionSummary.mjs.map +1 -1
  19. package/dist/components/InboxNotification.js +20 -4
  20. package/dist/components/InboxNotification.js.map +1 -1
  21. package/dist/components/InboxNotification.mjs +20 -4
  22. package/dist/components/InboxNotification.mjs.map +1 -1
  23. package/dist/components/Thread.js +5 -0
  24. package/dist/components/Thread.js.map +1 -1
  25. package/dist/components/Thread.mjs +5 -0
  26. package/dist/components/Thread.mjs.map +1 -1
  27. package/dist/components/internal/Attachment.js +314 -0
  28. package/dist/components/internal/Attachment.js.map +1 -0
  29. package/dist/components/internal/Attachment.mjs +310 -0
  30. package/dist/components/internal/Attachment.mjs.map +1 -0
  31. package/dist/components/internal/InboxNotificationThread.js +12 -2
  32. package/dist/components/internal/InboxNotificationThread.js.map +1 -1
  33. package/dist/components/internal/InboxNotificationThread.mjs +13 -3
  34. package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
  35. package/dist/icons/Attachment.js +15 -0
  36. package/dist/icons/Attachment.js.map +1 -0
  37. package/dist/icons/Attachment.mjs +13 -0
  38. package/dist/icons/Attachment.mjs.map +1 -0
  39. package/dist/icons/Spinner.js +3 -9
  40. package/dist/icons/Spinner.js.map +1 -1
  41. package/dist/icons/Spinner.mjs +4 -10
  42. package/dist/icons/Spinner.mjs.map +1 -1
  43. package/dist/icons/{Missing.js → Warning.js} +3 -3
  44. package/dist/icons/{Missing.js.map → Warning.js.map} +1 -1
  45. package/dist/icons/{Missing.mjs → Warning.mjs} +3 -3
  46. package/dist/icons/{Missing.mjs.map → Warning.mjs.map} +1 -1
  47. package/dist/index.d.mts +73 -4
  48. package/dist/index.d.ts +73 -4
  49. package/dist/index.js.map +1 -1
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/overrides.js +5 -0
  52. package/dist/overrides.js.map +1 -1
  53. package/dist/overrides.mjs +5 -0
  54. package/dist/overrides.mjs.map +1 -1
  55. package/dist/primitives/Composer/contexts.js +17 -3
  56. package/dist/primitives/Composer/contexts.js.map +1 -1
  57. package/dist/primitives/Composer/contexts.mjs +15 -4
  58. package/dist/primitives/Composer/contexts.mjs.map +1 -1
  59. package/dist/primitives/Composer/index.js +207 -30
  60. package/dist/primitives/Composer/index.js.map +1 -1
  61. package/dist/primitives/Composer/index.mjs +210 -35
  62. package/dist/primitives/Composer/index.mjs.map +1 -1
  63. package/dist/primitives/Composer/utils.js +231 -0
  64. package/dist/primitives/Composer/utils.js.map +1 -1
  65. package/dist/primitives/Composer/utils.mjs +229 -1
  66. package/dist/primitives/Composer/utils.mjs.map +1 -1
  67. package/dist/primitives/EmojiPicker/utils.js +2 -2
  68. package/dist/primitives/EmojiPicker/utils.js.map +1 -1
  69. package/dist/primitives/EmojiPicker/utils.mjs +1 -1
  70. package/dist/primitives/EmojiPicker/utils.mjs.map +1 -1
  71. package/dist/primitives/FileSize.js +33 -0
  72. package/dist/primitives/FileSize.js.map +1 -0
  73. package/dist/primitives/FileSize.mjs +31 -0
  74. package/dist/primitives/FileSize.mjs.map +1 -0
  75. package/dist/primitives/index.d.mts +87 -3
  76. package/dist/primitives/index.d.ts +87 -3
  77. package/dist/primitives/index.js +4 -0
  78. package/dist/primitives/index.js.map +1 -1
  79. package/dist/primitives/index.mjs +2 -0
  80. package/dist/primitives/index.mjs.map +1 -1
  81. package/dist/slate/plugins/{paste-html.js → paste.js} +16 -5
  82. package/dist/slate/plugins/paste.js.map +1 -0
  83. package/dist/slate/plugins/{paste-html.mjs → paste.mjs} +16 -5
  84. package/dist/slate/plugins/paste.mjs.map +1 -0
  85. package/dist/utils/data-transfer.js +20 -0
  86. package/dist/utils/data-transfer.js.map +1 -0
  87. package/dist/utils/data-transfer.mjs +18 -0
  88. package/dist/utils/data-transfer.mjs.map +1 -0
  89. package/dist/utils/download.js +14 -0
  90. package/dist/utils/download.js.map +1 -0
  91. package/dist/utils/download.mjs +12 -0
  92. package/dist/utils/download.mjs.map +1 -0
  93. package/dist/utils/format-file-size.js +45 -0
  94. package/dist/utils/format-file-size.js.map +1 -0
  95. package/dist/utils/format-file-size.mjs +43 -0
  96. package/dist/utils/format-file-size.mjs.map +1 -0
  97. package/dist/utils/intl.js +6 -0
  98. package/dist/utils/intl.js.map +1 -1
  99. package/dist/utils/intl.mjs +6 -1
  100. package/dist/utils/intl.mjs.map +1 -1
  101. package/dist/utils/use-initial.js +2 -1
  102. package/dist/utils/use-initial.js.map +1 -1
  103. package/dist/utils/use-initial.mjs +3 -2
  104. package/dist/utils/use-initial.mjs.map +1 -1
  105. package/dist/utils/use-latest.js.map +1 -1
  106. package/dist/utils/use-latest.mjs.map +1 -1
  107. package/dist/version.js +1 -1
  108. package/dist/version.js.map +1 -1
  109. package/dist/version.mjs +1 -1
  110. package/dist/version.mjs.map +1 -1
  111. package/package.json +4 -4
  112. package/src/styles/dark/index.css +1 -0
  113. package/src/styles/index.css +343 -62
  114. package/src/styles/utils.css +44 -0
  115. package/styles/dark/attributes.css +1 -1
  116. package/styles/dark/attributes.css.map +1 -1
  117. package/styles/dark/media-query.css +1 -1
  118. package/styles/dark/media-query.css.map +1 -1
  119. package/styles.css +1 -1
  120. package/styles.css.map +1 -1
  121. package/dist/slate/plugins/paste-html.js.map +0 -1
  122. package/dist/slate/plugins/paste-html.mjs.map +0 -1
  123. package/dist/utils/chunk.js +0 -12
  124. package/dist/utils/chunk.js.map +0 -1
  125. package/dist/utils/chunk.mjs +0 -10
  126. package/dist/utils/chunk.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Comment.mjs","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n CommentData,\n CommentReaction as CommentReactionData,\n} from \"@liveblocks/core\";\nimport {\n RoomContext,\n useAddReaction,\n useDeleteComment,\n useEditComment,\n useMarkThreadAsRead,\n useRemoveReaction,\n} from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { CheckIcon } from \"../icons/Check\";\nimport { CrossIcon } from \"../icons/Cross\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EditIcon } from \"../icons/Edit\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { EmojiAddIcon } from \"../icons/EmojiAdd\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ComposerSubmitComment } from \"../primitives\";\nimport * as CommentPrimitive from \"../primitives/Comment\";\nimport type {\n CommentBodyLinkProps,\n CommentBodyMentionProps,\n CommentLinkProps,\n CommentMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport { classNames } from \"../utils/class-names\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useVisibleCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Composer } from \"./Composer\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { List } from \"./internal/List\";\nimport {\n ShortcutTooltip,\n ShortcutTooltipKey,\n Tooltip,\n TooltipProvider,\n} from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\nconst REACTIONS_TRUNCATE = 5;\n\nexport interface CommentProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The comment to display.\n */\n comment: CommentData;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n\n /**\n * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`.\n */\n showDeleted?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to indent the comment's content.\n */\n indentContent?: boolean;\n\n /**\n * The event handler called when the comment is edited.\n */\n onCommentEdit?: (comment: CommentData) => void;\n\n /**\n * The event handler called when the comment is deleted.\n */\n onCommentDelete?: (comment: CommentData) => void;\n\n /**\n * The event handler called when clicking on the author.\n */\n onAuthorClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n additionalActions?: ReactNode;\n\n /**\n * @internal\n */\n additionalActionsClassName?: string;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\nexport function CommentMention({\n userId,\n className,\n ...props\n}: CommentBodyMentionProps & CommentMentionProps) {\n const currentId = useCurrentUserId();\n return (\n <CommentPrimitive.Mention\n className={classNames(\"lb-comment-mention\", className)}\n data-self={userId === currentId ? \"\" : undefined}\n {...props}\n >\n {MENTION_CHARACTER}\n <User userId={userId} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <CommentPrimitive.Link\n className={classNames(\"lb-comment-link\", className)}\n href={href}\n {...props}\n >\n {children}\n </CommentPrimitive.Link>\n );\n}\n\nexport function CommentNonInteractiveLink({\n href: _href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <span className={classNames(\"lb-comment-link\", className)} {...props}>\n {children}\n </span>\n );\n}\n\nconst CommentReactionButton = forwardRef<\n HTMLButtonElement,\n CommentReactionButtonProps\n>(({ reaction, overrides, className, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n return (\n <Button\n className={classNames(\"lb-comment-reaction\", className)}\n variant=\"outline\"\n aria-label={$.COMMENT_REACTION_DESCRIPTION(\n reaction.emoji,\n reaction.users.length\n )}\n {...props}\n ref={forwardedRef}\n >\n <Emoji className=\"lb-comment-reaction-emoji\" emoji={reaction.emoji} />\n <span className=\"lb-comment-reaction-count\">{reaction.users.length}</span>\n </Button>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddReaction();\n const removeReaction = useRemoveReaction();\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\n// A void component (which doesn't render anything) responsible for marking a thread\n// as read when the comment it's used in becomes visible.\n// Moving this logic into a separate component allows us to use the visibility\n// and focus hooks \"conditionally\" by conditionally rendering this component.\nfunction AutoMarkReadThreadIdHandler({\n threadId,\n commentRef,\n}: {\n threadId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkThreadAsRead();\n const isWindowFocused = useWindowFocus();\n\n useVisibleCallback(\n commentRef,\n () => {\n markThreadAsRead(threadId);\n },\n {\n // The underlying IntersectionObserver is only enabled when the window is focused\n enabled: isWindowFocused,\n }\n );\n\n return null;\n}\n\n/**\n * Displays a single comment.\n *\n * @example\n * <>\n * {thread.comments.map((comment) => (\n * <Comment key={comment.id} comment={comment} />\n * ))}\n * </>\n */\nexport const Comment = forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n onAuthorClick,\n onMentionClick,\n onCommentEdit,\n onCommentDelete,\n overrides,\n className,\n additionalActions,\n additionalActionsClassName,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const isInRoom = Boolean(useContext(RoomContext));\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteComment();\n const editComment = useEditComment();\n const addReaction = useAddReaction();\n const removeReaction = useRemoveReaction();\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const handleEditCancel = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n setEditing(false);\n },\n []\n );\n\n const handleEditSubmit = useCallback(\n ({ body }: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentEdit?.(comment);\n\n event.preventDefault();\n setEditing(false);\n editComment({\n commentId: comment.id,\n threadId: comment.threadId,\n body,\n });\n },\n [comment, editComment, onCommentEdit]\n );\n\n const handleDelete = useCallback(() => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentDelete?.(comment);\n\n deleteComment({\n commentId: comment.id,\n threadId: comment.threadId,\n });\n }, [comment, deleteComment, onCommentDelete]);\n\n const handleAuthorClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n onAuthorClick?.(comment.userId, event);\n },\n [comment.userId, onAuthorClick]\n );\n\n const handleReactionSelect = useCallback(\n (emoji: string) => {\n const reactionIndex = comment.reactions.findIndex(\n (reaction) => reaction.emoji === emoji\n );\n\n if (\n reactionIndex >= 0 &&\n currentUserId &&\n comment.reactions[reactionIndex].users.some(\n (user) => user.id === currentUserId\n )\n ) {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n } else {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [\n addReaction,\n comment.id,\n comment.reactions,\n comment.threadId,\n removeReaction,\n currentUserId,\n ]\n );\n\n useEffect(() => {\n const isWindowDefined = typeof window !== \"undefined\";\n if (!isWindowDefined) return;\n\n const hash = window.location.hash;\n const commentId = hash.slice(1);\n\n if (commentId === comment.id) {\n setTarget(true);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!showDeleted && !comment.body) {\n return null;\n }\n\n return (\n <TooltipProvider>\n {isInRoom && autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n />\n )}\n <div\n id={comment.id}\n className={classNames(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div\n className={classNames(\n \"lb-comment-actions\",\n additionalActionsClassName\n )}\n >\n {additionalActions ?? null}\n {showReactions && (\n <EmojiPicker\n onEmojiSelect={handleReactionSelect}\n onOpenChange={setReactionActionOpen}\n >\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n >\n <EmojiAddIcon className=\"lb-button-icon\" />\n </Button>\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n )}\n {comment.userId === currentUserId && (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={\n <>\n <DropdownItem\n onSelect={handleEdit}\n onClick={stopPropagation}\n >\n <EditIcon className=\"lb-dropdown-item-icon\" />\n {$.COMMENT_EDIT}\n </DropdownItem>\n <DropdownItem\n onSelect={handleDelete}\n onClick={stopPropagation}\n >\n <DeleteIcon className=\"lb-dropdown-item-icon\" />\n {$.COMMENT_DELETE}\n </DropdownItem>\n </>\n }\n >\n <Tooltip content={$.COMMENT_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n disabled={!comment.body}\n onClick={stopPropagation}\n aria-label={$.COMMENT_MORE}\n >\n <EllipsisIcon className=\"lb-button-icon\" />\n </Button>\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n )}\n </div>\n )}\n </div>\n <div className=\"lb-comment-content\">\n {isEditing ? (\n <Composer\n className=\"lb-comment-composer\"\n onComposerSubmit={handleEditSubmit}\n defaultValue={comment.body}\n autoFocus\n showAttribution={false}\n actions={\n <>\n <Tooltip\n content={$.COMMENT_EDIT_COMPOSER_CANCEL}\n aria-label={$.COMMENT_EDIT_COMPOSER_CANCEL}\n >\n <Button\n className=\"lb-composer-action\"\n onClick={handleEditCancel}\n >\n <CrossIcon className=\"lb-button-icon\" />\n </Button>\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut={<ShortcutTooltipKey name=\"enter\" />}\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n variant=\"primary\"\n className=\"lb-composer-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_EDIT_COMPOSER_SAVE}\n >\n <CheckIcon className=\"lb-button-icon\" />\n </Button>\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: ({ userId }) => (\n <CommentMention\n userId={userId}\n onClick={(event) => onMentionClick?.(userId, event)}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n />\n ))}\n <EmojiPicker onEmojiSelect={handleReactionSelect}>\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-reaction lb-comment-reaction-add\"\n variant=\"outline\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n >\n <EmojiAddIcon className=\"lb-button-icon\" />\n </Button>\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n </div>\n )}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n </TooltipProvider>\n );\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA;AAmFO;AAAwB;AAC7B;AACA;AAEF;AACE;AACA;AACG;AACsD;AACd;AACnC;AAGH;AAAK;AAGZ;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AACG;AACmD;AAClD;AACI;AAKV;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AACG;AAAuD;AAAO;AAInE;AAEA;AAIE;AACA;AACG;AACuD;AAC9C;AACM;AACH;AACM;AACjB;AACI;AACC;AAEJ;AAAgB;AAA4C;AAC5D;AAAe;AAGtB;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACA;AAEI;AAAgB;AAAkB;AAAe;AACnD;AACkB;AACT;AACA;AACZ;AACS;AACM;AAEnB;AAEU;AAGd;AACE;AAAsB;AAGxB;AAA4B;AAExB;AACE;AAAY;AACQ;AACC;AACH;AACjB;AAED;AAAe;AACK;AACC;AACH;AACjB;AACH;AACF;AAC0E;AAG5E;AACG;AACU;AACA;AACC;AAET;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEJ;AAC4B;AAC3B;AACA;AACI;AAKd;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACG;AACc;AACc;AAC3B;AACA;AACI;AACC;AAGX;AAMA;AAAqC;AACnC;AAEF;AAIE;AACA;AAEA;AAAA;AACE;AAEE;AAAyB;AAC3B;AACA;AAEW;AACX;AAGF;AACF;AAYO;AAAgB;AAEnB;AACE;AACgB;AAChB;AACc;AACE;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAGrB;AAEA;AACA;AACA;AAAY;AACS;AACD;AAClB;AACD;AACH;AACoC;AAGtC;AAEE;AAEA;AAAc;AACO;AACD;AACnB;AAGH;AAA0B;AAEtB;AAAqC;AACvC;AAC8B;AAGhC;AAA6B;AAEzB;AAAwC;AACL;AAGnC;AAGyC;AACf;AAGxB;AAAe;AACK;AACC;AACnB;AACD;AAED;AAAY;AACQ;AACC;AACnB;AACD;AACH;AACF;AACA;AACE;AACQ;AACA;AACA;AACR;AACA;AACF;AAGF;AACE;AACA;AAAsB;AAEtB;AACA;AAEA;AACE;AAAc;AAChB;AAGF;AACE;AAAO;AAGT;AAGO;AACa;AACF;AAGb;AACa;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACtB;AACH;AACC;AAEJ;AAAc;AACZ;AAAc;AACZ;AACW;AACM;AACP;AAEV;AAAe;AACb;AACW;AACM;AACP;AAEV;AAAe;AACb;AACW;AACI;AACJ;AAKP;AAAe;AASvB;AACY;AACT;AACA;AACF;AAIG;AACgB;AACD;AAEb;AAAmB;AACjB;AAA0B;AACxB;AACW;AACD;AACK;AAEb;AAAuB;AAO/B;AACO;AACQ;AACR;AAGD;AACW;AACD;AAER;AAAmB;AAGrB;AACW;AACD;AAER;AAAqB;AAG1B;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACS;AACV;AACK;AAEb;AAAuB;AASvC;AAAc;AAEV;AACW;AACQ;AACI;AACb;AACQ;AAGZ;AACY;AACG;AAEb;AACW;AACD;AAER;AAAoB;AAGxB;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAgC;AAC9B;AACS;AACE;AACD;AACK;AAEb;AAAoB;AAI7B;AAES;AACe;AAC1B;AAIC;AACW;AACI;AACF;AAEP;AACC;AACkD;AACpD;AAEI;AACR;AAGC;AAAc;AAEV;AACe;AACd;AACA;AACA;AAGH;AAA2B;AACzB;AAAmB;AACjB;AAA0B;AACxB;AACW;AACF;AACC;AACK;AAEb;AAAuB;AASrC;AAAc;AACZ;AAAY;AAKvB;AAGN;;"}
1
+ {"version":3,"file":"Comment.mjs","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n CommentAttachment,\n CommentData,\n CommentReaction as CommentReactionData,\n} from \"@liveblocks/core\";\nimport {\n RoomContext,\n useAddReaction,\n useAttachmentUrl,\n useDeleteComment,\n useEditComment,\n useMarkThreadAsRead,\n useRemoveReaction,\n} from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { CheckIcon } from \"../icons/Check\";\nimport { CrossIcon } from \"../icons/Cross\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EditIcon } from \"../icons/Edit\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { EmojiAddIcon } from \"../icons/EmojiAdd\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ComposerSubmitComment } from \"../primitives\";\nimport * as CommentPrimitive from \"../primitives/Comment\";\nimport type {\n CommentBodyLinkProps,\n CommentBodyMentionProps,\n CommentLinkProps,\n CommentMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { download } from \"../utils/download\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useVisibleCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { List } from \"./internal/List\";\nimport {\n ShortcutTooltip,\n ShortcutTooltipKey,\n Tooltip,\n TooltipProvider,\n} from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\nconst REACTIONS_TRUNCATE = 5;\n\nexport interface CommentProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The comment to display.\n */\n comment: CommentData;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n\n /**\n * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`.\n */\n showDeleted?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to indent the comment's content.\n */\n indentContent?: boolean;\n\n /**\n * The event handler called when the comment is edited.\n */\n onCommentEdit?: (comment: CommentData) => void;\n\n /**\n * The event handler called when the comment is deleted.\n */\n onCommentDelete?: (comment: CommentData) => void;\n\n /**\n * The event handler called when clicking on the author.\n */\n onAuthorClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: (\n args: CommentAttachmentArgs,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n additionalActions?: ReactNode;\n\n /**\n * @internal\n */\n additionalActionsClassName?: string;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\nexport function CommentMention({\n userId,\n className,\n ...props\n}: CommentBodyMentionProps & CommentMentionProps) {\n const currentId = useCurrentUserId();\n return (\n <CommentPrimitive.Mention\n className={classNames(\"lb-comment-mention\", className)}\n data-self={userId === currentId ? \"\" : undefined}\n {...props}\n >\n {MENTION_CHARACTER}\n <User userId={userId} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <CommentPrimitive.Link\n className={classNames(\"lb-comment-link\", className)}\n href={href}\n {...props}\n >\n {children}\n </CommentPrimitive.Link>\n );\n}\n\nexport function CommentNonInteractiveLink({\n href: _href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <span className={classNames(\"lb-comment-link\", className)} {...props}>\n {children}\n </span>\n );\n}\n\nconst CommentReactionButton = forwardRef<\n HTMLButtonElement,\n CommentReactionButtonProps\n>(({ reaction, overrides, className, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n return (\n <Button\n className={classNames(\"lb-comment-reaction\", className)}\n variant=\"outline\"\n aria-label={$.COMMENT_REACTION_DESCRIPTION(\n reaction.emoji,\n reaction.users.length\n )}\n {...props}\n ref={forwardedRef}\n >\n <Emoji className=\"lb-comment-reaction-emoji\" emoji={reaction.emoji} />\n <span className=\"lb-comment-reaction-count\">{reaction.users.length}</span>\n </Button>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddReaction();\n const removeReaction = useRemoveReaction();\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps) {\n const { url } = useAttachmentUrl(attachment.id);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={classNames(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps) {\n const { url } = useAttachmentUrl(attachment.id);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={classNames(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={classNames(\"lb-comment-attachment\", className)}\n {...props}\n />\n );\n}\n\n// A void component (which doesn't render anything) responsible for marking a thread\n// as read when the comment it's used in becomes visible.\n// Moving this logic into a separate component allows us to use the visibility\n// and focus hooks \"conditionally\" by conditionally rendering this component.\nfunction AutoMarkReadThreadIdHandler({\n threadId,\n commentRef,\n}: {\n threadId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkThreadAsRead();\n const isWindowFocused = useWindowFocus();\n\n useVisibleCallback(\n commentRef,\n () => {\n markThreadAsRead(threadId);\n },\n {\n // The underlying IntersectionObserver is only enabled when the window is focused\n enabled: isWindowFocused,\n }\n );\n\n return null;\n}\n\n/**\n * Displays a single comment.\n *\n * @example\n * <>\n * {thread.comments.map((comment) => (\n * <Comment key={comment.id} comment={comment} />\n * ))}\n * </>\n */\nexport const Comment = forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n overrides,\n className,\n additionalActions,\n additionalActionsClassName,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const isInRoom = Boolean(useContext(RoomContext));\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteComment();\n const editComment = useEditComment();\n const addReaction = useAddReaction();\n const removeReaction = useRemoveReaction();\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const handleEditCancel = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n setEditing(false);\n },\n []\n );\n\n const handleEditSubmit = useCallback(\n (\n { body, attachments }: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentEdit?.(comment);\n\n event.preventDefault();\n setEditing(false);\n editComment({\n commentId: comment.id,\n threadId: comment.threadId,\n body,\n attachments,\n });\n },\n [comment, editComment, onCommentEdit]\n );\n\n const handleDelete = useCallback(() => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentDelete?.(comment);\n\n deleteComment({\n commentId: comment.id,\n threadId: comment.threadId,\n });\n }, [comment, deleteComment, onCommentDelete]);\n\n const handleAuthorClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n onAuthorClick?.(comment.userId, event);\n },\n [comment.userId, onAuthorClick]\n );\n\n const handleReactionSelect = useCallback(\n (emoji: string) => {\n const reactionIndex = comment.reactions.findIndex(\n (reaction) => reaction.emoji === emoji\n );\n\n if (\n reactionIndex >= 0 &&\n currentUserId &&\n comment.reactions[reactionIndex].users.some(\n (user) => user.id === currentUserId\n )\n ) {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n } else {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [\n addReaction,\n comment.id,\n comment.reactions,\n comment.threadId,\n removeReaction,\n currentUserId,\n ]\n );\n\n useEffect(() => {\n const isWindowDefined = typeof window !== \"undefined\";\n if (!isWindowDefined) return;\n\n const hash = window.location.hash;\n const commentId = hash.slice(1);\n\n if (commentId === comment.id) {\n setTarget(true);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!showDeleted && !comment.body) {\n return null;\n }\n\n return (\n <TooltipProvider>\n {isInRoom && autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n />\n )}\n <div\n id={comment.id}\n className={classNames(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div\n className={classNames(\n \"lb-comment-actions\",\n additionalActionsClassName\n )}\n >\n {additionalActions ?? null}\n {showReactions && (\n <EmojiPicker\n onEmojiSelect={handleReactionSelect}\n onOpenChange={setReactionActionOpen}\n >\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n >\n <EmojiAddIcon className=\"lb-button-icon\" />\n </Button>\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n )}\n {comment.userId === currentUserId && (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={\n <>\n <DropdownItem\n onSelect={handleEdit}\n onClick={stopPropagation}\n >\n <EditIcon className=\"lb-dropdown-item-icon\" />\n {$.COMMENT_EDIT}\n </DropdownItem>\n <DropdownItem\n onSelect={handleDelete}\n onClick={stopPropagation}\n >\n <DeleteIcon className=\"lb-dropdown-item-icon\" />\n {$.COMMENT_DELETE}\n </DropdownItem>\n </>\n }\n >\n <Tooltip content={$.COMMENT_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n disabled={!comment.body}\n onClick={stopPropagation}\n aria-label={$.COMMENT_MORE}\n >\n <EllipsisIcon className=\"lb-button-icon\" />\n </Button>\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n )}\n </div>\n )}\n </div>\n <div className=\"lb-comment-content\">\n {isEditing ? (\n <Composer\n className=\"lb-comment-composer\"\n onComposerSubmit={handleEditSubmit}\n defaultValue={comment.body}\n defaultAttachments={comment.attachments}\n autoFocus\n showAttribution={false}\n showAttachments={showAttachments}\n actions={\n <>\n <Tooltip\n content={$.COMMENT_EDIT_COMPOSER_CANCEL}\n aria-label={$.COMMENT_EDIT_COMPOSER_CANCEL}\n >\n <Button\n className=\"lb-composer-action\"\n onClick={handleEditCancel}\n >\n <CrossIcon className=\"lb-button-icon\" />\n </Button>\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut={<ShortcutTooltipKey name=\"enter\" />}\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n variant=\"primary\"\n className=\"lb-composer-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_EDIT_COMPOSER_SAVE}\n >\n <CheckIcon className=\"lb-button-icon\" />\n </Button>\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: ({ userId }) => (\n <CommentMention\n userId={userId}\n onClick={(event) => onMentionClick?.(userId, event)}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showAttachments &&\n (mediaAttachments.length > 0 || fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n />\n ))}\n <EmojiPicker onEmojiSelect={handleReactionSelect}>\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-reaction lb-comment-reaction-add\"\n variant=\"outline\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n >\n <EmojiAddIcon className=\"lb-button-icon\" />\n </Button>\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n </div>\n )}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n </TooltipProvider>\n );\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA;AAqGO;AAAwB;AAC7B;AACA;AAEF;AACE;AACA;AACG;AACsD;AACd;AACnC;AAGH;AAAK;AAGZ;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AACG;AACmD;AAClD;AACI;AAKV;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AACG;AAAuD;AAAO;AAInE;AAEA;AAIE;AACA;AACG;AACuD;AAC9C;AACM;AACH;AACM;AACjB;AACI;AACC;AAEJ;AAAgB;AAA4C;AAC5D;AAAe;AAGtB;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACA;AAEI;AAAgB;AAAkB;AAAe;AACnD;AACkB;AACT;AACA;AACZ;AACS;AACM;AAEnB;AAEU;AAGd;AACE;AAAsB;AAGxB;AAA4B;AAExB;AACE;AAAY;AACQ;AACC;AACH;AACjB;AAED;AAAe;AACK;AACC;AACH;AACjB;AACH;AACF;AAC0E;AAG5E;AACG;AACU;AACA;AACC;AAET;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEJ;AAC4B;AAC3B;AACA;AACI;AAKd;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACG;AACc;AACc;AAC3B;AACA;AACI;AACC;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AAEF;AACE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACG;AACyD;AACpD;AACJ;AACA;AAC6B;AAGnC;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AAEF;AACE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACG;AACyD;AACpD;AACJ;AACA;AAC6B;AAGnC;AAEO;AAA6C;AAClD;AAEF;AACE;AACG;AACyD;AACpD;AAGV;AAMA;AAAqC;AACnC;AAEF;AAIE;AACA;AAEA;AAAA;AACE;AAEE;AAAyB;AAC3B;AACA;AAEW;AACX;AAGF;AACF;AAYO;AAAgB;AAEnB;AACE;AACgB;AAChB;AACc;AACE;AACE;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACA;AACA;AAAY;AACS;AACD;AAClB;AACA;AACD;AACH;AACoC;AAGtC;AAEE;AAEA;AAAc;AACO;AACD;AACnB;AAGH;AAA0B;AAEtB;AAAqC;AACvC;AAC8B;AAGhC;AAA6B;AAEzB;AAAwC;AACL;AAGnC;AAGyC;AACf;AAGxB;AAAe;AACK;AACC;AACnB;AACD;AAED;AAAY;AACQ;AACC;AACnB;AACD;AACH;AACF;AACA;AACE;AACQ;AACA;AACA;AACR;AACA;AACF;AAGF;AACE;AACA;AAAsB;AAEtB;AACA;AAEA;AACE;AAAc;AAChB;AAGF;AACE;AAAO;AAGT;AAGO;AACa;AACF;AAGb;AACa;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACtB;AACH;AACC;AAEJ;AAAc;AACZ;AAAc;AACZ;AACW;AACM;AACP;AAEV;AAAe;AACb;AACW;AACM;AACP;AAEV;AAAe;AACb;AACW;AACI;AACJ;AAKP;AAAe;AASvB;AACY;AACT;AACA;AACF;AAIG;AACgB;AACD;AAEb;AAAmB;AACjB;AAA0B;AACxB;AACW;AACD;AACK;AAEb;AAAuB;AAO/B;AACO;AACQ;AACR;AAGD;AACW;AACD;AAER;AAAmB;AAGrB;AACW;AACD;AAER;AAAqB;AAG1B;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACS;AACV;AACK;AAEb;AAAuB;AASvC;AAAc;AAEV;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AAGK;AACY;AACG;AAEb;AACW;AACD;AAER;AAAoB;AAGxB;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAgC;AAC9B;AACS;AACE;AACD;AACK;AAEb;AAAoB;AAI7B;AAES;AACe;AAC1B;AAIC;AACW;AACI;AACF;AAEP;AACC;AACkD;AACpD;AAEI;AACR;AAIC;AAAc;AAEV;AAAc;AAEV;AACiB;AAChB;AACA;AACA;AAML;AAAc;AAEV;AACiB;AAChB;AACA;AACA;AAQT;AAAc;AAEV;AACe;AACd;AACA;AACA;AAGH;AAA2B;AACzB;AAAmB;AACjB;AAA0B;AACxB;AACW;AACF;AACC;AACK;AAEb;AAAuB;AASrC;AAAc;AACZ;AAAY;AAKvB;AAGN;;"}
@@ -4,15 +4,19 @@
4
4
  var core = require('@liveblocks/core');
5
5
  var react = require('@liveblocks/react');
6
6
  var React = require('react');
7
+ var Attachment = require('../icons/Attachment.js');
7
8
  var Emoji = require('../icons/Emoji.js');
8
9
  var Mention = require('../icons/Mention.js');
9
10
  var Send = require('../icons/Send.js');
10
11
  var overrides = require('../overrides.js');
11
12
  var index = require('../primitives/Composer/index.js');
12
13
  var contexts = require('../primitives/Composer/contexts.js');
14
+ var utils = require('../primitives/Composer/utils.js');
13
15
  var mentions = require('../slate/plugins/mentions.js');
14
16
  var classNames = require('../utils/class-names.js');
15
17
  var useControllableState = require('../utils/use-controllable-state.js');
18
+ var useLayoutEffect = require('../utils/use-layout-effect.js');
19
+ var Attachment$1 = require('./internal/Attachment.js');
16
20
  var Attribution = require('./internal/Attribution.js');
17
21
  var Avatar = require('./internal/Avatar.js');
18
22
  var Button = require('./internal/Button.js');
@@ -24,6 +28,7 @@ var PopoverPrimitive = require('@radix-ui/react-popover');
24
28
 
25
29
  function ComposerInsertMentionEditorAction({
26
30
  label,
31
+ tooltipLabel,
27
32
  className,
28
33
  onClick,
29
34
  ...props
@@ -43,7 +48,7 @@ function ComposerInsertMentionEditorAction({
43
48
  [createMention, onClick]
44
49
  );
45
50
  return /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
46
- content: label
51
+ content: tooltipLabel ?? label
47
52
  }, /* @__PURE__ */ React.createElement(Button.Button, {
48
53
  className: classNames.classNames("lb-composer-editor-action", className),
49
54
  onMouseDown: preventDefault,
@@ -56,6 +61,7 @@ function ComposerInsertMentionEditorAction({
56
61
  }
57
62
  function ComposerInsertEmojiEditorAction({
58
63
  label,
64
+ tooltipLabel,
59
65
  onPickerOpenChange,
60
66
  className,
61
67
  ...props
@@ -71,7 +77,7 @@ function ComposerInsertEmojiEditorAction({
71
77
  onEmojiSelect: insertText,
72
78
  onOpenChange: onPickerOpenChange
73
79
  }, /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
74
- content: label
80
+ content: tooltipLabel ?? label
75
81
  }, /* @__PURE__ */ React.createElement(PopoverPrimitive.PopoverTrigger, {
76
82
  asChild: true
77
83
  }, /* @__PURE__ */ React.createElement(Button.Button, {
@@ -84,6 +90,32 @@ function ComposerInsertEmojiEditorAction({
84
90
  className: "lb-button-icon"
85
91
  })))));
86
92
  }
93
+ function ComposerAttachFilesEditorAction({
94
+ label,
95
+ tooltipLabel,
96
+ className,
97
+ ...props
98
+ }) {
99
+ const preventDefault = React.useCallback((event) => {
100
+ event.preventDefault();
101
+ }, []);
102
+ const stopPropagation = React.useCallback((event) => {
103
+ event.stopPropagation();
104
+ }, []);
105
+ return /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
106
+ content: tooltipLabel ?? label
107
+ }, /* @__PURE__ */ React.createElement(index.AttachFiles, {
108
+ asChild: true
109
+ }, /* @__PURE__ */ React.createElement(Button.Button, {
110
+ className: classNames.classNames("lb-composer-editor-action", className),
111
+ onMouseDown: preventDefault,
112
+ onClick: stopPropagation,
113
+ "aria-label": label,
114
+ ...props
115
+ }, /* @__PURE__ */ React.createElement(Attachment.AttachmentIcon, {
116
+ className: "lb-button-icon"
117
+ }))));
118
+ }
87
119
  function ComposerMention({ userId }) {
88
120
  return /* @__PURE__ */ React.createElement(index.Mention, {
89
121
  className: "lb-composer-mention"
@@ -116,68 +148,177 @@ function ComposerLink({ href, children }) {
116
148
  className: "lb-composer-link"
117
149
  }, children);
118
150
  }
151
+ function ComposerFileAttachment({
152
+ attachment,
153
+ className,
154
+ overrides,
155
+ ...props
156
+ }) {
157
+ const { removeAttachment } = contexts.useComposer();
158
+ const handleDeleteClick = React.useCallback(() => {
159
+ removeAttachment(attachment.id);
160
+ }, [attachment.id, removeAttachment]);
161
+ return /* @__PURE__ */ React.createElement(Attachment$1.FileAttachment, {
162
+ className: classNames.classNames("lb-composer-attachment", className),
163
+ ...props,
164
+ attachment,
165
+ onDeleteClick: handleDeleteClick,
166
+ preventFocusOnDelete: true,
167
+ overrides
168
+ });
169
+ }
170
+ function ComposerAttachments({
171
+ overrides,
172
+ className,
173
+ ...props
174
+ }) {
175
+ const { attachments } = contexts.useComposer();
176
+ if (attachments.length === 0) {
177
+ return null;
178
+ }
179
+ return /* @__PURE__ */ React.createElement("div", {
180
+ className: classNames.classNames("lb-composer-attachments", className),
181
+ ...props
182
+ }, /* @__PURE__ */ React.createElement("div", {
183
+ className: "lb-attachments"
184
+ }, attachments.map((attachment) => {
185
+ return /* @__PURE__ */ React.createElement(ComposerFileAttachment, {
186
+ key: attachment.id,
187
+ attachment,
188
+ overrides
189
+ });
190
+ })));
191
+ }
119
192
  const editorComponents = {
120
193
  Mention: ComposerMention,
121
194
  MentionSuggestions: ComposerMentionSuggestions,
122
195
  Link: ComposerLink
123
196
  };
124
- const ComposerWithContext = React.forwardRef(
125
- ({
197
+ function ComposerEditorContainer({
198
+ showAttachments = true,
199
+ showAttribution,
200
+ defaultValue,
201
+ isCollapsed,
202
+ overrides: overrides$1,
203
+ actions,
204
+ autoFocus,
205
+ hasResolveMentionSuggestions,
206
+ onEmojiPickerOpenChange,
207
+ onEmptyChange,
208
+ onEditorClick
209
+ }) {
210
+ const { isEmpty } = contexts.useComposer();
211
+ const { hasMaxAttachments } = contexts.useComposerAttachmentsContext();
212
+ const $ = overrides.useOverrides(overrides$1);
213
+ const [isDraggingOver, dropAreaProps] = utils.useComposerAttachmentsDropArea({
214
+ disabled: hasMaxAttachments
215
+ });
216
+ useLayoutEffect.useLayoutEffect(() => {
217
+ onEmptyChange(isEmpty);
218
+ }, [isEmpty, onEmptyChange]);
219
+ const preventDefault = React.useCallback((event) => {
220
+ event.preventDefault();
221
+ }, []);
222
+ const stopPropagation = React.useCallback((event) => {
223
+ event.stopPropagation();
224
+ }, []);
225
+ return /* @__PURE__ */ React.createElement("div", {
226
+ className: "lb-composer-editor-container",
227
+ ...dropAreaProps
228
+ }, /* @__PURE__ */ React.createElement(index.Editor, {
229
+ className: "lb-composer-editor",
230
+ onClick: onEditorClick,
231
+ placeholder: $.COMPOSER_PLACEHOLDER,
126
232
  defaultValue,
127
- disabled,
128
233
  autoFocus,
234
+ components: editorComponents,
235
+ dir: $.dir
236
+ }), showAttachments && /* @__PURE__ */ React.createElement(ComposerAttachments, {
237
+ overrides: overrides$1
238
+ }), (!isCollapsed || isDraggingOver) && /* @__PURE__ */ React.createElement("div", {
239
+ className: "lb-composer-footer"
240
+ }, /* @__PURE__ */ React.createElement("div", {
241
+ className: "lb-composer-editor-actions"
242
+ }, hasResolveMentionSuggestions && /* @__PURE__ */ React.createElement(ComposerInsertMentionEditorAction, {
243
+ label: $.COMPOSER_INSERT_MENTION
244
+ }), /* @__PURE__ */ React.createElement(ComposerInsertEmojiEditorAction, {
245
+ label: $.COMPOSER_INSERT_EMOJI,
246
+ onPickerOpenChange: onEmojiPickerOpenChange
247
+ }), showAttachments && /* @__PURE__ */ React.createElement(ComposerAttachFilesEditorAction, {
248
+ label: $.COMPOSER_ATTACH_FILES
249
+ })), showAttribution && /* @__PURE__ */ React.createElement(Attribution.Attribution, null), /* @__PURE__ */ React.createElement("div", {
250
+ className: "lb-composer-actions"
251
+ }, actions ?? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip.ShortcutTooltip, {
252
+ content: $.COMPOSER_SEND,
253
+ shortcut: /* @__PURE__ */ React.createElement(Tooltip.ShortcutTooltipKey, {
254
+ name: "enter"
255
+ })
256
+ }, /* @__PURE__ */ React.createElement(index.Submit, {
257
+ asChild: true
258
+ }, /* @__PURE__ */ React.createElement(Button.Button, {
259
+ onMouseDown: preventDefault,
260
+ onClick: stopPropagation,
261
+ className: "lb-composer-action",
262
+ variant: "primary",
263
+ "aria-label": $.COMPOSER_SEND
264
+ }, /* @__PURE__ */ React.createElement(Send.SendIcon, null))))))), showAttachments && isDraggingOver && /* @__PURE__ */ React.createElement("div", {
265
+ className: "lb-composer-attachments-drop-area"
266
+ }, /* @__PURE__ */ React.createElement("div", {
267
+ className: "lb-composer-attachments-drop-area-label"
268
+ }, /* @__PURE__ */ React.createElement(Attachment.AttachmentIcon, null), $.COMPOSER_ATTACH_FILES)));
269
+ }
270
+ const Composer = React.forwardRef(
271
+ ({
272
+ threadId,
273
+ commentId,
274
+ metadata,
275
+ defaultValue,
276
+ defaultAttachments,
277
+ onComposerSubmit,
129
278
  collapsed: controlledCollapsed,
130
279
  defaultCollapsed,
131
280
  onCollapsedChange: controlledOnCollapsedChange,
132
- actions,
133
281
  overrides: overrides$1,
134
- showAttribution,
135
- onFocus,
282
+ actions,
136
283
  onBlur,
137
284
  className,
285
+ onFocus,
286
+ autoFocus,
287
+ disabled,
288
+ showAttachments = true,
289
+ showAttribution,
138
290
  ...props
139
291
  }, forwardedRef) => {
140
292
  const client = react.useClient();
293
+ const createThread = react.useCreateThread();
294
+ const createComment = react.useCreateComment();
295
+ const editComment = react.useEditComment();
141
296
  const hasResolveMentionSuggestions = client[core.kInternal].resolveMentionSuggestions !== void 0;
142
- const self = react.useSelf();
143
- const isDisabled = React.useMemo(
144
- () => disabled || (self ? !self.canComment : false),
145
- [disabled, self?.canComment]
146
- );
147
- const { isEmpty } = contexts.useComposer();
297
+ const isEmptyRef = React.useRef(true);
298
+ const isEmojiPickerOpenRef = React.useRef(false);
148
299
  const $ = overrides.useOverrides(overrides$1);
149
- const [isEmojiPickerOpen, setEmojiPickerOpen] = React.useState(false);
150
- const [collapsed, onCollapsedChange] = useControllableState.useControllableState(
300
+ const [isCollapsed, onCollapsedChange] = useControllableState.useControllableState(
151
301
  controlledCollapsed === void 0 && defaultCollapsed === void 0 ? false : controlledCollapsed,
152
302
  controlledOnCollapsedChange,
153
303
  defaultCollapsed
154
304
  );
155
- const preventDefault = React.useCallback((event) => {
156
- event.preventDefault();
305
+ const setEmptyRef = React.useCallback((isEmpty) => {
306
+ isEmptyRef.current = isEmpty;
157
307
  }, []);
158
- const stopPropagation = React.useCallback((event) => {
159
- event.stopPropagation();
308
+ const setEmojiPickerOpenRef = React.useCallback((isEmojiPickerOpen) => {
309
+ isEmojiPickerOpenRef.current = isEmojiPickerOpen;
160
310
  }, []);
161
- const handleEditorClick = React.useCallback(
162
- (event) => {
163
- event.stopPropagation();
164
- if (isEmpty) {
165
- onCollapsedChange?.(false);
166
- }
167
- },
168
- [isEmpty, onCollapsedChange]
169
- );
170
311
  const handleFocus = React.useCallback(
171
312
  (event) => {
172
313
  onFocus?.(event);
173
314
  if (event.isDefaultPrevented()) {
174
315
  return;
175
316
  }
176
- if (isEmpty) {
317
+ if (isEmptyRef.current) {
177
318
  onCollapsedChange?.(false);
178
319
  }
179
320
  },
180
- [isEmpty, onCollapsedChange, onFocus]
321
+ [onCollapsedChange, onFocus]
181
322
  );
182
323
  const handleBlur = React.useCallback(
183
324
  (event) => {
@@ -185,75 +326,24 @@ const ComposerWithContext = React.forwardRef(
185
326
  if (event.isDefaultPrevented()) {
186
327
  return;
187
328
  }
188
- const isOutside = !event.currentTarget.contains(event.relatedTarget);
189
- if (isOutside && isEmpty && !isEmojiPickerOpen) {
329
+ const isOutside = !event.currentTarget.contains(
330
+ event.relatedTarget ?? document.activeElement
331
+ );
332
+ if (isOutside && isEmptyRef.current && !isEmojiPickerOpenRef.current) {
190
333
  onCollapsedChange?.(true);
191
334
  }
192
335
  },
193
- [isEmojiPickerOpen, isEmpty, onBlur, onCollapsedChange]
336
+ [onBlur, onCollapsedChange]
337
+ );
338
+ const handleEditorClick = React.useCallback(
339
+ (event) => {
340
+ event.stopPropagation();
341
+ if (isEmptyRef.current) {
342
+ onCollapsedChange?.(false);
343
+ }
344
+ },
345
+ [onCollapsedChange]
194
346
  );
195
- return /* @__PURE__ */ React.createElement("form", {
196
- className: classNames.classNames(
197
- "lb-root lb-composer lb-composer-form",
198
- className
199
- ),
200
- dir: $.dir,
201
- ...props,
202
- ref: forwardedRef,
203
- "data-collapsed": collapsed ? "" : void 0,
204
- onFocus: handleFocus,
205
- onBlur: handleBlur
206
- }, /* @__PURE__ */ React.createElement(index.Editor, {
207
- className: "lb-composer-editor",
208
- onClick: handleEditorClick,
209
- placeholder: $.COMPOSER_PLACEHOLDER,
210
- defaultValue,
211
- disabled: isDisabled,
212
- autoFocus,
213
- components: editorComponents,
214
- dir: $.dir
215
- }), !collapsed && /* @__PURE__ */ React.createElement("div", {
216
- className: "lb-composer-footer"
217
- }, /* @__PURE__ */ React.createElement("div", {
218
- className: "lb-composer-editor-actions"
219
- }, hasResolveMentionSuggestions && /* @__PURE__ */ React.createElement(ComposerInsertMentionEditorAction, {
220
- label: $.COMPOSER_INSERT_MENTION,
221
- disabled: isDisabled
222
- }), /* @__PURE__ */ React.createElement(ComposerInsertEmojiEditorAction, {
223
- label: $.COMPOSER_INSERT_EMOJI,
224
- onPickerOpenChange: setEmojiPickerOpen,
225
- disabled: isDisabled
226
- })), showAttribution && /* @__PURE__ */ React.createElement(Attribution.Attribution, null), /* @__PURE__ */ React.createElement("div", {
227
- className: "lb-composer-actions"
228
- }, actions ?? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip.ShortcutTooltip, {
229
- content: $.COMPOSER_SEND,
230
- shortcut: /* @__PURE__ */ React.createElement(Tooltip.ShortcutTooltipKey, {
231
- name: "enter"
232
- })
233
- }, /* @__PURE__ */ React.createElement(index.Submit, {
234
- disabled: isDisabled,
235
- asChild: true
236
- }, /* @__PURE__ */ React.createElement(Button.Button, {
237
- onMouseDown: preventDefault,
238
- onClick: stopPropagation,
239
- className: "lb-composer-action",
240
- variant: "primary",
241
- "aria-label": $.COMPOSER_SEND
242
- }, /* @__PURE__ */ React.createElement(Send.SendIcon, null))))))));
243
- }
244
- );
245
- const Composer = React.forwardRef(
246
- ({
247
- threadId,
248
- commentId,
249
- metadata,
250
- onComposerSubmit,
251
- onFocus,
252
- ...props
253
- }, forwardedRef) => {
254
- const createThread = react.useCreateThread();
255
- const createComment = react.useCreateComment();
256
- const editComment = react.useEditComment();
257
347
  const handleCommentSubmit = React.useCallback(
258
348
  (comment, event) => {
259
349
  onComposerSubmit?.(comment, event);
@@ -264,17 +354,20 @@ const Composer = React.forwardRef(
264
354
  editComment({
265
355
  commentId,
266
356
  threadId,
267
- body: comment.body
357
+ body: comment.body,
358
+ attachments: comment.attachments
268
359
  });
269
360
  } else if (threadId) {
270
361
  createComment({
271
362
  threadId,
272
- body: comment.body
363
+ body: comment.body,
364
+ attachments: comment.attachments
273
365
  });
274
366
  } else {
275
367
  createThread({
276
368
  body: comment.body,
277
- metadata: metadata ?? {}
369
+ metadata: metadata ?? {},
370
+ attachments: comment.attachments
278
371
  });
279
372
  }
280
373
  },
@@ -290,11 +383,31 @@ const Composer = React.forwardRef(
290
383
  );
291
384
  return /* @__PURE__ */ React.createElement(TooltipPrimitive.TooltipProvider, null, /* @__PURE__ */ React.createElement(index.Form, {
292
385
  onComposerSubmit: handleCommentSubmit,
293
- onFocus,
294
- asChild: true
295
- }, /* @__PURE__ */ React.createElement(ComposerWithContext, {
386
+ className: classNames.classNames(
387
+ "lb-root lb-composer lb-composer-form",
388
+ className
389
+ ),
390
+ dir: $.dir,
296
391
  ...props,
297
- ref: forwardedRef
392
+ ref: forwardedRef,
393
+ "data-collapsed": isCollapsed ? "" : void 0,
394
+ onFocus: handleFocus,
395
+ onBlur: handleBlur,
396
+ disabled,
397
+ defaultAttachments,
398
+ pasteFilesAsAttachments: showAttachments
399
+ }, /* @__PURE__ */ React.createElement(ComposerEditorContainer, {
400
+ defaultValue,
401
+ actions,
402
+ overrides: overrides$1,
403
+ isCollapsed,
404
+ showAttachments,
405
+ showAttribution,
406
+ hasResolveMentionSuggestions,
407
+ onEmptyChange: setEmptyRef,
408
+ onEmojiPickerOpenChange: setEmojiPickerOpenRef,
409
+ onEditorClick: handleEditorClick,
410
+ autoFocus
298
411
  })));
299
412
  }
300
413
  );