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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/components/Comment.js +26 -18
  2. package/dist/components/Comment.js.map +1 -1
  3. package/dist/components/Comment.mjs +27 -19
  4. package/dist/components/Comment.mjs.map +1 -1
  5. package/dist/components/Composer.js +41 -10
  6. package/dist/components/Composer.js.map +1 -1
  7. package/dist/components/Composer.mjs +45 -14
  8. package/dist/components/Composer.mjs.map +1 -1
  9. package/dist/components/Thread.js +5 -3
  10. package/dist/components/Thread.js.map +1 -1
  11. package/dist/components/Thread.mjs +6 -4
  12. package/dist/components/Thread.mjs.map +1 -1
  13. package/dist/components/internal/Attachment.js +32 -14
  14. package/dist/components/internal/Attachment.js.map +1 -1
  15. package/dist/components/internal/Attachment.mjs +32 -14
  16. package/dist/components/internal/Attachment.mjs.map +1 -1
  17. package/dist/components/internal/InboxNotificationThread.js +2 -1
  18. package/dist/components/internal/InboxNotificationThread.js.map +1 -1
  19. package/dist/components/internal/InboxNotificationThread.mjs +2 -1
  20. package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
  21. package/dist/primitives/Composer/contexts.js.map +1 -1
  22. package/dist/primitives/Composer/contexts.mjs.map +1 -1
  23. package/dist/primitives/Composer/index.js +31 -12
  24. package/dist/primitives/Composer/index.js.map +1 -1
  25. package/dist/primitives/Composer/index.mjs +32 -13
  26. package/dist/primitives/Composer/index.mjs.map +1 -1
  27. package/dist/primitives/Composer/utils.js +6 -4
  28. package/dist/primitives/Composer/utils.js.map +1 -1
  29. package/dist/primitives/Composer/utils.mjs +8 -6
  30. package/dist/primitives/Composer/utils.mjs.map +1 -1
  31. package/dist/shared.js +3 -19
  32. package/dist/shared.js.map +1 -1
  33. package/dist/shared.mjs +5 -21
  34. package/dist/shared.mjs.map +1 -1
  35. package/dist/version.js +1 -1
  36. package/dist/version.mjs +1 -1
  37. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"contexts.js","sources":["../../../src/primitives/Composer/contexts.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport type { CommentMixedAttachment } from \"@liveblocks/core\";\nimport { nn } from \"@liveblocks/core\";\nimport type { DropdownMenuProps } from \"@radix-ui/react-dropdown-menu\";\nimport type { Dispatch, Ref, SetStateAction } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { Editor as SlateEditor, Element as SlateElement } from \"slate\";\n\nexport type ComposerContext = {\n /**\n * Whether the composer is currently disabled.\n */\n isDisabled: boolean;\n\n /**\n * Whether the composer can currently be submitted.\n */\n canSubmit: boolean;\n\n /**\n * Whether the editor is currently focused.\n */\n isFocused: boolean;\n\n /**\n * Whether the editor is currently empty.\n */\n isEmpty: boolean;\n\n /**\n * Submit the composer programmatically.\n */\n submit: () => void;\n\n /**\n * Clear the composer programmatically.\n */\n clear: () => void;\n\n /**\n * Select the editor programmatically.\n */\n select: () => void;\n\n /**\n * Focus the editor programmatically.\n */\n focus: () => void;\n\n /**\n * Blur the editor programmatically.\n */\n blur: () => void;\n\n /**\n * Start creating a mention at the current selection.\n */\n createMention: () => void;\n\n /**\n * Insert text at the current selection.\n */\n insertText: (text: string) => void;\n\n /**\n * Open a file picker programmatically to create attachments.\n */\n attachFiles: () => void;\n\n /**\n * The composer's current attachments.\n */\n attachments: CommentMixedAttachment[];\n\n /**\n * Remove an attachment by its ID.\n */\n removeAttachment: (attachmentId: string) => void;\n};\n\nexport type ComposerEditorContext = {\n validate: (value: SlateElement[]) => void;\n editor: SlateEditor;\n setFocused: Dispatch<SetStateAction<boolean>>;\n};\n\nexport type ComposerAttachmentsContext = {\n hasMaxAttachments: boolean;\n createAttachments: (files: File[]) => void;\n isUploadingAttachments: boolean;\n maxAttachments: number;\n maxAttachmentSize: number;\n};\n\nexport type ComposerSuggestionsContext = {\n dir?: DropdownMenuProps[\"dir\"];\n id: string;\n itemId: (value?: string) => string | undefined;\n placement: Placement;\n selectedValue?: string;\n setSelectedValue: (value: string) => void;\n onItemSelect: (value: string) => void;\n ref: Ref<HTMLDivElement>;\n};\n\nexport const ComposerContext = createContext<ComposerContext | null>(null);\nexport const ComposerEditorContext =\n createContext<ComposerEditorContext | null>(null);\nexport const ComposerAttachmentsContext =\n createContext<ComposerAttachmentsContext | null>(null);\nexport const ComposerSuggestionsContext =\n createContext<ComposerSuggestionsContext | null>(null);\n\nexport function useComposerEditorContext() {\n const composerEditorContext = useContext(ComposerEditorContext);\n\n return nn(\n composerEditorContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerAttachmentsContextOrNull() {\n return useContext(ComposerAttachmentsContext);\n}\n\nexport function useComposerAttachmentsContext() {\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n\n return nn(\n composerAttachmentsContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerSuggestionsContext(\n source = \"useComposerSuggestionsContext\"\n) {\n const composerSuggestionsContext = useContext(ComposerSuggestionsContext);\n\n return nn(\n composerSuggestionsContext,\n `${source} can’t be used outside of Composer.Editor.`\n );\n}\n\nexport function useComposer(): ComposerContext {\n const composerContext = useContext(ComposerContext);\n\n return nn(composerContext, \"Composer.Form is missing from the React tree.\");\n}\n"],"names":["createContext","useContext","nn"],"mappings":";;;;;AAyGa,MAAA,eAAA,GAAkBA,oBAAsC,IAAI,EAAA;AAC5D,MAAA,qBAAA,GACXA,oBAA4C,IAAI,EAAA;AACrC,MAAA,0BAAA,GACXA,oBAAiD,IAAI,EAAA;AAC1C,MAAA,0BAAA,GACXA,oBAAiD,IAAI,EAAA;AAEhD,SAAS,wBAA2B,GAAA;AACzC,EAAM,MAAA,qBAAA,GAAwBC,iBAAW,qBAAqB,CAAA,CAAA;AAE9D,EAAO,OAAAC,OAAA;AAAA,IACL,qBAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEO,SAAS,mCAAsC,GAAA;AACpD,EAAA,OAAOD,iBAAW,0BAA0B,CAAA,CAAA;AAC9C,CAAA;AAEO,SAAS,6BAAgC,GAAA;AAC9C,EAAA,MAAM,6BAA6B,mCAAoC,EAAA,CAAA;AAEvE,EAAO,OAAAC,OAAA;AAAA,IACL,0BAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEgB,SAAA,6BAAA,CACd,SAAS,+BACT,EAAA;AACA,EAAM,MAAA,0BAAA,GAA6BD,iBAAW,0BAA0B,CAAA,CAAA;AAExE,EAAO,OAAAC,OAAA;AAAA,IACL,0BAAA;AAAA,IACA,CAAG,EAAA,MAAA,CAAA,+CAAA,CAAA;AAAA,GACL,CAAA;AACF,CAAA;AAEO,SAAS,WAA+B,GAAA;AAC7C,EAAM,MAAA,eAAA,GAAkBD,iBAAW,eAAe,CAAA,CAAA;AAElD,EAAO,OAAAC,OAAA,CAAG,iBAAiB,+CAA+C,CAAA,CAAA;AAC5E;;;;;;;;;;;;"}
