@liveblocks/react-ui 2.12.1-emails1 → 2.12.1-test2

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 (39) hide show
  1. package/dist/components/Comment.js +26 -18
  2. package/dist/components/Comment.js.map +1 -1
  3. package/dist/components/Comment.mjs +27 -19
  4. package/dist/components/Comment.mjs.map +1 -1
  5. package/dist/components/Composer.js +41 -10
  6. package/dist/components/Composer.js.map +1 -1
  7. package/dist/components/Composer.mjs +45 -14
  8. package/dist/components/Composer.mjs.map +1 -1
  9. package/dist/components/Thread.js +5 -3
  10. package/dist/components/Thread.js.map +1 -1
  11. package/dist/components/Thread.mjs +6 -4
  12. package/dist/components/Thread.mjs.map +1 -1
  13. package/dist/components/internal/Attachment.js +32 -14
  14. package/dist/components/internal/Attachment.js.map +1 -1
  15. package/dist/components/internal/Attachment.mjs +32 -14
  16. package/dist/components/internal/Attachment.mjs.map +1 -1
  17. package/dist/components/internal/InboxNotificationThread.js +2 -1
  18. package/dist/components/internal/InboxNotificationThread.js.map +1 -1
  19. package/dist/components/internal/InboxNotificationThread.mjs +2 -1
  20. package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
  21. package/dist/primitives/Composer/contexts.js.map +1 -1
  22. package/dist/primitives/Composer/contexts.mjs.map +1 -1
  23. package/dist/primitives/Composer/index.js +31 -12
  24. package/dist/primitives/Composer/index.js.map +1 -1
  25. package/dist/primitives/Composer/index.mjs +32 -13
  26. package/dist/primitives/Composer/index.mjs.map +1 -1
  27. package/dist/primitives/Composer/utils.js +6 -4
  28. package/dist/primitives/Composer/utils.js.map +1 -1
  29. package/dist/primitives/Composer/utils.mjs +8 -6
  30. package/dist/primitives/Composer/utils.mjs.map +1 -1
  31. package/dist/shared.js +3 -19
  32. package/dist/shared.js.map +1 -1
  33. package/dist/shared.mjs +5 -21
  34. package/dist/shared.mjs.map +1 -1
  35. package/dist/version.js +1 -1
  36. package/dist/version.js.map +1 -1
  37. package/dist/version.mjs +1 -1
  38. package/dist/version.mjs.map +1 -1
  39. package/package.json +4 -4
@@ -3,7 +3,9 @@
3
3
 
4
4
  var core = require('@liveblocks/core');
5
5
  var react = require('@liveblocks/react');
6
+ var _private = require('@liveblocks/react/_private');
6
7
  var React = require('react');
8
+ var index_js = require('use-sync-external-store/shim/index.js');
7
9
  var config = require('../config.js');
8
10
  var Attachment = require('../icons/Attachment.js');
9
11
  var Emoji = require('../icons/Emoji.js');
@@ -156,6 +158,7 @@ function ComposerFileAttachment({
156
158
  ...props
157
159
  }) {
158
160
  const { removeAttachment } = contexts.useComposer();
161
+ const { roomId } = contexts.useComposerEditorContext();
159
162
  const handleDeleteClick = React.useCallback(() => {
160
163
  removeAttachment(attachment.id);
161
164
  }, [attachment.id, removeAttachment]);
@@ -165,7 +168,8 @@ function ComposerFileAttachment({
165
168
  attachment,
166
169
  onDeleteClick: handleDeleteClick,
167
170
  preventFocusOnDelete: true,
168
- overrides
171
+ overrides,
172
+ roomId
169
173
  });
170
174
  }
171
175
  function ComposerAttachments({
@@ -273,6 +277,7 @@ function ComposerEditorContainer({
273
277
  className: "lb-composer-attachments-drop-area-label"
274
278
  }, /* @__PURE__ */ React.createElement(Attachment.AttachmentIcon, null), $.COMPOSER_ATTACH_FILES)));
275
279
  }
