@liveblocks/react-ui 2.12.3-emails1 → 2.13.1-emails1

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 (95) hide show
  1. package/dist/_private/index.d.mts +1 -1
  2. package/dist/_private/index.d.ts +1 -1
  3. package/dist/components/Comment.js +2 -0
  4. package/dist/components/Comment.js.map +1 -1
  5. package/dist/components/Comment.mjs +2 -0
  6. package/dist/components/Comment.mjs.map +1 -1
  7. package/dist/components/Composer.js +74 -6
  8. package/dist/components/Composer.js.map +1 -1
  9. package/dist/components/Composer.mjs +77 -9
  10. package/dist/components/Composer.mjs.map +1 -1
  11. package/dist/components/HistoryVersionSummary.js +1 -0
  12. package/dist/components/HistoryVersionSummary.js.map +1 -1
  13. package/dist/components/HistoryVersionSummary.mjs +1 -0
  14. package/dist/components/HistoryVersionSummary.mjs.map +1 -1
  15. package/dist/components/Thread.js +3 -0
  16. package/dist/components/Thread.js.map +1 -1
  17. package/dist/components/Thread.mjs +3 -0
  18. package/dist/components/Thread.mjs.map +1 -1
  19. package/dist/components/internal/Button.js.map +1 -1
  20. package/dist/components/internal/Button.mjs.map +1 -1
  21. package/dist/icons/Bold.js +15 -0
  22. package/dist/icons/Bold.js.map +1 -0
  23. package/dist/icons/Bold.mjs +13 -0
  24. package/dist/icons/Bold.mjs.map +1 -0
  25. package/dist/icons/Code.js +15 -0
  26. package/dist/icons/Code.js.map +1 -0
  27. package/dist/icons/Code.mjs +13 -0
  28. package/dist/icons/Code.mjs.map +1 -0
  29. package/dist/icons/Italic.js +15 -0
  30. package/dist/icons/Italic.js.map +1 -0
  31. package/dist/icons/Italic.mjs +13 -0
  32. package/dist/icons/Italic.mjs.map +1 -0
  33. package/dist/icons/Strikethrough.js +15 -0
  34. package/dist/icons/Strikethrough.js.map +1 -0
  35. package/dist/icons/Strikethrough.mjs +13 -0
  36. package/dist/icons/Strikethrough.mjs.map +1 -0
  37. package/dist/index.d.mts +94 -103
  38. package/dist/index.d.ts +94 -103
  39. package/dist/index.js +0 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.mjs +0 -1
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/overrides.js +15 -0
  44. package/dist/overrides.js.map +1 -1
  45. package/dist/overrides.mjs +15 -0
  46. package/dist/overrides.mjs.map +1 -1
  47. package/dist/primitives/Composer/contexts.js +12 -0
  48. package/dist/primitives/Composer/contexts.js.map +1 -1
  49. package/dist/primitives/Composer/contexts.mjs +11 -1
  50. package/dist/primitives/Composer/contexts.mjs.map +1 -1
  51. package/dist/primitives/Composer/index.js +251 -76
  52. package/dist/primitives/Composer/index.js.map +1 -1
  53. package/dist/primitives/Composer/index.mjs +239 -85
  54. package/dist/primitives/Composer/index.mjs.map +1 -1
  55. package/dist/primitives/Composer/utils.js +94 -21
  56. package/dist/primitives/Composer/utils.js.map +1 -1
  57. package/dist/primitives/Composer/utils.mjs +91 -20
  58. package/dist/primitives/Composer/utils.mjs.map +1 -1
  59. package/dist/primitives/index.d.mts +57 -1
  60. package/dist/primitives/index.d.ts +57 -1
  61. package/dist/slate/plugins/auto-formatting.js +1 -2
  62. package/dist/slate/plugins/auto-formatting.js.map +1 -1
  63. package/dist/slate/plugins/auto-formatting.mjs +1 -2
  64. package/dist/slate/plugins/auto-formatting.mjs.map +1 -1
  65. package/dist/slate/plugins/auto-links.js +3 -2
  66. package/dist/slate/plugins/auto-links.js.map +1 -1
  67. package/dist/slate/plugins/auto-links.mjs +2 -1
  68. package/dist/slate/plugins/auto-links.mjs.map +1 -1
  69. package/dist/slate/plugins/custom-links.js +3 -2
  70. package/dist/slate/plugins/custom-links.js.map +1 -1
  71. package/dist/slate/plugins/custom-links.mjs +2 -1
  72. package/dist/slate/plugins/custom-links.mjs.map +1 -1
  73. package/dist/slate/plugins/mentions.js +3 -4
  74. package/dist/slate/plugins/mentions.js.map +1 -1
  75. package/dist/slate/plugins/mentions.mjs +4 -5
  76. package/dist/slate/plugins/mentions.mjs.map +1 -1
  77. package/dist/slate/utils/marks.js +27 -8
  78. package/dist/slate/utils/marks.js.map +1 -1
  79. package/dist/slate/utils/marks.mjs +27 -10
  80. package/dist/slate/utils/marks.mjs.map +1 -1
  81. package/dist/utils/use-observable.js +15 -0
  82. package/dist/utils/use-observable.js.map +1 -0
  83. package/dist/utils/use-observable.mjs +13 -0
  84. package/dist/utils/use-observable.mjs.map +1 -0
  85. package/dist/version.js +1 -1
  86. package/dist/version.mjs +1 -1
  87. package/package.json +4 -4
  88. package/src/styles/constants.css +1 -1
  89. package/src/styles/index.css +70 -9
  90. package/styles.css +1 -1
  91. package/styles.css.map +1 -1
  92. package/dist/slate/utils/is-selection-collapsed.js +0 -10
  93. package/dist/slate/utils/is-selection-collapsed.js.map +0 -1
  94. package/dist/slate/utils/is-selection-collapsed.mjs +0 -8
  95. package/dist/slate/utils/is-selection-collapsed.mjs.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 { kInternal, Permission } from \"@liveblocks/core\";\nimport { useClient } from \"@liveblocks/react\";\nimport {\n useCreateRoomComment,\n useCreateRoomThread,\n useEditRoomComment,\n useRoomOrNull,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport type {\n ComponentPropsWithoutRef,\n FocusEvent,\n FormEvent,\n ForwardedRef,\n MouseEvent,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport React, { createContext, forwardRef, useCallback, useRef } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nimport { useLiveblocksUIConfig } from \"../config\";\nimport { AttachmentIcon } from \"../icons/Attachment\";\nimport { EmojiIcon } from \"../icons/Emoji\";\nimport { MentionIcon } from \"../icons/Mention\";\nimport { SendIcon } from \"../icons/Send\";\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 ComposerSubmitComment,\n} from \"../primitives/Composer/types\";\nimport { useComposerAttachmentsDropArea } from \"../primitives/Composer/utils\";\nimport { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport { classNames } from \"../utils/class-names\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\nimport { useLayoutEffect } from \"../utils/use-layout-effect\";\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 {\n ShortcutTooltip,\n ShortcutTooltipKey,\n Tooltip,\n TooltipProvider,\n} 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\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 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 | \"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={classNames(\"lb-composer-editor-action\", className)}\n onMouseDown={preventDefault}\n onClick={handleClick}\n aria-label={label}\n {...props}\n >\n <MentionIcon className=\"lb-button-icon\" />\n </Button>\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={classNames(\"lb-composer-editor-action\", className)}\n onMouseDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n {...props}\n >\n <EmojiIcon className=\"lb-button-icon\" />\n </Button>\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={classNames(\"lb-composer-editor-action\", className)}\n onMouseDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n {...props}\n >\n <AttachmentIcon className=\"lb-button-icon\" />\n </Button>\n </ComposerPrimitive.AttachFiles>\n </Tooltip>\n );\n}\n\nfunction ComposerMention({ userId }: ComposerEditorMentionProps) {\n return (\n <ComposerPrimitive.Mention className=\"lb-composer-mention\">\n {MENTION_CHARACTER}\n <User userId={userId} />\n </ComposerPrimitive.Mention>\n );\n}\n\nfunction ComposerMentionSuggestions({\n userIds,\n}: ComposerEditorMentionSuggestionsProps) {\n return userIds.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 {userIds.map((userId) => (\n <ComposerPrimitive.SuggestionsListItem\n key={userId}\n className=\"lb-composer-suggestions-list-item lb-composer-mention-suggestion\"\n value={userId}\n >\n <Avatar\n userId={userId}\n className=\"lb-composer-mention-suggestion-avatar\"\n />\n <User\n userId={userId}\n className=\"lb-composer-mention-suggestion-user\"\n />\n </ComposerPrimitive.SuggestionsListItem>\n ))}\n </ComposerPrimitive.SuggestionsList>\n </ComposerPrimitive.Suggestions>\n ) : null;\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={classNames(\"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\n className={classNames(\"lb-composer-attachments\", className)}\n {...props}\n >\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 editorComponents: ComposerEditorComponents = {\n Mention: ComposerMention,\n MentionSuggestions: ComposerMentionSuggestions,\n Link: ComposerLink,\n};\n\nfunction ComposerEditorContainer({\n showAttachments = 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\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={editorComponents}\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\n content={$.COMPOSER_SEND}\n shortcut={<ShortcutTooltipKey name=\"enter\" />}\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n onMouseDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-composer-action\"\n variant=\"primary\"\n aria-label={$.COMPOSER_SEND}\n >\n <SendIcon />\n </Button>\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 showAttribution,\n roomId: _roomId,\n ...props\n }: ComposerProps<M>,\n forwardedRef: ForwardedRef<HTMLFormElement>\n ) => {\n const client = useClient();\n const room = useRoomOrNull();\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 client[kInternal].resolveMentionSuggestions !== undefined;\n const isEmptyRef = useRef(true);\n const isEmojiPickerOpenRef = useRef(false);\n const $ = useOverrides(overrides);\n const [isCollapsed, onCollapsedChange] = useControllableState(\n // If the composer is neither controlled nor uncontrolled, it defaults to controlled as uncollapsed.\n controlledCollapsed === undefined && defaultCollapsed === undefined\n ? false\n : controlledCollapsed,\n controlledOnCollapsedChange,\n defaultCollapsed\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 handleCommentSubmit = useCallback(\n (comment: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(comment, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\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={handleCommentSubmit}\n className={classNames(\n \"lb-root lb-composer lb-composer-form\",\n className\n )}\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 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":["createContext"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgNA;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;AAC6D;AAC/C;AACJ;AACG;AACR;AAEH;AAAsB;AAI/B;AAEA;AAAyC;AACvC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAA2B;AAA0B;AACnD;AAAiC;AAC/B;AAA0B;AACxB;AAC6D;AAC/C;AACJ;AACG;AACR;AAEH;AAAoB;AAMjC;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAiC;AAC/B;AAAqC;AACnC;AAC6D;AAC/C;AACJ;AACG;AACR;AAEH;AAAyB;AAKpC;AAEA;AACE;AACG;AAAoC;AAElC;AAAK;AAGZ;AAEA;AAAoC;AAEpC;AACE;AACG;AAAwC;AACtC;AAA4C;AAExC;AACM;AACK;AACH;AAEN;AACC;AACU;AAEX;AACC;AACU;AAOxB;AAEA;AACE;AACG;AAAuB;AAAsB;AAIlD;AAWA;AAAgC;AAC9B;AACA;AACA;AAEF;AACE;AACA;AAEA;AACE;AAA8B;AAGhC;AACG;AAC0D;AACrD;AACJ;AACe;AACK;AACpB;AACA;AAGN;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAO;AAGT;AACG;AAC2D;AACtD;AAEH;AAAc;AAEX;AACG;AACiB;AAChB;AACA;AACF;AAMZ;AAEA;AAAmD;AACxC;AACW;AAEtB;AAEA;AAAiC;AACb;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AAEA;AAAuE;AAC/C;AAGxB;AACE;AAAqB;AAGvB;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAc;AAAmC;AAC/C;AACW;AACD;AACM;AACf;AACA;AACY;AACZ;AACO;AAEY;AAAoB;AAEtC;AAAc;AACZ;AAAc;AAEV;AACU;AACT;AAGH;AACU;AACW;AACpB;AAGC;AACU;AACT;AAKL;AAAc;AAGR;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAgC;AAC9B;AACc;AACJ;AACC;AACF;AACM;AAY3B;AAAc;AACZ;AAAc;AAQzB;AAEqCA;AAQ9B;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACkB;AAClB;AACQ;AACL;AAIL;AACA;AAEA;AACA;AACE;AAAU;AACR;AACF;AAGF;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AAInC;AACJ;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;AAA4B;AAExB;AAEA;AACE;AAAA;AAGF;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;AAEK;AACmB;AACP;AACT;AACA;AACF;AACO;AACH;AACC;AAC8B;AAC1B;AACD;AACe;AACvB;AACyB;AACF;AACvB;AAEC;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACU;AACV;AACf;AACA;AAGN;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 { kInternal, Permission } from \"@liveblocks/core\";\nimport { useClient } from \"@liveblocks/react\";\nimport {\n useCreateRoomComment,\n useCreateRoomThread,\n useEditRoomComment,\n useRoomOrNull,\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 React, {\n createContext,\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n} from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nimport { useLiveblocksUIConfig } from \"../config\";\nimport { FLOATING_ELEMENT_SIDE_OFFSET } 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 { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport type { ComposerBodyMark } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\nimport { useLayoutEffect } from \"../utils/use-layout-effect\";\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 {\n ShortcutTooltip,\n ShortcutTooltipKey,\n Tooltip,\n TooltipProvider,\n} 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 shortcut?: ReactNode;\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={classNames(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={handleClick}\n aria-label={label}\n {...props}\n >\n <MentionIcon className=\"lb-button-icon\" />\n </Button>\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={classNames(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n {...props}\n >\n <EmojiIcon className=\"lb-button-icon\" />\n </Button>\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={classNames(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n {...props}\n >\n <AttachmentIcon className=\"lb-button-icon\" />\n </Button>\n </ComposerPrimitive.AttachFiles>\n </Tooltip>\n );\n}\n\nfunction ComposerMention({ userId }: ComposerEditorMentionProps) {\n return (\n <ComposerPrimitive.Mention className=\"lb-composer-mention\">\n {MENTION_CHARACTER}\n <User userId={userId} />\n </ComposerPrimitive.Mention>\n );\n}\n\nfunction ComposerMentionSuggestions({\n userIds,\n}: ComposerEditorMentionSuggestionsProps) {\n return userIds.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 {userIds.map((userId) => (\n <ComposerPrimitive.SuggestionsListItem\n key={userId}\n className=\"lb-composer-suggestions-list-item lb-composer-mention-suggestion\"\n value={userId}\n >\n <Avatar\n userId={userId}\n className=\"lb-composer-mention-suggestion-avatar\"\n />\n <User\n userId={userId}\n className=\"lb-composer-mention-suggestion-user\"\n />\n </ComposerPrimitive.SuggestionsListItem>\n ))}\n </ComposerPrimitive.SuggestionsList>\n </ComposerPrimitive.Suggestions>\n ) : null;\n}\n\nfunction MarkToggle({ mark, shortcut, children, ...props }: 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=\"toggle\">\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: () => (\n <MarkToggle\n mark=\"bold\"\n shortcut={\n <>\n <ShortcutTooltipKey name=\"mod\" />\n <span>B</span>\n </>\n }\n >\n <BoldIcon />\n </MarkToggle>\n ),\n italic: () => (\n <MarkToggle\n mark=\"italic\"\n shortcut={\n <>\n <ShortcutTooltipKey name=\"mod\" />\n <span>I</span>\n </>\n }\n >\n <ItalicIcon />\n </MarkToggle>\n ),\n strikethrough: () => (\n <MarkToggle\n mark=\"strikethrough\"\n shortcut={\n <>\n <ShortcutTooltipKey name=\"mod\" />\n <ShortcutTooltipKey name=\"shift\" />\n <span>S</span>\n </>\n }\n >\n <StrikethroughIcon />\n </MarkToggle>\n ),\n code: () => (\n <MarkToggle\n mark=\"code\"\n shortcut={\n <>\n <ShortcutTooltipKey name=\"mod\" />\n <span>E</span>\n </>\n }\n >\n <CodeIcon />\n </MarkToggle>\n ),\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={classNames(\"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\n className={classNames(\"lb-composer-attachments\", className)}\n {...props}\n >\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\n content={$.COMPOSER_SEND}\n shortcut={<ShortcutTooltipKey name=\"enter\" />}\n >\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 >\n <SendIcon />\n </Button>\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 client = useClient();\n const room = useRoomOrNull();\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 client[kInternal].resolveMentionSuggestions !== undefined;\n const isEmptyRef = useRef(true);\n const isEmojiPickerOpenRef = useRef(false);\n const $ = useOverrides(overrides);\n const [isCollapsed, onCollapsedChange] = useControllableState(\n // If the composer is neither controlled nor uncontrolled, it defaults to controlled as uncollapsed.\n controlledCollapsed === undefined && defaultCollapsed === undefined\n ? false\n : controlledCollapsed,\n controlledOnCollapsedChange,\n defaultCollapsed\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 handleCommentSubmit = useCallback(\n (comment: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(comment, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\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={handleCommentSubmit}\n className={classNames(\n \"lb-root lb-composer lb-composer-form\",\n className\n )}\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":["createContext"],"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;AAC6D;AAC7C;AACN;AACG;AACR;AAEH;AAAsB;AAI/B;AAEA;AAAyC;AACvC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAA2B;AAA0B;AACnD;AAAiC;AAC/B;AAA0B;AACxB;AAC6D;AAC7C;AACN;AACG;AACR;AAEH;AAAoB;AAMjC;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAiC;AAC/B;AAAqC;AACnC;AAC6D;AAC7C;AACN;AACG;AACR;AAEH;AAAyB;AAKpC;AAEA;AACE;AACG;AAAoC;AAElC;AAAK;AAGZ;AAEA;AAAoC;AAEpC;AACE;AACG;AAAwC;AACtC;AAA4C;AAExC;AACM;AACK;AACH;AAEN;AACC;AACU;AAEX;AACC;AACU;AAOxB;AAEA;AACE;AACA;AACE;AAAkC;AAGpC;AACG;AACU;AACT;AAC2C;AAE1C;AAA6B;AAAmB;AAAK;AACnD;AAAmB;AAAe;AAM3C;AAMA;AAAiC;AAE5B;AACM;AAGA;AAAwB;AAE3B;AAIJ;AAGC;AACM;AAGA;AAAwB;AAE3B;AAIJ;AAGC;AACM;AAGA;AAAwB;AACxB;AAAwB;AAE3B;AAIJ;AAGC;AACM;AAGA;AAAwB;AAE3B;AAMR;AAEA;AACG;AAAY;AAGf;AACE;AACG;AAA4C;AAIjD;AAEA;AACE;AACG;AAAuB;AAAsB;AAIlD;AAWA;AAAgC;AAC9B;AACA;AACA;AAEF;AACE;AACA;AAEA;AACE;AAA8B;AAGhC;AACG;AAC0D;AACrD;AACJ;AACe;AACK;AACpB;AACA;AAGN;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAO;AAGT;AACG;AAC2D;AACtD;AAEH;AAAc;AAEX;AACG;AACiB;AAChB;AACA;AACF;AAMZ;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;AAC/C;AACW;AACD;AACM;AACf;AACA;AACA;AACA;AACO;AAEY;AAAoB;AAEtC;AAAc;AACZ;AAAc;AAEV;AACU;AACT;AAGH;AACU;AACW;AACpB;AAGC;AACU;AACT;AAKL;AAAc;AAGR;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAgC;AAC9B;AACgB;AACN;AACC;AACF;AACM;AAY3B;AAAc;AACZ;AAAc;AAQzB;AAEqCA;AAQ9B;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACkB;AACO;AACzB;AACQ;AACL;AAIL;AACA;AAEA;AACA;AACE;AAAU;AACR;AACF;AAGF;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AAInC;AACJ;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;AAA4B;AAExB;AAEA;AACE;AAAA;AAGF;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;AAEK;AACmB;AACP;AACT;AACA;AACF;AACO;AACH;AACC;AAC8B;AAC1B;AACD;AACe;AACvB;AACyB;AACF;AACvB;AAEC;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACU;AACV;AACf;AACA;AAGN;AAGN;;"}