1
+ {"version":3,"file":"contexts.js","sources":["../../../src/primitives/Composer/contexts.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport type { CommentMixedAttachment } from \"@liveblocks/core\";\nimport { nn } from \"@liveblocks/core\";\nimport type { DropdownMenuProps } from \"@radix-ui/react-dropdown-menu\";\nimport type { Dispatch, Ref, SetStateAction } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { Editor as SlateEditor, Element as SlateElement } from \"slate\";\n\nexport type ComposerContext = {\n /**\n * Whether the composer is currently disabled.\n */\n isDisabled: boolean;\n\n /**\n * Whether the composer can currently be submitted.\n */\n canSubmit: boolean;\n\n /**\n * Whether the editor is currently focused.\n */\n isFocused: boolean;\n\n /**\n * Whether the editor is currently empty.\n */\n isEmpty: boolean;\n\n /**\n * Submit the composer programmatically.\n */\n submit: () => void;\n\n /**\n * Clear the composer programmatically.\n */\n clear: () => void;\n\n /**\n * Select the editor programmatically.\n */\n select: () => void;\n\n /**\n * Focus the editor programmatically.\n */\n focus: () => void;\n\n /**\n * Blur the editor programmatically.\n */\n blur: () => void;\n\n /**\n * Start creating a mention at the current selection.\n */\n createMention: () => void;\n\n /**\n * Insert text at the current selection.\n */\n insertText: (text: string) => void;\n\n /**\n * Open a file picker programmatically to create attachments.\n */\n attachFiles: () => void;\n\n /**\n * The composer's current attachments.\n */\n attachments: CommentMixedAttachment[];\n\n /**\n * Remove an attachment by its ID.\n */\n removeAttachment: (attachmentId: string) => void;\n};\n\nexport type ComposerEditorContext = {\n validate: (value: SlateElement[]) => void;\n editor: SlateEditor;\n setFocused: Dispatch<SetStateAction<boolean>>;\n roomId: string;\n};\n\nexport type ComposerAttachmentsContext = {\n hasMaxAttachments: boolean;\n createAttachments: (files: File[]) => void;\n isUploadingAttachments: boolean;\n maxAttachments: number;\n maxAttachmentSize: number;\n};\n\nexport type ComposerSuggestionsContext = {\n dir?: DropdownMenuProps[\"dir\"];\n id: string;\n itemId: (value?: string) => string | undefined;\n placement: Placement;\n selectedValue?: string;\n setSelectedValue: (value: string) => void;\n onItemSelect: (value: string) => void;\n ref: Ref<HTMLDivElement>;\n};\n\nexport const ComposerContext = createContext<ComposerContext | null>(null);\nexport const ComposerEditorContext =\n createContext<ComposerEditorContext | null>(null);\nexport const ComposerAttachmentsContext =\n createContext<ComposerAttachmentsContext | null>(null);\nexport const ComposerSuggestionsContext =\n createContext<ComposerSuggestionsContext | null>(null);\n\nexport function useComposerEditorContext() {\n const composerEditorContext = useContext(ComposerEditorContext);\n\n return nn(\n composerEditorContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerAttachmentsContextOrNull() {\n return useContext(ComposerAttachmentsContext);\n}\n\nexport function useComposerAttachmentsContext() {\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n\n return nn(\n composerAttachmentsContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerSuggestionsContext(\n source = \"useComposerSuggestionsContext\"\n) {\n const composerSuggestionsContext = useContext(ComposerSuggestionsContext);\n\n return nn(\n composerSuggestionsContext,\n `${source} can’t be used outside of Composer.Editor.`\n );\n}\n\nexport function useComposer(): ComposerContext {\n const composerContext = useContext(ComposerContext);\n\n return nn(composerContext, \"Composer.Form is missing from the React tree.\");\n}\n"],"names":["createContext","useContext","nn"],"mappings":";;;;;AA0Ga,MAAA,eAAA,GAAkBA,oBAAsC,IAAI,EAAA;AAC5D,MAAA,qBAAA,GACXA,oBAA4C,IAAI,EAAA;AACrC,MAAA,0BAAA,GACXA,oBAAiD,IAAI,EAAA;AAC1C,MAAA,0BAAA,GACXA,oBAAiD,IAAI,EAAA;AAEhD,SAAS,wBAA2B,GAAA;AACzC,EAAM,MAAA,qBAAA,GAAwBC,iBAAW,qBAAqB,CAAA,CAAA;AAE9D,EAAO,OAAAC,OAAA;AAAA,IACL,qBAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEO,SAAS,mCAAsC,GAAA;AACpD,EAAA,OAAOD,iBAAW,0BAA0B,CAAA,CAAA;AAC9C,CAAA;AAEO,SAAS,6BAAgC,GAAA;AAC9C,EAAA,MAAM,6BAA6B,mCAAoC,EAAA,CAAA;AAEvE,EAAO,OAAAC,OAAA;AAAA,IACL,0BAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEgB,SAAA,6BAAA,CACd,SAAS,+BACT,EAAA;AACA,EAAM,MAAA,0BAAA,GAA6BD,iBAAW,0BAA0B,CAAA,CAAA;AAExE,EAAO,OAAAC,OAAA;AAAA,IACL,0BAAA;AAAA,IACA,CAAG,EAAA,MAAA,CAAA,+CAAA,CAAA;AAAA,GACL,CAAA;AACF,CAAA;AAEO,SAAS,WAA+B,GAAA;AAC7C,EAAM,MAAA,eAAA,GAAkBD,iBAAW,eAAe,CAAA,CAAA;AAElD,EAAO,OAAAC,OAAA,CAAG,iBAAiB,+CAA+C,CAAA,CAAA;AAC5E;;;;;;;;;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"contexts.mjs","sources":["../../../src/primitives/Composer/contexts.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport type { CommentMixedAttachment } from \"@liveblocks/core\";\nimport { nn } from \"@liveblocks/core\";\nimport type { DropdownMenuProps } from \"@radix-ui/react-dropdown-menu\";\nimport type { Dispatch, Ref, SetStateAction } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { Editor as SlateEditor, Element as SlateElement } from \"slate\";\n\nexport type ComposerContext = {\n /**\n * Whether the composer is currently disabled.\n */\n isDisabled: boolean;\n\n /**\n * Whether the composer can currently be submitted.\n */\n canSubmit: boolean;\n\n /**\n * Whether the editor is currently focused.\n */\n isFocused: boolean;\n\n /**\n * Whether the editor is currently empty.\n */\n isEmpty: boolean;\n\n /**\n * Submit the composer programmatically.\n */\n submit: () => void;\n\n /**\n * Clear the composer programmatically.\n */\n clear: () => void;\n\n /**\n * Select the editor programmatically.\n */\n select: () => void;\n\n /**\n * Focus the editor programmatically.\n */\n focus: () => void;\n\n /**\n * Blur the editor programmatically.\n */\n blur: () => void;\n\n /**\n * Start creating a mention at the current selection.\n */\n createMention: () => void;\n\n /**\n * Insert text at the current selection.\n */\n insertText: (text: string) => void;\n\n /**\n * Open a file picker programmatically to create attachments.\n */\n attachFiles: () => void;\n\n /**\n * The composer's current attachments.\n */\n attachments: CommentMixedAttachment[];\n\n /**\n * Remove an attachment by its ID.\n */\n removeAttachment: (attachmentId: string) => void;\n};\n\nexport type ComposerEditorContext = {\n validate: (value: SlateElement[]) => void;\n editor: SlateEditor;\n setFocused: Dispatch<SetStateAction<boolean>>;\n};\n\nexport type ComposerAttachmentsContext = {\n hasMaxAttachments: boolean;\n createAttachments: (files: File[]) => void;\n isUploadingAttachments: boolean;\n maxAttachments: number;\n maxAttachmentSize: number;\n};\n\nexport type ComposerSuggestionsContext = {\n dir?: DropdownMenuProps[\"dir\"];\n id: string;\n itemId: (value?: string) => string | undefined;\n placement: Placement;\n selectedValue?: string;\n setSelectedValue: (value: string) => void;\n onItemSelect: (value: string) => void;\n ref: Ref<HTMLDivElement>;\n};\n\nexport const ComposerContext = createContext<ComposerContext | null>(null);\nexport const ComposerEditorContext =\n createContext<ComposerEditorContext | null>(null);\nexport const ComposerAttachmentsContext =\n createContext<ComposerAttachmentsContext | null>(null);\nexport const ComposerSuggestionsContext =\n createContext<ComposerSuggestionsContext | null>(null);\n\nexport function useComposerEditorContext() {\n const composerEditorContext = useContext(ComposerEditorContext);\n\n return nn(\n composerEditorContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerAttachmentsContextOrNull() {\n return useContext(ComposerAttachmentsContext);\n}\n\nexport function useComposerAttachmentsContext() {\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n\n return nn(\n composerAttachmentsContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerSuggestionsContext(\n source = \"useComposerSuggestionsContext\"\n) {\n const composerSuggestionsContext = useContext(ComposerSuggestionsContext);\n\n return nn(\n composerSuggestionsContext,\n `${source} can’t be used outside of Composer.Editor.`\n );\n}\n\nexport function useComposer(): ComposerContext {\n const composerContext = useContext(ComposerContext);\n\n return nn(composerContext, \"Composer.Form is missing from the React tree.\");\n}\n"],"names":[],"mappings":";;;AAyGa,MAAA,eAAA,GAAkB,cAAsC,IAAI,EAAA;AAC5D,MAAA,qBAAA,GACX,cAA4C,IAAI,EAAA;AACrC,MAAA,0BAAA,GACX,cAAiD,IAAI,EAAA;AAC1C,MAAA,0BAAA,GACX,cAAiD,IAAI,EAAA;AAEhD,SAAS,wBAA2B,GAAA;AACzC,EAAM,MAAA,qBAAA,GAAwB,WAAW,qBAAqB,CAAA,CAAA;AAE9D,EAAO,OAAA,EAAA;AAAA,IACL,qBAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEO,SAAS,mCAAsC,GAAA;AACpD,EAAA,OAAO,WAAW,0BAA0B,CAAA,CAAA;AAC9C,CAAA;AAEO,SAAS,6BAAgC,GAAA;AAC9C,EAAA,MAAM,6BAA6B,mCAAoC,EAAA,CAAA;AAEvE,EAAO,OAAA,EAAA;AAAA,IACL,0BAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEgB,SAAA,6BAAA,CACd,SAAS,+BACT,EAAA;AACA,EAAM,MAAA,0BAAA,GAA6B,WAAW,0BAA0B,CAAA,CAAA;AAExE,EAAO,OAAA,EAAA;AAAA,IACL,0BAAA;AAAA,IACA,CAAG,EAAA,MAAA,CAAA,+CAAA,CAAA;AAAA,GACL,CAAA;AACF,CAAA;AAEO,SAAS,WAA+B,GAAA;AAC7C,EAAM,MAAA,eAAA,GAAkB,WAAW,eAAe,CAAA,CAAA;AAElD,EAAO,OAAA,EAAA,CAAG,iBAAiB,+CAA+C,CAAA,CAAA;AAC5E;;;;"}
1
+ {"version":3,"file":"contexts.mjs","sources":["../../../src/primitives/Composer/contexts.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport type { CommentMixedAttachment } from \"@liveblocks/core\";\nimport { nn } from \"@liveblocks/core\";\nimport type { DropdownMenuProps } from \"@radix-ui/react-dropdown-menu\";\nimport type { Dispatch, Ref, SetStateAction } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { Editor as SlateEditor, Element as SlateElement } from \"slate\";\n\nexport type ComposerContext = {\n /**\n * Whether the composer is currently disabled.\n */\n isDisabled: boolean;\n\n /**\n * Whether the composer can currently be submitted.\n */\n canSubmit: boolean;\n\n /**\n * Whether the editor is currently focused.\n */\n isFocused: boolean;\n\n /**\n * Whether the editor is currently empty.\n */\n isEmpty: boolean;\n\n /**\n * Submit the composer programmatically.\n */\n submit: () => void;\n\n /**\n * Clear the composer programmatically.\n */\n clear: () => void;\n\n /**\n * Select the editor programmatically.\n */\n select: () => void;\n\n /**\n * Focus the editor programmatically.\n */\n focus: () => void;\n\n /**\n * Blur the editor programmatically.\n */\n blur: () => void;\n\n /**\n * Start creating a mention at the current selection.\n */\n createMention: () => void;\n\n /**\n * Insert text at the current selection.\n */\n insertText: (text: string) => void;\n\n /**\n * Open a file picker programmatically to create attachments.\n */\n attachFiles: () => void;\n\n /**\n * The composer's current attachments.\n */\n attachments: CommentMixedAttachment[];\n\n /**\n * Remove an attachment by its ID.\n */\n removeAttachment: (attachmentId: string) => void;\n};\n\nexport type ComposerEditorContext = {\n validate: (value: SlateElement[]) => void;\n editor: SlateEditor;\n setFocused: Dispatch<SetStateAction<boolean>>;\n roomId: string;\n};\n\nexport type ComposerAttachmentsContext = {\n hasMaxAttachments: boolean;\n createAttachments: (files: File[]) => void;\n isUploadingAttachments: boolean;\n maxAttachments: number;\n maxAttachmentSize: number;\n};\n\nexport type ComposerSuggestionsContext = {\n dir?: DropdownMenuProps[\"dir\"];\n id: string;\n itemId: (value?: string) => string | undefined;\n placement: Placement;\n selectedValue?: string;\n setSelectedValue: (value: string) => void;\n onItemSelect: (value: string) => void;\n ref: Ref<HTMLDivElement>;\n};\n\nexport const ComposerContext = createContext<ComposerContext | null>(null);\nexport const ComposerEditorContext =\n createContext<ComposerEditorContext | null>(null);\nexport const ComposerAttachmentsContext =\n createContext<ComposerAttachmentsContext | null>(null);\nexport const ComposerSuggestionsContext =\n createContext<ComposerSuggestionsContext | null>(null);\n\nexport function useComposerEditorContext() {\n const composerEditorContext = useContext(ComposerEditorContext);\n\n return nn(\n composerEditorContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerAttachmentsContextOrNull() {\n return useContext(ComposerAttachmentsContext);\n}\n\nexport function useComposerAttachmentsContext() {\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n\n return nn(\n composerAttachmentsContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerSuggestionsContext(\n source = \"useComposerSuggestionsContext\"\n) {\n const composerSuggestionsContext = useContext(ComposerSuggestionsContext);\n\n return nn(\n composerSuggestionsContext,\n `${source} can’t be used outside of Composer.Editor.`\n );\n}\n\nexport function useComposer(): ComposerContext {\n const composerContext = useContext(ComposerContext);\n\n return nn(composerContext, \"Composer.Form is missing from the React tree.\");\n}\n"],"names":[],"mappings":";;;AA0Ga,MAAA,eAAA,GAAkB,cAAsC,IAAI,EAAA;AAC5D,MAAA,qBAAA,GACX,cAA4C,IAAI,EAAA;AACrC,MAAA,0BAAA,GACX,cAAiD,IAAI,EAAA;AAC1C,MAAA,0BAAA,GACX,cAAiD,IAAI,EAAA;AAEhD,SAAS,wBAA2B,GAAA;AACzC,EAAM,MAAA,qBAAA,GAAwB,WAAW,qBAAqB,CAAA,CAAA;AAE9D,EAAO,OAAA,EAAA;AAAA,IACL,qBAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEO,SAAS,mCAAsC,GAAA;AACpD,EAAA,OAAO,WAAW,0BAA0B,CAAA,CAAA;AAC9C,CAAA;AAEO,SAAS,6BAAgC,GAAA;AAC9C,EAAA,MAAM,6BAA6B,mCAAoC,EAAA,CAAA;AAEvE,EAAO,OAAA,EAAA;AAAA,IACL,0BAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEgB,SAAA,6BAAA,CACd,SAAS,+BACT,EAAA;AACA,EAAM,MAAA,0BAAA,GAA6B,WAAW,0BAA0B,CAAA,CAAA;AAExE,EAAO,OAAA,EAAA;AAAA,IACL,0BAAA;AAAA,IACA,CAAG,EAAA,MAAA,CAAA,+CAAA,CAAA;AAAA,GACL,CAAA;AACF,CAAA;AAEO,SAAS,WAA+B,GAAA;AAC7C,EAAM,MAAA,eAAA,GAAkB,WAAW,eAAe,CAAA,CAAA;AAElD,EAAO,OAAA,EAAA,CAAG,iBAAiB,+CAA+C,CAAA,CAAA;AAC5E;;;;"}
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var reactDom = require('@floating-ui/react-dom');
5
- var react = require('@liveblocks/react');
5
+ var core = require('@liveblocks/core');
6
6
  var _private = require('@liveblocks/react/_private');
7
7
  var reactSlot = require('@radix-ui/react-slot');
8
8
  var React = require('react');