280
+ React.createContext(null);
276
281
  const Composer = React.forwardRef(
277
282
  ({
278
283
  threadId,
@@ -293,12 +298,20 @@ const Composer = React.forwardRef(
293
298
  disabled,
294
299
  showAttachments = true,
295
300
  showAttribution,
301
+ roomId: _roomId,
296
302
  ...props
297
303
  }, forwardedRef) => {
298
304
  const client = react.useClient();
299
- const createThread = react.useCreateThread();
300
- const createComment = react.useCreateComment();
301
- const editComment = react.useEditComment();
305
+ const room = _private.useRoomOrNull();
306
+ const roomId = _roomId !== void 0 ? _roomId : room?.id;
307
+ if (roomId === void 0) {
308
+ throw new Error(
309
+ "Composer must be a descendant of RoomProvider component"
310
+ );
311
+ }
312
+ const createThread = _private.useCreateRoomThread(roomId);
313
+ const createComment = _private.useCreateRoomComment(roomId);
314
+ const editComment = _private.useEditRoomComment(roomId);
302
315
  const { preventUnsavedComposerChanges } = config.useLiveblocksUIConfig();
303
316
  const hasResolveMentionSuggestions = client[core.kInternal].resolveMentionSuggestions !== void 0;
304
317
  const isEmptyRef = React.useRef(true);
@@ -309,6 +322,23 @@ const Composer = React.forwardRef(
309
322
  controlledOnCollapsedChange,
310
323
  defaultCollapsed
311
324
  );
325
+ const canCommentFallback = index_js.useSyncExternalStore(
326
+ React.useCallback(
327
+ (callback) => {
328
+ if (room === null)
329
+ return () => {
330
+ };
331
+ return room.events.self.subscribeOnce(callback);
332
+ },
333
+ [room]
334
+ ),
335
+ React.useCallback(() => {
336
+ return room?.getSelf()?.canComment ?? true;
337
+ }, [room]),
338
+ React.useCallback(() => true, [])
339
+ );
340
+ const permissions = _private.useRoomPermissions(roomId);
341
+ const canComment = permissions.size > 0 ? permissions.has(core.Permission.CommentsWrite) || permissions.has(core.Permission.Write) : canCommentFallback;
312
342
  const setEmptyRef = React.useCallback((isEmpty) => {
313
343
  isEmptyRef.current = isEmpty;
314
344
  }, []);
@@ -321,11 +351,11 @@ const Composer = React.forwardRef(
321
351
  if (event.isDefaultPrevented()) {
322
352
  return;
323
353
  }
324
- if (isEmptyRef.current) {
354
+ if (isEmptyRef.current && canComment) {
325
355
  onCollapsedChange?.(false);
326
356
  }
327
357
  },
328
- [onCollapsedChange, onFocus]
358
+ [onCollapsedChange, onFocus, canComment]
329
359
  );
330
360
  const handleBlur = React.useCallback(
331
361
  (event) => {
@@ -345,11 +375,11 @@ const Composer = React.forwardRef(
345
375
  const handleEditorClick = React.useCallback(
346
376
  (event) => {
347
377
  event.stopPropagation();
348
- if (isEmptyRef.current) {
378
+ if (isEmptyRef.current && canComment) {
349
379
  onCollapsedChange?.(false);
350
380
  }
351
381
  },
352
- [onCollapsedChange]
382
+ [onCollapsedChange, canComment]
353
383
  );
354
384
  const handleCommentSubmit = React.useCallback(
355
385
  (comment, event) => {
@@ -400,10 +430,11 @@ const Composer = React.forwardRef(
400
430
  "data-collapsed": isCollapsed ? "" : void 0,
401
431
  onFocus: handleFocus,
402
432
  onBlur: handleBlur,
403
- disabled,
433
+ disabled: disabled || !canComment,
404
434
  defaultAttachments,
405
435
  pasteFilesAsAttachments: showAttachments,
406
- preventUnsavedChanges: preventUnsavedComposerChanges
436
+ preventUnsavedChanges: preventUnsavedComposerChanges,
437
+ roomId
407
438
  }, /* @__PURE__ */ React.createElement(ComposerEditorContainer, {
408
439
  defaultValue,
409
440
  actions,
@@ -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 } from \"@liveblocks/core\";\nimport {\n useClient,\n useCreateComment,\n useCreateThread,\n useEditComment,\n} from \"@liveblocks/react\";\nimport type {\n ComponentPropsWithoutRef,\n FocusEvent,\n FormEvent,\n ForwardedRef,\n MouseEvent,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport React, { forwardRef, useCallback, useRef } from \"react\";\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} 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\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\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 />\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\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 ...props\n }: ComposerProps<M>,\n forwardedRef: ForwardedRef<HTMLFormElement>\n ) => {\n const client = useClient();\n const createThread = useCreateThread();\n const createComment = useCreateComment();\n const editComment = useEditComment();\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 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) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, onFocus]\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) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange]\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}\n defaultAttachments={defaultAttachments}\n pasteFilesAsAttachments={showAttachments}\n preventUnsavedChanges={preventUnsavedComposerChanges}\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuMA;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;AAEA;AACE;AAA8B;AAGhC;AACG;AAC0D;AACrD;AACJ;AACe;AACK;AACpB;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;AAQO;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACkB;AAClB;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AAInC;AACJ;AACA;AAGF;AACE;AAAqB;AAGvB;AACE;AAA+B;AAGjC;AAAoB;AAEhB;AAEA;AACE;AAAA;AAGF;AACE;AAAyB;AAC3B;AACF;AAC2B;AAG7B;AAAmB;AAEf;AAEA;AACE;AAAA;AAGF;AAAuC;AACL;AAGlC;AACE;AAAwB;AAC1B;AACF;AAC0B;AAG5B;AAA0B;AAEtB;AAEA;AACE;AAAyB;AAC3B;AACF;AACkB;AAGpB;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;AACR;AACA;AACyB;AACF;AAEtB;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 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,7 +1,9 @@
1
1
  'use client';
2
- import { kInternal } from '@liveblocks/core';
3
- import { useClient, useCreateThread, useCreateComment, useEditComment } from '@liveblocks/react';
4
- import React__default, { forwardRef, useRef, useCallback } from 'react';
2
+ import { kInternal, Permission } from '@liveblocks/core';
3
+ import { useClient } from '@liveblocks/react';
4
+ import { useRoomOrNull, useCreateRoomThread, useCreateRoomComment, useEditRoomComment, useRoomPermissions } from '@liveblocks/react/_private';
5
+ import React__default, { createContext, forwardRef, useRef, useCallback } from 'react';
6
+ import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js';
5
7
  import { useLiveblocksUIConfig } from '../config.mjs';
6
8
  import { AttachmentIcon } from '../icons/Attachment.mjs';
7
9
  import { EmojiIcon } from '../icons/Emoji.mjs';
@@ -9,7 +11,7 @@ import { MentionIcon } from '../icons/Mention.mjs';
9
11
  import { SendIcon } from '../icons/Send.mjs';
10
12
  import { useOverrides } from '../overrides.mjs';
11
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';
12
- import { useComposer, useComposerAttachmentsContext } from '../primitives/Composer/contexts.mjs';
14
+ import { useComposer, useComposerAttachmentsContext, useComposerEditorContext } from '../primitives/Composer/contexts.mjs';
13
15
  import { useComposerAttachmentsDropArea } from '../primitives/Composer/utils.mjs';
14
16
  import { MENTION_CHARACTER } from '../slate/plugins/mentions.mjs';
15
17
  import { classNames } from '../utils/class-names.mjs';
@@ -154,6 +156,7 @@ function ComposerFileAttachment({
154
156
  ...props
155
157
  }) {
156
158
  const { removeAttachment } = useComposer();
159
+ const { roomId } = useComposerEditorContext();
157
160
  const handleDeleteClick = useCallback(() => {
158
161
  removeAttachment(attachment.id);
159
162
  }, [attachment.id, removeAttachment]);
@@ -163,7 +166,8 @@ function ComposerFileAttachment({
163
166
  attachment,
164
167
  onDeleteClick: handleDeleteClick,
165
168
  preventFocusOnDelete: true,
166
- overrides
169
+ overrides,
170
+ roomId
167
171
  });
168
172
  }
169
173
  function ComposerAttachments({
@@ -271,6 +275,7 @@ function ComposerEditorContainer({
271
275
  className: "lb-composer-attachments-drop-area-label"
272
276
  }, /* @__PURE__ */ React__default.createElement(AttachmentIcon, null), $.COMPOSER_ATTACH_FILES)));
273
277
  }
278
+ createContext(null);
274
279
  const Composer = forwardRef(
275
280
  ({
276
281
  threadId,
@@ -291,12 +296,20 @@ const Composer = forwardRef(
291
296
  disabled,
292
297
  showAttachments = true,
293
298
  showAttribution,
299
+ roomId: _roomId,
294
300
  ...props
295
301
  }, forwardedRef) => {
296
302
  const client = useClient();
297
- const createThread = useCreateThread();
298
- const createComment = useCreateComment();
299
- const editComment = useEditComment();
303
+ const room = useRoomOrNull();
304
+ const roomId = _roomId !== void 0 ? _roomId : room?.id;
305
+ if (roomId === void 0) {
306
+ throw new Error(
307
+ "Composer must be a descendant of RoomProvider component"
308
+ );
309
+ }
310
+ const createThread = useCreateRoomThread(roomId);
311
+ const createComment = useCreateRoomComment(roomId);
312
+ const editComment = useEditRoomComment(roomId);
300
313
  const { preventUnsavedComposerChanges } = useLiveblocksUIConfig();
301
314
  const hasResolveMentionSuggestions = client[kInternal].resolveMentionSuggestions !== void 0;
302
315
  const isEmptyRef = useRef(true);
@@ -307,6 +320,23 @@ const Composer = forwardRef(
307
320
  controlledOnCollapsedChange,
308
321
  defaultCollapsed
309
322
  );
323
+ const canCommentFallback = useSyncExternalStore(
324
+ useCallback(
325
+ (callback) => {
326
+ if (room === null)
327
+ return () => {
328
+ };
329
+ return room.events.self.subscribeOnce(callback);
330
+ },
331
+ [room]
332
+ ),
333
+ useCallback(() => {
334
+ return room?.getSelf()?.canComment ?? true;
335
+ }, [room]),
336
+ useCallback(() => true, [])
337
+ );
338
+ const permissions = useRoomPermissions(roomId);
339
+ const canComment = permissions.size > 0 ? permissions.has(Permission.CommentsWrite) || permissions.has(Permission.Write) : canCommentFallback;
310
340
  const setEmptyRef = useCallback((isEmpty) => {
311
341
  isEmptyRef.current = isEmpty;
312
342
  }, []);
@@ -319,11 +349,11 @@ const Composer = forwardRef(
319
349
  if (event.isDefaultPrevented()) {
320
350
  return;
321
351
  }
322
- if (isEmptyRef.current) {
352
+ if (isEmptyRef.current && canComment) {
323
353
  onCollapsedChange?.(false);
324
354
  }
325
355
  },
326
- [onCollapsedChange, onFocus]
356
+ [onCollapsedChange, onFocus, canComment]
327
357
  );
328
358
  const handleBlur = useCallback(
329
359
  (event) => {
@@ -343,11 +373,11 @@ const Composer = forwardRef(
343
373
  const handleEditorClick = useCallback(
344
374
  (event) => {
345
375
  event.stopPropagation();
346
- if (isEmptyRef.current) {
376
+ if (isEmptyRef.current && canComment) {
347
377
  onCollapsedChange?.(false);
348
378
  }
349
379
  },
350
- [onCollapsedChange]
380
+ [onCollapsedChange, canComment]
351
381
  );
352
382
  const handleCommentSubmit = useCallback(
353
383
  (comment, event) => {
@@ -398,10 +428,11 @@ const Composer = forwardRef(
398
428
  "data-collapsed": isCollapsed ? "" : void 0,
399
429
  onFocus: handleFocus,
400
430
  onBlur: handleBlur,
401
- disabled,
431
+ disabled: disabled || !canComment,
402
432
  defaultAttachments,
403
433
  pasteFilesAsAttachments: showAttachments,
404
- preventUnsavedChanges: preventUnsavedComposerChanges
434
+ preventUnsavedChanges: preventUnsavedComposerChanges,
435
+ roomId
405
436
  }, /* @__PURE__ */ React__default.createElement(ComposerEditorContainer, {
406
437
  defaultValue,
407
438
  actions,
@@ -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 } from \"@liveblocks/core\";\nimport {\n useClient,\n useCreateComment,\n useCreateThread,\n useEditComment,\n} from \"@liveblocks/react\";\nimport type {\n ComponentPropsWithoutRef,\n FocusEvent,\n FormEvent,\n ForwardedRef,\n MouseEvent,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport React, { forwardRef, useCallback, useRef } from \"react\";\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} 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\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\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 />\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\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 ...props\n }: ComposerProps<M>,\n forwardedRef: ForwardedRef<HTMLFormElement>\n ) => {\n const client = useClient();\n const createThread = useCreateThread();\n const createComment = useCreateComment();\n const editComment = useEditComment();\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 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) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, onFocus]\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) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange]\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}\n defaultAttachments={defaultAttachments}\n pasteFilesAsAttachments={showAttachments}\n preventUnsavedChanges={preventUnsavedComposerChanges}\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAuMA;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;AAEA;AACE;AAA8B;AAGhC;AACG;AAC0D;AACrD;AACJ;AACe;AACK;AACpB;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;AAQO;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACkB;AAClB;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AAInC;AACJ;AACA;AAGF;AACE;AAAqB;AAGvB;AACE;AAA+B;AAGjC;AAAoB;AAEhB;AAEA;AACE;AAAA;AAGF;AACE;AAAyB;AAC3B;AACF;AAC2B;AAG7B;AAAmB;AAEf;AAEA;AACE;AAAA;AAGF;AAAuC;AACL;AAGlC;AACE;AAAwB;AAC1B;AACF;AAC0B;AAG5B;AAA0B;AAEtB;AAEA;AACE;AAAyB;AAC3B;AACF;AACkB;AAGpB;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;AACR;AACA;AACyB;AACF;AAEtB;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 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;;"}
@@ -2,6 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var react = require('@liveblocks/react');
5
+ var _private = require('@liveblocks/react/_private');
5
6
  var TogglePrimitive = require('@radix-ui/react-toggle');
6
7
  var React = require('react');
7
8
  var ArrowDown = require('../icons/ArrowDown.js');
@@ -57,8 +58,8 @@ const Thread = React.forwardRef(
57
58
  className,
58
59
  ...props
59
60
  }, forwardedRef) => {
60
- const markThreadAsResolved = react.useMarkThreadAsResolved();
61
- const markThreadAsUnresolved = react.useMarkThreadAsUnresolved();
61
+ const markThreadAsResolved = _private.useMarkRoomThreadAsResolved(thread.roomId);
62
+ const markThreadAsUnresolved = _private.useMarkRoomThreadAsUnresolved(thread.roomId);
62
63
  const $ = overrides.useOverrides(overrides$1);
63
64
  const firstCommentIndex = React.useMemo(() => {
64
65
  return showDeletedComments ? 0 : thread.comments.findIndex((comment) => comment.body);
@@ -195,7 +196,8 @@ const Thread = React.forwardRef(
195
196
  overrides: {
196
197
  COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,
197
198
  COMPOSER_SEND: $.THREAD_COMPOSER_SEND
198
- }
199
+ },
200
+ roomId: thread.roomId
199
201
  })));
200
202
  }
201
203
  );
@@ -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 {\n useMarkThreadAsResolved,\n useMarkThreadAsUnresolved,\n useThreadSubscription,\n} from \"@liveblocks/react\";\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 = useMarkThreadAsResolved();\n const markThreadAsUnresolved = useMarkThreadAsUnresolved();\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 />\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;AAIR;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 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,5 +1,6 @@
1
1
  'use client';
2
- import { useMarkThreadAsResolved, useMarkThreadAsUnresolved, useThreadSubscription } from '@liveblocks/react';
2
+ import { useThreadSubscription } from '@liveblocks/react';
3
+ import { useMarkRoomThreadAsResolved, useMarkRoomThreadAsUnresolved } from '@liveblocks/react/_private';
3
4
  import * as TogglePrimitive from '@radix-ui/react-toggle';
4
5
  import React__default, { forwardRef, useMemo, useState, useEffect, useCallback, Fragment } from 'react';
5
6
  import { ArrowDownIcon } from '../icons/ArrowDown.mjs';
@@ -36,8 +37,8 @@ const Thread = forwardRef(
36
37
  className,
37
38
  ...props
38
39
  }, forwardedRef) => {
39
- const markThreadAsResolved = useMarkThreadAsResolved();
40
- const markThreadAsUnresolved = useMarkThreadAsUnresolved();
40
+ const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);
41
+ const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);
41
42
  const $ = useOverrides(overrides);
42
43
  const firstCommentIndex = useMemo(() => {
43
44
  return showDeletedComments ? 0 : thread.comments.findIndex((comment) => comment.body);
@@ -174,7 +175,8 @@ const Thread = forwardRef(
174
175
  overrides: {
175
176
  COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,
176
177
  COMPOSER_SEND: $.THREAD_COMPOSER_SEND
177
- }
178
+ },
179
+ roomId: thread.roomId
178
180
  })));
179
181
  }
180
182
  );