{"version":3,"file":"Composer.mjs","sources":["../../src/components/Composer.tsx"],"sourcesContent":["\"use client\";\n\nimport { type BaseMetadata, kInternal } from \"@liveblocks/core\";\nimport { useRoomContextBundle } 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, useMemo, useState } from \"react\";\n\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 { useComposer } from \"../primitives/Composer/contexts\";\nimport type {\n  ComposerEditorComponents,\n  ComposerEditorLinkProps,\n  ComposerEditorMentionProps,\n  ComposerEditorMentionSuggestionsProps,\n  ComposerEditorProps,\n  ComposerSubmitComment,\n} from \"../primitives/Composer/types\";\nimport { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport type { ThreadMetadata } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\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}\n\ninterface EmojiEditorActionProps extends EditorActionProps {\n  onPickerOpenChange?: EmojiPickerProps[\"onOpenChange\"];\n}\n\ntype ComposerCreateThreadProps<TThreadMetadata extends BaseMetadata> = {\n  threadId?: never;\n  commentId?: never;\n\n  /**\n   * The metadata of the thread to create.\n   */\n  metadata?: TThreadMetadata;\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<\n  TThreadMetadata extends BaseMetadata = ThreadMetadata,\n> = Omit<ComponentPropsWithoutRef<\"form\">, \"defaultValue\"> &\n  (\n    | ComposerCreateThreadProps<TThreadMetadata>\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     * 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 the composer is disabled.\n     */\n    disabled?: ComposerEditorProps[\"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\nfunction ComposerInsertMentionEditorAction({\n  label,\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={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  onPickerOpenChange,\n  className,\n  ...props\n}: EmojiEditorActionProps) {\n  const { insertText } = useComposer();\n\n  const preventDefault = useCallback((event: SyntheticEvent) => {\n    event.preventDefault();\n  }, []);\n\n  return (\n    <EmojiPicker onEmojiSelect={insertText} onOpenChange={onPickerOpenChange}>\n      <Tooltip content={label}>\n        <EmojiPickerTrigger asChild>\n          <Button\n            className={classNames(\"lb-composer-editor-action\", className)}\n            onMouseDown={preventDefault}\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 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\nconst editorComponents: ComposerEditorComponents = {\n  Mention: ComposerMention,\n  MentionSuggestions: ComposerMentionSuggestions,\n  Link: ComposerLink,\n};\n\nconst ComposerWithContext = forwardRef<\n  HTMLFormElement,\n  Omit<ComposerProps, \"threadId\" | \"commentId\" | \"onComposerSubmit\">\n>(\n  (\n    {\n      defaultValue,\n      disabled,\n      autoFocus,\n      collapsed: controlledCollapsed,\n      defaultCollapsed,\n      onCollapsedChange: controlledOnCollapsedChange,\n      actions,\n      overrides,\n      showAttribution,\n      onFocus,\n      onBlur,\n      className,\n      ...props\n    },\n    forwardedRef\n  ) => {\n    const {\n      [kInternal]: { hasResolveMentionSuggestions },\n      useSelf,\n    } = useRoomContextBundle();\n    const self = useSelf();\n    const isDisabled = useMemo(\n      () => disabled || !self?.canComment,\n      [disabled, self?.canComment]\n    );\n    const { isEmpty } = useComposer();\n    const $ = useOverrides(overrides);\n    const [isEmojiPickerOpen, setEmojiPickerOpen] = useState(false);\n    const [collapsed, 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 preventDefault = useCallback((event: SyntheticEvent) => {\n      event.preventDefault();\n    }, []);\n\n    const stopPropagation = useCallback((event: SyntheticEvent) => {\n      event.stopPropagation();\n    }, []);\n\n    const handleEditorClick = useCallback(\n      (event: MouseEvent<HTMLDivElement>) => {\n        event.stopPropagation();\n\n        if (isEmpty) {\n          onCollapsedChange?.(false);\n        }\n      },\n      [isEmpty, onCollapsedChange]\n    );\n\n    const handleFocus = useCallback(\n      (event: FocusEvent<HTMLFormElement>) => {\n        onFocus?.(event);\n\n        if (event.isDefaultPrevented()) {\n          return;\n        }\n\n        if (isEmpty) {\n          onCollapsedChange?.(false);\n        }\n      },\n      [isEmpty, 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(event.relatedTarget);\n\n        if (isOutside && isEmpty && !isEmojiPickerOpen) {\n          onCollapsedChange?.(true);\n        }\n      },\n      [isEmojiPickerOpen, isEmpty, onBlur, onCollapsedChange]\n    );\n\n    return (\n      <form\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={collapsed ? \"\" : undefined}\n        onFocus={handleFocus}\n        onBlur={handleBlur}\n      >\n        <ComposerPrimitive.Editor\n          className=\"lb-composer-editor\"\n          onClick={handleEditorClick}\n          placeholder={$.COMPOSER_PLACEHOLDER}\n          defaultValue={defaultValue}\n          disabled={isDisabled}\n          autoFocus={autoFocus}\n          components={editorComponents}\n          dir={$.dir}\n        />\n        {!collapsed && (\n          <div className=\"lb-composer-footer\">\n            <div className=\"lb-composer-editor-actions\">\n              {hasResolveMentionSuggestions && (\n                <ComposerInsertMentionEditorAction\n                  label={$.COMPOSER_INSERT_MENTION}\n                  disabled={isDisabled}\n                />\n              )}\n              <ComposerInsertEmojiEditorAction\n                label={$.COMPOSER_INSERT_EMOJI}\n                onPickerOpenChange={setEmojiPickerOpen}\n                disabled={isDisabled}\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 disabled={isDisabled} 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      </form>\n    );\n  }\n);\n\n/**\n * Displays a composer to create comments.\n *\n * @example\n * <Composer />\n */\nexport const Composer = forwardRef(\n  <TThreadMetadata extends BaseMetadata = ThreadMetadata>(\n    {\n      threadId,\n      commentId,\n      metadata,\n      onComposerSubmit,\n      ...props\n    }: ComposerProps<TThreadMetadata>,\n    forwardedRef: ForwardedRef<HTMLFormElement>\n  ) => {\n    const { useCreateThread, useCreateComment, useEditComment } =\n      useRoomContextBundle();\n    const createThread = useCreateThread();\n    const createComment = useCreateComment();\n    const editComment = useEditComment();\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          });\n        } else if (threadId) {\n          createComment({\n            threadId,\n            body: comment.body,\n          });\n        } else {\n          createThread({\n            body: comment.body,\n            metadata: metadata ?? {},\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 onComposerSubmit={handleCommentSubmit} asChild>\n          <ComposerWithContext {...props} ref={forwardedRef} />\n        </ComposerPrimitive.Form>\n      </TooltipProvider>\n    );\n  }\n) as <TThreadMetadata extends BaseMetadata = ThreadMetadata>(\n  props: ComposerProps<TThreadMetadata> & RefAttributes<HTMLFormElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsJA;AAA2C;AACzC;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AAAoB;AAEhB;AAEA;AACE;AACA;AAAc;AAChB;AACF;AACuB;AAGzB;AACG;AAAiB;AACf;AAC6D;AAC/C;AACJ;AACG;AACR;AAEH;AAAsB;AAI/B;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACG;AAA2B;AAA0B;AACnD;AAAiB;AACf;AAA0B;AACxB;AAC6D;AAC/C;AACD;AACR;AAEH;AAAoB;AAMjC;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;AAEA;AAAmD;AACxC;AACW;AAEtB;AAEA;AAA4B;AAKxB;AACE;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AAAM;AACwC;AAC5C;AAEF;AACA;AAAmB;AACQ;AACE;AAE7B;AACA;AACA;AACA;AAAuC;AAIjC;AACJ;AACA;AAGF;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AAA0B;AAEtB;AAEA;AACE;AAAyB;AAC3B;AACF;AAC2B;AAG7B;AAAoB;AAEhB;AAEA;AACE;AAAA;AAGF;AACE;AAAyB;AAC3B;AACF;AACoC;AAGtC;AAAmB;AAEf;AAEA;AACE;AAAA;AAGF;AAEA;AACE;AAAwB;AAC1B;AACF;AACsD;AAGxD;AACG;AACY;AACT;AACA;AACF;AACO;AACH;AACC;AAC4B;AACxB;AACD;AAEP;AACW;AACD;AACM;AACf;AACU;AACV;AACY;AACL;AAGN;AAAc;AACZ;AAAc;AAEV;AACU;AACC;AAGb;AACU;AACW;AACV;AAIb;AAAc;AAGR;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAmC;AAAmB;AACpD;AACc;AACJ;AACC;AACF;AACM;AAWhC;AAGN;AAQO;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACG;AAIL;AAEA;AACA;AACA;AAEA;AAA4B;AAExB;AAEA;AACE;AAAA;AAGF;AACE;AAAY;AACV;AACA;AACc;AACf;AAED;AAAc;AACZ;AACc;AACf;AAED;AAAa;AACG;AACS;AACxB;AACH;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AAEK;AAAyC;AAA4B;AACnE;AAAwB;AAAY;AAEzC;AAGN;;"}