@@ -418,7 +418,7 @@ const ComposerEditor = React.forwardRef(
418
418
  dir,
419
419
  ...props
420
420
  }, forwardedRef) => {
421
- const { editor, validate, setFocused } = contexts.useComposerEditorContext();
421
+ const { editor, validate, setFocused, roomId } = contexts.useComposerEditorContext();
422
422
  const {
423
423
  submit,
424
424
  focus,
@@ -437,7 +437,10 @@ const ComposerEditor = React.forwardRef(
437
437
  [components]
438
438
  );
439
439
  const [mentionDraft, setMentionDraft] = React.useState();
440
- const mentionSuggestions = _private.useMentionSuggestions(mentionDraft?.text);
440
+ const mentionSuggestions = _private.useMentionSuggestions(
441
+ roomId,
442
+ mentionDraft?.text
443
+ );
441
444
  const [
442
445
  selectedMentionSuggestionIndex,
443
446
  setPreviousSelectedMentionSuggestionIndex,
@@ -657,6 +660,17 @@ const ComposerEditor = React.forwardRef(
657
660
  );
658
661
  const MAX_ATTACHMENTS = 10;
659
662
  const MAX_ATTACHMENT_SIZE = 1024 * 1024 * 1024;
663
+ function prepareAttachment(file) {
664
+ return {
665
+ type: "localAttachment",
666
+ status: "idle",
667
+ id: core.createCommentAttachmentId(),
668
+ name: file.name,
669
+ size: file.size,
670
+ mimeType: file.type,
671
+ file
672
+ };
673
+ }
660
674
  const ComposerForm = React.forwardRef(
661
675
  ({
662
676
  children,
@@ -667,13 +681,18 @@ const ComposerForm = React.forwardRef(
667
681
  preventUnsavedChanges = true,
668
682
  disabled,
669
683
  asChild,
684
+ roomId: _roomId,
670
685
  ...props
671
686
  }, forwardedRef) => {
672
687
  const Component = asChild ? reactSlot.Slot : "form";
673
- const room = react.useRoom();
674
688
  const [isEmpty$1, setEmpty] = React.useState(true);
675
689
  const [isSubmitting, setSubmitting] = React.useState(false);
676
690
  const [isFocused, setFocused] = React.useState(false);
691
+ const room = _private.useRoomOrNull();
692
+ const roomId = _roomId !== void 0 ? _roomId : room?.id;
693
+ if (roomId === void 0) {
694
+ throw new Error("Composer.Form must be a descendant of RoomProvider.");
695
+ }
677
696
  const maxAttachments = MAX_ATTACHMENTS;
678
697
  const maxAttachmentSize = MAX_ATTACHMENT_SIZE;
679
698
  const {
@@ -683,15 +702,14 @@ const ComposerForm = React.forwardRef(
683
702
  removeAttachment,
684
703
  clearAttachments
685
704
  } = utils.useComposerAttachmentsManager(defaultAttachments, {
686
- maxFileSize: maxAttachmentSize
705
+ maxFileSize: maxAttachmentSize,
706
+ roomId
687
707
  });
688
708
  const numberOfAttachments = attachments.length;
689
709
  const hasMaxAttachments = numberOfAttachments >= maxAttachments;
690
710
  const isDisabled = React.useMemo(() => {
691
- const self = room.getSelf();
692
- const canComment = self?.canComment ?? true;
693
- return isSubmitting || disabled || !canComment;
694
- }, [isSubmitting, disabled, room]);
711
+ return isSubmitting || disabled === true;
712
+ }, [isSubmitting, disabled]);
695
713
  const canSubmit = React.useMemo(() => {
696
714
  return !isEmpty$1 && !isUploadingAttachments;
697
715
  }, [isEmpty$1, isUploadingAttachments]);
@@ -715,10 +733,10 @@ const ComposerForm = React.forwardRef(
715
733
  maxAttachments - numberOfAttachments
716
734
  );
717
735
  files.splice(numberOfAcceptedFiles);
718
- const attachments2 = files.map((file) => room.prepareAttachment(file));
736
+ const attachments2 = files.map((file) => prepareAttachment(file));
719
737
  addAttachments(attachments2);
720
738
  },
721
- [addAttachments, maxAttachments, numberOfAttachments, room]
739
+ [addAttachments, maxAttachments, numberOfAttachments]
722
740
  );
723
741
  const createAttachmentsRef = React.useRef(createAttachments);
724
742
  React.useEffect(() => {
@@ -871,7 +889,8 @@ const ComposerForm = React.forwardRef(
871
889
  value: {
872
890
  editor,
873
891
  validate,
874
- setFocused
892
+ setFocused,
893
+ roomId
875
894
  }
876
895
  }, /* @__PURE__ */ React.createElement(contexts.ComposerAttachmentsContext.Provider, {
877
896
  value: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/primitives/Composer/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n DetectOverflowOptions,\n UseFloatingOptions,\n} from \"@floating-ui/react-dom\";\nimport {\n autoUpdate,\n flip,\n hide,\n limitShift,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport type { CommentAttachment, CommentBody } from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport {\n useMentionSuggestions,\n useSyncSource,\n} from \"@liveblocks/react/_private\";\nimport { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport type {\n AriaAttributes,\n ChangeEvent,\n FocusEvent,\n FormEvent,\n KeyboardEvent,\n MouseEvent,\n PointerEvent,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Descendant as SlateDescendant,\n Element as SlateElement,\n} from \"slate\";\nimport {\n createEditor,\n Editor as SlateEditor,\n insertText as insertSlateText,\n Transforms as SlateTransforms,\n} from \"slate\";\nimport { withHistory } from \"slate-history\";\nimport type {\n RenderElementProps,\n RenderElementSpecificProps,\n RenderLeafProps,\n RenderPlaceholderProps,\n} from \"slate-react\";\nimport {\n Editable,\n ReactEditor,\n Slate,\n useSelected,\n useSlateStatic,\n withReact,\n} from \"slate-react\";\n\nimport { useLiveblocksUIConfig } from \"../../config\";\nimport { FLOATING_ELEMENT_COLLISION_PADDING } from \"../../constants\";\nimport { withAutoFormatting } from \"../../slate/plugins/auto-formatting\";\nimport { withAutoLinks } from \"../../slate/plugins/auto-links\";\nimport { withCustomLinks } from \"../../slate/plugins/custom-links\";\nimport { withEmptyClearFormatting } from \"../../slate/plugins/empty-clear-formatting\";\nimport type { MentionDraft } from \"../../slate/plugins/mentions\";\nimport {\n getMentionDraftAtSelection,\n insertMention,\n insertMentionCharacter,\n MENTION_CHARACTER,\n withMentions,\n} from \"../../slate/plugins/mentions\";\nimport { withNormalize } from \"../../slate/plugins/normalize\";\nimport { withPaste } from \"../../slate/plugins/paste\";\nimport { getDOMRange } from \"../../slate/utils/get-dom-range\";\nimport { isEmpty as isEditorEmpty } from \"../../slate/utils/is-empty\";\nimport { leaveMarkEdge, toggleMark } from \"../../slate/utils/marks\";\nimport type {\n ComposerBody as ComposerBodyData,\n ComposerBodyAutoLink,\n ComposerBodyCustomLink,\n ComposerBodyMention,\n} from \"../../types\";\nimport { isKey } from \"../../utils/is-key\";\nimport { Persist, useAnimationPersist, usePersist } from \"../../utils/Persist\";\nimport { Portal } from \"../../utils/Portal\";\nimport { requestSubmit } from \"../../utils/request-submit\";\nimport { useId } from \"../../utils/use-id\";\nimport { useIndex } from \"../../utils/use-index\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLayoutEffect } from \"../../utils/use-layout-effect\";\nimport { useRefs } from \"../../utils/use-refs\";\nimport { toAbsoluteUrl } from \"../Comment/utils\";\nimport {\n ComposerAttachmentsContext,\n ComposerContext,\n ComposerEditorContext,\n ComposerSuggestionsContext,\n useComposer,\n useComposerAttachmentsContext,\n useComposerEditorContext,\n useComposerSuggestionsContext,\n} from \"./contexts\";\nimport type {\n ComposerAttachFilesProps,\n ComposerAttachmentsDropAreaProps,\n ComposerEditorComponents,\n ComposerEditorElementProps,\n ComposerEditorLinkWrapperProps,\n ComposerEditorMentionSuggestionsWrapperProps,\n ComposerEditorMentionWrapperProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerLinkProps,\n ComposerMentionProps,\n ComposerSubmitProps,\n ComposerSuggestionsListItemProps,\n ComposerSuggestionsListProps,\n ComposerSuggestionsProps,\n SuggestionsPosition,\n} from \"./types\";\nimport {\n commentBodyToComposerBody,\n composerBodyToCommentBody,\n getPlacementFromPosition,\n getSideAndAlignFromPlacement,\n useComposerAttachmentsDropArea,\n useComposerAttachmentsManager,\n} from \"./utils\";\n\nconst MENTION_SUGGESTIONS_POSITION: SuggestionsPosition = \"top\";\n\nconst COMPOSER_MENTION_NAME = \"ComposerMention\";\nconst COMPOSER_LINK_NAME = \"ComposerLink\";\nconst COMPOSER_SUGGESTIONS_NAME = \"ComposerSuggestions\";\nconst COMPOSER_SUGGESTIONS_LIST_NAME = \"ComposerSuggestionsList\";\nconst COMPOSER_SUGGESTIONS_LIST_ITEM_NAME = \"ComposerSuggestionsListItem\";\nconst COMPOSER_SUBMIT_NAME = \"ComposerSubmit\";\nconst COMPOSER_EDITOR_NAME = \"ComposerEditor\";\nconst COMPOSER_ATTACH_FILES_NAME = \"ComposerAttachFiles\";\nconst COMPOSER_ATTACHMENTS_DROP_AREA_NAME = \"ComposerAttachmentsDropArea\";\nconst COMPOSER_FORM_NAME = \"ComposerForm\";\n\nconst emptyCommentBody: CommentBody = {\n version: 1,\n content: [{ type: \"paragraph\", children: [{ text: \"\" }] }],\n};\n\nfunction createComposerEditor({\n createAttachments,\n pasteFilesAsAttachments,\n}: {\n createAttachments: (files: File[]) => void;\n pasteFilesAsAttachments?: boolean;\n}) {\n return withNormalize(\n withMentions(\n withCustomLinks(\n withAutoLinks(\n withAutoFormatting(\n withEmptyClearFormatting(\n withPaste(withHistory(withReact(createEditor())), {\n createAttachments,\n pasteFilesAsAttachments,\n })\n )\n )\n )\n )\n )\n );\n}\n\nfunction ComposerEditorMentionWrapper({\n Mention,\n attributes,\n children,\n element,\n}: ComposerEditorMentionWrapperProps) {\n const isSelected = useSelected();\n\n return (\n <span {...attributes}>\n {element.id ? (\n <Mention userId={element.id} isSelected={isSelected} />\n ) : null}\n {children}\n </span>\n );\n}\n\nfunction ComposerEditorLinkWrapper({\n Link,\n attributes,\n element,\n children,\n}: ComposerEditorLinkWrapperProps) {\n const href = useMemo(\n () => toAbsoluteUrl(element.url) ?? element.url,\n [element.url]\n );\n\n return (\n <span {...attributes}>\n <Link href={href}>{children}</Link>\n </span>\n );\n}\n\nfunction ComposerEditorMentionSuggestionsWrapper({\n id,\n itemId,\n userIds,\n selectedUserId,\n setSelectedUserId,\n mentionDraft,\n onItemSelect,\n position = MENTION_SUGGESTIONS_POSITION,\n dir,\n MentionSuggestions,\n}: ComposerEditorMentionSuggestionsWrapperProps) {\n const editor = useSlateStatic();\n const { isFocused } = useComposer();\n const [content, setContent] = useState<HTMLDivElement | null>(null);\n const [contentZIndex, setContentZIndex] = useState<string>();\n const contentRef = useCallback(setContent, [setContent]);\n const { portalContainer } = useLiveblocksUIConfig();\n const floatingOptions: UseFloatingOptions = useMemo(() => {\n const detectOverflowOptions: DetectOverflowOptions = {\n padding: FLOATING_ELEMENT_COLLISION_PADDING,\n };\n\n return {\n strategy: \"fixed\",\n placement: getPlacementFromPosition(position, dir),\n middleware: [\n flip({ ...detectOverflowOptions, crossAxis: false }),\n hide(detectOverflowOptions),\n shift({\n ...detectOverflowOptions,\n limiter: limitShift(),\n }),\n size({\n ...detectOverflowOptions,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: (...args) => {\n return autoUpdate(...args, {\n animationFrame: true,\n });\n },\n };\n }, [position, dir]);\n const {\n refs: { setReference, setFloating },\n strategy,\n isPositioned,\n placement,\n x,\n y,\n } = useFloating(floatingOptions);\n\n // Copy `z-index` from content to wrapper.\n // Inspired by https://github.com/radix-ui/primitives/blob/main/packages/react/popper/src/Popper.tsx\n useLayoutEffect(() => {\n if (content) {\n setContentZIndex(window.getComputedStyle(content).zIndex);\n }\n }, [content]);\n\n useLayoutEffect(() => {\n if (!mentionDraft) {\n return;\n }\n\n const domRange = getDOMRange(editor, mentionDraft.range);\n\n if (domRange) {\n setReference({\n getBoundingClientRect: () => domRange.getBoundingClientRect(),\n getClientRects: () => domRange.getClientRects(),\n });\n }\n }, [setReference, editor, mentionDraft]);\n\n return (\n <Persist>\n {mentionDraft?.range && isFocused && userIds ? (\n <ComposerSuggestionsContext.Provider\n value={{\n id,\n itemId,\n selectedValue: selectedUserId,\n setSelectedValue: setSelectedUserId,\n onItemSelect,\n placement,\n dir,\n ref: contentRef,\n }}\n >\n <Portal\n ref={setFloating}\n container={portalContainer}\n style={{\n position: strategy,\n top: 0,\n left: 0,\n transform: isPositioned\n ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`\n : \"translate3d(0, -200%, 0)\",\n minWidth: \"max-content\",\n zIndex: contentZIndex,\n }}\n >\n <MentionSuggestions\n userIds={userIds}\n selectedUserId={selectedUserId}\n />\n </Portal>\n </ComposerSuggestionsContext.Provider>\n ) : null}\n </Persist>\n );\n}\n\nfunction ComposerEditorElement({\n Mention,\n Link,\n ...props\n}: ComposerEditorElementProps) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { attributes, children, element } = props;\n\n switch (element.type) {\n case \"mention\":\n return (\n <ComposerEditorMentionWrapper\n Mention={Mention}\n {...(props as RenderElementSpecificProps<ComposerBodyMention>)}\n />\n );\n case \"auto-link\":\n case \"custom-link\":\n return (\n <ComposerEditorLinkWrapper\n Link={Link}\n {...(props as RenderElementSpecificProps<\n ComposerBodyAutoLink | ComposerBodyCustomLink\n >)}\n />\n );\n case \"paragraph\":\n return (\n <p {...attributes} style={{ position: \"relative\" }}>\n {children}\n </p>\n );\n default:\n return null;\n }\n}\n\n// <code><s><em><strong>text</strong></s></em></code>\nfunction ComposerEditorLeaf({ attributes, children, leaf }: RenderLeafProps) {\n if (leaf.bold) {\n children = <strong>{children}</strong>;\n }\n\n if (leaf.italic) {\n children = <em>{children}</em>;\n }\n\n if (leaf.strikethrough) {\n children = <s>{children}</s>;\n }\n\n if (leaf.code) {\n children = <code>{children}</code>;\n }\n\n return <span {...attributes}>{children}</span>;\n}\n\nfunction ComposerEditorPlaceholder({\n attributes,\n children,\n}: RenderPlaceholderProps) {\n const { opacity: _opacity, ...style } = attributes.style;\n\n return (\n <span {...attributes} style={style} data-placeholder=\"\">\n {children}\n </span>\n );\n}\n\n/**\n * Displays mentions within `Composer.Editor`.\n *\n * @example\n * <Composer.Mention>@{userId}</Composer.Mention>\n */\nconst ComposerMention = forwardRef<HTMLSpanElement, ComposerMentionProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"span\";\n const isSelected = useSelected();\n\n return (\n <Component\n data-selected={isSelected || undefined}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Displays links within `Composer.Editor`.\n *\n * @example\n * <Composer.Link href={href}>{children}</Composer.Link>\n */\nconst ComposerLink = forwardRef<HTMLAnchorElement, ComposerLinkProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"a\";\n\n return (\n <Component\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Contains suggestions within `Composer.Editor`.\n */\nconst ComposerSuggestions = forwardRef<\n HTMLDivElement,\n ComposerSuggestionsProps\n>(({ children, style, asChild, ...props }, forwardedRef) => {\n const [isPresent] = usePersist();\n const ref = useRef<HTMLDivElement>(null);\n const {\n ref: contentRef,\n placement,\n dir,\n } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_NAME);\n const mergedRefs = useRefs(forwardedRef, contentRef, ref);\n const [side, align] = useMemo(\n () => getSideAndAlignFromPlacement(placement),\n [placement]\n );\n const Component = asChild ? Slot : \"div\";\n useAnimationPersist(ref);\n\n return (\n <Component\n dir={dir}\n {...props}\n data-state={isPresent ? \"open\" : \"closed\"}\n data-side={side}\n data-align={align}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n maxHeight: \"var(--lb-composer-suggestions-available-height)\",\n overflowY: \"auto\",\n ...style,\n }}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a list of suggestions within `Composer.Editor`.\n *\n * @example\n * <Composer.SuggestionsList>\n * {userIds.map((userId) => (\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n * ))}\n * </Composer.SuggestionsList>\n */\nconst ComposerSuggestionsList = forwardRef<\n HTMLUListElement,\n ComposerSuggestionsListProps\n>(({ children, asChild, ...props }, forwardedRef) => {\n const { id } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_NAME);\n const Component = asChild ? Slot : \"ul\";\n\n return (\n <Component\n role=\"listbox\"\n id={id}\n aria-label=\"Suggestions list\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a suggestion within `Composer.SuggestionsList`.\n *\n * @example\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n */\nconst ComposerSuggestionsListItem = forwardRef<\n HTMLLIElement,\n ComposerSuggestionsListItemProps\n>(\n (\n {\n value,\n children,\n onPointerMove,\n onPointerDown,\n onClick,\n asChild,\n ...props\n },\n forwardedRef\n ) => {\n const ref = useRef<HTMLLIElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const { selectedValue, setSelectedValue, itemId, onItemSelect } =\n useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_ITEM_NAME);\n const Component = asChild ? Slot : \"li\";\n const isSelected = useMemo(\n () => selectedValue === value,\n [selectedValue, value]\n );\n // TODO: Support props.id if provided, it will need to be sent up to Composer.Editor to use it in aria-activedescendant\n const id = useMemo(() => itemId(value), [itemId, value]);\n\n useEffect(() => {\n if (ref?.current && isSelected) {\n ref.current.scrollIntoView({ block: \"nearest\" });\n }\n }, [isSelected]);\n\n const handlePointerMove = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerMove?.(event);\n\n if (!event.isDefaultPrevented()) {\n setSelectedValue(value);\n }\n },\n [onPointerMove, setSelectedValue, value]\n );\n\n const handlePointerDown = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerDown?.(event);\n\n event.preventDefault();\n event.stopPropagation();\n },\n [onPointerDown]\n );\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n\n const wasDefaultPrevented = event.isDefaultPrevented();\n\n event.preventDefault();\n event.stopPropagation();\n\n if (!wasDefaultPrevented) {\n onItemSelect(value);\n }\n },\n [onClick, onItemSelect, value]\n );\n\n return (\n <Component\n role=\"option\"\n id={id}\n data-selected={isSelected || undefined}\n aria-selected={isSelected || undefined}\n onPointerMove={handlePointerMove}\n onPointerDown={handlePointerDown}\n onClick={handleClick}\n {...props}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n }\n);\n\nconst defaultEditorComponents: ComposerEditorComponents = {\n Link: ({ href, children }) => {\n return <ComposerLink href={href}>{children}</ComposerLink>;\n },\n Mention: ({ userId }) => {\n return (\n <ComposerMention>\n {MENTION_CHARACTER}\n {userId}\n </ComposerMention>\n );\n },\n MentionSuggestions: ({ userIds }) => {\n return userIds.length > 0 ? (\n <ComposerSuggestions>\n <ComposerSuggestionsList>\n {userIds.map((userId) => (\n <ComposerSuggestionsListItem key={userId} value={userId}>\n {userId}\n </ComposerSuggestionsListItem>\n ))}\n </ComposerSuggestionsList>\n </ComposerSuggestions>\n ) : null;\n },\n};\n\n/**\n * Displays the composer's editor.\n *\n * @example\n * <Composer.Editor placeholder=\"Write a comment…\" />\n */\nconst ComposerEditor = forwardRef<HTMLDivElement, ComposerEditorProps>(\n (\n {\n defaultValue,\n onKeyDown,\n onFocus,\n onBlur,\n disabled,\n autoFocus,\n components,\n dir,\n ...props\n },\n forwardedRef\n ) => {\n const { editor, validate, setFocused } = useComposerEditorContext();\n const {\n submit,\n focus,\n select,\n canSubmit,\n isDisabled: isComposerDisabled,\n isFocused,\n } = useComposer();\n const isDisabled = isComposerDisabled || disabled;\n const initialBody = useInitial(defaultValue ?? emptyCommentBody);\n const initialEditorValue = useMemo(() => {\n return commentBodyToComposerBody(initialBody);\n }, [initialBody]);\n const { Link, Mention, MentionSuggestions } = useMemo(\n () => ({ ...defaultEditorComponents, ...components }),\n [components]\n );\n\n const [mentionDraft, setMentionDraft] = useState<MentionDraft>();\n const mentionSuggestions = useMentionSuggestions(mentionDraft?.text);\n const [\n selectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setNextSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n ] = useIndex(0, mentionSuggestions?.length ?? 0);\n const id = useId();\n const suggestionsListId = useMemo(\n () => `liveblocks-suggestions-list-${id}`,\n [id]\n );\n const suggestionsListItemId = useCallback(\n (userId?: string) =>\n userId ? `liveblocks-suggestions-list-item-${id}-${userId}` : undefined,\n [id]\n );\n const renderElement = useCallback(\n (props: RenderElementProps) => {\n return (\n <ComposerEditorElement Mention={Mention} Link={Link} {...props} />\n );\n },\n [Link, Mention]\n );\n\n const handleChange = useCallback(\n (value: SlateDescendant[]) => {\n validate(value as SlateElement[]);\n\n setMentionDraft(getMentionDraftAtSelection(editor));\n },\n [editor, validate]\n );\n\n const createMention = useCallback(\n (userId?: string) => {\n if (!mentionDraft || !userId) {\n return;\n }\n\n SlateTransforms.select(editor, mentionDraft.range);\n insertMention(editor, userId);\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n },\n [editor, mentionDraft, setSelectedMentionSuggestionIndex]\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n // Allow leaving marks with ArrowLeft\n if (isKey(event, \"ArrowLeft\")) {\n leaveMarkEdge(editor, \"start\");\n }\n\n // Allow leaving marks with ArrowRight\n if (isKey(event, \"ArrowRight\")) {\n leaveMarkEdge(editor, \"end\");\n }\n\n if (mentionDraft && mentionSuggestions?.length) {\n // Select the next mention suggestion on ArrowDown\n if (isKey(event, \"ArrowDown\")) {\n event.preventDefault();\n setNextSelectedMentionSuggestionIndex();\n }\n\n // Select the previous mention suggestion on ArrowUp\n if (isKey(event, \"ArrowUp\")) {\n event.preventDefault();\n setPreviousSelectedMentionSuggestionIndex();\n }\n\n // Create a mention on Enter/Tab\n if (isKey(event, \"Enter\") || isKey(event, \"Tab\")) {\n event.preventDefault();\n\n const userId = mentionSuggestions?.[selectedMentionSuggestionIndex];\n createMention(userId);\n }\n\n // Close the suggestions on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n }\n } else {\n // Blur the editor on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n ReactEditor.blur(editor);\n }\n\n // Submit the editor on Enter\n if (isKey(event, \"Enter\", { shift: false })) {\n // Even if submitting is not possible, don't do anything else on Enter. (e.g. creating a new line)\n event.preventDefault();\n\n if (canSubmit) {\n submit();\n }\n }\n\n // Create a new line on Shift + Enter\n if (isKey(event, \"Enter\", { shift: true })) {\n event.preventDefault();\n editor.insertBreak();\n }\n\n // Toggle bold on Command/Control + B\n if (isKey(event, \"b\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"bold\");\n }\n\n // Toggle italic on Command/Control + I\n if (isKey(event, \"i\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"italic\");\n }\n\n // Toggle strikethrough on Command/Control + Shift + S\n if (isKey(event, \"s\", { mod: true, shift: true })) {\n event.preventDefault();\n toggleMark(editor, \"strikethrough\");\n }\n\n // Toggle code on Command/Control + E\n if (isKey(event, \"e\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"code\");\n }\n }\n },\n [\n createMention,\n editor,\n canSubmit,\n mentionDraft,\n mentionSuggestions,\n selectedMentionSuggestionIndex,\n onKeyDown,\n setNextSelectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n submit,\n ]\n );\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(true);\n }\n },\n [onFocus, setFocused]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(false);\n }\n },\n [onBlur, setFocused]\n );\n\n const selectedMentionSuggestionUserId = useMemo(\n () => mentionSuggestions?.[selectedMentionSuggestionIndex],\n [selectedMentionSuggestionIndex, mentionSuggestions]\n );\n const setSelectedMentionSuggestionUserId = useCallback(\n (userId: string) => {\n const index = mentionSuggestions?.indexOf(userId);\n\n if (index !== undefined && index >= 0) {\n setSelectedMentionSuggestionIndex(index);\n }\n },\n [setSelectedMentionSuggestionIndex, mentionSuggestions]\n );\n\n const propsWhileSuggesting: AriaAttributes = useMemo(\n () =>\n mentionDraft\n ? {\n role: \"combobox\",\n \"aria-autocomplete\": \"list\",\n \"aria-expanded\": true,\n \"aria-controls\": suggestionsListId,\n \"aria-activedescendant\": suggestionsListItemId(\n selectedMentionSuggestionUserId\n ),\n }\n : {},\n [\n mentionDraft,\n suggestionsListId,\n suggestionsListItemId,\n selectedMentionSuggestionUserId,\n ]\n );\n\n useImperativeHandle(forwardedRef, () => {\n return ReactEditor.toDOMNode(editor, editor) as HTMLDivElement;\n }, [editor]);\n\n // Manually focus the editor when `autoFocus` is true\n useEffect(() => {\n if (autoFocus) {\n focus();\n }\n }, [autoFocus, editor, focus]);\n\n // Manually add a selection in the editor if the selection\n // is still empty after being focused\n useEffect(() => {\n if (isFocused && editor.selection === null) {\n select();\n }\n }, [editor, select, isFocused]);\n\n return (\n <Slate\n editor={editor}\n initialValue={initialEditorValue}\n onChange={handleChange}\n >\n <Editable\n dir={dir}\n enterKeyHint={mentionDraft ? \"enter\" : \"send\"}\n autoCapitalize=\"sentences\"\n aria-label=\"Composer editor\"\n data-focused={isFocused || undefined}\n data-disabled={isDisabled || undefined}\n {...propsWhileSuggesting}\n {...props}\n readOnly={isDisabled}\n disabled={isDisabled}\n onKeyDown={handleKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n renderElement={renderElement}\n renderLeaf={ComposerEditorLeaf}\n renderPlaceholder={ComposerEditorPlaceholder}\n />\n <ComposerEditorMentionSuggestionsWrapper\n dir={dir}\n mentionDraft={mentionDraft}\n selectedUserId={selectedMentionSuggestionUserId}\n setSelectedUserId={setSelectedMentionSuggestionUserId}\n userIds={mentionSuggestions}\n id={suggestionsListId}\n itemId={suggestionsListItemId}\n onItemSelect={createMention}\n MentionSuggestions={MentionSuggestions}\n />\n </Slate>\n );\n }\n);\n\nconst MAX_ATTACHMENTS = 10;\nconst MAX_ATTACHMENT_SIZE = 1024 * 1024 * 1024; // 1 GB\n\n/**\n * Surrounds the composer's content and handles submissions.\n *\n * @example\n * <Composer.Form onComposerSubmit={({ body }) => {}}>\n *\t <Composer.Editor />\n * <Composer.Submit />\n * </Composer.Form>\n */\nconst ComposerForm = forwardRef<HTMLFormElement, ComposerFormProps>(\n (\n {\n children,\n onSubmit,\n onComposerSubmit,\n defaultAttachments = [],\n pasteFilesAsAttachments,\n preventUnsavedChanges = true,\n disabled,\n asChild,\n ...props\n },\n forwardedRef\n ) => {\n const Component = asChild ? Slot : \"form\";\n const room = useRoom();\n const [isEmpty, setEmpty] = useState(true);\n const [isSubmitting, setSubmitting] = useState(false);\n const [isFocused, setFocused] = useState(false);\n // Later: Offer as Composer.Form props: { maxAttachments: number; maxAttachmentSize: number; supportedAttachmentMimeTypes: string[]; }\n const maxAttachments = MAX_ATTACHMENTS;\n const maxAttachmentSize = MAX_ATTACHMENT_SIZE;\n const {\n attachments,\n isUploadingAttachments,\n addAttachments,\n removeAttachment,\n clearAttachments,\n } = useComposerAttachmentsManager(defaultAttachments, {\n maxFileSize: maxAttachmentSize,\n });\n const numberOfAttachments = attachments.length;\n const hasMaxAttachments = numberOfAttachments >= maxAttachments;\n const isDisabled = useMemo(() => {\n const self = room.getSelf();\n const canComment = self?.canComment ?? true;\n\n return isSubmitting || disabled || !canComment;\n }, [isSubmitting, disabled, room]);\n const canSubmit = useMemo(() => {\n return !isEmpty && !isUploadingAttachments;\n }, [isEmpty, isUploadingAttachments]);\n const ref = useRef<HTMLFormElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const syncSource = useSyncSource();\n\n // Mark the composer as a pending update when it has unsubmitted (draft)\n // text or attachments\n const isPending = !preventUnsavedChanges\n ? false\n : !isEmpty || isUploadingAttachments || attachments.length > 0;\n\n useEffect(() => {\n syncSource?.setSyncStatus(\n isPending ? \"has-local-changes\" : \"synchronized\"\n );\n }, [syncSource, isPending]);\n\n const createAttachments = useCallback(\n (files: File[]) => {\n if (!files.length) {\n return;\n }\n\n const numberOfAcceptedFiles = Math.max(\n 0,\n maxAttachments - numberOfAttachments\n );\n\n files.splice(numberOfAcceptedFiles);\n\n const attachments = files.map((file) => room.prepareAttachment(file));\n\n addAttachments(attachments);\n },\n [addAttachments, maxAttachments, numberOfAttachments, room]\n );\n\n const createAttachmentsRef = useRef(createAttachments);\n\n useEffect(() => {\n createAttachmentsRef.current = createAttachments;\n }, [createAttachments]);\n\n const stableCreateAttachments = useCallback((files: File[]) => {\n createAttachmentsRef.current(files);\n }, []);\n\n const editor = useInitial(() =>\n createComposerEditor({\n createAttachments: stableCreateAttachments,\n pasteFilesAsAttachments,\n })\n );\n\n const validate = useCallback(\n (value: SlateElement[]) => {\n setEmpty(isEditorEmpty(editor, value));\n },\n [editor]\n );\n\n const submit = useCallback(() => {\n if (!canSubmit) {\n return;\n }\n\n // We need to wait for the next frame in some cases like when composing diacritics,\n // we want any native handling to be done first while still being handled on `keydown`.\n requestAnimationFrame(() => {\n if (ref.current) {\n requestSubmit(ref.current);\n }\n });\n }, [canSubmit]);\n\n const clear = useCallback(() => {\n SlateTransforms.delete(editor, {\n at: {\n anchor: SlateEditor.start(editor, []),\n focus: SlateEditor.end(editor, []),\n },\n });\n }, [editor]);\n\n const select = useCallback(() => {\n SlateTransforms.select(editor, {\n anchor: SlateEditor.end(editor, []),\n focus: SlateEditor.end(editor, []),\n });\n }, [editor]);\n\n const focus = useCallback(\n (resetSelection = true) => {\n if (!ReactEditor.isFocused(editor)) {\n SlateTransforms.select(\n editor,\n resetSelection || !editor.selection\n ? SlateEditor.end(editor, [])\n : editor.selection\n );\n ReactEditor.focus(editor);\n }\n },\n [editor]\n );\n\n const blur = useCallback(() => {\n ReactEditor.blur(editor);\n }, [editor]);\n\n const createMention = useCallback(() => {\n if (disabled) {\n return;\n }\n\n focus();\n insertMentionCharacter(editor);\n }, [disabled, editor, focus]);\n\n const insertText = useCallback(\n (text: string) => {\n if (disabled) {\n return;\n }\n\n focus(false);\n insertSlateText(editor, text);\n },\n [disabled, editor, focus]\n );\n\n const attachFiles = useCallback(() => {\n if (disabled) {\n return;\n }\n\n if (fileInputRef.current) {\n fileInputRef.current.click();\n }\n }, [disabled]);\n\n const handleAttachmentsInputChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n if (disabled) {\n return;\n }\n\n if (event.target.files) {\n createAttachments(Array.from(event.target.files));\n\n // Reset the input value to allow selecting the same file(s) again\n event.target.value = \"\";\n }\n },\n [createAttachments, disabled]\n );\n\n const onSubmitEnd = useCallback(() => {\n clear();\n blur();\n clearAttachments();\n setSubmitting(false);\n }, [blur, clear, clearAttachments]);\n\n const handleSubmit = useCallback(\n (event: FormEvent<HTMLFormElement>) => {\n if (disabled) {\n return;\n }\n\n // In some situations (e.g. pressing Enter while composing diacritics), it's possible\n // for the form to be submitted as empty even though we already checked whether the\n // editor was empty when handling the key press.\n const isEmpty = isEditorEmpty(editor, editor.children);\n\n // We even prevent the user's `onSubmit` handler from being called if the editor is empty.\n if (isEmpty) {\n event.preventDefault();\n\n return;\n }\n\n onSubmit?.(event);\n\n if (!onComposerSubmit || event.isDefaultPrevented()) {\n event.preventDefault();\n\n return;\n }\n\n const body = composerBodyToCommentBody(\n editor.children as ComposerBodyData\n );\n // Only non-local attachments are included to be submitted.\n const commentAttachments: CommentAttachment[] = attachments\n .filter(\n (attachment) =>\n attachment.type === \"attachment\" ||\n (attachment.type === \"localAttachment\" &&\n attachment.status === \"uploaded\")\n )\n .map((attachment) => {\n return {\n id: attachment.id,\n type: \"attachment\",\n mimeType: attachment.mimeType,\n size: attachment.size,\n name: attachment.name,\n };\n });\n\n const promise = onComposerSubmit(\n { body, attachments: commentAttachments },\n event\n );\n\n event.preventDefault();\n\n if (promise) {\n setSubmitting(true);\n promise.then(onSubmitEnd);\n } else {\n onSubmitEnd();\n }\n },\n [disabled, editor, attachments, onComposerSubmit, onSubmit, onSubmitEnd]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <ComposerEditorContext.Provider\n value={{\n editor,\n validate,\n setFocused,\n }}\n >\n <ComposerAttachmentsContext.Provider\n value={{\n createAttachments,\n isUploadingAttachments,\n hasMaxAttachments,\n maxAttachments,\n maxAttachmentSize,\n }}\n >\n <ComposerContext.Provider\n value={{\n isDisabled,\n isFocused,\n isEmpty,\n canSubmit,\n submit,\n clear,\n select,\n focus,\n blur,\n createMention,\n insertText,\n attachments,\n attachFiles,\n removeAttachment,\n }}\n >\n <Component {...props} onSubmit={handleSubmit} ref={mergedRefs}>\n <input\n type=\"file\"\n multiple\n ref={fileInputRef}\n onChange={handleAttachmentsInputChange}\n onClick={stopPropagation}\n tabIndex={-1}\n style={{ display: \"none\" }}\n />\n <Slottable>{children}</Slottable>\n </Component>\n </ComposerContext.Provider>\n </ComposerAttachmentsContext.Provider>\n </ComposerEditorContext.Provider>\n );\n }\n);\n\n/**\n * A button to submit the composer.\n *\n * @example\n * <Composer.Submit>Send</Composer.Submit>\n */\nconst ComposerSubmit = forwardRef<HTMLButtonElement, ComposerSubmitProps>(\n ({ children, disabled, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"button\";\n const { canSubmit, isDisabled: isComposerDisabled } = useComposer();\n const isDisabled = isComposerDisabled || disabled || !canSubmit;\n\n return (\n <Component\n type=\"submit\"\n {...props}\n ref={forwardedRef}\n disabled={isDisabled}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * A button which opens a file picker to create attachments.\n *\n * @example\n * <Composer.AttachFiles>Attach files</Composer.AttachFiles>\n */\nconst ComposerAttachFiles = forwardRef<\n HTMLButtonElement,\n ComposerAttachFilesProps\n>(({ children, onClick, disabled, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"button\";\n const { hasMaxAttachments } = useComposerAttachmentsContext();\n const { isDisabled: isComposerDisabled, attachFiles } = useComposer();\n const isDisabled = isComposerDisabled || hasMaxAttachments || disabled;\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n attachFiles();\n }\n },\n [attachFiles, onClick]\n );\n\n return (\n <Component\n type=\"button\"\n {...props}\n onClick={handleClick}\n ref={forwardedRef}\n disabled={isDisabled}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * A drop area which accepts files to create attachments.\n *\n * @example\n * <Composer.AttachmentsDropArea>\n * Drop files here\n * </Composer.AttachmentsDropArea>\n */\nconst ComposerAttachmentsDropArea = forwardRef<\n HTMLDivElement,\n ComposerAttachmentsDropAreaProps\n>(\n (\n {\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n disabled,\n asChild,\n ...props\n },\n forwardedRef\n ) => {\n const Component = asChild ? Slot : \"div\";\n const { isDisabled: isComposerDisabled } = useComposer();\n const isDisabled = isComposerDisabled || disabled;\n const [, dropAreaProps] = useComposerAttachmentsDropArea({\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n disabled: isDisabled,\n });\n\n return (\n <Component\n {...dropAreaProps}\n data-disabled={isDisabled ? \"\" : undefined}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n);\n\nif (process.env.NODE_ENV !== \"production\") {\n ComposerAttachFiles.displayName = COMPOSER_ATTACH_FILES_NAME;\n ComposerAttachmentsDropArea.displayName = COMPOSER_ATTACHMENTS_DROP_AREA_NAME;\n ComposerEditor.displayName = COMPOSER_EDITOR_NAME;\n ComposerForm.displayName = COMPOSER_FORM_NAME;\n ComposerMention.displayName = COMPOSER_MENTION_NAME;\n ComposerLink.displayName = COMPOSER_LINK_NAME;\n ComposerSubmit.displayName = COMPOSER_SUBMIT_NAME;\n ComposerSuggestions.displayName = COMPOSER_SUGGESTIONS_NAME;\n ComposerSuggestionsList.displayName = COMPOSER_SUGGESTIONS_LIST_NAME;\n ComposerSuggestionsListItem.displayName = COMPOSER_SUGGESTIONS_LIST_ITEM_NAME;\n}\n\n// NOTE: Every export from this file will be available publicly as Composer.*\nexport {\n ComposerAttachFiles as AttachFiles,\n ComposerAttachmentsDropArea as AttachmentsDropArea,\n ComposerEditor as Editor,\n ComposerForm as Form,\n ComposerLink as Link,\n ComposerMention as Mention,\n ComposerSubmit as Submit,\n ComposerSuggestions as Suggestions,\n ComposerSuggestionsList as SuggestionsList,\n ComposerSuggestionsListItem as SuggestionsListItem,\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2IA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAsC;AAC3B;AAEX;AAEA;AAA8B;AAC5B;AAEF;AAIE;AAAO;AACL;AACE;AACE;AACE;AACE;AACoD;AAChD;AACA;AACD;AACH;AACF;AACF;AACF;AACF;AAEJ;AAEA;AAAsC;AACpC;AACA;AACA;AAEF;AACE;AAEA;AACG;AAAS;AAEL;AAAwB;AAAI;AAKrC;AAEA;AAAmC;AACjC;AACA;AACA;AAEF;AACE;AAAa;AACiC;AAChC;AAGd;AACG;AAAS;AACP;AAAK;AAGZ;AAEA;AAAiD;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAqD;AAC1C;AAGX;AAAO;AACK;AACuC;AACrC;AACyC;AACzB;AACpB;AACD;AACiB;AACrB;AACI;AACA;AAED;AAAwB;AACtB;AACG;AAEL;AAAwB;AACtB;AACG;AACL;AACF;AACD;AACH;AAEE;AAA2B;AACT;AACjB;AACH;AACF;AAEF;AAAM;AAC8B;AAClC;AACA;AACA;AACA;AACA;AAKF;AACE;AACE;AAAwD;AAC1D;AAGF;AACE;AACE;AAAA;AAGF;AAEA;AACE;AAAa;AACiD;AACd;AAC/C;AACH;AAGF;AAGO;AACQ;AACL;AACA;AACe;AACG;AAClB;AACA;AACA;AACK;AACP;AAEC;AACM;AACM;AACJ;AACK;AACL;AACC;AAGF;AACM;AACF;AACV;AAEC;AACC;AACA;AAOd;AAEA;AAA+B;AAC7B;AACA;AAEF;AAEE;AAEA;AAAsB;AAElB;AACG;AACC;AACK;AACP;AAEC;AAEH;AACG;AACC;AACK;AAGP;AAGF;AACG;AAAM;AAA0C;AAEjD;AAGF;AAAO;AAEb;AAGA;AACE;AACE;AAA6B;AAG/B;AACE;AAAyB;AAG3B;AACE;AAAwB;AAG1B;AACE;AAA2B;AAG7B;AAAQ;AAAS;AACnB;AAEA;AAAmC;AACjC;AAEF;AACE;AAEA;AACG;AAAS;AAAY;AAA+B;AAIzD;AAQA;AAAwB;AAEpB;AACA;AAEA;AACG;AAC8B;AACzB;AACC;AAGP;AAGN;AAQA;AAAqB;AAEjB;AAEA;AACG;AACQ;AACH;AACA;AACC;AAGP;AAGN;AAKM;AAIJ;AACA;AACA;AAAM;AACC;AACL;AACA;AAEF;AACA;AAAsB;AACwB;AAClC;AAEZ;AACA;AAEA;AACG;AACC;AACI;AAC6B;AACtB;AACC;AACL;AACI;AACM;AACJ;AACA;AACR;AACL;AACK;AAKX;AAcM;AAIJ;AACA;AAEA;AACG;AACM;AACL;AACW;AACP;AACC;AAKX;AAUA;AAAoC;AAKhC;AACE;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AAEA;AACA;AAAmB;AACO;AACH;AAGvB;AAEA;AACE;AACE;AAA+C;AACjD;AAGF;AAA0B;AAEtB;AAEA;AACE;AAAsB;AACxB;AACF;AACuC;AAGzC;AAA0B;AAEtB;AAEA;AACA;AAAsB;AACxB;AACc;AAGhB;AAAoB;AAEhB;AAEA;AAEA;AACA;AAEA;AACE;AAAkB;AACpB;AACF;AAC6B;AAG/B;AACG;AACM;AACL;AAC6B;AACA;AACd;AACA;AACN;AACL;AACC;AAGP;AAGN;AAEA;AAA0D;AAEtD;AAAQ;AAAa;AAAsB;AAC7C;AAEE;AAIE;AAEJ;AAEE;AAIS;AAAiC;AAAe;AAMrD;AAER;AAQA;AAAuB;AAEnB;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AAAM;AACJ;AACA;AACA;AACA;AACY;AACZ;AAEF;AACA;AACA;AACE;AAA4C;AAE9C;AAA8C;AACO;AACxC;AAGb;AACA;AACA;AAAM;AACJ;AACA;AACA;AACA;AAEF;AACA;AAA0B;AACa;AAClC;AAEL;AAA8B;AAEoC;AAC7D;AAEL;AAAsB;AAElB;AACG;AAAsB;AAAkB;AAAgB;AAAO;AAEpE;AACc;AAGhB;AAAqB;AAEjB;AAEA;AAAkD;AACpD;AACiB;AAGnB;AAAsB;AAElB;AACE;AAAA;AAGF;AACA;AACA;AACA;AAAmC;AACrC;AACwD;AAG1D;AAAsB;AAElB;AAEA;AACE;AAAA;AAIF;AACE;AAA6B;AAI/B;AACE;AAA2B;AAG7B;AAEE;AACE;AACA;AAAsC;AAIxC;AACE;AACA;AAA0C;AAI5C;AACE;AAEA;AACA;AAAoB;AAItB;AACE;AACA;AACA;AAAmC;AACrC;AAGA;AACE;AACA;AAAuB;AAIzB;AAEE;AAEA;AACE;AAAO;AACT;AAIF;AACE;AACA;AAAmB;AAIrB;AACE;AACA;AAAyB;AAI3B;AACE;AACA;AAA2B;AAI7B;AACE;AACA;AAAkC;AAIpC;AACE;AACA;AAAyB;AAC3B;AACF;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AAAoB;AAEhB;AAEA;AACE;AAAe;AACjB;AACF;AACoB;AAGtB;AAAmB;AAEf;AAEA;AACE;AAAgB;AAClB;AACF;AACmB;AAGrB;AAAwC;AACX;AACwB;AAErD;AAA2C;AAEvC;AAEA;AACE;AAAuC;AACzC;AACF;AACsD;AAGxD;AAA6C;AAGrC;AACQ;AACe;AACJ;AACA;AACQ;AACvB;AACF;AAED;AACP;AACE;AACA;AACA;AACA;AACF;AAGF;AACE;AAA2C;AAI7C;AACE;AACE;AAAM;AACR;AAKF;AACE;AACE;AAAO;AACT;AAGF;AACG;AACC;AACc;AACJ;AAET;AACC;AACuC;AACxB;AACJ;AACgB;AACE;AACzB;AACA;AACM;AACA;AACC;AACF;AACD;AACR;AACY;AACO;AAEpB;AACC;AACA;AACgB;AACG;AACV;AACL;AACI;AACM;AACd;AAEJ;AAGN;AAEA;AACA;AAWA;AAAqB;AAEjB;AACE;AACA;AACA;AACsB;AACtB;AACwB;AACxB;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAM;AACJ;AACA;AACA;AACA;AACA;AACoD;AACvC;AAEf;AACA;AACA;AACE;AACA;AAEA;AAAoC;AAEtC;AACE;AAAoB;AAEtB;AACA;AACA;AACA;AAIA;AAIA;AACE;AAAY;AACwB;AACpC;AAGF;AAA0B;AAEtB;AACE;AAAA;AAGF;AAAmC;AACjC;AACiB;AAGnB;AAEA;AAEA;AAA0B;AAC5B;AAC0D;AAG5D;AAEA;AACE;AAA+B;AAGjC;AACE;AAAkC;AAGpC;AAAe;AACQ;AACA;AACnB;AACD;AAGH;AAAiB;AAEb;AAAqC;AACvC;AACO;AAGT;AACE;AACE;AAAA;AAKF;AACE;AACE;AAAyB;AAC3B;AACD;AAGH;AACE;AAA+B;AACzB;AACkC;AACH;AACnC;AACD;AAGH;AACE;AAA+B;AACK;AACD;AAClC;AAGH;AAAc;AAEV;AACE;AAAgB;AACd;AAGW;AAEb;AAAwB;AAC1B;AACF;AACO;AAGT;AACE;AAAuB;AAGzB;AACE;AACE;AAAA;AAGF;AACA;AAA6B;AAG/B;AAAmB;AAEf;AACE;AAAA;AAGF;AACA;AAA4B;AAC9B;AACwB;AAG1B;AACE;AACE;AAAA;AAGF;AACE;AAA2B;AAC7B;AAGF;AAAqC;AAEjC;AACE;AAAA;AAGF;AACE;AAGA;AAAqB;AACvB;AACF;AAC4B;AAG9B;AACE;AACA;AACA;AACA;AAAmB;AAGrB;AAAqB;AAEjB;AACE;AAAA;AAMF;AAGA;AACE;AAEA;AAAA;AAGF;AAEA;AACE;AAEA;AAAA;AAGF;AAAa;AACJ;AAGT;AACG;AAI2B;AAG1B;AAAO;AACU;AACT;AACe;AACJ;AACA;AACnB;AAGJ;AAAgB;AAC0B;AACxC;AAGF;AAEA;AACE;AACA;AAAwB;AAExB;AAAY;AACd;AACF;AACuE;AAGzE;AACE;AAAsB;AAGxB;AACG;AACQ;AACL;AACA;AACA;AACF;AAEC;AACQ;AACL;AACA;AACA;AACA;AACA;AACF;AAEC;AACQ;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAEC;AAAc;AAAiB;AAAmB;AAChD;AACM;AACG;AACH;AACK;AACD;AACC;AACe;AAMnC;AAGN;AAQA;AAAuB;AAEnB;AACA;AACA;AAEA;AACG;AACM;AACD;AACC;AACK;AAGZ;AAGN;AAQM;AAIJ;AACA;AACA;AACA;AAEA;AAAoB;AAEhB;AAEA;AACE;AAAY;AACd;AACF;AACqB;AAGvB;AACG;AACM;AACD;AACK;AACJ;AACK;AAKhB;AAUA;AAAoC;AAKhC;AACE;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AAAyD;AACvD;AACA;AACA;AACA;AACU;AAGZ;AACG;AACK;AAC6B;AAC7B;AACC;AACP;AAGN;AAEA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../src/primitives/Composer/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n DetectOverflowOptions,\n UseFloatingOptions,\n} from \"@floating-ui/react-dom\";\nimport {\n autoUpdate,\n flip,\n hide,\n limitShift,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport {\n type CommentAttachment,\n type CommentBody,\n type CommentLocalAttachment,\n createCommentAttachmentId,\n} from \"@liveblocks/core\";\nimport {\n useMentionSuggestions,\n useRoomOrNull,\n useSyncSource,\n} from \"@liveblocks/react/_private\";\nimport { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport type {\n AriaAttributes,\n ChangeEvent,\n FocusEvent,\n FormEvent,\n KeyboardEvent,\n MouseEvent,\n PointerEvent,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Descendant as SlateDescendant,\n Element as SlateElement,\n} from \"slate\";\nimport {\n createEditor,\n Editor as SlateEditor,\n insertText as insertSlateText,\n Transforms as SlateTransforms,\n} from \"slate\";\nimport { withHistory } from \"slate-history\";\nimport type {\n RenderElementProps,\n RenderElementSpecificProps,\n RenderLeafProps,\n RenderPlaceholderProps,\n} from \"slate-react\";\nimport {\n Editable,\n ReactEditor,\n Slate,\n useSelected,\n useSlateStatic,\n withReact,\n} from \"slate-react\";\n\nimport { useLiveblocksUIConfig } from \"../../config\";\nimport { FLOATING_ELEMENT_COLLISION_PADDING } from \"../../constants\";\nimport { withAutoFormatting } from \"../../slate/plugins/auto-formatting\";\nimport { withAutoLinks } from \"../../slate/plugins/auto-links\";\nimport { withCustomLinks } from \"../../slate/plugins/custom-links\";\nimport { withEmptyClearFormatting } from \"../../slate/plugins/empty-clear-formatting\";\nimport type { MentionDraft } from \"../../slate/plugins/mentions\";\nimport {\n getMentionDraftAtSelection,\n insertMention,\n insertMentionCharacter,\n MENTION_CHARACTER,\n withMentions,\n} from \"../../slate/plugins/mentions\";\nimport { withNormalize } from \"../../slate/plugins/normalize\";\nimport { withPaste } from \"../../slate/plugins/paste\";\nimport { getDOMRange } from \"../../slate/utils/get-dom-range\";\nimport { isEmpty as isEditorEmpty } from \"../../slate/utils/is-empty\";\nimport { leaveMarkEdge, toggleMark } from \"../../slate/utils/marks\";\nimport type {\n ComposerBody as ComposerBodyData,\n ComposerBodyAutoLink,\n ComposerBodyCustomLink,\n ComposerBodyMention,\n} from \"../../types\";\nimport { isKey } from \"../../utils/is-key\";\nimport { Persist, useAnimationPersist, usePersist } from \"../../utils/Persist\";\nimport { Portal } from \"../../utils/Portal\";\nimport { requestSubmit } from \"../../utils/request-submit\";\nimport { useId } from \"../../utils/use-id\";\nimport { useIndex } from \"../../utils/use-index\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLayoutEffect } from \"../../utils/use-layout-effect\";\nimport { useRefs } from \"../../utils/use-refs\";\nimport { toAbsoluteUrl } from \"../Comment/utils\";\nimport {\n ComposerAttachmentsContext,\n ComposerContext,\n ComposerEditorContext,\n ComposerSuggestionsContext,\n useComposer,\n useComposerAttachmentsContext,\n useComposerEditorContext,\n useComposerSuggestionsContext,\n} from \"./contexts\";\nimport type {\n ComposerAttachFilesProps,\n ComposerAttachmentsDropAreaProps,\n ComposerEditorComponents,\n ComposerEditorElementProps,\n ComposerEditorLinkWrapperProps,\n ComposerEditorMentionSuggestionsWrapperProps,\n ComposerEditorMentionWrapperProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerLinkProps,\n ComposerMentionProps,\n ComposerSubmitProps,\n ComposerSuggestionsListItemProps,\n ComposerSuggestionsListProps,\n ComposerSuggestionsProps,\n SuggestionsPosition,\n} from \"./types\";\nimport {\n commentBodyToComposerBody,\n composerBodyToCommentBody,\n getPlacementFromPosition,\n getSideAndAlignFromPlacement,\n useComposerAttachmentsDropArea,\n useComposerAttachmentsManager,\n} from \"./utils\";\n\nconst MENTION_SUGGESTIONS_POSITION: SuggestionsPosition = \"top\";\n\nconst COMPOSER_MENTION_NAME = \"ComposerMention\";\nconst COMPOSER_LINK_NAME = \"ComposerLink\";\nconst COMPOSER_SUGGESTIONS_NAME = \"ComposerSuggestions\";\nconst COMPOSER_SUGGESTIONS_LIST_NAME = \"ComposerSuggestionsList\";\nconst COMPOSER_SUGGESTIONS_LIST_ITEM_NAME = \"ComposerSuggestionsListItem\";\nconst COMPOSER_SUBMIT_NAME = \"ComposerSubmit\";\nconst COMPOSER_EDITOR_NAME = \"ComposerEditor\";\nconst COMPOSER_ATTACH_FILES_NAME = \"ComposerAttachFiles\";\nconst COMPOSER_ATTACHMENTS_DROP_AREA_NAME = \"ComposerAttachmentsDropArea\";\nconst COMPOSER_FORM_NAME = \"ComposerForm\";\n\nconst emptyCommentBody: CommentBody = {\n version: 1,\n content: [{ type: \"paragraph\", children: [{ text: \"\" }] }],\n};\n\nfunction createComposerEditor({\n createAttachments,\n pasteFilesAsAttachments,\n}: {\n createAttachments: (files: File[]) => void;\n pasteFilesAsAttachments?: boolean;\n}) {\n return withNormalize(\n withMentions(\n withCustomLinks(\n withAutoLinks(\n withAutoFormatting(\n withEmptyClearFormatting(\n withPaste(withHistory(withReact(createEditor())), {\n createAttachments,\n pasteFilesAsAttachments,\n })\n )\n )\n )\n )\n )\n );\n}\n\nfunction ComposerEditorMentionWrapper({\n Mention,\n attributes,\n children,\n element,\n}: ComposerEditorMentionWrapperProps) {\n const isSelected = useSelected();\n\n return (\n <span {...attributes}>\n {element.id ? (\n <Mention userId={element.id} isSelected={isSelected} />\n ) : null}\n {children}\n </span>\n );\n}\n\nfunction ComposerEditorLinkWrapper({\n Link,\n attributes,\n element,\n children,\n}: ComposerEditorLinkWrapperProps) {\n const href = useMemo(\n () => toAbsoluteUrl(element.url) ?? element.url,\n [element.url]\n );\n\n return (\n <span {...attributes}>\n <Link href={href}>{children}</Link>\n </span>\n );\n}\n\nfunction ComposerEditorMentionSuggestionsWrapper({\n id,\n itemId,\n userIds,\n selectedUserId,\n setSelectedUserId,\n mentionDraft,\n onItemSelect,\n position = MENTION_SUGGESTIONS_POSITION,\n dir,\n MentionSuggestions,\n}: ComposerEditorMentionSuggestionsWrapperProps) {\n const editor = useSlateStatic();\n const { isFocused } = useComposer();\n const [content, setContent] = useState<HTMLDivElement | null>(null);\n const [contentZIndex, setContentZIndex] = useState<string>();\n const contentRef = useCallback(setContent, [setContent]);\n const { portalContainer } = useLiveblocksUIConfig();\n const floatingOptions: UseFloatingOptions = useMemo(() => {\n const detectOverflowOptions: DetectOverflowOptions = {\n padding: FLOATING_ELEMENT_COLLISION_PADDING,\n };\n\n return {\n strategy: \"fixed\",\n placement: getPlacementFromPosition(position, dir),\n middleware: [\n flip({ ...detectOverflowOptions, crossAxis: false }),\n hide(detectOverflowOptions),\n shift({\n ...detectOverflowOptions,\n limiter: limitShift(),\n }),\n size({\n ...detectOverflowOptions,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: (...args) => {\n return autoUpdate(...args, {\n animationFrame: true,\n });\n },\n };\n }, [position, dir]);\n const {\n refs: { setReference, setFloating },\n strategy,\n isPositioned,\n placement,\n x,\n y,\n } = useFloating(floatingOptions);\n\n // Copy `z-index` from content to wrapper.\n // Inspired by https://github.com/radix-ui/primitives/blob/main/packages/react/popper/src/Popper.tsx\n useLayoutEffect(() => {\n if (content) {\n setContentZIndex(window.getComputedStyle(content).zIndex);\n }\n }, [content]);\n\n useLayoutEffect(() => {\n if (!mentionDraft) {\n return;\n }\n\n const domRange = getDOMRange(editor, mentionDraft.range);\n\n if (domRange) {\n setReference({\n getBoundingClientRect: () => domRange.getBoundingClientRect(),\n getClientRects: () => domRange.getClientRects(),\n });\n }\n }, [setReference, editor, mentionDraft]);\n\n return (\n <Persist>\n {mentionDraft?.range && isFocused && userIds ? (\n <ComposerSuggestionsContext.Provider\n value={{\n id,\n itemId,\n selectedValue: selectedUserId,\n setSelectedValue: setSelectedUserId,\n onItemSelect,\n placement,\n dir,\n ref: contentRef,\n }}\n >\n <Portal\n ref={setFloating}\n container={portalContainer}\n style={{\n position: strategy,\n top: 0,\n left: 0,\n transform: isPositioned\n ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`\n : \"translate3d(0, -200%, 0)\",\n minWidth: \"max-content\",\n zIndex: contentZIndex,\n }}\n >\n <MentionSuggestions\n userIds={userIds}\n selectedUserId={selectedUserId}\n />\n </Portal>\n </ComposerSuggestionsContext.Provider>\n ) : null}\n </Persist>\n );\n}\n\nfunction ComposerEditorElement({\n Mention,\n Link,\n ...props\n}: ComposerEditorElementProps) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { attributes, children, element } = props;\n\n switch (element.type) {\n case \"mention\":\n return (\n <ComposerEditorMentionWrapper\n Mention={Mention}\n {...(props as RenderElementSpecificProps<ComposerBodyMention>)}\n />\n );\n case \"auto-link\":\n case \"custom-link\":\n return (\n <ComposerEditorLinkWrapper\n Link={Link}\n {...(props as RenderElementSpecificProps<\n ComposerBodyAutoLink | ComposerBodyCustomLink\n >)}\n />\n );\n case \"paragraph\":\n return (\n <p {...attributes} style={{ position: \"relative\" }}>\n {children}\n </p>\n );\n default:\n return null;\n }\n}\n\n// <code><s><em><strong>text</strong></s></em></code>\nfunction ComposerEditorLeaf({ attributes, children, leaf }: RenderLeafProps) {\n if (leaf.bold) {\n children = <strong>{children}</strong>;\n }\n\n if (leaf.italic) {\n children = <em>{children}</em>;\n }\n\n if (leaf.strikethrough) {\n children = <s>{children}</s>;\n }\n\n if (leaf.code) {\n children = <code>{children}</code>;\n }\n\n return <span {...attributes}>{children}</span>;\n}\n\nfunction ComposerEditorPlaceholder({\n attributes,\n children,\n}: RenderPlaceholderProps) {\n const { opacity: _opacity, ...style } = attributes.style;\n\n return (\n <span {...attributes} style={style} data-placeholder=\"\">\n {children}\n </span>\n );\n}\n\n/**\n * Displays mentions within `Composer.Editor`.\n *\n * @example\n * <Composer.Mention>@{userId}</Composer.Mention>\n */\nconst ComposerMention = forwardRef<HTMLSpanElement, ComposerMentionProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"span\";\n const isSelected = useSelected();\n\n return (\n <Component\n data-selected={isSelected || undefined}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Displays links within `Composer.Editor`.\n *\n * @example\n * <Composer.Link href={href}>{children}</Composer.Link>\n */\nconst ComposerLink = forwardRef<HTMLAnchorElement, ComposerLinkProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"a\";\n\n return (\n <Component\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Contains suggestions within `Composer.Editor`.\n */\nconst ComposerSuggestions = forwardRef<\n HTMLDivElement,\n ComposerSuggestionsProps\n>(({ children, style, asChild, ...props }, forwardedRef) => {\n const [isPresent] = usePersist();\n const ref = useRef<HTMLDivElement>(null);\n const {\n ref: contentRef,\n placement,\n dir,\n } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_NAME);\n const mergedRefs = useRefs(forwardedRef, contentRef, ref);\n const [side, align] = useMemo(\n () => getSideAndAlignFromPlacement(placement),\n [placement]\n );\n const Component = asChild ? Slot : \"div\";\n useAnimationPersist(ref);\n\n return (\n <Component\n dir={dir}\n {...props}\n data-state={isPresent ? \"open\" : \"closed\"}\n data-side={side}\n data-align={align}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n maxHeight: \"var(--lb-composer-suggestions-available-height)\",\n overflowY: \"auto\",\n ...style,\n }}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a list of suggestions within `Composer.Editor`.\n *\n * @example\n * <Composer.SuggestionsList>\n * {userIds.map((userId) => (\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n * ))}\n * </Composer.SuggestionsList>\n */\nconst ComposerSuggestionsList = forwardRef<\n HTMLUListElement,\n ComposerSuggestionsListProps\n>(({ children, asChild, ...props }, forwardedRef) => {\n const { id } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_NAME);\n const Component = asChild ? Slot : \"ul\";\n\n return (\n <Component\n role=\"listbox\"\n id={id}\n aria-label=\"Suggestions list\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a suggestion within `Composer.SuggestionsList`.\n *\n * @example\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n */\nconst ComposerSuggestionsListItem = forwardRef<\n HTMLLIElement,\n ComposerSuggestionsListItemProps\n>(\n (\n {\n value,\n children,\n onPointerMove,\n onPointerDown,\n onClick,\n asChild,\n ...props\n },\n forwardedRef\n ) => {\n const ref = useRef<HTMLLIElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const { selectedValue, setSelectedValue, itemId, onItemSelect } =\n useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_ITEM_NAME);\n const Component = asChild ? Slot : \"li\";\n const isSelected = useMemo(\n () => selectedValue === value,\n [selectedValue, value]\n );\n // TODO: Support props.id if provided, it will need to be sent up to Composer.Editor to use it in aria-activedescendant\n const id = useMemo(() => itemId(value), [itemId, value]);\n\n useEffect(() => {\n if (ref?.current && isSelected) {\n ref.current.scrollIntoView({ block: \"nearest\" });\n }\n }, [isSelected]);\n\n const handlePointerMove = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerMove?.(event);\n\n if (!event.isDefaultPrevented()) {\n setSelectedValue(value);\n }\n },\n [onPointerMove, setSelectedValue, value]\n );\n\n const handlePointerDown = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerDown?.(event);\n\n event.preventDefault();\n event.stopPropagation();\n },\n [onPointerDown]\n );\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n\n const wasDefaultPrevented = event.isDefaultPrevented();\n\n event.preventDefault();\n event.stopPropagation();\n\n if (!wasDefaultPrevented) {\n onItemSelect(value);\n }\n },\n [onClick, onItemSelect, value]\n );\n\n return (\n <Component\n role=\"option\"\n id={id}\n data-selected={isSelected || undefined}\n aria-selected={isSelected || undefined}\n onPointerMove={handlePointerMove}\n onPointerDown={handlePointerDown}\n onClick={handleClick}\n {...props}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n }\n);\n\nconst defaultEditorComponents: ComposerEditorComponents = {\n Link: ({ href, children }) => {\n return <ComposerLink href={href}>{children}</ComposerLink>;\n },\n Mention: ({ userId }) => {\n return (\n <ComposerMention>\n {MENTION_CHARACTER}\n {userId}\n </ComposerMention>\n );\n },\n MentionSuggestions: ({ userIds }) => {\n return userIds.length > 0 ? (\n <ComposerSuggestions>\n <ComposerSuggestionsList>\n {userIds.map((userId) => (\n <ComposerSuggestionsListItem key={userId} value={userId}>\n {userId}\n </ComposerSuggestionsListItem>\n ))}\n </ComposerSuggestionsList>\n </ComposerSuggestions>\n ) : null;\n },\n};\n\n/**\n * Displays the composer's editor.\n *\n * @example\n * <Composer.Editor placeholder=\"Write a comment…\" />\n */\nconst ComposerEditor = forwardRef<HTMLDivElement, ComposerEditorProps>(\n (\n {\n defaultValue,\n onKeyDown,\n onFocus,\n onBlur,\n disabled,\n autoFocus,\n components,\n dir,\n ...props\n },\n forwardedRef\n ) => {\n const { editor, validate, setFocused, roomId } = useComposerEditorContext();\n const {\n submit,\n focus,\n select,\n canSubmit,\n isDisabled: isComposerDisabled,\n isFocused,\n } = useComposer();\n const isDisabled = isComposerDisabled || disabled;\n const initialBody = useInitial(defaultValue ?? emptyCommentBody);\n const initialEditorValue = useMemo(() => {\n return commentBodyToComposerBody(initialBody);\n }, [initialBody]);\n const { Link, Mention, MentionSuggestions } = useMemo(\n () => ({ ...defaultEditorComponents, ...components }),\n [components]\n );\n\n const [mentionDraft, setMentionDraft] = useState<MentionDraft>();\n const mentionSuggestions = useMentionSuggestions(\n roomId,\n mentionDraft?.text\n );\n const [\n selectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setNextSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n ] = useIndex(0, mentionSuggestions?.length ?? 0);\n const id = useId();\n const suggestionsListId = useMemo(\n () => `liveblocks-suggestions-list-${id}`,\n [id]\n );\n const suggestionsListItemId = useCallback(\n (userId?: string) =>\n userId ? `liveblocks-suggestions-list-item-${id}-${userId}` : undefined,\n [id]\n );\n const renderElement = useCallback(\n (props: RenderElementProps) => {\n return (\n <ComposerEditorElement Mention={Mention} Link={Link} {...props} />\n );\n },\n [Link, Mention]\n );\n\n const handleChange = useCallback(\n (value: SlateDescendant[]) => {\n validate(value as SlateElement[]);\n\n setMentionDraft(getMentionDraftAtSelection(editor));\n },\n [editor, validate]\n );\n\n const createMention = useCallback(\n (userId?: string) => {\n if (!mentionDraft || !userId) {\n return;\n }\n\n SlateTransforms.select(editor, mentionDraft.range);\n insertMention(editor, userId);\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n },\n [editor, mentionDraft, setSelectedMentionSuggestionIndex]\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n // Allow leaving marks with ArrowLeft\n if (isKey(event, \"ArrowLeft\")) {\n leaveMarkEdge(editor, \"start\");\n }\n\n // Allow leaving marks with ArrowRight\n if (isKey(event, \"ArrowRight\")) {\n leaveMarkEdge(editor, \"end\");\n }\n\n if (mentionDraft && mentionSuggestions?.length) {\n // Select the next mention suggestion on ArrowDown\n if (isKey(event, \"ArrowDown\")) {\n event.preventDefault();\n setNextSelectedMentionSuggestionIndex();\n }\n\n // Select the previous mention suggestion on ArrowUp\n if (isKey(event, \"ArrowUp\")) {\n event.preventDefault();\n setPreviousSelectedMentionSuggestionIndex();\n }\n\n // Create a mention on Enter/Tab\n if (isKey(event, \"Enter\") || isKey(event, \"Tab\")) {\n event.preventDefault();\n\n const userId = mentionSuggestions?.[selectedMentionSuggestionIndex];\n createMention(userId);\n }\n\n // Close the suggestions on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n }\n } else {\n // Blur the editor on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n ReactEditor.blur(editor);\n }\n\n // Submit the editor on Enter\n if (isKey(event, \"Enter\", { shift: false })) {\n // Even if submitting is not possible, don't do anything else on Enter. (e.g. creating a new line)\n event.preventDefault();\n\n if (canSubmit) {\n submit();\n }\n }\n\n // Create a new line on Shift + Enter\n if (isKey(event, \"Enter\", { shift: true })) {\n event.preventDefault();\n editor.insertBreak();\n }\n\n // Toggle bold on Command/Control + B\n if (isKey(event, \"b\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"bold\");\n }\n\n // Toggle italic on Command/Control + I\n if (isKey(event, \"i\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"italic\");\n }\n\n // Toggle strikethrough on Command/Control + Shift + S\n if (isKey(event, \"s\", { mod: true, shift: true })) {\n event.preventDefault();\n toggleMark(editor, \"strikethrough\");\n }\n\n // Toggle code on Command/Control + E\n if (isKey(event, \"e\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"code\");\n }\n }\n },\n [\n createMention,\n editor,\n canSubmit,\n mentionDraft,\n mentionSuggestions,\n selectedMentionSuggestionIndex,\n onKeyDown,\n setNextSelectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n submit,\n ]\n );\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(true);\n }\n },\n [onFocus, setFocused]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(false);\n }\n },\n [onBlur, setFocused]\n );\n\n const selectedMentionSuggestionUserId = useMemo(\n () => mentionSuggestions?.[selectedMentionSuggestionIndex],\n [selectedMentionSuggestionIndex, mentionSuggestions]\n );\n const setSelectedMentionSuggestionUserId = useCallback(\n (userId: string) => {\n const index = mentionSuggestions?.indexOf(userId);\n\n if (index !== undefined && index >= 0) {\n setSelectedMentionSuggestionIndex(index);\n }\n },\n [setSelectedMentionSuggestionIndex, mentionSuggestions]\n );\n\n const propsWhileSuggesting: AriaAttributes = useMemo(\n () =>\n mentionDraft\n ? {\n role: \"combobox\",\n \"aria-autocomplete\": \"list\",\n \"aria-expanded\": true,\n \"aria-controls\": suggestionsListId,\n \"aria-activedescendant\": suggestionsListItemId(\n selectedMentionSuggestionUserId\n ),\n }\n : {},\n [\n mentionDraft,\n suggestionsListId,\n suggestionsListItemId,\n selectedMentionSuggestionUserId,\n ]\n );\n\n useImperativeHandle(forwardedRef, () => {\n return ReactEditor.toDOMNode(editor, editor) as HTMLDivElement;\n }, [editor]);\n\n // Manually focus the editor when `autoFocus` is true\n useEffect(() => {\n if (autoFocus) {\n focus();\n }\n }, [autoFocus, editor, focus]);\n\n // Manually add a selection in the editor if the selection\n // is still empty after being focused\n useEffect(() => {\n if (isFocused && editor.selection === null) {\n select();\n }\n }, [editor, select, isFocused]);\n\n return (\n <Slate\n editor={editor}\n initialValue={initialEditorValue}\n onChange={handleChange}\n >\n <Editable\n dir={dir}\n enterKeyHint={mentionDraft ? \"enter\" : \"send\"}\n autoCapitalize=\"sentences\"\n aria-label=\"Composer editor\"\n data-focused={isFocused || undefined}\n data-disabled={isDisabled || undefined}\n {...propsWhileSuggesting}\n {...props}\n readOnly={isDisabled}\n disabled={isDisabled}\n onKeyDown={handleKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n renderElement={renderElement}\n renderLeaf={ComposerEditorLeaf}\n renderPlaceholder={ComposerEditorPlaceholder}\n />\n <ComposerEditorMentionSuggestionsWrapper\n dir={dir}\n mentionDraft={mentionDraft}\n selectedUserId={selectedMentionSuggestionUserId}\n setSelectedUserId={setSelectedMentionSuggestionUserId}\n userIds={mentionSuggestions}\n id={suggestionsListId}\n itemId={suggestionsListItemId}\n onItemSelect={createMention}\n MentionSuggestions={MentionSuggestions}\n />\n </Slate>\n );\n }\n);\n\nconst MAX_ATTACHMENTS = 10;\nconst MAX_ATTACHMENT_SIZE = 1024 * 1024 * 1024; // 1 GB\n\nfunction prepareAttachment(file: File): CommentLocalAttachment {\n return {\n type: \"localAttachment\",\n status: \"idle\",\n id: createCommentAttachmentId(),\n name: file.name,\n size: file.size,\n mimeType: file.type,\n file,\n };\n}\n\n/**\n * Surrounds the composer's content and handles submissions.\n *\n * @example\n * <Composer.Form onComposerSubmit={({ body }) => {}}>\n *\t <Composer.Editor />\n * <Composer.Submit />\n * </Composer.Form>\n */\nconst ComposerForm = forwardRef<HTMLFormElement, ComposerFormProps>(\n (\n {\n children,\n onSubmit,\n onComposerSubmit,\n defaultAttachments = [],\n pasteFilesAsAttachments,\n preventUnsavedChanges = true,\n disabled,\n asChild,\n roomId: _roomId,\n ...props\n },\n forwardedRef\n ) => {\n const Component = asChild ? Slot : \"form\";\n const [isEmpty, setEmpty] = useState(true);\n const [isSubmitting, setSubmitting] = useState(false);\n const [isFocused, setFocused] = useState(false);\n const room = useRoomOrNull();\n\n const roomId = _roomId !== undefined ? _roomId : room?.id;\n if (roomId === undefined) {\n throw new Error(\"Composer.Form must be a descendant of RoomProvider.\");\n }\n\n // Later: Offer as Composer.Form props: { maxAttachments: number; maxAttachmentSize: number; supportedAttachmentMimeTypes: string[]; }\n const maxAttachments = MAX_ATTACHMENTS;\n const maxAttachmentSize = MAX_ATTACHMENT_SIZE;\n\n const {\n attachments,\n isUploadingAttachments,\n addAttachments,\n removeAttachment,\n clearAttachments,\n } = useComposerAttachmentsManager(defaultAttachments, {\n maxFileSize: maxAttachmentSize,\n roomId,\n });\n const numberOfAttachments = attachments.length;\n const hasMaxAttachments = numberOfAttachments >= maxAttachments;\n\n const isDisabled = useMemo(() => {\n return isSubmitting || disabled === true;\n }, [isSubmitting, disabled]);\n const canSubmit = useMemo(() => {\n return !isEmpty && !isUploadingAttachments;\n }, [isEmpty, isUploadingAttachments]);\n const ref = useRef<HTMLFormElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const syncSource = useSyncSource();\n\n // Mark the composer as a pending update when it has unsubmitted (draft)\n // text or attachments\n const isPending = !preventUnsavedChanges\n ? false\n : !isEmpty || isUploadingAttachments || attachments.length > 0;\n\n useEffect(() => {\n syncSource?.setSyncStatus(\n isPending ? \"has-local-changes\" : \"synchronized\"\n );\n }, [syncSource, isPending]);\n\n const createAttachments = useCallback(\n (files: File[]) => {\n if (!files.length) {\n return;\n }\n\n const numberOfAcceptedFiles = Math.max(\n 0,\n maxAttachments - numberOfAttachments\n );\n\n files.splice(numberOfAcceptedFiles);\n\n const attachments = files.map((file) => prepareAttachment(file));\n\n addAttachments(attachments);\n },\n [addAttachments, maxAttachments, numberOfAttachments]\n );\n\n const createAttachmentsRef = useRef(createAttachments);\n\n useEffect(() => {\n createAttachmentsRef.current = createAttachments;\n }, [createAttachments]);\n\n const stableCreateAttachments = useCallback((files: File[]) => {\n createAttachmentsRef.current(files);\n }, []);\n\n const editor = useInitial(() =>\n createComposerEditor({\n createAttachments: stableCreateAttachments,\n pasteFilesAsAttachments,\n })\n );\n\n const validate = useCallback(\n (value: SlateElement[]) => {\n setEmpty(isEditorEmpty(editor, value));\n },\n [editor]\n );\n\n const submit = useCallback(() => {\n if (!canSubmit) {\n return;\n }\n\n // We need to wait for the next frame in some cases like when composing diacritics,\n // we want any native handling to be done first while still being handled on `keydown`.\n requestAnimationFrame(() => {\n if (ref.current) {\n requestSubmit(ref.current);\n }\n });\n }, [canSubmit]);\n\n const clear = useCallback(() => {\n SlateTransforms.delete(editor, {\n at: {\n anchor: SlateEditor.start(editor, []),\n focus: SlateEditor.end(editor, []),\n },\n });\n }, [editor]);\n\n const select = useCallback(() => {\n SlateTransforms.select(editor, {\n anchor: SlateEditor.end(editor, []),\n focus: SlateEditor.end(editor, []),\n });\n }, [editor]);\n\n const focus = useCallback(\n (resetSelection = true) => {\n if (!ReactEditor.isFocused(editor)) {\n SlateTransforms.select(\n editor,\n resetSelection || !editor.selection\n ? SlateEditor.end(editor, [])\n : editor.selection\n );\n ReactEditor.focus(editor);\n }\n },\n [editor]\n );\n\n const blur = useCallback(() => {\n ReactEditor.blur(editor);\n }, [editor]);\n\n const createMention = useCallback(() => {\n if (disabled) {\n return;\n }\n\n focus();\n insertMentionCharacter(editor);\n }, [disabled, editor, focus]);\n\n const insertText = useCallback(\n (text: string) => {\n if (disabled) {\n return;\n }\n\n focus(false);\n insertSlateText(editor, text);\n },\n [disabled, editor, focus]\n );\n\n const attachFiles = useCallback(() => {\n if (disabled) {\n return;\n }\n\n if (fileInputRef.current) {\n fileInputRef.current.click();\n }\n }, [disabled]);\n\n const handleAttachmentsInputChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n if (disabled) {\n return;\n }\n\n if (event.target.files) {\n createAttachments(Array.from(event.target.files));\n\n // Reset the input value to allow selecting the same file(s) again\n event.target.value = \"\";\n }\n },\n [createAttachments, disabled]\n );\n\n const onSubmitEnd = useCallback(() => {\n clear();\n blur();\n clearAttachments();\n setSubmitting(false);\n }, [blur, clear, clearAttachments]);\n\n const handleSubmit = useCallback(\n (event: FormEvent<HTMLFormElement>) => {\n if (disabled) {\n return;\n }\n\n // In some situations (e.g. pressing Enter while composing diacritics), it's possible\n // for the form to be submitted as empty even though we already checked whether the\n // editor was empty when handling the key press.\n const isEmpty = isEditorEmpty(editor, editor.children);\n\n // We even prevent the user's `onSubmit` handler from being called if the editor is empty.\n if (isEmpty) {\n event.preventDefault();\n\n return;\n }\n\n onSubmit?.(event);\n\n if (!onComposerSubmit || event.isDefaultPrevented()) {\n event.preventDefault();\n\n return;\n }\n\n const body = composerBodyToCommentBody(\n editor.children as ComposerBodyData\n );\n // Only non-local attachments are included to be submitted.\n const commentAttachments: CommentAttachment[] = attachments\n .filter(\n (attachment) =>\n attachment.type === \"attachment\" ||\n (attachment.type === \"localAttachment\" &&\n attachment.status === \"uploaded\")\n )\n .map((attachment) => {\n return {\n id: attachment.id,\n type: \"attachment\",\n mimeType: attachment.mimeType,\n size: attachment.size,\n name: attachment.name,\n };\n });\n\n const promise = onComposerSubmit(\n { body, attachments: commentAttachments },\n event\n );\n\n event.preventDefault();\n\n if (promise) {\n setSubmitting(true);\n promise.then(onSubmitEnd);\n } else {\n onSubmitEnd();\n }\n },\n [disabled, editor, attachments, onComposerSubmit, onSubmit, onSubmitEnd]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <ComposerEditorContext.Provider\n value={{\n editor,\n validate,\n setFocused,\n roomId,\n }}\n >\n <ComposerAttachmentsContext.Provider\n value={{\n createAttachments,\n isUploadingAttachments,\n hasMaxAttachments,\n maxAttachments,\n maxAttachmentSize,\n }}\n >\n <ComposerContext.Provider\n value={{\n isDisabled,\n isFocused,\n isEmpty,\n canSubmit,\n submit,\n clear,\n select,\n focus,\n blur,\n createMention,\n insertText,\n attachments,\n attachFiles,\n removeAttachment,\n }}\n >\n <Component {...props} onSubmit={handleSubmit} ref={mergedRefs}>\n <input\n type=\"file\"\n multiple\n ref={fileInputRef}\n onChange={handleAttachmentsInputChange}\n onClick={stopPropagation}\n tabIndex={-1}\n style={{ display: \"none\" }}\n />\n <Slottable>{children}</Slottable>\n </Component>\n </ComposerContext.Provider>\n </ComposerAttachmentsContext.Provider>\n </ComposerEditorContext.Provider>\n );\n }\n);\n\n/**\n * A button to submit the composer.\n *\n * @example\n * <Composer.Submit>Send</Composer.Submit>\n */\nconst ComposerSubmit = forwardRef<HTMLButtonElement, ComposerSubmitProps>(\n ({ children, disabled, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"button\";\n const { canSubmit, isDisabled: isComposerDisabled } = useComposer();\n const isDisabled = isComposerDisabled || disabled || !canSubmit;\n\n return (\n <Component\n type=\"submit\"\n {...props}\n ref={forwardedRef}\n disabled={isDisabled}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * A button which opens a file picker to create attachments.\n *\n * @example\n * <Composer.AttachFiles>Attach files</Composer.AttachFiles>\n */\nconst ComposerAttachFiles = forwardRef<\n HTMLButtonElement,\n ComposerAttachFilesProps\n>(({ children, onClick, disabled, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"button\";\n const { hasMaxAttachments } = useComposerAttachmentsContext();\n const { isDisabled: isComposerDisabled, attachFiles } = useComposer();\n const isDisabled = isComposerDisabled || hasMaxAttachments || disabled;\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n attachFiles();\n }\n },\n [attachFiles, onClick]\n );\n\n return (\n <Component\n type=\"button\"\n {...props}\n onClick={handleClick}\n ref={forwardedRef}\n disabled={isDisabled}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * A drop area which accepts files to create attachments.\n *\n * @example\n * <Composer.AttachmentsDropArea>\n * Drop files here\n * </Composer.AttachmentsDropArea>\n */\nconst ComposerAttachmentsDropArea = forwardRef<\n HTMLDivElement,\n ComposerAttachmentsDropAreaProps\n>(\n (\n {\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n disabled,\n asChild,\n ...props\n },\n forwardedRef\n ) => {\n const Component = asChild ? Slot : \"div\";\n const { isDisabled: isComposerDisabled } = useComposer();\n const isDisabled = isComposerDisabled || disabled;\n const [, dropAreaProps] = useComposerAttachmentsDropArea({\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n disabled: isDisabled,\n });\n\n return (\n <Component\n {...dropAreaProps}\n data-disabled={isDisabled ? \"\" : undefined}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n);\n\nif (process.env.NODE_ENV !== \"production\") {\n ComposerAttachFiles.displayName = COMPOSER_ATTACH_FILES_NAME;\n ComposerAttachmentsDropArea.displayName = COMPOSER_ATTACHMENTS_DROP_AREA_NAME;\n ComposerEditor.displayName = COMPOSER_EDITOR_NAME;\n ComposerForm.displayName = COMPOSER_FORM_NAME;\n ComposerMention.displayName = COMPOSER_MENTION_NAME;\n ComposerLink.displayName = COMPOSER_LINK_NAME;\n ComposerSubmit.displayName = COMPOSER_SUBMIT_NAME;\n ComposerSuggestions.displayName = COMPOSER_SUGGESTIONS_NAME;\n ComposerSuggestionsList.displayName = COMPOSER_SUGGESTIONS_LIST_NAME;\n ComposerSuggestionsListItem.displayName = COMPOSER_SUGGESTIONS_LIST_ITEM_NAME;\n}\n\n// NOTE: Every export from this file will be available publicly as Composer.*\nexport {\n ComposerAttachFiles as AttachFiles,\n ComposerAttachmentsDropArea as AttachmentsDropArea,\n ComposerEditor as Editor,\n ComposerForm as Form,\n ComposerLink as Link,\n ComposerMention as Mention,\n ComposerSubmit as Submit,\n ComposerSuggestions as Suggestions,\n ComposerSuggestionsList as SuggestionsList,\n ComposerSuggestionsListItem as SuggestionsListItem,\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgJA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAsC;AAC3B;AAEX;AAEA;AAA8B;AAC5B;AAEF;AAIE;AAAO;AACL;AACE;AACE;AACE;AACE;AACoD;AAChD;AACA;AACD;AACH;AACF;AACF;AACF;AACF;AAEJ;AAEA;AAAsC;AACpC;AACA;AACA;AAEF;AACE;AAEA;AACG;AAAS;AAEL;AAAwB;AAAI;AAKrC;AAEA;AAAmC;AACjC;AACA;AACA;AAEF;AACE;AAAa;AACiC;AAChC;AAGd;AACG;AAAS;AACP;AAAK;AAGZ;AAEA;AAAiD;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAqD;AAC1C;AAGX;AAAO;AACK;AACuC;AACrC;AACyC;AACzB;AACpB;AACD;AACiB;AACrB;AACI;AACA;AAED;AAAwB;AACtB;AACG;AAEL;AAAwB;AACtB;AACG;AACL;AACF;AACD;AACH;AAEE;AAA2B;AACT;AACjB;AACH;AACF;AAEF;AAAM;AAC8B;AAClC;AACA;AACA;AACA;AACA;AAKF;AACE;AACE;AAAwD;AAC1D;AAGF;AACE;AACE;AAAA;AAGF;AAEA;AACE;AAAa;AACiD;AACd;AAC/C;AACH;AAGF;AAGO;AACQ;AACL;AACA;AACe;AACG;AAClB;AACA;AACA;AACK;AACP;AAEC;AACM;AACM;AACJ;AACK;AACL;AACC;AAGF;AACM;AACF;AACV;AAEC;AACC;AACA;AAOd;AAEA;AAA+B;AAC7B;AACA;AAEF;AAEE;AAEA;AAAsB;AAElB;AACG;AACC;AACK;AACP;AAEC;AAEH;AACG;AACC;AACK;AAGP;AAGF;AACG;AAAM;AAA0C;AAEjD;AAGF;AAAO;AAEb;AAGA;AACE;AACE;AAA6B;AAG/B;AACE;AAAyB;AAG3B;AACE;AAAwB;AAG1B;AACE;AAA2B;AAG7B;AAAQ;AAAS;AACnB;AAEA;AAAmC;AACjC;AAEF;AACE;AAEA;AACG;AAAS;AAAY;AAA+B;AAIzD;AAQA;AAAwB;AAEpB;AACA;AAEA;AACG;AAC8B;AACzB;AACC;AAGP;AAGN;AAQA;AAAqB;AAEjB;AAEA;AACG;AACQ;AACH;AACA;AACC;AAGP;AAGN;AAKM;AAIJ;AACA;AACA;AAAM;AACC;AACL;AACA;AAEF;AACA;AAAsB;AACwB;AAClC;AAEZ;AACA;AAEA;AACG;AACC;AACI;AAC6B;AACtB;AACC;AACL;AACI;AACM;AACJ;AACA;AACR;AACL;AACK;AAKX;AAcM;AAIJ;AACA;AAEA;AACG;AACM;AACL;AACW;AACP;AACC;AAKX;AAUA;AAAoC;AAKhC;AACE;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AAEA;AACA;AAAmB;AACO;AACH;AAGvB;AAEA;AACE;AACE;AAA+C;AACjD;AAGF;AAA0B;AAEtB;AAEA;AACE;AAAsB;AACxB;AACF;AACuC;AAGzC;AAA0B;AAEtB;AAEA;AACA;AAAsB;AACxB;AACc;AAGhB;AAAoB;AAEhB;AAEA;AAEA;AACA;AAEA;AACE;AAAkB;AACpB;AACF;AAC6B;AAG/B;AACG;AACM;AACL;AAC6B;AACA;AACd;AACA;AACN;AACL;AACC;AAGP;AAGN;AAEA;AAA0D;AAEtD;AAAQ;AAAa;AAAsB;AAC7C;AAEE;AAIE;AAEJ;AAEE;AAIS;AAAiC;AAAe;AAMrD;AAER;AAQA;AAAuB;AAEnB;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AAAM;AACJ;AACA;AACA;AACA;AACY;AACZ;AAEF;AACA;AACA;AACE;AAA4C;AAE9C;AAA8C;AACO;AACxC;AAGb;AACA;AAA2B;AACzB;AACc;AAEhB;AAAM;AACJ;AACA;AACA;AACA;AAEF;AACA;AAA0B;AACa;AAClC;AAEL;AAA8B;AAEoC;AAC7D;AAEL;AAAsB;AAElB;AACG;AAAsB;AAAkB;AAAgB;AAAO;AAEpE;AACc;AAGhB;AAAqB;AAEjB;AAEA;AAAkD;AACpD;AACiB;AAGnB;AAAsB;AAElB;AACE;AAAA;AAGF;AACA;AACA;AACA;AAAmC;AACrC;AACwD;AAG1D;AAAsB;AAElB;AAEA;AACE;AAAA;AAIF;AACE;AAA6B;AAI/B;AACE;AAA2B;AAG7B;AAEE;AACE;AACA;AAAsC;AAIxC;AACE;AACA;AAA0C;AAI5C;AACE;AAEA;AACA;AAAoB;AAItB;AACE;AACA;AACA;AAAmC;AACrC;AAGA;AACE;AACA;AAAuB;AAIzB;AAEE;AAEA;AACE;AAAO;AACT;AAIF;AACE;AACA;AAAmB;AAIrB;AACE;AACA;AAAyB;AAI3B;AACE;AACA;AAA2B;AAI7B;AACE;AACA;AAAkC;AAIpC;AACE;AACA;AAAyB;AAC3B;AACF;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AAAoB;AAEhB;AAEA;AACE;AAAe;AACjB;AACF;AACoB;AAGtB;AAAmB;AAEf;AAEA;AACE;AAAgB;AAClB;AACF;AACmB;AAGrB;AAAwC;AACX;AACwB;AAErD;AAA2C;AAEvC;AAEA;AACE;AAAuC;AACzC;AACF;AACsD;AAGxD;AAA6C;AAGrC;AACQ;AACe;AACJ;AACA;AACQ;AACvB;AACF;AAED;AACP;AACE;AACA;AACA;AACA;AACF;AAGF;AACE;AAA2C;AAI7C;AACE;AACE;AAAM;AACR;AAKF;AACE;AACE;AAAO;AACT;AAGF;AACG;AACC;AACc;AACJ;AAET;AACC;AACuC;AACxB;AACJ;AACgB;AACE;AACzB;AACA;AACM;AACA;AACC;AACF;AACD;AACR;AACY;AACO;AAEpB;AACC;AACA;AACgB;AACG;AACV;AACL;AACI;AACM;AACd;AAEJ;AAGN;AAEA;AACA;AAEA;AACE;AAAO;AACC;AACE;AACsB;AACnB;AACA;AACI;AACf;AAEJ;AAWA;AAAqB;AAEjB;AACE;AACA;AACA;AACsB;AACtB;AACwB;AACxB;AACA;AACQ;AACL;AAIL;AACA;AACA;AACA;AACA;AAEA;AACA;AACE;AAAqE;AAIvE;AACA;AAEA;AAAM;AACJ;AACA;AACA;AACA;AACA;AACoD;AACvC;AACb;AAEF;AACA;AAEA;AACE;AAAoC;AAEtC;AACE;AAAoB;AAEtB;AACA;AACA;AACA;AAIA;AAIA;AACE;AAAY;AACwB;AACpC;AAGF;AAA0B;AAEtB;AACE;AAAA;AAGF;AAAmC;AACjC;AACiB;AAGnB;AAEA;AAEA;AAA0B;AAC5B;AACoD;AAGtD;AAEA;AACE;AAA+B;AAGjC;AACE;AAAkC;AAGpC;AAAe;AACQ;AACA;AACnB;AACD;AAGH;AAAiB;AAEb;AAAqC;AACvC;AACO;AAGT;AACE;AACE;AAAA;AAKF;AACE;AACE;AAAyB;AAC3B;AACD;AAGH;AACE;AAA+B;AACzB;AACkC;AACH;AACnC;AACD;AAGH;AACE;AAA+B;AACK;AACD;AAClC;AAGH;AAAc;AAEV;AACE;AAAgB;AACd;AAGW;AAEb;AAAwB;AAC1B;AACF;AACO;AAGT;AACE;AAAuB;AAGzB;AACE;AACE;AAAA;AAGF;AACA;AAA6B;AAG/B;AAAmB;AAEf;AACE;AAAA;AAGF;AACA;AAA4B;AAC9B;AACwB;AAG1B;AACE;AACE;AAAA;AAGF;AACE;AAA2B;AAC7B;AAGF;AAAqC;AAEjC;AACE;AAAA;AAGF;AACE;AAGA;AAAqB;AACvB;AACF;AAC4B;AAG9B;AACE;AACA;AACA;AACA;AAAmB;AAGrB;AAAqB;AAEjB;AACE;AAAA;AAMF;AAGA;AACE;AAEA;AAAA;AAGF;AAEA;AACE;AAEA;AAAA;AAGF;AAAa;AACJ;AAGT;AACG;AAI2B;AAG1B;AAAO;AACU;AACT;AACe;AACJ;AACA;AACnB;AAGJ;AAAgB;AAC0B;AACxC;AAGF;AAEA;AACE;AACA;AAAwB;AAExB;AAAY;AACd;AACF;AACuE;AAGzE;AACE;AAAsB;AAGxB;AACG;AACQ;AACL;AACA;AACA;AACA;AACF;AAEC;AACQ;AACL;AACA;AACA;AACA;AACA;AACF;AAEC;AACQ;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAEC;AAAc;AAAiB;AAAmB;AAChD;AACM;AACG;AACH;AACK;AACD;AACC;AACe;AAMnC;AAGN;AAQA;AAAuB;AAEnB;AACA;AACA;AAEA;AACG;AACM;AACD;AACC;AACK;AAGZ;AAGN;AAQM;AAIJ;AACA;AACA;AACA;AAEA;AAAoB;AAEhB;AAEA;AACE;AAAY;AACd;AACF;AACqB;AAGvB;AACG;AACM;AACD;AACK;AACJ;AACK;AAKhB;AAUA;AAAoC;AAKhC;AACE;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AAAyD;AACvD;AACA;AACA;AACA;AACU;AAGZ;AACG;AACK;AAC6B;AAC7B;AACC;AACP;AAGN;AAEA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;;;;;;;;;;;"}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { flip, hide, shift, limitShift, size, autoUpdate, useFloating } from '@floating-ui/react-dom';
3
- import { useRoom } from '@liveblocks/react';
4
- import { useMentionSuggestions, useSyncSource } from '@liveblocks/react/_private';
3
+ import { createCommentAttachmentId } from '@liveblocks/core';
4
+ import { useMentionSuggestions, useRoomOrNull, useSyncSource } from '@liveblocks/react/_private';
5
5
  import { Slottable, Slot } from '@radix-ui/react-slot';
6
6
  import React__default, { forwardRef, useRef, useMemo, useEffect, useCallback, useState, useImperativeHandle } from 'react';
7
7
  import { Transforms, Editor, insertText, createEditor } from 'slate';
@@ -416,7 +416,7 @@ const ComposerEditor = forwardRef(
416
416
  dir,
417
417
  ...props
418
418
  }, forwardedRef) => {
419
- const { editor, validate, setFocused } = useComposerEditorContext();
419
+ const { editor, validate, setFocused, roomId } = useComposerEditorContext();
420
420
  const {
421
421
  submit,
422
422
  focus,
@@ -435,7 +435,10 @@ const ComposerEditor = forwardRef(
435
435
  [components]
436
436
  );
437
437
  const [mentionDraft, setMentionDraft] = useState();
438
- const mentionSuggestions = useMentionSuggestions(mentionDraft?.text);
438
+ const mentionSuggestions = useMentionSuggestions(
439
+ roomId,
440
+ mentionDraft?.text
441
+ );
439
442
  const [
440
443
  selectedMentionSuggestionIndex,
441
444
  setPreviousSelectedMentionSuggestionIndex,
@@ -655,6 +658,17 @@ const ComposerEditor = forwardRef(
655
658
  );
656
659
  const MAX_ATTACHMENTS = 10;
657
660
  const MAX_ATTACHMENT_SIZE = 1024 * 1024 * 1024;
661
+ function prepareAttachment(file) {
662
+ return {
663
+ type: "localAttachment",
664
+ status: "idle",
665
+ id: createCommentAttachmentId(),
666
+ name: file.name,
667
+ size: file.size,
668
+ mimeType: file.type,
669
+ file
670
+ };
671
+ }
658
672
  const ComposerForm = forwardRef(
659
673
  ({
660
674
  children,
@@ -665,13 +679,18 @@ const ComposerForm = forwardRef(
665
679
  preventUnsavedChanges = true,
666
680
  disabled,
667
681
  asChild,
682
+ roomId: _roomId,
668
683
  ...props
669
684
  }, forwardedRef) => {
670
685
  const Component = asChild ? Slot : "form";
671
- const room = useRoom();
672
686
  const [isEmpty$1, setEmpty] = useState(true);
673
687
  const [isSubmitting, setSubmitting] = useState(false);
674
688
  const [isFocused, setFocused] = useState(false);
689
+ const room = useRoomOrNull();
690
+ const roomId = _roomId !== void 0 ? _roomId : room?.id;
691
+ if (roomId === void 0) {
692
+ throw new Error("Composer.Form must be a descendant of RoomProvider.");
693
+ }
675
694
  const maxAttachments = MAX_ATTACHMENTS;
676
695
  const maxAttachmentSize = MAX_ATTACHMENT_SIZE;
677
696
  const {
@@ -681,15 +700,14 @@ const ComposerForm = forwardRef(
681
700
  removeAttachment,
682
701
  clearAttachments
683
702
  } = useComposerAttachmentsManager(defaultAttachments, {
684
- maxFileSize: maxAttachmentSize
703
+ maxFileSize: maxAttachmentSize,
704
+ roomId
685
705
  });
686
706
  const numberOfAttachments = attachments.length;
687
707
  const hasMaxAttachments = numberOfAttachments >= maxAttachments;
688
708
  const isDisabled = useMemo(() => {
689
- const self = room.getSelf();
690
- const canComment = self?.canComment ?? true;
691
- return isSubmitting || disabled || !canComment;
692
- }, [isSubmitting, disabled, room]);
709
+ return isSubmitting || disabled === true;
710
+ }, [isSubmitting, disabled]);
693
711
  const canSubmit = useMemo(() => {
694
712
  return !isEmpty$1 && !isUploadingAttachments;
695
713
  }, [isEmpty$1, isUploadingAttachments]);
@@ -713,10 +731,10 @@ const ComposerForm = forwardRef(
713
731
  maxAttachments - numberOfAttachments
714
732
  );
715
733
  files.splice(numberOfAcceptedFiles);
716
- const attachments2 = files.map((file) => room.prepareAttachment(file));
734
+ const attachments2 = files.map((file) => prepareAttachment(file));
717
735
  addAttachments(attachments2);
718
736
  },
719
- [addAttachments, maxAttachments, numberOfAttachments, room]
737
+ [addAttachments, maxAttachments, numberOfAttachments]
720
738
  );
721
739
  const createAttachmentsRef = useRef(createAttachments);
722
740
  useEffect(() => {
@@ -869,7 +887,8 @@ const ComposerForm = forwardRef(
869
887
  value: {
870
888
  editor,
871
889
  validate,
872
- setFocused
890
+ setFocused,
891
+ roomId
873
892
  }
874
893
  }, /* @__PURE__ */ React__default.createElement(ComposerAttachmentsContext.Provider, {
875
894
  value: {