@@ -2,15 +2,20 @@
2
2
  import { kInternal, Permission } from '@liveblocks/core';
3
3
  import { useClient } from '@liveblocks/react';
4
4
  import { useRoomOrNull, useCreateRoomThread, useCreateRoomComment, useEditRoomComment, useRoomPermissions } from '@liveblocks/react/_private';
5
- import React__default, { createContext, forwardRef, useRef, useCallback } from 'react';
5
+ import React__default, { createContext, forwardRef, useRef, useCallback, useMemo } from 'react';
6
6
  import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js';
7
7
  import { useLiveblocksUIConfig } from '../config.mjs';
8
+ import { FLOATING_ELEMENT_SIDE_OFFSET } from '../constants.mjs';
8
9
  import { AttachmentIcon } from '../icons/Attachment.mjs';
10
+ import { BoldIcon } from '../icons/Bold.mjs';
11
+ import { CodeIcon } from '../icons/Code.mjs';
9
12
  import { EmojiIcon } from '../icons/Emoji.mjs';
13
+ import { ItalicIcon } from '../icons/Italic.mjs';
10
14
  import { MentionIcon } from '../icons/Mention.mjs';
11
15
  import { SendIcon } from '../icons/Send.mjs';
16
+ import { StrikethroughIcon } from '../icons/Strikethrough.mjs';
12
17
  import { useOverrides } from '../overrides.mjs';
