@liveblocks/react-ui 3.16.0-flow3 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_private/index.cjs +0 -2
- package/dist/_private/index.cjs.map +1 -1
- package/dist/_private/index.d.cts +1 -13
- package/dist/_private/index.d.ts +1 -13
- package/dist/_private/index.js +0 -1
- package/dist/_private/index.js.map +1 -1
- package/dist/components/Comment.cjs +23 -21
- package/dist/components/Comment.cjs.map +1 -1
- package/dist/components/Comment.js +23 -21
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/Cursors.cjs +97 -29
- package/dist/components/Cursors.cjs.map +1 -1
- package/dist/components/Cursors.js +98 -30
- package/dist/components/Cursors.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -24
- package/dist/index.d.ts +8 -24
- package/dist/index.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
- package/dist/utils/cursor-spring.cjs +0 -86
- package/dist/utils/cursor-spring.cjs.map +0 -1
- package/dist/utils/cursor-spring.js +0 -84
- package/dist/utils/cursor-spring.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Comment.js","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n assertNever,\n type BaseMetadata,\n type CommentAttachment,\n type CommentData,\n type CommentReaction as CommentReactionData,\n type DCM,\n type GroupMentionData,\n MENTION_CHARACTER,\n type MentionData,\n Permission,\n} from \"@liveblocks/core\";\nimport {\n useAddRoomCommentReaction,\n useDeleteRoomComment,\n useEditRoomComment,\n useRemoveRoomCommentReaction,\n useRoomAttachmentUrl,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n ComponentsProvider,\n type GlobalComponents,\n useComponents,\n} from \"../components\";\nimport { CheckIcon } from \"../icons/Check\";\nimport { CrossIcon } from \"../icons/Cross\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EditIcon } from \"../icons/Edit\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { EmojiPlusIcon } from \"../icons/EmojiPlus\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ComposerSubmitComment } from \"../primitives\";\nimport * as CommentPrimitive from \"../primitives/Comment\";\nimport type {\n CommentBodyLinkProps,\n CommentBodyMentionProps,\n CommentLinkProps,\n CommentMentionProps as CommentPrimitiveMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { download } from \"../utils/download\";\nimport { useIsGroupMentionMember } from \"../utils/use-group-mention\";\nimport { useRefs } from \"../utils/use-refs\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button, CustomButton } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { Group } from \"./internal/Group\";\nimport { List } from \"./internal/List\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\nconst REACTIONS_TRUNCATE = 5;\n\nexport interface CommentProps<CM extends BaseMetadata = DCM>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n /**\n * The comment to display.\n */\n comment: CommentData<CM>;\n\n /**\n * The comment's avatar.\n * Can be combined with `Comment.Avatar` to easily follow default styles.\n */\n avatar?: ReactNode;\n\n /**\n * The comment's author.\n * Can be combined with `Comment.Author` to easily follow default styles.\n */\n author?: ReactNode;\n\n /**\n * The comment's date.\n * Can be combined with `Comment.Date` to easily follow default styles,\n * or the `Timestamp` primitive for more control.\n */\n date?: ReactNode;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n\n /**\n * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`.\n */\n showDeleted?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to show the composer's formatting controls when editing the comment.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Whether to indent the comment's content.\n */\n indentContent?: boolean;\n\n /**\n * Additional content to display below the comment's body.\n */\n additionalContent?: ReactNode;\n\n /**\n * The event handler called when the comment is edited.\n */\n onCommentEdit?: (comment: CommentData) => void;\n\n /**\n * The event handler called when the comment is deleted.\n */\n onCommentDelete?: (comment: CommentData) => void;\n\n /**\n * The event handler called when clicking on the author.\n */\n onAuthorClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: (\n mention: MentionData,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: (\n args: CommentAttachmentArgs,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * Add (or change) items to display in the comment's dropdown.\n */\n dropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * Override the comment's content.\n */\n children?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n actionsClassName?: string;\n\n /**\n * @internal\n */\n internalDropdownItems?: ReactNode;\n}\n\nexport interface CommentAvatarProps\n extends Omit<ComponentProps<\"div\">, \"children\"> {\n /**\n * The user ID to display the avatar for.\n */\n userId: string;\n}\n\nexport interface CommentAuthorProps\n extends Omit<ComponentProps<\"span\">, \"children\"> {\n /**\n * The user ID to display the author for.\n */\n userId: string;\n}\n\nexport interface CommentDateProps\n extends Omit<ComponentProps<\"span\">, \"children\"> {\n /**\n * The date to display.\n */\n date: Date | string | number;\n\n /**\n * The locale used when formatting the date.\n */\n locale?: string;\n}\n\nfunction CommentAvatar(props: CommentAvatarProps) {\n return <Avatar {...props} />;\n}\n\nfunction CommentAuthor(props: CommentAuthorProps) {\n return <User {...props} />;\n}\n\nfunction CommentDate({ locale, date, className, ...props }: CommentDateProps) {\n return (\n <Timestamp\n locale={locale}\n date={date}\n className={cn(\"lb-date\", className)}\n {...(props as Omit<ComponentProps<\"time\">, \"children\" | \"title\">)}\n />\n );\n}\n\nexport interface CommentDropdownItemProps\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onSelect\"> {\n /**\n * An optional icon displayed in this dropdown item.\n */\n icon?: ReactNode;\n\n /**\n * The event handler called when the dropdown item is selected.\n */\n onSelect?: (event: Event) => void;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\ninterface CommentMentionProps\n extends CommentBodyMentionProps,\n CommentPrimitiveMentionProps {\n overrides?: CommentProps[\"overrides\"];\n}\n\nfunction CommentUserMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const currentId = useCurrentUserId();\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={mention.id === currentId ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <User userId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nfunction CommentGroupMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const isMember = useIsGroupMentionMember(mention as GroupMentionData);\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={isMember ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <Group groupId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentMention({ mention, ...props }: CommentMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return <CommentUserMention mention={mention} {...props} />;\n\n case \"group\":\n return <CommentGroupMention mention={mention} {...props} />;\n\n default:\n return assertNever(mention, \"Unhandled mention kind\");\n }\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n const { Anchor } = useComponents();\n\n return (\n <CommentPrimitive.Link\n className={cn(\"lb-comment-link\", className)}\n href={href}\n {...props}\n asChild\n >\n <Anchor {...props}>{children}</Anchor>\n </CommentPrimitive.Link>\n );\n}\n\nexport function CommentNonInteractiveLink({\n href: _href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <span className={cn(\"lb-comment-link\", className)} {...props}>\n {children}\n </span>\n );\n}\n\nconst CommentReactionButton = forwardRef<\n HTMLButtonElement,\n CommentReactionButtonProps\n>(({ reaction, overrides, className, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n return (\n <CustomButton\n className={cn(\"lb-comment-reaction\", className)}\n variant=\"outline\"\n aria-label={$.COMMENT_REACTION_DESCRIPTION(\n reaction.emoji,\n reaction.users.length\n )}\n {...props}\n ref={forwardedRef}\n >\n <Emoji className=\"lb-comment-reaction-emoji\" emoji={reaction.emoji} />\n <span className=\"lb-comment-reaction-count\">{reaction.users.length}</span>\n </CustomButton>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n allowMediaPreview={false}\n {...props}\n />\n );\n}\n\nconst CommentDropdownItem = forwardRef<\n HTMLDivElement,\n CommentDropdownItemProps\n>(({ children, icon, onSelect, onClick, ...props }, forwardedRef) => {\n const handleClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n }\n },\n [onClick]\n );\n\n return (\n <DropdownItem\n icon={icon}\n onSelect={onSelect}\n onClick={handleClick}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </DropdownItem>\n );\n});\n\n/**\n * Displays a single comment.\n *\n * @example\n * <>\n * {thread.comments.map((comment) => (\n * <Comment key={comment.id} comment={comment} />\n * ))}\n * </>\n */\nexport const Comment = Object.assign(\n forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n showComposerFormattingControls = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n dropdownItems,\n overrides,\n components,\n additionalContent,\n avatar,\n author,\n date,\n className,\n actions,\n actionsClassName,\n internalDropdownItems,\n children,\n ...props\n },\n forwardedRef\n ) => {\n const bodyId = `${comment.id}:body`;\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteRoomComment(comment.roomId);\n const editComment = useEditRoomComment(comment.roomId);\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const permissions = useRoomPermissions(comment.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const handleEditCancel = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n setEditing(false);\n },\n []\n );\n\n const handleEditSubmit = useCallback(\n (\n { body, attachments }: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentEdit?.(comment);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\n setEditing(false);\n editComment({\n commentId: comment.id,\n threadId: comment.threadId,\n body,\n attachments,\n });\n },\n [comment, editComment, onCommentEdit]\n );\n\n const handleDelete = useCallback(() => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentDelete?.(comment);\n\n deleteComment({\n commentId: comment.id,\n threadId: comment.threadId,\n });\n }, [comment, deleteComment, onCommentDelete]);\n\n const handleAuthorClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n onAuthorClick?.(comment.userId, event);\n },\n [comment.userId, onAuthorClick]\n );\n\n const handleReactionSelect = useCallback(\n (emoji: string) => {\n const reactionIndex = comment.reactions.findIndex(\n (reaction) => reaction.emoji === emoji\n );\n\n if (\n reactionIndex >= 0 &&\n currentUserId &&\n comment.reactions[reactionIndex]?.users.some(\n (user) => user.id === currentUserId\n )\n ) {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n } else {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [\n addReaction,\n comment.id,\n comment.reactions,\n comment.threadId,\n removeReaction,\n currentUserId,\n ]\n );\n\n useEffect(() => {\n const isWindowDefined = typeof window !== \"undefined\";\n if (!isWindowDefined) return;\n\n const hash = window.location.hash;\n const commentId = hash.slice(1);\n\n if (commentId === comment.id) {\n setTarget(true);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!showDeleted && !comment.body) {\n return null;\n }\n\n const commentDropdownItems =\n comment.userId === currentUserId ? (\n <>\n <CommentDropdownItem onSelect={handleEdit} icon={<EditIcon />}>\n {$.COMMENT_EDIT}\n </CommentDropdownItem>\n <CommentDropdownItem onSelect={handleDelete} icon={<DeleteIcon />}>\n {$.COMMENT_DELETE}\n </CommentDropdownItem>\n </>\n ) : null;\n const defaultDropdownItems =\n internalDropdownItems || commentDropdownItems ? (\n <>\n {internalDropdownItems}\n {commentDropdownItems}\n </>\n ) : null;\n\n const dropdownContent =\n typeof dropdownItems === \"function\" ? (\n dropdownItems({ children: defaultDropdownItems, comment })\n ) : defaultDropdownItems || dropdownItems ? (\n <>\n {defaultDropdownItems}\n {dropdownItems}\n </>\n ) : null;\n\n let content: ReactNode;\n\n if (isEditing) {\n content = (\n <Composer\n className=\"lb-comment-composer\"\n onComposerSubmit={handleEditSubmit}\n defaultValue={comment.body}\n defaultAttachments={comment.attachments}\n autoFocus\n showAttribution={false}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n actions={\n <>\n <Tooltip\n content={$.COMMENT_EDIT_COMPOSER_CANCEL}\n aria-label={$.COMMENT_EDIT_COMPOSER_CANCEL}\n >\n <Button\n className=\"lb-composer-action\"\n onClick={handleEditCancel}\n icon={<CrossIcon />}\n />\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut=\"Enter\"\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n variant=\"primary\"\n className=\"lb-composer-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_EDIT_COMPOSER_SAVE}\n icon={<CheckIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n roomId={comment.roomId}\n />\n );\n } else {\n content = comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n id={bodyId}\n body={comment.body}\n components={{\n Mention: ({ mention }) => (\n <CommentMention\n mention={mention}\n onClick={(event) => onMentionClick?.(mention, event)}\n overrides={overrides}\n />\n ),\n Link: CommentLink,\n }}\n />\n {additionalContent}\n {showAttachments &&\n (mediaAttachments.length > 0 || fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n disabled={!canComment}\n />\n ))}\n {canComment ? (\n <EmojiPicker onEmojiSelect={handleReactionSelect}>\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-reaction lb-comment-reaction-add\"\n variant=\"outline\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n </div>\n )}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n );\n\n content =\n typeof children === \"function\"\n ? children({ comment, children: content })\n : (children ?? content);\n }\n\n return (\n <TooltipProvider>\n <ComponentsProvider components={components}>\n <div\n role=\"article\"\n id={comment.id}\n className={cn(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n aria-labelledby={bodyId}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n {avatar ? (\n <div\n className=\"lb-comment-avatar\"\n onClick={handleAuthorClick}\n >\n {avatar}\n </div>\n ) : (\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n )}\n <span className=\"lb-comment-details-labels\">\n {author ? (\n <span\n className=\"lb-comment-author\"\n onClick={handleAuthorClick}\n >\n {author}\n </span>\n ) : (\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n )}\n {date ? (\n <span className=\"lb-comment-date\">{date}</span>\n ) : (\n <span className=\"lb-comment-date\">\n <CommentDate\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n )}\n </span>\n </div>\n {showActions && !isEditing && (\n <div className={cn(\"lb-comment-actions\", actionsClassName)}>\n {actions ?? null}\n {showReactions && canComment ? (\n <EmojiPicker\n onEmojiSelect={handleReactionSelect}\n onOpenChange={setReactionActionOpen}\n >\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n {dropdownContent ? (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={dropdownContent}\n >\n <Tooltip content={$.COMMENT_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n disabled={!comment.body}\n onClick={stopPropagation}\n aria-label={$.COMMENT_MORE}\n icon={<EllipsisIcon />}\n />\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n ) : null}\n </div>\n )}\n </div>\n <div className=\"lb-comment-content\">{content}</div>\n </div>\n </ComponentsProvider>\n </TooltipProvider>\n );\n }\n ) as <CM extends BaseMetadata = DCM>(\n props: CommentProps<CM> & RefAttributes<HTMLDivElement>\n ) => JSX.Element,\n {\n /**\n * Displays a dropdown item in the comment's dropdown.\n */\n DropdownItem: CommentDropdownItem,\n\n /**\n * Displays a comment's avatar.\n */\n Avatar: CommentAvatar,\n\n /**\n * Displays a comment's author.\n */\n Author: CommentAuthor,\n\n /**\n * Displays a comment's date.\n */\n Date: CommentDate,\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA;AAmKA;AACE;AACF;AAEA;AACE;AACF;AAEA;AACE;AACE;AAAC;AAAA;AACC;AACA;AACkC;AAC7B;AAAA;AAGX;AAwCA;AAA4B;AAC1B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AACb;AACvC;AAEJ;AAAuD;AAC7B;AAAA;AAAA;AAGhC;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AAC7B;AACvB;AAEJ;AAAuD;AAC3B;AAAA;AAAA;AAGlC;AAEO;AACL;AAAsB;AAElB;AAAwD;AAGxD;AAAyD;AAGzD;AAAoD;AAE1D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AAC2C;AAC1C;AACI;AACG;AAEsB;AAAA;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AAKF;AAEA;AAIE;AACA;AACE;AAAC;AAAA;AAC+C;AACtC;AACM;AACH;AACM;AACjB;AACI;AACC;AAEL;AAAoE;AACD;AAAA;AAAA;AAGzE;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACD;AAAC;AAAA;AAGE;AACkB;AACT;AACA;AAAA;AACZ;AACS;AACM;AAEnB;AAEU;AAGd;AACE;AAAsB;AAGxB;AAA4B;AAExB;AACE;AAAY;AACQ;AACC;AACH;AACjB;AAED;AAAe;AACK;AACC;AACH;AACjB;AACH;AACF;AAC0E;AAG5E;AACE;AAAC;AAAA;AACU;AACA;AACC;AAEV;AAAiB;AAAhB;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEL;AAAC;AAAA;AAC4B;AAC3B;AACA;AACI;AAAA;AACN;AAAA;AACF;AAAA;AAGN;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACE;AAAC;AAAA;AACc;AACc;AAC3B;AACA;AACI;AACC;AAAA;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACE;AAAC;AAAA;AACiD;AAC7B;AACf;AAAA;AAGV;AAEA;AAIE;AAAoB;AAEhB;AAEA;AACE;AAAsB;AACxB;AACF;AACQ;AAGV;AACE;AAAC;AAAA;AACC;AACA;AACS;AACL;AACC;AAEJ;AAAA;AAGP;AAYO;AAAuB;AAC5B;AAEI;AACE;AACgB;AAChB;AACc;AACE;AACE;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACA;AAMA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACE;AAAA;AAGF;AACA;AAEA;AACA;AAAY;AACS;AACD;AAClB;AACA;AACD;AACH;AACoC;AAGtC;AAEE;AAEA;AAAc;AACO;AACD;AACnB;AAGH;AAA0B;AAEtB;AAAqC;AACvC;AAC8B;AAGhC;AAA6B;AAEzB;AAAwC;AACL;AAGnC;AAG0C;AAChB;AAGxB;AAAe;AACK;AACC;AACnB;AACD;AAED;AAAY;AACQ;AACC;AACnB;AACD;AACH;AACF;AACA;AACE;AACQ;AACA;AACA;AACR;AACA;AACF;AAGF;AACE;AACA;AAAsB;AAEtB;AACA;AAEA;AACE;AAAc;AAChB;AAGF;AACE;AAAO;AAGT;AAGM;AAEA;AAGA;AAGN;AAGO;AAAA;AACA;AAIP;AAKO;AAAA;AACA;AAIP;AAEA;AACE;AACE;AAAC;AAAA;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AACwB;AAGpB;AAAA;AAAC;AAAA;AACY;AACG;AAEd;AAAC;AAAA;AACW;AACD;AACQ;AAAA;AACnB;AAAA;AACF;AACA;AAAC;AAAA;AACY;AACF;AAGP;AAAC;AAAA;AACS;AACE;AACD;AACK;AACG;AAAA;AAErB;AAAA;AACF;AACF;AAES;AACe;AAC1B;AACgB;AAAA;AAClB;AAGF;AAEI;AAAA;AAAkB;AAAjB;AACW;AACN;AACU;AACF;AAER;AAAC;AAAA;AACC;AACmD;AACnD;AAAA;AACF;AAEI;AACR;AAAA;AACF;AACC;AAII;AAGK;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAIE;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAEJ;AAGC;AACC;AAAC;AAAA;AAEC;AACA;AACA;AACW;AAAA;AAJG;AAMjB;AAKO;AAAC;AAAA;AACW;AACF;AACC;AACK;AACO;AAAA;AAK3B;AACN;AASN;AAGmB;AAGrB;AAGM;AAAC;AAAA;AACM;AACO;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACZ;AACV;AACH;AACC;AAEL;AACE;AACG;AACC;AAAC;AAAA;AACW;AACD;AAER;AAAA;AAGH;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAGC;AACC;AAAC;AAAA;AACW;AACD;AAER;AAAA;AAGH;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAME;AAAA;AAAC;AAAA;AACW;AACI;AACJ;AAAA;AACZ;AAGK;AAAA;AAGD;AACF;AAEJ;AAEJ;AACF;AAGK;AAAW;AAEV;AAAC;AAAA;AACgB;AACD;AAIV;AAAC;AAAA;AACW;AACD;AACK;AACO;AAAA;AAG3B;AAAA;AAEA;AAEF;AAAC;AAAA;AACO;AACQ;AACR;AACG;AAIL;AAAC;AAAA;AACW;AACS;AACV;AACK;AACM;AAAA;AAG1B;AAAA;AAEA;AACN;AAEJ;AAC6C;AAAA;AAAA;AAGnD;AAEJ;AACF;AAGA;AAAA;AAAA;AAAA;AAIgB;AAAA;AAAA;AAAA;AAKN;AAAA;AAAA;AAAA;AAKA;AAAA;AAAA;AAAA;AAKF;AAEV;;"}
|
|
1
|
+
{"version":3,"file":"Comment.js","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n assertNever,\n type BaseMetadata,\n type CommentAttachment,\n type CommentData,\n type CommentReaction as CommentReactionData,\n type DCM,\n type GroupMentionData,\n MENTION_CHARACTER,\n type MentionData,\n Permission,\n} from \"@liveblocks/core\";\nimport {\n useAddRoomCommentReaction,\n useDeleteRoomComment,\n useEditRoomComment,\n useRemoveRoomCommentReaction,\n useRoomAttachmentUrl,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n ComponentsProvider,\n type GlobalComponents,\n useComponents,\n} from \"../components\";\nimport { CheckIcon } from \"../icons/Check\";\nimport { CrossIcon } from \"../icons/Cross\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EditIcon } from \"../icons/Edit\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { EmojiPlusIcon } from \"../icons/EmojiPlus\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ComposerSubmitComment } from \"../primitives\";\nimport * as CommentPrimitive from \"../primitives/Comment\";\nimport type {\n CommentBodyLinkProps,\n CommentBodyMentionProps,\n CommentLinkProps,\n CommentMentionProps as CommentPrimitiveMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { download } from \"../utils/download\";\nimport { useIsGroupMentionMember } from \"../utils/use-group-mention\";\nimport { useRefs } from \"../utils/use-refs\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button, CustomButton } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { Group } from \"./internal/Group\";\nimport { List } from \"./internal/List\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\nconst REACTIONS_TRUNCATE = 5;\n\nexport interface CommentProps<CM extends BaseMetadata = DCM>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n /**\n * The comment to display.\n */\n comment: CommentData<CM>;\n\n /**\n * The comment's avatar.\n * Can be combined with `Comment.Avatar` to easily follow default styles.\n */\n avatar?: ReactNode;\n\n /**\n * The comment's author.\n * Can be combined with `Comment.Author` to easily follow default styles.\n */\n author?: ReactNode;\n\n /**\n * The comment's date.\n * Can be combined with `Comment.Date` to easily follow default styles,\n * or the `Timestamp` primitive for more control.\n */\n date?: ReactNode;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n\n /**\n * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`.\n */\n showDeleted?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to show the composer's formatting controls when editing the comment.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Whether to indent the comment's content.\n */\n indentContent?: boolean;\n\n /**\n * Additional content to display below the comment's body.\n */\n additionalContent?: ReactNode;\n\n /**\n * Override the comment's body.\n */\n body?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * The event handler called when the comment is edited.\n */\n onCommentEdit?: (comment: CommentData) => void;\n\n /**\n * The event handler called when the comment is deleted.\n */\n onCommentDelete?: (comment: CommentData) => void;\n\n /**\n * The event handler called when clicking on the author.\n */\n onAuthorClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: (\n mention: MentionData,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: (\n args: CommentAttachmentArgs,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * Add (or change) items to display in the comment's dropdown.\n */\n dropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * Override the comment's content.\n */\n children?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n actionsClassName?: string;\n\n /**\n * @internal\n */\n internalDropdownItems?: ReactNode;\n}\n\nexport interface CommentAvatarProps\n extends Omit<ComponentProps<\"div\">, \"children\"> {\n /**\n * The user ID to display the avatar for.\n */\n userId: string;\n}\n\nexport interface CommentAuthorProps\n extends Omit<ComponentProps<\"span\">, \"children\"> {\n /**\n * The user ID to display the author for.\n */\n userId: string;\n}\n\nexport interface CommentDateProps\n extends Omit<ComponentProps<\"span\">, \"children\"> {\n /**\n * The date to display.\n */\n date: Date | string | number;\n\n /**\n * The locale used when formatting the date.\n */\n locale?: string;\n}\n\nfunction CommentAvatar(props: CommentAvatarProps) {\n return <Avatar {...props} />;\n}\n\nfunction CommentAuthor(props: CommentAuthorProps) {\n return <User {...props} />;\n}\n\nfunction CommentDate({ locale, date, className, ...props }: CommentDateProps) {\n return (\n <Timestamp\n locale={locale}\n date={date}\n className={cn(\"lb-date\", className)}\n {...(props as Omit<ComponentProps<\"time\">, \"children\" | \"title\">)}\n />\n );\n}\n\nexport interface CommentDropdownItemProps\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onSelect\"> {\n /**\n * An optional icon displayed in this dropdown item.\n */\n icon?: ReactNode;\n\n /**\n * The event handler called when the dropdown item is selected.\n */\n onSelect?: (event: Event) => void;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\ninterface CommentMentionProps\n extends CommentBodyMentionProps,\n CommentPrimitiveMentionProps {\n overrides?: CommentProps[\"overrides\"];\n}\n\nfunction CommentUserMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const currentId = useCurrentUserId();\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={mention.id === currentId ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <User userId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nfunction CommentGroupMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const isMember = useIsGroupMentionMember(mention as GroupMentionData);\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={isMember ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <Group groupId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentMention({ mention, ...props }: CommentMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return <CommentUserMention mention={mention} {...props} />;\n\n case \"group\":\n return <CommentGroupMention mention={mention} {...props} />;\n\n default:\n return assertNever(mention, \"Unhandled mention kind\");\n }\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n const { Anchor } = useComponents();\n\n return (\n <CommentPrimitive.Link\n className={cn(\"lb-comment-link\", className)}\n href={href}\n {...props}\n asChild\n >\n <Anchor {...props}>{children}</Anchor>\n </CommentPrimitive.Link>\n );\n}\n\nexport function CommentNonInteractiveLink({\n href: _href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <span className={cn(\"lb-comment-link\", className)} {...props}>\n {children}\n </span>\n );\n}\n\nconst CommentReactionButton = forwardRef<\n HTMLButtonElement,\n CommentReactionButtonProps\n>(({ reaction, overrides, className, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n return (\n <CustomButton\n className={cn(\"lb-comment-reaction\", className)}\n variant=\"outline\"\n aria-label={$.COMMENT_REACTION_DESCRIPTION(\n reaction.emoji,\n reaction.users.length\n )}\n {...props}\n ref={forwardedRef}\n >\n <Emoji className=\"lb-comment-reaction-emoji\" emoji={reaction.emoji} />\n <span className=\"lb-comment-reaction-count\">{reaction.users.length}</span>\n </CustomButton>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n allowMediaPreview={false}\n {...props}\n />\n );\n}\n\nconst CommentDropdownItem = forwardRef<\n HTMLDivElement,\n CommentDropdownItemProps\n>(({ children, icon, onSelect, onClick, ...props }, forwardedRef) => {\n const handleClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n }\n },\n [onClick]\n );\n\n return (\n <DropdownItem\n icon={icon}\n onSelect={onSelect}\n onClick={handleClick}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </DropdownItem>\n );\n});\n\n/**\n * Displays a single comment.\n *\n * @example\n * <>\n * {thread.comments.map((comment) => (\n * <Comment key={comment.id} comment={comment} />\n * ))}\n * </>\n */\nexport const Comment = Object.assign(\n forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n showComposerFormattingControls = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n dropdownItems,\n overrides,\n components,\n additionalContent,\n body,\n avatar,\n author,\n date,\n className,\n actions,\n actionsClassName,\n internalDropdownItems,\n children,\n ...props\n },\n forwardedRef\n ) => {\n const bodyId = `${comment.id}:body`;\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteRoomComment(comment.roomId);\n const editComment = useEditRoomComment(comment.roomId);\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const permissions = useRoomPermissions(comment.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const handleEditCancel = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n setEditing(false);\n },\n []\n );\n\n const handleEditSubmit = useCallback(\n (\n { body, attachments }: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentEdit?.(comment);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\n setEditing(false);\n editComment({\n commentId: comment.id,\n threadId: comment.threadId,\n body,\n attachments,\n });\n },\n [comment, editComment, onCommentEdit]\n );\n\n const handleDelete = useCallback(() => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentDelete?.(comment);\n\n deleteComment({\n commentId: comment.id,\n threadId: comment.threadId,\n });\n }, [comment, deleteComment, onCommentDelete]);\n\n const handleAuthorClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n onAuthorClick?.(comment.userId, event);\n },\n [comment.userId, onAuthorClick]\n );\n\n const handleReactionSelect = useCallback(\n (emoji: string) => {\n const reactionIndex = comment.reactions.findIndex(\n (reaction) => reaction.emoji === emoji\n );\n\n if (\n reactionIndex >= 0 &&\n currentUserId &&\n comment.reactions[reactionIndex]?.users.some(\n (user) => user.id === currentUserId\n )\n ) {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n } else {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [\n addReaction,\n comment.id,\n comment.reactions,\n comment.threadId,\n removeReaction,\n currentUserId,\n ]\n );\n\n useEffect(() => {\n const isWindowDefined = typeof window !== \"undefined\";\n if (!isWindowDefined) return;\n\n const hash = window.location.hash;\n const commentId = hash.slice(1);\n\n if (commentId === comment.id) {\n setTarget(true);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!showDeleted && !comment.body) {\n return null;\n }\n\n const commentDropdownItems =\n comment.userId === currentUserId ? (\n <>\n <CommentDropdownItem onSelect={handleEdit} icon={<EditIcon />}>\n {$.COMMENT_EDIT}\n </CommentDropdownItem>\n <CommentDropdownItem onSelect={handleDelete} icon={<DeleteIcon />}>\n {$.COMMENT_DELETE}\n </CommentDropdownItem>\n </>\n ) : null;\n const defaultDropdownItems =\n internalDropdownItems || commentDropdownItems ? (\n <>\n {internalDropdownItems}\n {commentDropdownItems}\n </>\n ) : null;\n\n const dropdownContent =\n typeof dropdownItems === \"function\" ? (\n dropdownItems({ children: defaultDropdownItems, comment })\n ) : defaultDropdownItems || dropdownItems ? (\n <>\n {defaultDropdownItems}\n {dropdownItems}\n </>\n ) : null;\n\n let content: ReactNode;\n\n if (isEditing) {\n content = (\n <Composer\n className=\"lb-comment-composer\"\n onComposerSubmit={handleEditSubmit}\n defaultValue={comment.body}\n defaultAttachments={comment.attachments}\n autoFocus\n showAttribution={false}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n actions={\n <>\n <Tooltip\n content={$.COMMENT_EDIT_COMPOSER_CANCEL}\n aria-label={$.COMMENT_EDIT_COMPOSER_CANCEL}\n >\n <Button\n className=\"lb-composer-action\"\n onClick={handleEditCancel}\n icon={<CrossIcon />}\n />\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut=\"Enter\"\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n variant=\"primary\"\n className=\"lb-composer-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_EDIT_COMPOSER_SAVE}\n icon={<CheckIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n roomId={comment.roomId}\n />\n );\n } else {\n const defaultBody = (\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n id={bodyId}\n body={comment.body}\n components={{\n Mention: ({ mention }) => (\n <CommentMention\n mention={mention}\n onClick={(event) => onMentionClick?.(mention, event)}\n overrides={overrides}\n />\n ),\n Link: CommentLink,\n }}\n />\n );\n\n content = comment.body ? (\n <>\n {body === undefined\n ? defaultBody\n : typeof body === \"function\"\n ? body({ comment, children: defaultBody })\n : body}\n {additionalContent}\n {showAttachments &&\n (mediaAttachments.length > 0 || fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n disabled={!canComment}\n />\n ))}\n {canComment ? (\n <EmojiPicker onEmojiSelect={handleReactionSelect}>\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-reaction lb-comment-reaction-add\"\n variant=\"outline\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n </div>\n )}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n );\n\n content =\n typeof children === \"function\"\n ? children({ comment, children: content })\n : (children ?? content);\n }\n\n return (\n <TooltipProvider>\n <ComponentsProvider components={components}>\n <div\n role=\"article\"\n id={comment.id}\n className={cn(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n aria-labelledby={bodyId}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n {avatar ? (\n <div\n className=\"lb-comment-avatar\"\n onClick={handleAuthorClick}\n >\n {avatar}\n </div>\n ) : (\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n )}\n <span className=\"lb-comment-details-labels\">\n {author ? (\n <span\n className=\"lb-comment-author\"\n onClick={handleAuthorClick}\n >\n {author}\n </span>\n ) : (\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n )}\n {date ? (\n <span className=\"lb-comment-date\">{date}</span>\n ) : (\n <span className=\"lb-comment-date\">\n <CommentDate\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n )}\n </span>\n </div>\n {showActions && !isEditing && (\n <div className={cn(\"lb-comment-actions\", actionsClassName)}>\n {actions ?? null}\n {showReactions && canComment ? (\n <EmojiPicker\n onEmojiSelect={handleReactionSelect}\n onOpenChange={setReactionActionOpen}\n >\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n {dropdownContent ? (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={dropdownContent}\n >\n <Tooltip content={$.COMMENT_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n disabled={!comment.body}\n onClick={stopPropagation}\n aria-label={$.COMMENT_MORE}\n icon={<EllipsisIcon />}\n />\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n ) : null}\n </div>\n )}\n </div>\n <div className=\"lb-comment-content\">{content}</div>\n </div>\n </ComponentsProvider>\n </TooltipProvider>\n );\n }\n ) as <CM extends BaseMetadata = DCM>(\n props: CommentProps<CM> & RefAttributes<HTMLDivElement>\n ) => JSX.Element,\n {\n /**\n * Displays a dropdown item in the comment's dropdown.\n */\n DropdownItem: CommentDropdownItem,\n\n /**\n * Displays a comment's avatar.\n */\n Avatar: CommentAvatar,\n\n /**\n * Displays a comment's author.\n */\n Author: CommentAuthor,\n\n /**\n * Displays a comment's date.\n */\n Date: CommentDate,\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA;AA0KA;AACE;AACF;AAEA;AACE;AACF;AAEA;AACE;AACE;AAAC;AAAA;AACC;AACA;AACkC;AAC7B;AAAA;AAGX;AAwCA;AAA4B;AAC1B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AACb;AACvC;AAEJ;AAAuD;AAC7B;AAAA;AAAA;AAGhC;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AAC7B;AACvB;AAEJ;AAAuD;AAC3B;AAAA;AAAA;AAGlC;AAEO;AACL;AAAsB;AAElB;AAAwD;AAGxD;AAAyD;AAGzD;AAAoD;AAE1D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AAC2C;AAC1C;AACI;AACG;AAEsB;AAAA;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AAKF;AAEA;AAIE;AACA;AACE;AAAC;AAAA;AAC+C;AACtC;AACM;AACH;AACM;AACjB;AACI;AACC;AAEL;AAAoE;AACD;AAAA;AAAA;AAGzE;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACD;AAAC;AAAA;AAGE;AACkB;AACT;AACA;AAAA;AACZ;AACS;AACM;AAEnB;AAEU;AAGd;AACE;AAAsB;AAGxB;AAA4B;AAExB;AACE;AAAY;AACQ;AACC;AACH;AACjB;AAED;AAAe;AACK;AACC;AACH;AACjB;AACH;AACF;AAC0E;AAG5E;AACE;AAAC;AAAA;AACU;AACA;AACC;AAEV;AAAiB;AAAhB;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEL;AAAC;AAAA;AAC4B;AAC3B;AACA;AACI;AAAA;AACN;AAAA;AACF;AAAA;AAGN;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACE;AAAC;AAAA;AACc;AACc;AAC3B;AACA;AACI;AACC;AAAA;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACE;AAAC;AAAA;AACiD;AAC7B;AACf;AAAA;AAGV;AAEA;AAIE;AAAoB;AAEhB;AAEA;AACE;AAAsB;AACxB;AACF;AACQ;AAGV;AACE;AAAC;AAAA;AACC;AACA;AACS;AACL;AACC;AAEJ;AAAA;AAGP;AAYO;AAAuB;AAC5B;AAEI;AACE;AACgB;AAChB;AACc;AACE;AACE;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACA;AAMA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACE;AAAA;AAGF;AACA;AAEA;AACA;AAAY;AACS;AACD;AAClB;AACA;AACD;AACH;AACoC;AAGtC;AAEE;AAEA;AAAc;AACO;AACD;AACnB;AAGH;AAA0B;AAEtB;AAAqC;AACvC;AAC8B;AAGhC;AAA6B;AAEzB;AAAwC;AACL;AAGnC;AAG0C;AAChB;AAGxB;AAAe;AACK;AACC;AACnB;AACD;AAED;AAAY;AACQ;AACC;AACnB;AACD;AACH;AACF;AACA;AACE;AACQ;AACA;AACA;AACR;AACA;AACF;AAGF;AACE;AACA;AAAsB;AAEtB;AACA;AAEA;AACE;AAAc;AAChB;AAGF;AACE;AAAO;AAGT;AAGM;AAEA;AAGA;AAGN;AAGO;AAAA;AACA;AAIP;AAKO;AAAA;AACA;AAIP;AAEA;AACE;AACE;AAAC;AAAA;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AACwB;AAGpB;AAAA;AAAC;AAAA;AACY;AACG;AAEd;AAAC;AAAA;AACW;AACD;AACQ;AAAA;AACnB;AAAA;AACF;AACA;AAAC;AAAA;AACY;AACF;AAGP;AAAC;AAAA;AACS;AACE;AACD;AACK;AACG;AAAA;AAErB;AAAA;AACF;AACF;AAES;AACe;AAC1B;AACgB;AAAA;AAClB;AAGF;AACE;AAAkB;AAAjB;AACW;AACN;AACU;AACF;AAER;AAAC;AAAA;AACC;AACmD;AACnD;AAAA;AACF;AAEI;AACR;AAAA;AAIJ;AAEK;AAIK;AACL;AAII;AAGK;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAIE;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAEJ;AAGC;AACC;AAAC;AAAA;AAEC;AACA;AACA;AACW;AAAA;AAJG;AAMjB;AAKO;AAAC;AAAA;AACW;AACF;AACC;AACK;AACO;AAAA;AAK3B;AACN;AASN;AAGmB;AAGrB;AAGM;AAAC;AAAA;AACM;AACO;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACZ;AACV;AACH;AACC;AAEL;AACE;AACG;AACC;AAAC;AAAA;AACW;AACD;AAER;AAAA;AAGH;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAGC;AACC;AAAC;AAAA;AACW;AACD;AAER;AAAA;AAGH;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAME;AAAA;AAAC;AAAA;AACW;AACI;AACJ;AAAA;AACZ;AAGK;AAAA;AAGD;AACF;AAEJ;AAEJ;AACF;AAGK;AAAW;AAEV;AAAC;AAAA;AACgB;AACD;AAIV;AAAC;AAAA;AACW;AACD;AACK;AACO;AAAA;AAG3B;AAAA;AAEA;AAEF;AAAC;AAAA;AACO;AACQ;AACR;AACG;AAIL;AAAC;AAAA;AACW;AACS;AACV;AACK;AACM;AAAA;AAG1B;AAAA;AAEA;AACN;AAEJ;AAC6C;AAAA;AAAA;AAGnD;AAEJ;AACF;AAGA;AAAA;AAAA;AAAA;AAIgB;AAAA;AAAA;AAAA;AAKN;AAAA;AAAA;AAAA;AAKA;AAAA;AAAA;AAAA;AAKF;AAEV;;"}
|
|
@@ -5,13 +5,15 @@ var core = require('@liveblocks/core');
|
|
|
5
5
|
var react = require('@liveblocks/react');
|
|
6
6
|
var _private = require('@liveblocks/react/_private');
|
|
7
7
|
var react$1 = require('react');
|
|
8
|
+
var animationLoop = require('../utils/animation-loop.cjs');
|
|
8
9
|
var cn = require('../utils/cn.cjs');
|
|
9
|
-
var cursorSpring = require('../utils/cursor-spring.cjs');
|
|
10
10
|
var useRefs = require('../utils/use-refs.cjs');
|
|
11
|
-
var useStableComponent = require('../utils/use-stable-component.cjs');
|
|
12
11
|
var useWindowFocus = require('../utils/use-window-focus.cjs');
|
|
13
12
|
var Cursor = require('./Cursor.cjs');
|
|
14
13
|
|
|
14
|
+
const STIFFNESS = 320;
|
|
15
|
+
const DAMPING = 32;
|
|
16
|
+
const EPSILON = 0.01;
|
|
15
17
|
const DEFAULT_PRESENCE_KEY = "cursor";
|
|
16
18
|
function $string(value) {
|
|
17
19
|
return typeof value === "string" ? value : void 0;
|
|
@@ -22,34 +24,103 @@ function $coordinates(value) {
|
|
|
22
24
|
}
|
|
23
25
|
return void 0;
|
|
24
26
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const loop = animationLoop.makeAnimationLoop();
|
|
28
|
+
function makeCoordinatesSpring() {
|
|
29
|
+
const updates = core.makeEventSource();
|
|
30
|
+
let value = null;
|
|
31
|
+
let target = null;
|
|
32
|
+
const velocity = { x: 0, y: 0 };
|
|
33
|
+
const spring = {
|
|
34
|
+
active: false,
|
|
35
|
+
step(dt) {
|
|
36
|
+
if (value === null || target === null) {
|
|
37
|
+
spring.active = false;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const k = STIFFNESS;
|
|
41
|
+
const d = DAMPING;
|
|
42
|
+
const dx = value.x - target.x;
|
|
43
|
+
const dy = value.y - target.y;
|
|
44
|
+
velocity.x += (-k * dx - d * velocity.x) * dt;
|
|
45
|
+
velocity.y += (-k * dy - d * velocity.y) * dt;
|
|
46
|
+
const nx = value.x + velocity.x * dt;
|
|
47
|
+
const ny = value.y + velocity.y * dt;
|
|
48
|
+
if (nx !== value.x || ny !== value.y) {
|
|
49
|
+
value.x = nx;
|
|
50
|
+
value.y = ny;
|
|
51
|
+
updates.notify(value);
|
|
52
|
+
}
|
|
53
|
+
if (Math.abs(velocity.x) < EPSILON && Math.abs(velocity.y) < EPSILON && Math.abs(target.x - value.x) < EPSILON && Math.abs(target.y - value.y) < EPSILON) {
|
|
54
|
+
if (value.x !== target.x || value.y !== target.y) {
|
|
55
|
+
value.x = target.x;
|
|
56
|
+
value.y = target.y;
|
|
57
|
+
updates.notify(value);
|
|
58
|
+
}
|
|
59
|
+
velocity.x = 0;
|
|
60
|
+
velocity.y = 0;
|
|
61
|
+
spring.active = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
get() {
|
|
67
|
+
return value;
|
|
68
|
+
},
|
|
69
|
+
set(point) {
|
|
70
|
+
if (point === null) {
|
|
71
|
+
value = null;
|
|
72
|
+
target = null;
|
|
73
|
+
velocity.x = 0;
|
|
74
|
+
velocity.y = 0;
|
|
75
|
+
spring.active = false;
|
|
76
|
+
loop.remove(spring);
|
|
77
|
+
updates.notify(null);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (value === null) {
|
|
81
|
+
value = { x: point.x, y: point.y };
|
|
82
|
+
target = point;
|
|
83
|
+
velocity.x = 0;
|
|
84
|
+
velocity.y = 0;
|
|
85
|
+
updates.notify(value);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
target = point;
|
|
89
|
+
if (!spring.active && (value.x !== target.x || value.y !== target.y)) {
|
|
90
|
+
spring.active = true;
|
|
91
|
+
loop.add(spring);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
subscribe: updates.subscribe,
|
|
95
|
+
dispose() {
|
|
96
|
+
spring.active = false;
|
|
97
|
+
loop.remove(spring);
|
|
98
|
+
updates.dispose();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
33
101
|
}
|
|
34
102
|
function PresenceCursor({
|
|
35
103
|
connectionId,
|
|
36
104
|
presenceKey,
|
|
37
105
|
sizeRef,
|
|
38
|
-
sizeEvents
|
|
39
|
-
Cursor: Cursor$1
|
|
106
|
+
sizeEvents
|
|
40
107
|
}) {
|
|
41
108
|
const room = react.useRoom();
|
|
42
109
|
const cursorRef = react$1.useRef(null);
|
|
43
110
|
const userId = react.useOther(connectionId, (other) => $string(other.id));
|
|
111
|
+
const { user, isLoading } = react.useUser(userId ?? "");
|
|
112
|
+
const hasUserInfo = userId !== void 0 && !isLoading;
|
|
113
|
+
const color = $string(user?.color);
|
|
114
|
+
const name = $string(user?.name);
|
|
44
115
|
_private.useLayoutEffect(() => {
|
|
45
|
-
const spring =
|
|
116
|
+
const spring = makeCoordinatesSpring();
|
|
46
117
|
function update() {
|
|
47
118
|
const element = cursorRef.current;
|
|
48
119
|
const coordinates = spring.get();
|
|
49
120
|
if (!element) {
|
|
50
121
|
return;
|
|
51
122
|
}
|
|
52
|
-
if (coordinates === null) {
|
|
123
|
+
if (!hasUserInfo || coordinates === null) {
|
|
53
124
|
element.style.transform = "translate3d(0, 0, 0)";
|
|
54
125
|
element.style.display = "none";
|
|
55
126
|
return;
|
|
@@ -73,21 +144,19 @@ function PresenceCursor({
|
|
|
73
144
|
unsubscribeSize();
|
|
74
145
|
unsubscribeOther();
|
|
75
146
|
};
|
|
76
|
-
}, [room, connectionId, presenceKey, sizeRef, sizeEvents]);
|
|
77
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
147
|
+
}, [room, connectionId, presenceKey, sizeRef, sizeEvents, hasUserInfo]);
|
|
148
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
149
|
+
Cursor.Cursor,
|
|
150
|
+
{
|
|
151
|
+
color,
|
|
152
|
+
label: name,
|
|
153
|
+
ref: cursorRef,
|
|
154
|
+
style: { display: "none" }
|
|
155
|
+
}
|
|
156
|
+
);
|
|
78
157
|
}
|
|
79
158
|
const Cursors = react$1.forwardRef(
|
|
80
|
-
({
|
|
81
|
-
className,
|
|
82
|
-
children,
|
|
83
|
-
presenceKey = DEFAULT_PRESENCE_KEY,
|
|
84
|
-
components,
|
|
85
|
-
...props
|
|
86
|
-
}, forwardedRef) => {
|
|
87
|
-
const Cursor = useStableComponent.useStableComponent(
|
|
88
|
-
components?.Cursor,
|
|
89
|
-
DefaultCursorWithUserInfo
|
|
90
|
-
);
|
|
159
|
+
({ className, children, presenceKey = DEFAULT_PRESENCE_KEY, ...props }, forwardedRef) => {
|
|
91
160
|
const containerRef = react$1.useRef(null);
|
|
92
161
|
const mergedRefs = useRefs.useRefs(forwardedRef, containerRef);
|
|
93
162
|
const updateMyPresence = react.useUpdateMyPresence();
|
|
@@ -169,8 +238,7 @@ const Cursors = react$1.forwardRef(
|
|
|
169
238
|
connectionId,
|
|
170
239
|
presenceKey,
|
|
171
240
|
sizeRef,
|
|
172
|
-
sizeEvents
|
|
173
|
-
Cursor
|
|
241
|
+
sizeEvents
|
|
174
242
|
},
|
|
175
243
|
connectionId
|
|
176
244
|
)) }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Cursors.cjs","sources":["../../src/components/Cursors.tsx"],"sourcesContent":["import type { EventSource } from \"@liveblocks/core\";\nimport { isPlainObject, makeEventSource } from \"@liveblocks/core\";\nimport {\n useOther,\n useOthersConnectionIds,\n useRoom,\n useUpdateMyPresence,\n useUser,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport type {\n ComponentPropsWithoutRef,\n ComponentType,\n MutableRefObject,\n PointerEvent,\n} from \"react\";\nimport { forwardRef, useCallback, useEffect, useRef, useState } from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { cn } from \"../utils/cn\";\nimport { makeCursorSpring } from \"../utils/cursor-spring\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useStableComponent } from \"../utils/use-stable-component\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Cursor as DefaultCursor } from \"./Cursor\";\n\nconst DEFAULT_PRESENCE_KEY = \"cursor\";\n\nexport interface CursorsCursorProps {\n /**\n * The user ID for this cursor.\n */\n userId: string;\n\n /**\n * The connection ID for this cursor.\n */\n connectionId: number;\n}\n\ninterface CursorsComponents {\n /**\n * The component used to display each cursor.\n */\n Cursor: ComponentType<CursorsCursorProps>;\n}\n\nexport interface CursorsProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The key used to store the cursors in users' Presence.\n * This can be used to have multiple `Cursors` in a single room.\n *\n * Defaults to `\"cursor\"`.\n */\n presenceKey?: string;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & CursorsComponents>;\n}\n\ntype Coordinates = {\n x: number;\n y: number;\n};\n\ntype Size = {\n width: number;\n height: number;\n};\n\nfunction $string(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction $coordinates(value: unknown): Coordinates | undefined {\n if (\n isPlainObject(value) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n ) {\n return value as Coordinates;\n }\n\n return undefined;\n}\n\nfunction DefaultCursorWithUserInfo({ userId }: CursorsCursorProps) {\n const { user, isLoading } = useUser(userId ?? \"\");\n const color = $string(user?.color);\n const name = $string(user?.name);\n\n if (isLoading) {\n return null;\n }\n\n return <DefaultCursor color={color} label={name} />;\n}\n\nfunction PresenceCursor({\n connectionId,\n presenceKey,\n sizeRef,\n sizeEvents,\n Cursor,\n}: {\n connectionId: number;\n presenceKey: string;\n sizeRef: MutableRefObject<Size | null>;\n sizeEvents: EventSource<void>;\n Cursor: ComponentType<CursorsCursorProps>;\n}) {\n const room = useRoom();\n const cursorRef = useRef<HTMLDivElement>(null);\n const userId = useOther(connectionId, (other) => $string(other.id));\n\n useLayoutEffect(() => {\n const spring = makeCursorSpring();\n\n function update() {\n const element = cursorRef.current;\n const coordinates = spring.get();\n\n if (!element) {\n return;\n }\n\n if (coordinates === null) {\n element.style.transform = \"translate3d(0, 0, 0)\";\n element.style.display = \"none\";\n return;\n }\n\n if (sizeRef.current) {\n element.style.transform = `translate3d(${coordinates.x * sizeRef.current.width}px, ${coordinates.y * sizeRef.current.height}px, 0)`;\n }\n\n element.style.display = \"\";\n }\n\n const unsubscribeSpring = spring.subscribe(update);\n const unsubscribeSize = sizeEvents.subscribe(update);\n update();\n\n const unsubscribeOther = room.events.others.subscribe(({ others }) => {\n const other = others.find((other) => other.connectionId === connectionId);\n const cursor = $coordinates(other?.presence[presenceKey]);\n\n spring.set(cursor ?? null);\n });\n\n return () => {\n spring.dispose();\n unsubscribeSpring();\n unsubscribeSize();\n unsubscribeOther();\n };\n }, [room, connectionId, presenceKey, sizeRef, sizeEvents]);\n\n return (\n <div ref={cursorRef} style={{ display: \"none\" }}>\n {userId ? (\n <Cursor userId={userId} connectionId={connectionId} />\n ) : (\n <DefaultCursor />\n )}\n </div>\n );\n}\n\n/**\n * Displays multiplayer cursors.\n */\nexport const Cursors = forwardRef<HTMLDivElement, CursorsProps>(\n (\n {\n className,\n children,\n presenceKey = DEFAULT_PRESENCE_KEY,\n components,\n ...props\n },\n forwardedRef\n ) => {\n const Cursor = useStableComponent(\n components?.Cursor,\n DefaultCursorWithUserInfo\n );\n const containerRef = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, containerRef);\n const updateMyPresence = useUpdateMyPresence();\n const othersConnectionIds = useOthersConnectionIds();\n const sizeRef = useRef<Size | null>(null);\n const [sizeEvents] = useState(() => makeEventSource<void>());\n const isWindowFocused = useWindowFocus();\n\n useEffect(() => {\n const container = containerRef.current;\n\n if (!container) {\n return;\n }\n\n function setSize(size: Size) {\n sizeRef.current = size;\n sizeEvents.notify();\n }\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (entry.target === container) {\n setSize({\n width: entry.contentRect.width,\n height: entry.contentRect.height,\n });\n }\n }\n });\n\n setSize({\n width: container.clientWidth,\n height: container.clientHeight,\n });\n\n observer.observe(container);\n\n return () => {\n observer.disconnect();\n };\n }, [sizeEvents]);\n\n const handlePointerMove = useCallback(\n (event: PointerEvent) => {\n const container = containerRef.current;\n\n if (!container) {\n return;\n }\n\n const bounds = container.getBoundingClientRect();\n\n if (bounds.width === 0 || bounds.height === 0) {\n return;\n }\n\n updateMyPresence({\n [presenceKey]: {\n x: (event.clientX - bounds.left) / bounds.width,\n y: (event.clientY - bounds.top) / bounds.height,\n },\n });\n },\n [updateMyPresence, presenceKey]\n );\n\n const handlePointerLeave = useCallback(() => {\n updateMyPresence({\n [presenceKey]: null,\n });\n }, [updateMyPresence, presenceKey]);\n\n useEffect(() => {\n if (!isWindowFocused) {\n updateMyPresence({\n [presenceKey]: null,\n });\n }\n }, [isWindowFocused, updateMyPresence, presenceKey]);\n\n return (\n <div\n className={cn(\"lb-root lb-cursors\", className)}\n {...props}\n onPointerMove={handlePointerMove}\n onPointerLeave={handlePointerLeave}\n ref={mergedRefs}\n >\n <div className=\"lb-cursors-container\">\n {othersConnectionIds.map((connectionId) => (\n <PresenceCursor\n key={connectionId}\n connectionId={connectionId}\n presenceKey={presenceKey}\n sizeRef={sizeRef}\n sizeEvents={sizeEvents}\n Cursor={Cursor}\n />\n ))}\n </div>\n\n {children}\n </div>\n );\n }\n);\n"],"names":["isPlainObject","useUser","jsx","DefaultCursor","Cursor","useRoom","useRef","useOther","useLayoutEffect","makeCursorSpring","other","forwardRef","useStableComponent","useRefs","useUpdateMyPresence","useOthersConnectionIds","useState","makeEventSource","useWindowFocus","useEffect","useCallback","jsxs","cn"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Cursors.cjs","sources":["../../src/components/Cursors.tsx"],"sourcesContent":["import type { EventSource } from \"@liveblocks/core\";\nimport { isPlainObject, makeEventSource } from \"@liveblocks/core\";\nimport {\n useOther,\n useOthersConnectionIds,\n useRoom,\n useUpdateMyPresence,\n useUser,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport type {\n ComponentPropsWithoutRef,\n MutableRefObject,\n PointerEvent,\n} from \"react\";\nimport { forwardRef, useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { type Animatable, makeAnimationLoop } from \"../utils/animation-loop\";\nimport { cn } from \"../utils/cn\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Cursor } from \"./Cursor\";\n\nconst STIFFNESS = 320;\nconst DAMPING = 32;\nconst EPSILON = 0.01;\n\nconst DEFAULT_PRESENCE_KEY = \"cursor\";\n\nexport interface CursorsProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The key used to store the cursors in users' Presence.\n * This can be used to have multiple `Cursors` in a single room.\n */\n presenceKey?: string;\n}\n\ntype Coordinates = {\n x: number;\n y: number;\n};\n\ntype Size = {\n width: number;\n height: number;\n};\n\nfunction $string(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction $coordinates(value: unknown): Coordinates | undefined {\n if (\n isPlainObject(value) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n ) {\n return value as Coordinates;\n }\n\n return undefined;\n}\n\n// Use a shared animation loop for all (active) springs.\nconst loop = makeAnimationLoop();\n\nfunction makeCoordinatesSpring() {\n const updates = makeEventSource<Coordinates | null>();\n let value: Coordinates | null = null;\n let target: Coordinates | null = null;\n const velocity = { x: 0, y: 0 };\n\n const spring: Animatable = {\n active: false,\n step(dt: number) {\n if (value === null || target === null) {\n spring.active = false;\n return;\n }\n\n const k = STIFFNESS;\n const d = DAMPING;\n const dx = value.x - target.x;\n const dy = value.y - target.y;\n\n velocity.x += (-k * dx - d * velocity.x) * dt;\n velocity.y += (-k * dy - d * velocity.y) * dt;\n\n const nx = value.x + velocity.x * dt;\n const ny = value.y + velocity.y * dt;\n\n if (nx !== value.x || ny !== value.y) {\n value.x = nx;\n value.y = ny;\n updates.notify(value);\n }\n\n if (\n Math.abs(velocity.x) < EPSILON &&\n Math.abs(velocity.y) < EPSILON &&\n Math.abs(target.x - value.x) < EPSILON &&\n Math.abs(target.y - value.y) < EPSILON\n ) {\n if (value.x !== target.x || value.y !== target.y) {\n value.x = target.x;\n value.y = target.y;\n updates.notify(value);\n }\n\n velocity.x = 0;\n velocity.y = 0;\n spring.active = false;\n }\n },\n };\n\n return {\n get() {\n return value;\n },\n set(point: Coordinates | null) {\n if (point === null) {\n value = null;\n target = null;\n velocity.x = 0;\n velocity.y = 0;\n spring.active = false;\n loop.remove(spring);\n updates.notify(null);\n return;\n }\n\n if (value === null) {\n value = { x: point.x, y: point.y };\n target = point;\n velocity.x = 0;\n velocity.y = 0;\n updates.notify(value);\n return;\n }\n\n target = point;\n\n if (!spring.active && (value.x !== target.x || value.y !== target.y)) {\n spring.active = true;\n loop.add(spring);\n }\n },\n subscribe: updates.subscribe,\n dispose() {\n spring.active = false;\n loop.remove(spring);\n updates.dispose();\n },\n };\n}\n\nfunction PresenceCursor({\n connectionId,\n presenceKey,\n sizeRef,\n sizeEvents,\n}: {\n connectionId: number;\n presenceKey: string;\n sizeRef: MutableRefObject<Size | null>;\n sizeEvents: EventSource<void>;\n}) {\n const room = useRoom();\n const cursorRef = useRef<HTMLDivElement>(null);\n const userId = useOther(connectionId, (other) => $string(other.id));\n const { user, isLoading } = useUser(userId ?? \"\");\n const hasUserInfo = userId !== undefined && !isLoading;\n const color = $string(user?.color);\n const name = $string(user?.name);\n\n useLayoutEffect(() => {\n const spring = makeCoordinatesSpring();\n\n function update() {\n const element = cursorRef.current;\n const coordinates = spring.get();\n\n if (!element) {\n return;\n }\n\n if (!hasUserInfo || coordinates === null) {\n element.style.transform = \"translate3d(0, 0, 0)\";\n element.style.display = \"none\";\n return;\n }\n\n if (sizeRef.current) {\n element.style.transform = `translate3d(${coordinates.x * sizeRef.current.width}px, ${coordinates.y * sizeRef.current.height}px, 0)`;\n }\n\n element.style.display = \"\";\n }\n\n const unsubscribeSpring = spring.subscribe(update);\n const unsubscribeSize = sizeEvents.subscribe(update);\n update();\n\n const unsubscribeOther = room.events.others.subscribe(({ others }) => {\n const other = others.find((other) => other.connectionId === connectionId);\n const cursor = $coordinates(other?.presence[presenceKey]);\n\n spring.set(cursor ?? null);\n });\n\n return () => {\n spring.dispose();\n unsubscribeSpring();\n unsubscribeSize();\n unsubscribeOther();\n };\n }, [room, connectionId, presenceKey, sizeRef, sizeEvents, hasUserInfo]);\n\n return (\n <Cursor\n color={color}\n label={name}\n ref={cursorRef}\n style={{ display: \"none\" }}\n />\n );\n}\n\n/**\n * Displays multiplayer cursors.\n */\nexport const Cursors = forwardRef<HTMLDivElement, CursorsProps>(\n (\n { className, children, presenceKey = DEFAULT_PRESENCE_KEY, ...props },\n forwardedRef\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, containerRef);\n const updateMyPresence = useUpdateMyPresence();\n const othersConnectionIds = useOthersConnectionIds();\n const sizeRef = useRef<Size | null>(null);\n const [sizeEvents] = useState(() => makeEventSource<void>());\n const isWindowFocused = useWindowFocus();\n\n useEffect(() => {\n const container = containerRef.current;\n\n if (!container) {\n return;\n }\n\n function setSize(size: Size) {\n sizeRef.current = size;\n sizeEvents.notify();\n }\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (entry.target === container) {\n setSize({\n width: entry.contentRect.width,\n height: entry.contentRect.height,\n });\n }\n }\n });\n\n setSize({\n width: container.clientWidth,\n height: container.clientHeight,\n });\n\n observer.observe(container);\n\n return () => {\n observer.disconnect();\n };\n }, [sizeEvents]);\n\n const handlePointerMove = useCallback(\n (event: PointerEvent) => {\n const container = containerRef.current;\n\n if (!container) {\n return;\n }\n\n const bounds = container.getBoundingClientRect();\n\n if (bounds.width === 0 || bounds.height === 0) {\n return;\n }\n\n updateMyPresence({\n [presenceKey]: {\n x: (event.clientX - bounds.left) / bounds.width,\n y: (event.clientY - bounds.top) / bounds.height,\n },\n });\n },\n [updateMyPresence, presenceKey]\n );\n\n const handlePointerLeave = useCallback(() => {\n updateMyPresence({\n [presenceKey]: null,\n });\n }, [updateMyPresence, presenceKey]);\n\n useEffect(() => {\n if (!isWindowFocused) {\n updateMyPresence({\n [presenceKey]: null,\n });\n }\n }, [isWindowFocused, updateMyPresence, presenceKey]);\n\n return (\n <div\n className={cn(\"lb-root lb-cursors\", className)}\n {...props}\n onPointerMove={handlePointerMove}\n onPointerLeave={handlePointerLeave}\n ref={mergedRefs}\n >\n <div className=\"lb-cursors-container\">\n {othersConnectionIds.map((connectionId) => (\n <PresenceCursor\n key={connectionId}\n connectionId={connectionId}\n presenceKey={presenceKey}\n sizeRef={sizeRef}\n sizeEvents={sizeEvents}\n />\n ))}\n </div>\n\n {children}\n </div>\n );\n }\n);\n"],"names":["isPlainObject","makeAnimationLoop","makeEventSource","useRoom","useRef","useOther","useUser","useLayoutEffect","other","jsx","Cursor","forwardRef","useRefs","useUpdateMyPresence","useOthersConnectionIds","useState","useWindowFocus","useEffect","useCallback","jsxs","cn"],"mappings":"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"}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { isPlainObject, makeEventSource } from '@liveblocks/core';
|
|
3
|
-
import {
|
|
3
|
+
import { useRoom, useOther, useUser, useUpdateMyPresence, useOthersConnectionIds } from '@liveblocks/react';
|
|
4
4
|
import { useLayoutEffect } from '@liveblocks/react/_private';
|
|
5
5
|
import { useRef, forwardRef, useState, useEffect, useCallback } from 'react';
|
|
6
|
+
import { makeAnimationLoop } from '../utils/animation-loop.js';
|
|
6
7
|
import { cn } from '../utils/cn.js';
|
|
7
|
-
import { makeCursorSpring } from '../utils/cursor-spring.js';
|
|
8
8
|
import { useRefs } from '../utils/use-refs.js';
|
|
9
|
-
import { useStableComponent } from '../utils/use-stable-component.js';
|
|
10
9
|
import { useWindowFocus } from '../utils/use-window-focus.js';
|
|
11
10
|
import { Cursor } from './Cursor.js';
|
|
12
11
|
|
|
12
|
+
const STIFFNESS = 320;
|
|
13
|
+
const DAMPING = 32;
|
|
14
|
+
const EPSILON = 0.01;
|
|
13
15
|
const DEFAULT_PRESENCE_KEY = "cursor";
|
|
14
16
|
function $string(value) {
|
|
15
17
|
return typeof value === "string" ? value : void 0;
|
|
@@ -20,34 +22,103 @@ function $coordinates(value) {
|
|
|
20
22
|
}
|
|
21
23
|
return void 0;
|
|
22
24
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
const loop = makeAnimationLoop();
|
|
26
|
+
function makeCoordinatesSpring() {
|
|
27
|
+
const updates = makeEventSource();
|
|
28
|
+
let value = null;
|
|
29
|
+
let target = null;
|
|
30
|
+
const velocity = { x: 0, y: 0 };
|
|
31
|
+
const spring = {
|
|
32
|
+
active: false,
|
|
33
|
+
step(dt) {
|
|
34
|
+
if (value === null || target === null) {
|
|
35
|
+
spring.active = false;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const k = STIFFNESS;
|
|
39
|
+
const d = DAMPING;
|
|
40
|
+
const dx = value.x - target.x;
|
|
41
|
+
const dy = value.y - target.y;
|
|
42
|
+
velocity.x += (-k * dx - d * velocity.x) * dt;
|
|
43
|
+
velocity.y += (-k * dy - d * velocity.y) * dt;
|
|
44
|
+
const nx = value.x + velocity.x * dt;
|
|
45
|
+
const ny = value.y + velocity.y * dt;
|
|
46
|
+
if (nx !== value.x || ny !== value.y) {
|
|
47
|
+
value.x = nx;
|
|
48
|
+
value.y = ny;
|
|
49
|
+
updates.notify(value);
|
|
50
|
+
}
|
|
51
|
+
if (Math.abs(velocity.x) < EPSILON && Math.abs(velocity.y) < EPSILON && Math.abs(target.x - value.x) < EPSILON && Math.abs(target.y - value.y) < EPSILON) {
|
|
52
|
+
if (value.x !== target.x || value.y !== target.y) {
|
|
53
|
+
value.x = target.x;
|
|
54
|
+
value.y = target.y;
|
|
55
|
+
updates.notify(value);
|
|
56
|
+
}
|
|
57
|
+
velocity.x = 0;
|
|
58
|
+
velocity.y = 0;
|
|
59
|
+
spring.active = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
get() {
|
|
65
|
+
return value;
|
|
66
|
+
},
|
|
67
|
+
set(point) {
|
|
68
|
+
if (point === null) {
|
|
69
|
+
value = null;
|
|
70
|
+
target = null;
|
|
71
|
+
velocity.x = 0;
|
|
72
|
+
velocity.y = 0;
|
|
73
|
+
spring.active = false;
|
|
74
|
+
loop.remove(spring);
|
|
75
|
+
updates.notify(null);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (value === null) {
|
|
79
|
+
value = { x: point.x, y: point.y };
|
|
80
|
+
target = point;
|
|
81
|
+
velocity.x = 0;
|
|
82
|
+
velocity.y = 0;
|
|
83
|
+
updates.notify(value);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
target = point;
|
|
87
|
+
if (!spring.active && (value.x !== target.x || value.y !== target.y)) {
|
|
88
|
+
spring.active = true;
|
|
89
|
+
loop.add(spring);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
subscribe: updates.subscribe,
|
|
93
|
+
dispose() {
|
|
94
|
+
spring.active = false;
|
|
95
|
+
loop.remove(spring);
|
|
96
|
+
updates.dispose();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
31
99
|
}
|
|
32
100
|
function PresenceCursor({
|
|
33
101
|
connectionId,
|
|
34
102
|
presenceKey,
|
|
35
103
|
sizeRef,
|
|
36
|
-
sizeEvents
|
|
37
|
-
Cursor: Cursor$1
|
|
104
|
+
sizeEvents
|
|
38
105
|
}) {
|
|
39
106
|
const room = useRoom();
|
|
40
107
|
const cursorRef = useRef(null);
|
|
41
108
|
const userId = useOther(connectionId, (other) => $string(other.id));
|
|
109
|
+
const { user, isLoading } = useUser(userId ?? "");
|
|
110
|
+
const hasUserInfo = userId !== void 0 && !isLoading;
|
|
111
|
+
const color = $string(user?.color);
|
|
112
|
+
const name = $string(user?.name);
|
|
42
113
|
useLayoutEffect(() => {
|
|
43
|
-
const spring =
|
|
114
|
+
const spring = makeCoordinatesSpring();
|
|
44
115
|
function update() {
|
|
45
116
|
const element = cursorRef.current;
|
|
46
117
|
const coordinates = spring.get();
|
|
47
118
|
if (!element) {
|
|
48
119
|
return;
|
|
49
120
|
}
|
|
50
|
-
if (coordinates === null) {
|
|
121
|
+
if (!hasUserInfo || coordinates === null) {
|
|
51
122
|
element.style.transform = "translate3d(0, 0, 0)";
|
|
52
123
|
element.style.display = "none";
|
|
53
124
|
return;
|
|
@@ -71,21 +142,19 @@ function PresenceCursor({
|
|
|
71
142
|
unsubscribeSize();
|
|
72
143
|
unsubscribeOther();
|
|
73
144
|
};
|
|
74
|
-
}, [room, connectionId, presenceKey, sizeRef, sizeEvents]);
|
|
75
|
-
return /* @__PURE__ */ jsx(
|
|
145
|
+
}, [room, connectionId, presenceKey, sizeRef, sizeEvents, hasUserInfo]);
|
|
146
|
+
return /* @__PURE__ */ jsx(
|
|
147
|
+
Cursor,
|
|
148
|
+
{
|
|
149
|
+
color,
|
|
150
|
+
label: name,
|
|
151
|
+
ref: cursorRef,
|
|
152
|
+
style: { display: "none" }
|
|
153
|
+
}
|
|
154
|
+
);
|
|
76
155
|
}
|
|
77
156
|
const Cursors = forwardRef(
|
|
78
|
-
({
|
|
79
|
-
className,
|
|
80
|
-
children,
|
|
81
|
-
presenceKey = DEFAULT_PRESENCE_KEY,
|
|
82
|
-
components,
|
|
83
|
-
...props
|
|
84
|
-
}, forwardedRef) => {
|
|
85
|
-
const Cursor = useStableComponent(
|
|
86
|
-
components?.Cursor,
|
|
87
|
-
DefaultCursorWithUserInfo
|
|
88
|
-
);
|
|
157
|
+
({ className, children, presenceKey = DEFAULT_PRESENCE_KEY, ...props }, forwardedRef) => {
|
|
89
158
|
const containerRef = useRef(null);
|
|
90
159
|
const mergedRefs = useRefs(forwardedRef, containerRef);
|
|
91
160
|
const updateMyPresence = useUpdateMyPresence();
|
|
@@ -167,8 +236,7 @@ const Cursors = forwardRef(
|
|
|
167
236
|
connectionId,
|
|
168
237
|
presenceKey,
|
|
169
238
|
sizeRef,
|
|
170
|
-
sizeEvents
|
|
171
|
-
Cursor
|
|
239
|
+
sizeEvents
|
|
172
240
|
},
|
|
173
241
|
connectionId
|
|
174
242
|
)) }),
|