13
- import { Form as ComposerForm, Editor as ComposerEditor, Submit as ComposerSubmit, AttachFiles as ComposerAttachFiles, Mention as ComposerMention$1, Suggestions as ComposerSuggestions, SuggestionsList as ComposerSuggestionsList, SuggestionsListItem as ComposerSuggestionsListItem, Link as ComposerLink$1 } from '../primitives/Composer/index.mjs';
18
+ import { Form as ComposerForm, Editor as ComposerEditor, Submit as ComposerSubmit, AttachFiles as ComposerAttachFiles, MarkToggle as ComposerMarkToggle, FloatingToolbar as ComposerFloatingToolbar$1, Mention as ComposerMention$1, Suggestions as ComposerSuggestions, SuggestionsList as ComposerSuggestionsList, SuggestionsListItem as ComposerSuggestionsListItem, Link as ComposerLink$1 } from '../primitives/Composer/index.mjs';
14
19
  import { useComposer, useComposerAttachmentsContext, useComposerEditorContext } from '../primitives/Composer/contexts.mjs';
15
20
  import { useComposerAttachmentsDropArea } from '../primitives/Composer/utils.mjs';
16
21
  import { MENTION_CHARACTER } from '../slate/plugins/mentions.mjs';
@@ -22,7 +27,7 @@ import { Attribution } from './internal/Attribution.mjs';
22
27
  import { Avatar } from './internal/Avatar.mjs';
23
28
  import { Button } from './internal/Button.mjs';
24
29
  import { EmojiPicker } from './internal/EmojiPicker.mjs';
25
- import { ShortcutTooltip, ShortcutTooltipKey, Tooltip } from './internal/Tooltip.mjs';
30
+ import { ShortcutTooltipKey, ShortcutTooltip, Tooltip } from './internal/Tooltip.mjs';
26
31
  import { User } from './internal/User.mjs';
27
32
  import { TooltipProvider } from '@radix-ui/react-tooltip';
28
33
  import { PopoverTrigger } from '@radix-ui/react-popover';
@@ -52,7 +57,7 @@ function ComposerInsertMentionEditorAction({
52
57
  content: tooltipLabel ?? label
53
58
  }, /* @__PURE__ */ React__default.createElement(Button, {
54
59
  className: classNames("lb-composer-editor-action", className),
55
- onMouseDown: preventDefault,
60
+ onPointerDown: preventDefault,
56
61
  onClick: handleClick,
57
62
  "aria-label": label,
58
63
  ...props
@@ -83,7 +88,7 @@ function ComposerInsertEmojiEditorAction({
83
88
  asChild: true
84
89
  }, /* @__PURE__ */ React__default.createElement(Button, {
85
90
  className: classNames("lb-composer-editor-action", className),
86
- onMouseDown: preventDefault,
91
+ onPointerDown: preventDefault,
87
92
  onClick: stopPropagation,
88
93
  "aria-label": label,
89
94
  ...props
@@ -109,7 +114,7 @@ function ComposerAttachFilesEditorAction({
109
114
  asChild: true
110
115
  }, /* @__PURE__ */ React__default.createElement(Button, {
111
116
  className: classNames("lb-composer-editor-action", className),
112
- onMouseDown: preventDefault,
117
+ onPointerDown: preventDefault,
113
118
  onClick: stopPropagation,
114
119
  "aria-label": label,
115
120
  ...props
@@ -143,6 +148,60 @@ function ComposerMentionSuggestions({
143
148
  className: "lb-composer-mention-suggestion-user"
144
149
  }))))) : null;
145
150
  }
151
+ function MarkToggle({ mark, shortcut, children, ...props }) {
152
+ const $ = useOverrides();
153
+ const label = useMemo(() => {
154
+ return $.COMPOSER_TOGGLE_MARK(mark);
155
+ }, [$, mark]);
156
+ return /* @__PURE__ */ React__default.createElement(ShortcutTooltip, {
157
+ content: label,
158
+ shortcut,
159
+ sideOffset: FLOATING_ELEMENT_SIDE_OFFSET + 2
160
+ }, /* @__PURE__ */ React__default.createElement(ComposerMarkToggle, {
161
+ mark,
162
+ asChild: true,
163
+ ...props
164
+ }, /* @__PURE__ */ React__default.createElement(Button, {
165
+ "aria-label": label,
166
+ variant: "toggle"
167
+ }, children)));
168
+ }
169
+ const markToggles = {
170
+ bold: () => /* @__PURE__ */ React__default.createElement(MarkToggle, {
171
+ mark: "bold",
172
+ shortcut: /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(ShortcutTooltipKey, {
173
+ name: "mod"
174
+ }), /* @__PURE__ */ React__default.createElement("span", null, "B"))
175
+ }, /* @__PURE__ */ React__default.createElement(BoldIcon, null)),
176
+ italic: () => /* @__PURE__ */ React__default.createElement(MarkToggle, {
177
+ mark: "italic",
178
+ shortcut: /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(ShortcutTooltipKey, {
179
+ name: "mod"
180
+ }), /* @__PURE__ */ React__default.createElement("span", null, "I"))
181
+ }, /* @__PURE__ */ React__default.createElement(ItalicIcon, null)),
182
+ strikethrough: () => /* @__PURE__ */ React__default.createElement(MarkToggle, {
183
+ mark: "strikethrough",
184
+ shortcut: /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(ShortcutTooltipKey, {
185
+ name: "mod"
186
+ }), /* @__PURE__ */ React__default.createElement(ShortcutTooltipKey, {
187
+ name: "shift"
188
+ }), /* @__PURE__ */ React__default.createElement("span", null, "S"))
189
+ }, /* @__PURE__ */ React__default.createElement(StrikethroughIcon, null)),
190
+ code: () => /* @__PURE__ */ React__default.createElement(MarkToggle, {
191
+ mark: "code",
192
+ shortcut: /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(ShortcutTooltipKey, {
193
+ name: "mod"
194
+ }), /* @__PURE__ */ React__default.createElement("span", null, "E"))
195
+ }, /* @__PURE__ */ React__default.createElement(CodeIcon, null))
196
+ };
197
+ const markTogglesList = Object.entries(markToggles).map(([mark, Toggle]) => /* @__PURE__ */ React__default.createElement(Toggle, {
198
+ key: mark
199
+ }));
200
+ function ComposerFloatingToolbar() {
201
+ return /* @__PURE__ */ React__default.createElement(ComposerFloatingToolbar$1, {
202
+ className: "lb-root lb-portal lb-elevation lb-composer-floating-toolbar"
203
+ }, markTogglesList);
204
+ }
146
205
  function ComposerLink({ href, children }) {
147
206
  return /* @__PURE__ */ React__default.createElement(ComposerLink$1, {
148
207
  href,
@@ -192,13 +251,14 @@ function ComposerAttachments({
192
251
  });
193
252
  })));
194
253
  }
195
- const editorComponents = {
254
+ const editorRequiredComponents = {
196
255
  Mention: ComposerMention,
197
256
  MentionSuggestions: ComposerMentionSuggestions,
198
257
  Link: ComposerLink
199
258
  };
200
259
  function ComposerEditorContainer({
201
260
  showAttachments = true,
261
+ showFormattingControls = true,
202
262
  showAttribution,
203
263
  defaultValue,
204
264
  isCollapsed,
@@ -214,6 +274,12 @@ function ComposerEditorContainer({
214
274
  const { isEmpty } = useComposer();
215
275
  const { hasMaxAttachments } = useComposerAttachmentsContext();
216
276
  const $ = useOverrides(overrides);
277
+ const components = useMemo(() => {
278
+ return {
279
+ ...editorRequiredComponents,
280
+ FloatingToolbar: showFormattingControls ? ComposerFloatingToolbar : void 0
281
+ };
282
+ }, [showFormattingControls]);
217
283
  const [isDraggingOver, dropAreaProps] = useComposerAttachmentsDropArea({
218
284
  disabled: disabled || hasMaxAttachments
219
285
  });
@@ -235,7 +301,7 @@ function ComposerEditorContainer({
235
301
  placeholder: $.COMPOSER_PLACEHOLDER,
236
302
  defaultValue,
237
303
  autoFocus,
238
- components: editorComponents,
304
+ components,
239
305
  disabled,
240
306
  dir: $.dir
241
307
  }), showAttachments && /* @__PURE__ */ React__default.createElement(ComposerAttachments, {
@@ -264,7 +330,7 @@ function ComposerEditorContainer({
264
330
  }, /* @__PURE__ */ React__default.createElement(ComposerSubmit, {
265
331
  asChild: true
266
332
  }, /* @__PURE__ */ React__default.createElement(Button, {
267
- onMouseDown: preventDefault,
333
+ onPointerDown: preventDefault,
268
334
  onClick: stopPropagation,
269
335
  className: "lb-composer-action",
270
336
  variant: "primary",
@@ -295,6 +361,7 @@ const Composer = forwardRef(
295
361
  autoFocus,
296
362
  disabled,
297
363
  showAttachments = true,
364
+ showFormattingControls = true,
298
365
  showAttribution,
299
366
  roomId: _roomId,
300
367
  ...props
@@ -440,6 +507,7 @@ const Composer = forwardRef(
440
507
  isCollapsed,
441
508
  showAttachments,
442
509
  showAttribution,
510
+ showFormattingControls,
443
511
  hasResolveMentionSuggestions,
444
512
  onEmptyChange: setEmptyRef,
445
513
  onEmojiPickerOpenChange: setEmojiPickerOpenRef,
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.mjs","sources":["../../src/components/Composer.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentAttachment,\n CommentMixedAttachment,\n DM,\n} from \"@liveblocks/core\";\nimport { kInternal, Permission } from \"@liveblocks/core\";\nimport { useClient } from \"@liveblocks/react\";\nimport {\n useCreateRoomComment,\n useCreateRoomThread,\n useEditRoomComment,\n useRoomOrNull,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport type {\n ComponentPropsWithoutRef,\n FocusEvent,\n FormEvent,\n ForwardedRef,\n MouseEvent,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport React, { createContext, forwardRef, useCallback, useRef } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nimport { useLiveblocksUIConfig } from \"../config\";\nimport { AttachmentIcon } from \"../icons/Attachment\";\nimport { EmojiIcon } from \"../icons/Emoji\";\nimport { MentionIcon } from \"../icons/Mention\";\nimport { SendIcon } from \"../icons/Send\";\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 ComposerSubmitComment,\n} from \"../primitives/Composer/types\";\nimport { useComposerAttachmentsDropArea } from \"../primitives/Composer/utils\";\nimport { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport { classNames } from \"../utils/class-names\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\nimport { useLayoutEffect } from \"../utils/use-layout-effect\";\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 {\n ShortcutTooltip,\n ShortcutTooltipKey,\n Tooltip,\n TooltipProvider,\n} 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\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 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 | \"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={classNames(\"lb-composer-editor-action\", className)}\n onMouseDown={preventDefault}\n onClick={handleClick}\n aria-label={label}\n {...props}\n >\n <MentionIcon className=\"lb-button-icon\" />\n </Button>\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={classNames(\"lb-composer-editor-action\", className)}\n onMouseDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n {...props}\n >\n <EmojiIcon className=\"lb-button-icon\" />\n </Button>\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={classNames(\"lb-composer-editor-action\", className)}\n onMouseDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n {...props}\n >\n <AttachmentIcon className=\"lb-button-icon\" />\n </Button>\n </ComposerPrimitive.AttachFiles>\n </Tooltip>\n );\n}\n\nfunction ComposerMention({ userId }: ComposerEditorMentionProps) {\n return (\n <ComposerPrimitive.Mention className=\"lb-composer-mention\">\n {MENTION_CHARACTER}\n <User userId={userId} />\n </ComposerPrimitive.Mention>\n );\n}\n\nfunction ComposerMentionSuggestions({\n userIds,\n}: ComposerEditorMentionSuggestionsProps) {\n return userIds.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 {userIds.map((userId) => (\n <ComposerPrimitive.SuggestionsListItem\n key={userId}\n className=\"lb-composer-suggestions-list-item lb-composer-mention-suggestion\"\n value={userId}\n >\n <Avatar\n userId={userId}\n className=\"lb-composer-mention-suggestion-avatar\"\n />\n <User\n userId={userId}\n className=\"lb-composer-mention-suggestion-user\"\n />\n </ComposerPrimitive.SuggestionsListItem>\n ))}\n </ComposerPrimitive.SuggestionsList>\n </ComposerPrimitive.Suggestions>\n ) : null;\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={classNames(\"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\n className={classNames(\"lb-composer-attachments\", className)}\n {...props}\n >\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 editorComponents: ComposerEditorComponents = {\n Mention: ComposerMention,\n MentionSuggestions: ComposerMentionSuggestions,\n Link: ComposerLink,\n};\n\nfunction ComposerEditorContainer({\n showAttachments = 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\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={editorComponents}\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\n content={$.COMPOSER_SEND}\n shortcut={<ShortcutTooltipKey name=\"enter\" />}\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n onMouseDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-composer-action\"\n variant=\"primary\"\n aria-label={$.COMPOSER_SEND}\n >\n <SendIcon />\n </Button>\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 showAttribution,\n roomId: _roomId,\n ...props\n }: ComposerProps<M>,\n forwardedRef: ForwardedRef<HTMLFormElement>\n ) => {\n const client = useClient();\n const room = useRoomOrNull();\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 client[kInternal].resolveMentionSuggestions !== undefined;\n const isEmptyRef = useRef(true);\n const isEmojiPickerOpenRef = useRef(false);\n const $ = useOverrides(overrides);\n const [isCollapsed, onCollapsedChange] = useControllableState(\n // If the composer is neither controlled nor uncontrolled, it defaults to controlled as uncollapsed.\n controlledCollapsed === undefined && defaultCollapsed === undefined\n ? false\n : controlledCollapsed,\n controlledOnCollapsedChange,\n defaultCollapsed\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 handleCommentSubmit = useCallback(\n (comment: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(comment, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\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={handleCommentSubmit}\n className={classNames(\n \"lb-root lb-composer lb-composer-form\",\n className\n )}\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 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgNA;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;AAC6D;AAC/C;AACJ;AACG;AACR;AAEH;AAAsB;AAI/B;AAEA;AAAyC;AACvC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAA2B;AAA0B;AACnD;AAAiC;AAC/B;AAA0B;AACxB;AAC6D;AAC/C;AACJ;AACG;AACR;AAEH;AAAoB;AAMjC;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAiC;AAC/B;AAAqC;AACnC;AAC6D;AAC/C;AACJ;AACG;AACR;AAEH;AAAyB;AAKpC;AAEA;AACE;AACG;AAAoC;AAElC;AAAK;AAGZ;AAEA;AAAoC;AAEpC;AACE;AACG;AAAwC;AACtC;AAA4C;AAExC;AACM;AACK;AACH;AAEN;AACC;AACU;AAEX;AACC;AACU;AAOxB;AAEA;AACE;AACG;AAAuB;AAAsB;AAIlD;AAWA;AAAgC;AAC9B;AACA;AACA;AAEF;AACE;AACA;AAEA;AACE;AAA8B;AAGhC;AACG;AAC0D;AACrD;AACJ;AACe;AACK;AACpB;AACA;AAGN;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAO;AAGT;AACG;AAC2D;AACtD;AAEH;AAAc;AAEX;AACG;AACiB;AAChB;AACA;AACF;AAMZ;AAEA;AAAmD;AACxC;AACW;AAEtB;AAEA;AAAiC;AACb;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AAEA;AAAuE;AAC/C;AAGxB;AACE;AAAqB;AAGvB;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAc;AAAmC;AAC/C;AACW;AACD;AACM;AACf;AACA;AACY;AACZ;AACO;AAEY;AAAoB;AAEtC;AAAc;AACZ;AAAc;AAEV;AACU;AACT;AAGH;AACU;AACW;AACpB;AAGC;AACU;AACT;AAKL;AAAc;AAGR;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAgC;AAC9B;AACc;AACJ;AACC;AACF;AACM;AAY3B;AAAc;AACZ;AAAc;AAQzB;AAEqC;AAQ9B;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACkB;AAClB;AACQ;AACL;AAIL;AACA;AAEA;AACA;AACE;AAAU;AACR;AACF;AAGF;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AAInC;AACJ;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;AAA4B;AAExB;AAEA;AACE;AAAA;AAGF;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;AAEK;AACmB;AACP;AACT;AACA;AACF;AACO;AACH;AACC;AAC8B;AAC1B;AACD;AACe;AACvB;AACyB;AACF;AACvB;AAEC;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACU;AACV;AACf;AACA;AAGN;AAGN;;"}
1
+ {"version":3,"file":"Composer.mjs","sources":["../../src/components/Composer.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentAttachment,\n CommentMixedAttachment,\n DM,\n} from \"@liveblocks/core\";\nimport { kInternal, Permission } from \"@liveblocks/core\";\nimport { useClient } from \"@liveblocks/react\";\nimport {\n useCreateRoomComment,\n useCreateRoomThread,\n useEditRoomComment,\n useRoomOrNull,\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 React, {\n createContext,\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n} from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nimport { useLiveblocksUIConfig } from \"../config\";\nimport { FLOATING_ELEMENT_SIDE_OFFSET } 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 { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport type { ComposerBodyMark } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\nimport { useLayoutEffect } from \"../utils/use-layout-effect\";\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 {\n ShortcutTooltip,\n ShortcutTooltipKey,\n Tooltip,\n TooltipProvider,\n} 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 shortcut?: ReactNode;\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={classNames(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={handleClick}\n aria-label={label}\n {...props}\n >\n <MentionIcon className=\"lb-button-icon\" />\n </Button>\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={classNames(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n {...props}\n >\n <EmojiIcon className=\"lb-button-icon\" />\n </Button>\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={classNames(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n {...props}\n >\n <AttachmentIcon className=\"lb-button-icon\" />\n </Button>\n </ComposerPrimitive.AttachFiles>\n </Tooltip>\n );\n}\n\nfunction ComposerMention({ userId }: ComposerEditorMentionProps) {\n return (\n <ComposerPrimitive.Mention className=\"lb-composer-mention\">\n {MENTION_CHARACTER}\n <User userId={userId} />\n </ComposerPrimitive.Mention>\n );\n}\n\nfunction ComposerMentionSuggestions({\n userIds,\n}: ComposerEditorMentionSuggestionsProps) {\n return userIds.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 {userIds.map((userId) => (\n <ComposerPrimitive.SuggestionsListItem\n key={userId}\n className=\"lb-composer-suggestions-list-item lb-composer-mention-suggestion\"\n value={userId}\n >\n <Avatar\n userId={userId}\n className=\"lb-composer-mention-suggestion-avatar\"\n />\n <User\n userId={userId}\n className=\"lb-composer-mention-suggestion-user\"\n />\n </ComposerPrimitive.SuggestionsListItem>\n ))}\n </ComposerPrimitive.SuggestionsList>\n </ComposerPrimitive.Suggestions>\n ) : null;\n}\n\nfunction MarkToggle({ mark, shortcut, children, ...props }: 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=\"toggle\">\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: () => (\n <MarkToggle\n mark=\"bold\"\n shortcut={\n <>\n <ShortcutTooltipKey name=\"mod\" />\n <span>B</span>\n </>\n }\n >\n <BoldIcon />\n </MarkToggle>\n ),\n italic: () => (\n <MarkToggle\n mark=\"italic\"\n shortcut={\n <>\n <ShortcutTooltipKey name=\"mod\" />\n <span>I</span>\n </>\n }\n >\n <ItalicIcon />\n </MarkToggle>\n ),\n strikethrough: () => (\n <MarkToggle\n mark=\"strikethrough\"\n shortcut={\n <>\n <ShortcutTooltipKey name=\"mod\" />\n <ShortcutTooltipKey name=\"shift\" />\n <span>S</span>\n </>\n }\n >\n <StrikethroughIcon />\n </MarkToggle>\n ),\n code: () => (\n <MarkToggle\n mark=\"code\"\n shortcut={\n <>\n <ShortcutTooltipKey name=\"mod\" />\n <span>E</span>\n </>\n }\n >\n <CodeIcon />\n </MarkToggle>\n ),\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={classNames(\"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\n className={classNames(\"lb-composer-attachments\", className)}\n {...props}\n >\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\n content={$.COMPOSER_SEND}\n shortcut={<ShortcutTooltipKey name=\"enter\" />}\n >\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 >\n <SendIcon />\n </Button>\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 client = useClient();\n const room = useRoomOrNull();\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 client[kInternal].resolveMentionSuggestions !== undefined;\n const isEmptyRef = useRef(true);\n const isEmojiPickerOpenRef = useRef(false);\n const $ = useOverrides(overrides);\n const [isCollapsed, onCollapsedChange] = useControllableState(\n // If the composer is neither controlled nor uncontrolled, it defaults to controlled as uncollapsed.\n controlledCollapsed === undefined && defaultCollapsed === undefined\n ? false\n : controlledCollapsed,\n controlledOnCollapsedChange,\n defaultCollapsed\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 handleCommentSubmit = useCallback(\n (comment: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(comment, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\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={handleCommentSubmit}\n className={classNames(\n \"lb-root lb-composer lb-composer-form\",\n className\n )}\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;AAC6D;AAC7C;AACN;AACG;AACR;AAEH;AAAsB;AAI/B;AAEA;AAAyC;AACvC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAA2B;AAA0B;AACnD;AAAiC;AAC/B;AAA0B;AACxB;AAC6D;AAC7C;AACN;AACG;AACR;AAEH;AAAoB;AAMjC;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAiC;AAC/B;AAAqC;AACnC;AAC6D;AAC7C;AACN;AACG;AACR;AAEH;AAAyB;AAKpC;AAEA;AACE;AACG;AAAoC;AAElC;AAAK;AAGZ;AAEA;AAAoC;AAEpC;AACE;AACG;AAAwC;AACtC;AAA4C;AAExC;AACM;AACK;AACH;AAEN;AACC;AACU;AAEX;AACC;AACU;AAOxB;AAEA;AACE;AACA;AACE;AAAkC;AAGpC;AACG;AACU;AACT;AAC2C;AAE1C;AAA6B;AAAmB;AAAK;AACnD;AAAmB;AAAe;AAM3C;AAMA;AAAiC;AAE5B;AACM;AAGA;AAAwB;AAE3B;AAIJ;AAGC;AACM;AAGA;AAAwB;AAE3B;AAIJ;AAGC;AACM;AAGA;AAAwB;AACxB;AAAwB;AAE3B;AAIJ;AAGC;AACM;AAGA;AAAwB;AAE3B;AAMR;AAEA;AACG;AAAY;AAGf;AACE;AACG;AAA4C;AAIjD;AAEA;AACE;AACG;AAAuB;AAAsB;AAIlD;AAWA;AAAgC;AAC9B;AACA;AACA;AAEF;AACE;AACA;AAEA;AACE;AAA8B;AAGhC;AACG;AAC0D;AACrD;AACJ;AACe;AACK;AACpB;AACA;AAGN;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAO;AAGT;AACG;AAC2D;AACtD;AAEH;AAAc;AAEX;AACG;AACiB;AAChB;AACA;AACF;AAMZ;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;AAC/C;AACW;AACD;AACM;AACf;AACA;AACA;AACA;AACO;AAEY;AAAoB;AAEtC;AAAc;AACZ;AAAc;AAEV;AACU;AACT;AAGH;AACU;AACW;AACpB;AAGC;AACU;AACT;AAKL;AAAc;AAGR;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAgC;AAC9B;AACgB;AACN;AACC;AACF;AACM;AAY3B;AAAc;AACZ;AAAc;AAQzB;AAEqC;AAQ9B;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACkB;AACO;AACzB;AACQ;AACL;AAIL;AACA;AAEA;AACA;AACE;AAAU;AACR;AACF;AAGF;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AAInC;AACJ;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;AAA4B;AAExB;AAEA;AACE;AAAA;AAGF;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;AAEK;AACmB;AACP;AACT;AACA;AACF;AACO;AACH;AACC;AAC8B;AAC1B;AACD;AACe;AACvB;AACyB;AACF;AACvB;AAEC;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACU;AACV;AACf;AACA;AAGN;AAGN;;"}
@@ -6,6 +6,7 @@ var overrides = require('../overrides.js');
6
6
  require('../primitives/Comment/index.js');
7
7
  require('../primitives/Composer/index.js');
8
8
  require('../primitives/Composer/contexts.js');
9
+ require('@floating-ui/react-dom');
9
10
  require('@liveblocks/core');
10
11
  require('@liveblocks/react');
11
12
  require('use-sync-external-store/shim/index.js');
@@ -1 +1 @@
1
- {"version":3,"file":"HistoryVersionSummary.js","sources":["../../src/components/HistoryVersionSummary.tsx"],"sourcesContent":["\"use client\";\n\nimport type { HistoryVersion } from \"@liveblocks/core\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport React, { forwardRef } from \"react\";\n\nimport { useOverrides } from \"../overrides\";\nimport { Timestamp } from \"../primitives\";\nimport { classNames } from \"../utils/class-names\";\nimport { List } from \"./internal/List\";\nimport { User } from \"./internal/User\";\n\nconst AUTHORS_TRUNCATE = 3;\n\nexport interface HistoryVersionSummaryProps\n extends ComponentPropsWithoutRef<\"button\"> {\n version: HistoryVersion;\n selected?: boolean;\n}\n\n/**\n * Displays some information about a version.\n *\n * @example\n * <HistoryVersionSummary version={version} />\n */\nexport const HistoryVersionSummary = forwardRef<\n HTMLButtonElement,\n HistoryVersionSummaryProps\n>(({ version, selected, className, ...props }, forwardedRef) => {\n const $ = useOverrides();\n\n return (\n <button\n {...props}\n className={classNames(\"lb-root lb-history-version-summary\", className)}\n ref={forwardedRef}\n data-selected={selected ? \"\" : undefined}\n >\n <Timestamp\n locale={$.locale}\n date={version.createdAt}\n className=\"lb-date lb-history-version-summary-date\"\n />\n <span className=\"lb-history-version-summary-authors\">\n <List\n values={version.authors.map((author) => (\n <User key={author.id} userId={author.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={AUTHORS_TRUNCATE}\n locale={$.locale}\n />\n </span>\n </button>\n );\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAYA;AAca;AAIX;AAEA;AACG;AACK;AACiE;AAChE;AAC0B;AAE9B;AACW;AACI;AACJ;AAEX;AAAe;AACb;AAEI;AAAiB;AAAmB;AAAe;AACrD;AACkB;AACT;AACA;AAKpB;;"}
1
+ {"version":3,"file":"HistoryVersionSummary.js","sources":["../../src/components/HistoryVersionSummary.tsx"],"sourcesContent":["\"use client\";\n\nimport type { HistoryVersion } from \"@liveblocks/core\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport React, { forwardRef } from \"react\";\n\nimport { useOverrides } from \"../overrides\";\nimport { Timestamp } from \"../primitives\";\nimport { classNames } from \"../utils/class-names\";\nimport { List } from \"./internal/List\";\nimport { User } from \"./internal/User\";\n\nconst AUTHORS_TRUNCATE = 3;\n\nexport interface HistoryVersionSummaryProps\n extends ComponentPropsWithoutRef<\"button\"> {\n version: HistoryVersion;\n selected?: boolean;\n}\n\n/**\n * Displays some information about a version.\n *\n * @example\n * <HistoryVersionSummary version={version} />\n */\nexport const HistoryVersionSummary = forwardRef<\n HTMLButtonElement,\n HistoryVersionSummaryProps\n>(({ version, selected, className, ...props }, forwardedRef) => {\n const $ = useOverrides();\n\n return (\n <button\n {...props}\n className={classNames(\"lb-root lb-history-version-summary\", className)}\n ref={forwardedRef}\n data-selected={selected ? \"\" : undefined}\n >\n <Timestamp\n locale={$.locale}\n date={version.createdAt}\n className=\"lb-date lb-history-version-summary-date\"\n />\n <span className=\"lb-history-version-summary-authors\">\n <List\n values={version.authors.map((author) => (\n <User key={author.id} userId={author.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={AUTHORS_TRUNCATE}\n locale={$.locale}\n />\n </span>\n </button>\n );\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAYA;AAca;AAIX;AAEA;AACG;AACK;AACiE;AAChE;AAC0B;AAE9B;AACW;AACI;AACJ;AAEX;AAAe;AACb;AAEI;AAAiB;AAAmB;AAAe;AACrD;AACkB;AACT;AACA;AAKpB;;"}
@@ -4,6 +4,7 @@ import { useOverrides } from '../overrides.mjs';
4
4
  import '../primitives/Comment/index.mjs';
5
5
  import '../primitives/Composer/index.mjs';
6
6
  import '../primitives/Composer/contexts.mjs';
7
+ import '@floating-ui/react-dom';
7
8
  import '@liveblocks/core';
8
9
  import '@liveblocks/react';
9
10
  import 'use-sync-external-store/shim/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"HistoryVersionSummary.mjs","sources":["../../src/components/HistoryVersionSummary.tsx"],"sourcesContent":["\"use client\";\n\nimport type { HistoryVersion } from \"@liveblocks/core\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport React, { forwardRef } from \"react\";\n\nimport { useOverrides } from \"../overrides\";\nimport { Timestamp } from \"../primitives\";\nimport { classNames } from \"../utils/class-names\";\nimport { List } from \"./internal/List\";\nimport { User } from \"./internal/User\";\n\nconst AUTHORS_TRUNCATE = 3;\n\nexport interface HistoryVersionSummaryProps\n extends ComponentPropsWithoutRef<\"button\"> {\n version: HistoryVersion;\n selected?: boolean;\n}\n\n/**\n * Displays some information about a version.\n *\n * @example\n * <HistoryVersionSummary version={version} />\n */\nexport const HistoryVersionSummary = forwardRef<\n HTMLButtonElement,\n HistoryVersionSummaryProps\n>(({ version, selected, className, ...props }, forwardedRef) => {\n const $ = useOverrides();\n\n return (\n <button\n {...props}\n className={classNames(\"lb-root lb-history-version-summary\", className)}\n ref={forwardedRef}\n data-selected={selected ? \"\" : undefined}\n >\n <Timestamp\n locale={$.locale}\n date={version.createdAt}\n className=\"lb-date lb-history-version-summary-date\"\n />\n <span className=\"lb-history-version-summary-authors\">\n <List\n values={version.authors.map((author) => (\n <User key={author.id} userId={author.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={AUTHORS_TRUNCATE}\n locale={$.locale}\n />\n </span>\n </button>\n );\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAYA;AAca;AAIX;AAEA;AACG;AACK;AACiE;AAChE;AAC0B;AAE9B;AACW;AACI;AACJ;AAEX;AAAe;AACb;AAEI;AAAiB;AAAmB;AAAe;AACrD;AACkB;AACT;AACA;AAKpB;;"}
1
+ {"version":3,"file":"HistoryVersionSummary.mjs","sources":["../../src/components/HistoryVersionSummary.tsx"],"sourcesContent":["\"use client\";\n\nimport type { HistoryVersion } from \"@liveblocks/core\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport React, { forwardRef } from \"react\";\n\nimport { useOverrides } from \"../overrides\";\nimport { Timestamp } from \"../primitives\";\nimport { classNames } from \"../utils/class-names\";\nimport { List } from \"./internal/List\";\nimport { User } from \"./internal/User\";\n\nconst AUTHORS_TRUNCATE = 3;\n\nexport interface HistoryVersionSummaryProps\n extends ComponentPropsWithoutRef<\"button\"> {\n version: HistoryVersion;\n selected?: boolean;\n}\n\n/**\n * Displays some information about a version.\n *\n * @example\n * <HistoryVersionSummary version={version} />\n */\nexport const HistoryVersionSummary = forwardRef<\n HTMLButtonElement,\n HistoryVersionSummaryProps\n>(({ version, selected, className, ...props }, forwardedRef) => {\n const $ = useOverrides();\n\n return (\n <button\n {...props}\n className={classNames(\"lb-root lb-history-version-summary\", className)}\n ref={forwardedRef}\n data-selected={selected ? \"\" : undefined}\n >\n <Timestamp\n locale={$.locale}\n date={version.createdAt}\n className=\"lb-date lb-history-version-summary-date\"\n />\n <span className=\"lb-history-version-summary-authors\">\n <List\n values={version.authors.map((author) => (\n <User key={author.id} userId={author.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={AUTHORS_TRUNCATE}\n locale={$.locale}\n />\n </span>\n </button>\n );\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAYA;AAca;AAIX;AAEA;AACG;AACK;AACiE;AAChE;AAC0B;AAE9B;AACW;AACI;AACJ;AAEX;AAAe;AACb;AAEI;AAAiB;AAAmB;AAAe;AACrD;AACkB;AACT;AACA;AAKpB;;"}
@@ -46,6 +46,7 @@ const Thread = React.forwardRef(
46
46
  showReactions = true,
47
47
  showComposer = "collapsed",
48
48
  showAttachments = true,
49
+ showComposerFormattingControls = true,
49
50
  onResolvedChange,
50
51
  onCommentEdit,
51
52
  onCommentDelete,
@@ -154,6 +155,7 @@ const Thread = React.forwardRef(
154
155
  showActions,
155
156
  showReactions,
156
157
  showAttachments,
158
+ showComposerFormattingControls,
157
159
  onCommentEdit,
158
160
  onCommentDelete: handleCommentDelete,
159
161
  onAuthorClick,
@@ -192,6 +194,7 @@ const Thread = React.forwardRef(
192
194
  threadId: thread.id,
193
195
  defaultCollapsed: showComposer === "collapsed" ? true : void 0,
194
196
  showAttachments,
197
+ showFormattingControls: showComposerFormattingControls,
195
198
  onComposerSubmit,
196
199
  overrides: {
197
200
  COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,
@@ -1 +1 @@
1
- {"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentData,\n DM,\n ThreadData,\n} from \"@liveblocks/core\";\nimport { useThreadSubscription } from \"@liveblocks/react\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\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 React, {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { ResolveIcon } from \"../icons/Resolve\";\nimport { ResolvedIcon } from \"../icons/Resolved\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { classNames } from \"../utils/class-names\";\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 { 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 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/**\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 onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n overrides,\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 { status: subscriptionStatus, unreadSince } = useThreadSubscription(\n thread.id\n );\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 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 return (\n <TooltipProvider>\n <div\n className={classNames(\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 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 onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\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 >\n {thread.resolved ? (\n <ResolvedIcon className=\"lb-button-icon\" />\n ) : (\n <ResolveIcon className=\"lb-button-icon\" />\n )}\n </Button>\n </TogglePrimitive.Root>\n </Tooltip>\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 onComposerSubmit={onComposerSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsJO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAoD;AAC3C;AAET;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;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;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEJ;AAAc;AAEX;AACA;AAGA;AACG;AACc;AACH;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AACiB;AACjB;AACA;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AACiB;AACC;AACV;AAEN;AACW;AACD;AAID;AAIL;AAAuB;AAEvB;AAAsB;AAK7B;AAKV;AAGG;AAAsB;AACpB;AACW;AACI;AAEb;AAAe;AACb;AAAwB;AAO/B;AAKH;AACW;AACO;AACuC;AACxD;AACA;AACW;AACe;AACP;AACnB;AACe;AAIvB;AAGN;;"}
1
+ {"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentData,\n DM,\n ThreadData,\n} from \"@liveblocks/core\";\nimport { useThreadSubscription } from \"@liveblocks/react\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\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 React, {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { ResolveIcon } from \"../icons/Resolve\";\nimport { ResolvedIcon } from \"../icons/Resolved\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { classNames } from \"../utils/class-names\";\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 { 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/**\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 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 { status: subscriptionStatus, unreadSince } = useThreadSubscription(\n thread.id\n );\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 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 return (\n <TooltipProvider>\n <div\n className={classNames(\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 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 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 >\n {thread.resolved ? (\n <ResolvedIcon className=\"lb-button-icon\" />\n ) : (\n <ResolveIcon className=\"lb-button-icon\" />\n )}\n </Button>\n </TogglePrimitive.Root>\n </Tooltip>\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 }}\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2JO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAoD;AAC3C;AAET;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;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;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEJ;AAAc;AAEX;AACA;AAGA;AACG;AACc;AACH;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AACiB;AACC;AACV;AAEN;AACW;AACD;AAID;AAIL;AAAuB;AAEvB;AAAsB;AAK7B;AAKV;AAGG;AAAsB;AACpB;AACW;AACI;AAEb;AAAe;AACb;AAAwB;AAO/B;AAKH;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACW;AACe;AACP;AACnB;AACe;AAIvB;AAGN;;"}
@@ -25,6 +25,7 @@ const Thread = forwardRef(
25
25
  showReactions = true,
26
26
  showComposer = "collapsed",
27
27
  showAttachments = true,
28
+ showComposerFormattingControls = true,
28
29
  onResolvedChange,
29
30
  onCommentEdit,
30
31
  onCommentDelete,
@@ -133,6 +134,7 @@ const Thread = forwardRef(
133
134
  showActions,
134
135
  showReactions,
135
136
  showAttachments,
137
+ showComposerFormattingControls,
136
138
  onCommentEdit,
137
139
  onCommentDelete: handleCommentDelete,
138
140
  onAuthorClick,
@@ -171,6 +173,7 @@ const Thread = forwardRef(
171
173
  threadId: thread.id,
172
174
  defaultCollapsed: showComposer === "collapsed" ? true : void 0,
173
175
  showAttachments,
176
+ showFormattingControls: showComposerFormattingControls,
174
177
  onComposerSubmit,
175
178
  overrides: {
176
179
  COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,