@liveblocks/react-ui 3.1.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/_private/index.d.cts +272 -37
  2. package/dist/_private/index.d.ts +272 -37
  3. package/dist/components/AiChat.cjs +213 -63
  4. package/dist/components/AiChat.cjs.map +1 -1
  5. package/dist/components/AiChat.js +215 -65
  6. package/dist/components/AiChat.js.map +1 -1
  7. package/dist/components/Composer.cjs +2 -0
  8. package/dist/components/Composer.cjs.map +1 -1
  9. package/dist/components/Composer.js +2 -0
  10. package/dist/components/Composer.js.map +1 -1
  11. package/dist/components/Thread.cjs +2 -0
  12. package/dist/components/Thread.cjs.map +1 -1
  13. package/dist/components/Thread.js +2 -0
  14. package/dist/components/Thread.js.map +1 -1
  15. package/dist/components/internal/AiChatAssistantMessage.cjs +21 -15
  16. package/dist/components/internal/AiChatAssistantMessage.cjs.map +1 -1
  17. package/dist/components/internal/AiChatAssistantMessage.js +20 -14
  18. package/dist/components/internal/AiChatAssistantMessage.js.map +1 -1
  19. package/dist/components/internal/AiChatUserMessage.cjs +2 -1
  20. package/dist/components/internal/AiChatUserMessage.cjs.map +1 -1
  21. package/dist/components/internal/AiChatUserMessage.js +2 -1
  22. package/dist/components/internal/AiChatUserMessage.js.map +1 -1
  23. package/dist/components/internal/Prose.cjs +15 -7
  24. package/dist/components/internal/Prose.cjs.map +1 -1
  25. package/dist/components/internal/Prose.js +16 -8
  26. package/dist/components/internal/Prose.js.map +1 -1
  27. package/dist/index.d.cts +277 -4
  28. package/dist/index.d.ts +277 -4
  29. package/dist/primitives/Composer/index.cjs +5 -2
  30. package/dist/primitives/Composer/index.cjs.map +1 -1
  31. package/dist/primitives/Composer/index.js +5 -2
  32. package/dist/primitives/Composer/index.js.map +1 -1
  33. package/dist/primitives/Composer/utils.cjs +1 -2
  34. package/dist/primitives/Composer/utils.cjs.map +1 -1
  35. package/dist/primitives/Composer/utils.js +1 -2
  36. package/dist/primitives/Composer/utils.js.map +1 -1
  37. package/dist/primitives/Markdown.cjs +267 -336
  38. package/dist/primitives/Markdown.cjs.map +1 -1
  39. package/dist/primitives/Markdown.js +269 -338
  40. package/dist/primitives/Markdown.js.map +1 -1
  41. package/dist/primitives/index.d.cts +4 -0
  42. package/dist/primitives/index.d.ts +4 -0
  43. package/dist/utils/use-observable.cjs +2 -2
  44. package/dist/utils/use-observable.cjs.map +1 -1
  45. package/dist/utils/use-observable.js +1 -1
  46. package/dist/utils/use-observable.js.map +1 -1
  47. package/dist/utils/use-visible.cjs +8 -6
  48. package/dist/utils/use-visible.cjs.map +1 -1
  49. package/dist/utils/use-visible.js +7 -5
  50. package/dist/utils/use-visible.js.map +1 -1
  51. package/dist/version.cjs +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +4 -4
  54. package/src/styles/constants.css +2 -2
  55. package/src/styles/index.css +14 -1
  56. package/styles/dark/attributes.css +1 -1
  57. package/styles/dark/media-query.css +1 -1
  58. package/styles.css +1 -1
  59. package/styles.css.map +1 -1
  60. package/dist/utils/use-latest.cjs +0 -14
  61. package/dist/utils/use-latest.cjs.map +0 -1
  62. package/dist/utils/use-latest.js +0 -12
  63. package/dist/utils/use-latest.js.map +0 -1
@@ -1,16 +1,17 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { useAiChatMessages, RegisterAiKnowledge, RegisterAiTool } from '@liveblocks/react';
3
- import { forwardRef, useRef, useState, useImperativeHandle, useEffect } from 'react';
3
+ import { useLatest } from '@liveblocks/react/_private';
4
+ import { forwardRef, useEffect, useState, useRef, useImperativeHandle } from 'react';
4
5
  import { ArrowDownIcon } from '../icons/ArrowDown.js';
5
6
  import { SpinnerIcon } from '../icons/Spinner.js';
6
7
  import { useOverrides } from '../overrides.js';
7
8
  import { cn } from '../utils/cn.js';
8
- import { useVisible } from '../utils/use-visible.js';
9
+ import { useIntersectionCallback } from '../utils/use-visible.js';
9
10
  import { AiChatAssistantMessage } from './internal/AiChatAssistantMessage.js';
10
11
  import { AiChatComposer } from './internal/AiChatComposer.js';
11
12
  import { AiChatUserMessage } from './internal/AiChatUserMessage.js';
12
13
 
13
- const MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 50;
14
+ const MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 60;
14
15
  const defaultComponents = {
15
16
  Empty: () => null,
16
17
  Loading: () => /* @__PURE__ */ jsx("div", {
@@ -18,6 +19,156 @@ const defaultComponents = {
18
19
  children: /* @__PURE__ */ jsx(SpinnerIcon, {})
19
20
  })
20
21
  };
22
+ const AiChatMessages = forwardRef(
23
+ ({
24
+ messages,
25
+ copilotId,
26
+ overrides,
27
+ components,
28
+ lastSentMessageId,
29
+ scrollToBottom,
30
+ onScrollAtBottomChange,
31
+ containerRef,
32
+ footerRef,
33
+ messagesRef,
34
+ bottomTrailingMarkerRef,
35
+ trailingSpacerRef,
36
+ className,
37
+ ...props
38
+ }, forwardedRef) => {
39
+ const hasLastSentMessage = lastSentMessageId !== null;
40
+ useEffect(
41
+ () => {
42
+ if (!hasLastSentMessage) {
43
+ return;
44
+ }
45
+ const container = containerRef.current;
46
+ const footer = footerRef.current;
47
+ const messages2 = messagesRef.current;
48
+ if (!container || !footer || !messages2) {
49
+ return;
50
+ }
51
+ const trailingSpacer = trailingSpacerRef.current;
52
+ const bottomTrailingMarker = bottomTrailingMarkerRef.current;
53
+ let containerHeight = null;
54
+ let footerHeight = null;
55
+ let messagesHeight = null;
56
+ const resetTrailingSpace = () => {
57
+ trailingSpacer?.style.removeProperty("height");
58
+ bottomTrailingMarker?.style.removeProperty("top");
59
+ };
60
+ const resizeObserver = new ResizeObserver((entries) => {
61
+ if (!trailingSpacer || !bottomTrailingMarker) {
62
+ return;
63
+ }
64
+ const lastMessage = messages2.lastElementChild;
65
+ const penultimateMessage = lastMessage?.previousElementSibling;
66
+ if (!lastMessage || !penultimateMessage) {
67
+ resetTrailingSpace();
68
+ return;
69
+ }
70
+ if (container.scrollHeight === container.clientHeight) {
71
+ resetTrailingSpace();
72
+ return;
73
+ }
74
+ let updatedContainerHeight = containerHeight;
75
+ let updatedFooterHeight = footerHeight;
76
+ let updatedMessagesHeight = messagesHeight;
77
+ for (const entry of entries) {
78
+ const entryHeight = entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;
79
+ if (entry.target === container) {
80
+ updatedContainerHeight = entryHeight ?? null;
81
+ } else if (entry.target === footer) {
82
+ updatedFooterHeight = entryHeight ?? null;
83
+ } else if (entry.target === messages2) {
84
+ updatedMessagesHeight = entryHeight ?? null;
85
+ }
86
+ }
87
+ if (updatedContainerHeight === null || updatedFooterHeight === null || updatedMessagesHeight === null) {
88
+ resetTrailingSpace();
89
+ return;
90
+ }
91
+ if (updatedContainerHeight === containerHeight && updatedFooterHeight === footerHeight && updatedMessagesHeight === messagesHeight) {
92
+ return;
93
+ }
94
+ containerHeight = updatedContainerHeight;
95
+ footerHeight = updatedFooterHeight;
96
+ messagesHeight = updatedMessagesHeight;
97
+ const penultimateMessageScrollMarginTop = Number.parseFloat(
98
+ getComputedStyle(penultimateMessage).scrollMarginTop
99
+ );
100
+ const messagesRect = messages2.getBoundingClientRect();
101
+ const penultimateMessageRect = penultimateMessage.getBoundingClientRect();
102
+ const heightFromPenultimateMessageTopToMessagesListBottom = messagesRect.bottom - penultimateMessageRect.top;
103
+ const differenceHeight = penultimateMessageScrollMarginTop + heightFromPenultimateMessageTopToMessagesListBottom + (footerHeight ?? 0);
104
+ const trailingSpace = Math.max(containerHeight - differenceHeight, 0);
105
+ trailingSpacer.style.height = `${trailingSpace}px`;
106
+ bottomTrailingMarker.style.top = `${-trailingSpace}px`;
107
+ });
108
+ resizeObserver.observe(container);
109
+ resizeObserver.observe(footer);
110
+ resizeObserver.observe(messages2);
111
+ return () => {
112
+ resizeObserver.disconnect();
113
+ resetTrailingSpace();
114
+ };
115
+ },
116
+ [hasLastSentMessage]
117
+ );
118
+ useIntersectionCallback(
119
+ bottomTrailingMarkerRef,
120
+ (isIntersecting) => {
121
+ onScrollAtBottomChange.current(isIntersecting);
122
+ },
123
+ { root: containerRef, rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR }
124
+ );
125
+ useEffect(
126
+ () => {
127
+ scrollToBottom.current("instant");
128
+ },
129
+ []
130
+ );
131
+ useEffect(
132
+ () => {
133
+ if (lastSentMessageId) {
134
+ scrollToBottom.current("smooth", true);
135
+ }
136
+ },
137
+ [lastSentMessageId]
138
+ );
139
+ useEffect(
140
+ () => {
141
+ const onScrollAtBottomChangeCallback = onScrollAtBottomChange.current;
142
+ return () => {
143
+ onScrollAtBottomChangeCallback(null);
144
+ };
145
+ },
146
+ []
147
+ );
148
+ return /* @__PURE__ */ jsx("div", {
149
+ className: cn("lb-ai-chat-messages", className),
150
+ ref: forwardedRef,
151
+ ...props,
152
+ children: messages.map((message) => {
153
+ if (message.role === "user") {
154
+ return /* @__PURE__ */ jsx(AiChatUserMessage, {
155
+ message,
156
+ overrides
157
+ }, message.id);
158
+ } else if (message.role === "assistant") {
159
+ return /* @__PURE__ */ jsx(AiChatAssistantMessage, {
160
+ message,
161
+ overrides,
162
+ components,
163
+ copilotId
164
+ }, message.id);
165
+ } else {
166
+ return null;
167
+ }
168
+ })
169
+ });
170
+ }
171
+ );
21
172
  const AiChat = forwardRef(
22
173
  ({
23
174
  chatId,
@@ -32,38 +183,43 @@ const AiChat = forwardRef(
32
183
  ...props
33
184
  }, forwardedRef) => {
34
185
  const { messages, isLoading, error } = useAiChatMessages(chatId);
186
+ const [lastSentMessageId, setLastSentMessageId] = useState(null);
35
187
  const $ = useOverrides(overrides);
36
188
  const Empty = components?.Empty ?? defaultComponents.Empty;
37
189
  const Loading = components?.Loading ?? defaultComponents.Loading;
38
190
  const containerRef = useRef(null);
39
- const containerBottomRef = useRef(null);
40
- const isScrollIndicatorEnabled = !isLoading && !error;
41
- const isScrollAtBottom = useVisible(containerBottomRef, {
42
- enabled: isScrollIndicatorEnabled,
43
- root: containerRef,
44
- rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,
45
- initialValue: null
46
- });
47
- const isScrollIndicatorVisible = isScrollIndicatorEnabled && isScrollAtBottom !== null ? !isScrollAtBottom : false;
48
- const [lastSentMessageId, setLastSentMessageId] = useState(null);
191
+ const messagesRef = useRef(null);
192
+ const footerRef = useRef(null);
193
+ const bottomMarkerRef = useRef(null);
194
+ const bottomTrailingMarkerRef = useRef(null);
195
+ const trailingSpacerRef = useRef(null);
196
+ const [isScrollAtBottom, setScrollAtBottom] = useState(
197
+ null
198
+ );
199
+ const onScrollAtBottomChange = useLatest(setScrollAtBottom);
200
+ const isScrollIndicatorVisible = messages && isScrollAtBottom !== null ? !isScrollAtBottom : false;
49
201
  useImperativeHandle(
50
202
  forwardedRef,
51
203
  () => containerRef.current,
52
204
  []
53
205
  );
54
- const scrollToBottomCallbackRef = useRef(void 0);
55
- if (scrollToBottomCallbackRef.current === void 0) {
56
- scrollToBottomCallbackRef.current = function(behavior) {
57
- const container = containerRef.current;
58
- if (container === null)
59
- return;
60
- container.scrollTo({
61
- top: container.scrollHeight,
62
- behavior
63
- });
64
- };
65
- }
66
- const scrollToBottom = scrollToBottomCallbackRef.current;
206
+ const scrollToBottom = useLatest(
207
+ (behavior, includeTrailingSpace = false) => {
208
+ if (includeTrailingSpace) {
209
+ requestAnimationFrame(() => {
210
+ bottomMarkerRef.current?.scrollIntoView({
211
+ behavior,
212
+ block: "end"
213
+ });
214
+ });
215
+ } else {
216
+ bottomTrailingMarkerRef.current?.scrollIntoView({
217
+ behavior,
218
+ block: "end"
219
+ });
220
+ }
221
+ }
222
+ );
67
223
  return /* @__PURE__ */ jsxs("div", {
68
224
  ref: containerRef,
69
225
  ...props,
@@ -92,35 +248,35 @@ const AiChat = forwardRef(
92
248
  copilotId
93
249
  }) : /* @__PURE__ */ jsxs(Fragment, {
94
250
  children: [
95
- /* @__PURE__ */ jsx(AutoScrollHandler, {
251
+ /* @__PURE__ */ jsx(AiChatMessages, {
252
+ ref: messagesRef,
253
+ copilotId,
254
+ messages,
255
+ overrides,
256
+ components,
96
257
  lastSentMessageId,
97
- scrollToBottom
258
+ scrollToBottom,
259
+ onScrollAtBottomChange,
260
+ containerRef,
261
+ footerRef,
262
+ messagesRef,
263
+ bottomTrailingMarkerRef,
264
+ trailingSpacerRef
98
265
  }),
99
266
  /* @__PURE__ */ jsx("div", {
100
- className: "lb-ai-chat-messages",
101
- children: messages.map((message) => {
102
- if (message.role === "user") {
103
- return /* @__PURE__ */ jsx(AiChatUserMessage, {
104
- message,
105
- overrides
106
- }, message.id);
107
- } else if (message.role === "assistant") {
108
- return /* @__PURE__ */ jsx(AiChatAssistantMessage, {
109
- message,
110
- overrides,
111
- components,
112
- copilotId
113
- }, message.id);
114
- } else {
115
- return null;
116
- }
117
- })
267
+ ref: trailingSpacerRef,
268
+ "data-trailing-spacer": "",
269
+ style: {
270
+ pointerEvents: "none"
271
+ },
272
+ "aria-hidden": true
118
273
  })
119
274
  ]
120
275
  })
121
276
  }),
122
277
  /* @__PURE__ */ jsxs("div", {
123
278
  className: "lb-ai-chat-footer",
279
+ ref: footerRef,
124
280
  children: [
125
281
  /* @__PURE__ */ jsx("div", {
126
282
  className: "lb-ai-chat-footer-actions",
@@ -131,7 +287,7 @@ const AiChat = forwardRef(
131
287
  className: "lb-ai-chat-scroll-indicator-button",
132
288
  tabIndex: isScrollIndicatorVisible ? 0 : -1,
133
289
  "aria-hidden": !isScrollIndicatorVisible,
134
- onClick: () => scrollToBottom("smooth"),
290
+ onClick: () => scrollToBottom.current("smooth"),
135
291
  children: /* @__PURE__ */ jsx("span", {
136
292
  className: "lb-icon-container",
137
293
  children: /* @__PURE__ */ jsx(ArrowDownIcon, {})
@@ -149,30 +305,24 @@ const AiChat = forwardRef(
149
305
  }, chatId)
150
306
  ]
151
307
  }),
152
- /* @__PURE__ */ jsx("div", {
153
- ref: containerBottomRef,
308
+ messages && messages.length > 0 ? /* @__PURE__ */ jsx("div", {
309
+ ref: bottomMarkerRef,
154
310
  style: { position: "sticky", height: 0 },
155
311
  "aria-hidden": true,
156
- "data-scroll-at-bottom": isScrollAtBottom ? "" : void 0
157
- })
312
+ "data-bottom-marker": "",
313
+ children: /* @__PURE__ */ jsx("div", {
314
+ ref: bottomTrailingMarkerRef,
315
+ style: {
316
+ position: "absolute",
317
+ height: 0
318
+ },
319
+ "data-bottom-trailing-marker": ""
320
+ })
321
+ }) : null
158
322
  ]
159
323
  });
160
324
  }
161
325
  );
162
- function AutoScrollHandler({
163
- lastSentMessageId,
164
- scrollToBottom
165
- }) {
166
- useEffect(() => {
167
- scrollToBottom("instant");
168
- }, [scrollToBottom]);
169
- useEffect(() => {
170
- if (lastSentMessageId === null)
171
- return;
172
- scrollToBottom("smooth");
173
- }, [lastSentMessageId, scrollToBottom]);
174
- return null;
175
- }
176
326
 
177
327
  export { AiChat };
178
328
  //# sourceMappingURL=AiChat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiChat.js","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport {\n RegisterAiKnowledge,\n RegisterAiTool,\n useAiChatMessages,\n} from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatComposerOverrides,\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { useVisible } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatComposer } from \"./internal/AiChatComposer\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat id provided to the `AiChat` component.\n */\n chatId: string;\n /**\n * The copilot id provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\n// TODO: Add Markdown components\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n};\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 50;\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The id of the chat the composer belongs to.\n */\n chatId: string;\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n /**\n * The id of the copilot to use to send the message.\n */\n copilotId?: string;\n /**\n * The contextual knowledge to include in the chat. May be used by the assistant when generating responses.\n * Any knowledge you provide via this prop will be added to any already globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiChatMessageOverrides &\n AiChatComposerOverrides &\n AiChatOverrides\n >;\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge,\n tools = {},\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const containerBottomRef = useRef<HTMLDivElement | null>(null);\n const isScrollIndicatorEnabled = !isLoading && !error;\n const isScrollAtBottom = useVisible(containerBottomRef, {\n enabled: isScrollIndicatorEnabled,\n root: containerRef,\n rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,\n initialValue: null,\n });\n const isScrollIndicatorVisible =\n isScrollIndicatorEnabled && isScrollAtBottom !== null\n ? !isScrollAtBottom\n : false;\n\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n const scrollToBottomCallbackRef =\n useRef<(behavior: \"instant\" | \"smooth\") => void>(undefined);\n if (scrollToBottomCallbackRef.current === undefined) {\n scrollToBottomCallbackRef.current = function (\n behavior: \"instant\" | \"smooth\"\n ) {\n const container = containerRef.current;\n if (container === null) return;\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior,\n });\n };\n }\n const scrollToBottom = scrollToBottomCallbackRef.current;\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={cn(\n \"lb-root lb-ai-chat\",\n layout === \"compact\"\n ? \"lb-ai-chat:layout-compact\"\n : \"lb-ai-chat:layout-inset\",\n className\n )}\n >\n {knowledge\n ? knowledge.map((source, index) => (\n <RegisterAiKnowledge\n key={index}\n description={source.description}\n value={source.value}\n // knowledgeKey={source.knowledgeKey}\n />\n ))\n : null}\n\n {Object.entries(tools).map(([name, tool]) => (\n <RegisterAiTool key={name} chatId={chatId} name={name} tool={tool} />\n ))}\n\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AutoScrollHandler\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n />\n <div className=\"lb-ai-chat-messages\">\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n copilotId={copilotId}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\">\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiChatComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={overrides}\n autoFocus={autoFocus}\n onUserMessageCreate={({ id }) => setLastSentMessageId(id)}\n className={\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n }\n />\n </div>\n {/* This invisible element is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is fully scrolled.\n */}\n <div\n ref={containerBottomRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-scroll-at-bottom={isScrollAtBottom ? \"\" : undefined}\n />\n </div>\n );\n }\n);\n\nfunction AutoScrollHandler({\n lastSentMessageId,\n scrollToBottom,\n}: {\n lastSentMessageId: MessageId | null;\n scrollToBottom: (behavior: \"instant\" | \"smooth\") => void;\n}) {\n // Scroll to bottom when the component first mounts\n useEffect(() => {\n scrollToBottom(\"instant\");\n }, [scrollToBottom]);\n\n // Scroll to bottom when sending a new message\n useEffect(() => {\n if (lastSentMessageId === null) return;\n scrollToBottom(\"smooth\");\n }, [lastSentMessageId, scrollToBottom]);\n\n return null;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AiChat.js","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport {\n RegisterAiKnowledge,\n RegisterAiTool,\n useAiChatMessages,\n} from \"@liveblocks/react\";\nimport { useLatest } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n type MutableRefObject,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatComposerOverrides,\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport type { MarkdownComponents } from \"../primitives/Markdown\";\nimport { cn } from \"../utils/cn\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatComposer } from \"./internal/AiChatComposer\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 60;\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat ID provided to the `AiChat` component.\n */\n chatId: string;\n\n /**\n * The copilot ID provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n\n /**\n * The components used to render Markdown content.\n */\n markdown?: Partial<MarkdownComponents>;\n};\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: string;\n\n /**\n * The contextual knowledge to include in the chat. May be used by the assistant when generating responses.\n * Any knowledge you provide via this prop will be added to any already globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiChatMessageOverrides &\n AiChatComposerOverrides &\n AiChatOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\ninterface AiChatMessagesProps extends ComponentProps<\"div\"> {\n messages: NonNullable<ReturnType<typeof useAiChatMessages>[\"messages\"]>;\n copilotId: AiChatProps[\"copilotId\"];\n overrides: AiChatProps[\"overrides\"];\n components: AiChatProps[\"components\"];\n lastSentMessageId: MessageId | null;\n scrollToBottom: MutableRefObject<\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace?: boolean) => void\n >;\n onScrollAtBottomChange: MutableRefObject<\n (isScrollAtBottom: boolean | null) => void\n >;\n containerRef: MutableRefObject<HTMLDivElement | null>;\n footerRef: MutableRefObject<HTMLDivElement | null>;\n messagesRef: MutableRefObject<HTMLDivElement | null>;\n bottomTrailingMarkerRef: MutableRefObject<HTMLDivElement | null>;\n trailingSpacerRef: MutableRefObject<HTMLDivElement | null>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nconst AiChatMessages = forwardRef<HTMLDivElement, AiChatMessagesProps>(\n (\n {\n messages,\n copilotId,\n overrides,\n components,\n lastSentMessageId,\n scrollToBottom,\n onScrollAtBottomChange,\n containerRef,\n footerRef,\n messagesRef,\n bottomTrailingMarkerRef,\n trailingSpacerRef,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const hasLastSentMessage = lastSentMessageId !== null;\n\n /**\n * Every time the container, footer, or messages list change size,\n * we calculate the trailing space that would allow the penultimate\n * message to be at the top of the viewport, and apply it.\n *\n * ┌─────────────────────────────────────────┐▲ A = The `scroll-margin-top`\n * │ ┌─────────────────────────┐ │▼▲ value of the penultimate message\n * │ │ The penultimate message │ │ │\n * │ └─────────────────────────┘ │ │ B = The height from the top of\n * │ │ │ the penultimate message to the\n * │ ┌─────────────────────────┐ │ │ bottom of the messages list,\n * │ │ The last message │ │ │ including the messages' heights,\n * │ └─────────────────────────┘ │ │ and any padding, gap, etc\n * │ │ │\n * ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤▲▼\n * │ ││ The trailing space needed to\n * │ = container height - (A + B + C) ││ allow the penultimate message\n * │ ││ to be at the top of the viewport\n * ├ ┬─────────────────────────────────────┬ ┤▼▲\n * │ │ │ │ │\n * │ │ │ │ │ C = The footer's height,\n * │ │ │ │ │ including any padding\n * │ └─────────────────────────────────────┘ │ │\n * └─────────────────────────────────────────┘ ▼\n */\n useEffect(\n () => {\n if (!hasLastSentMessage) {\n return;\n }\n\n const container = containerRef.current;\n const footer = footerRef.current;\n const messages = messagesRef.current;\n\n if (!container || !footer || !messages) {\n return;\n }\n\n const trailingSpacer = trailingSpacerRef.current;\n const bottomTrailingMarker = bottomTrailingMarkerRef.current;\n\n let containerHeight: number | null = null;\n let footerHeight: number | null = null;\n let messagesHeight: number | null = null;\n\n const resetTrailingSpace = () => {\n trailingSpacer?.style.removeProperty(\"height\");\n bottomTrailingMarker?.style.removeProperty(\"top\");\n };\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!trailingSpacer || !bottomTrailingMarker) {\n return;\n }\n\n const lastMessage = messages.lastElementChild;\n const penultimateMessage = lastMessage?.previousElementSibling;\n\n // If there's no last pair of messages, there's no need for any trailing space.\n if (!lastMessage || !penultimateMessage) {\n resetTrailingSpace();\n return;\n }\n\n // If the container's height is based on its content, the container isn't scrollable and there's no need for any trailing space.\n if (container.scrollHeight === container.clientHeight) {\n resetTrailingSpace();\n return;\n }\n\n let updatedContainerHeight: number | null = containerHeight;\n let updatedFooterHeight: number | null = footerHeight;\n let updatedMessagesHeight: number | null = messagesHeight;\n\n for (const entry of entries) {\n const entryHeight =\n entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n\n if (entry.target === container) {\n updatedContainerHeight = entryHeight ?? null;\n } else if (entry.target === footer) {\n updatedFooterHeight = entryHeight ?? null;\n } else if (entry.target === messages) {\n updatedMessagesHeight = entryHeight ?? null;\n }\n }\n\n // If we don't have all the heights, we can't compute the trailing space.\n if (\n updatedContainerHeight === null ||\n updatedFooterHeight === null ||\n updatedMessagesHeight === null\n ) {\n resetTrailingSpace();\n return;\n }\n\n // If none of the heights have changed, we don't need to do anything.\n if (\n updatedContainerHeight === containerHeight &&\n updatedFooterHeight === footerHeight &&\n updatedMessagesHeight === messagesHeight\n ) {\n return;\n }\n\n // Now that we have compared them, we can update the heights.\n containerHeight = updatedContainerHeight;\n footerHeight = updatedFooterHeight;\n messagesHeight = updatedMessagesHeight;\n\n // A\n const penultimateMessageScrollMarginTop = Number.parseFloat(\n getComputedStyle(penultimateMessage as HTMLElement).scrollMarginTop\n );\n\n // B\n const messagesRect = messages.getBoundingClientRect();\n const penultimateMessageRect =\n penultimateMessage.getBoundingClientRect();\n const heightFromPenultimateMessageTopToMessagesListBottom =\n messagesRect.bottom - penultimateMessageRect.top;\n\n // A + B + C\n const differenceHeight =\n penultimateMessageScrollMarginTop +\n heightFromPenultimateMessageTopToMessagesListBottom +\n (footerHeight ?? 0);\n\n // = container height - (A + B + C)\n const trailingSpace = Math.max(containerHeight - differenceHeight, 0);\n\n // Update the trailing space.\n trailingSpacer.style.height = `${trailingSpace}px`;\n\n // Offset what \"the bottom\" is to the \"scroll at the bottom\" detection logic,\n // so that it doesn't include the trailing space.\n bottomTrailingMarker.style.top = `${-trailingSpace}px`;\n });\n\n resizeObserver.observe(container);\n resizeObserver.observe(footer);\n resizeObserver.observe(messages);\n\n return () => {\n resizeObserver.disconnect();\n resetTrailingSpace();\n };\n },\n // This effect only uses stable refs.\n [hasLastSentMessage] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Update the \"scroll at bottom\" state when needed.\n */\n useIntersectionCallback(\n bottomTrailingMarkerRef,\n (isIntersecting) => {\n onScrollAtBottomChange.current(isIntersecting);\n },\n { root: containerRef, rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR }\n );\n\n /**\n * Instantly scroll to the bottom for the initial state.\n */\n useEffect(\n () => {\n scrollToBottom.current(\"instant\");\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Scroll to new messages when sending them.\n */\n useEffect(\n () => {\n if (lastSentMessageId) {\n scrollToBottom.current(\"smooth\", true);\n }\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [lastSentMessageId] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Reset the \"scroll at bottom\" state when the component unmounts.\n */\n useEffect(\n () => {\n const onScrollAtBottomChangeCallback = onScrollAtBottomChange.current;\n\n return () => {\n onScrollAtBottomChangeCallback(null);\n };\n },\n // `onScrollAtBottomChange` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n return (\n <div\n className={cn(\"lb-ai-chat-messages\", className)}\n ref={forwardedRef}\n {...props}\n >\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n copilotId={copilotId}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n );\n }\n);\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge,\n tools = {},\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const messagesRef = useRef<HTMLDivElement | null>(null);\n const footerRef = useRef<HTMLDivElement | null>(null);\n const bottomMarkerRef = useRef<HTMLDivElement | null>(null);\n const bottomTrailingMarkerRef = useRef<HTMLDivElement | null>(null);\n const trailingSpacerRef = useRef<HTMLDivElement | null>(null);\n\n const [isScrollAtBottom, setScrollAtBottom] = useState<boolean | null>(\n null\n );\n // `useState`'s setter is stable but this is for clarity in the places it's used.\n const onScrollAtBottomChange = useLatest(setScrollAtBottom);\n const isScrollIndicatorVisible =\n messages && isScrollAtBottom !== null ? !isScrollAtBottom : false;\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n const scrollToBottom = useLatest(\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace = false) => {\n if (includeTrailingSpace) {\n // Scroll to the bottom marker to include the trailing space,\n // and wait for a frame in case the trailing space hasn't\n // been updated yet. (e.g. when sending a new message)\n requestAnimationFrame(() => {\n bottomMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n });\n } else {\n // Scroll to the trailing space marker to only scroll to the\n // bottom of the messages, without including the trailing space.\n bottomTrailingMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n }\n }\n );\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={cn(\n \"lb-root lb-ai-chat\",\n layout === \"compact\"\n ? \"lb-ai-chat:layout-compact\"\n : \"lb-ai-chat:layout-inset\",\n className\n )}\n >\n {knowledge\n ? knowledge.map((source, index) => (\n <RegisterAiKnowledge\n key={index}\n description={source.description}\n value={source.value}\n // knowledgeKey={source.knowledgeKey}\n />\n ))\n : null}\n\n {Object.entries(tools).map(([name, tool]) => (\n <RegisterAiTool key={name} chatId={chatId} name={name} tool={tool} />\n ))}\n\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AiChatMessages\n ref={messagesRef}\n copilotId={copilotId}\n messages={messages}\n overrides={overrides}\n components={components}\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n onScrollAtBottomChange={onScrollAtBottomChange}\n containerRef={containerRef}\n footerRef={footerRef}\n messagesRef={messagesRef}\n bottomTrailingMarkerRef={bottomTrailingMarkerRef}\n trailingSpacerRef={trailingSpacerRef}\n />\n\n {/**\n * This trailing spacer is used to extend the scrollable area beyond its actual\n * content, to allow messages to appear at the top of the viewport for example.\n */}\n <div\n ref={trailingSpacerRef}\n data-trailing-spacer=\"\"\n style={{\n pointerEvents: \"none\",\n }}\n aria-hidden\n />\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\" ref={footerRef}>\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom.current(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiChatComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={overrides}\n autoFocus={autoFocus}\n onUserMessageCreate={({ id }) => setLastSentMessageId(id)}\n className={\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n }\n />\n </div>\n\n {/**\n * This invisible marker is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is scrolled to the bottom.\n */}\n {messages && messages.length > 0 ? (\n <div\n ref={bottomMarkerRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-bottom-marker=\"\"\n >\n {/**\n * This inner marker is absolutely offset by the same distance as the trailing space so its\n * visibility means the scrollable area is at the bottom of the messages, not the full bottom.\n */}\n <div\n ref={bottomTrailingMarkerRef}\n style={{\n position: \"absolute\",\n height: 0,\n }}\n data-bottom-trailing-marker=\"\"\n />\n </div>\n ) : null}\n </div>\n );\n }\n);\n"],"names":["messages"],"mappings":"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"}
@@ -415,6 +415,7 @@ const Composer = react.forwardRef(
415
415
  onFocus,
416
416
  autoFocus,
417
417
  disabled,
418
+ blurOnSubmit = true,
418
419
  showAttachments = true,
419
420
  showFormattingControls = true,
420
421
  showAttribution,
@@ -552,6 +553,7 @@ const Composer = react.forwardRef(
552
553
  defaultAttachments,
553
554
  pasteFilesAsAttachments: showAttachments,
554
555
  preventUnsavedChanges: preventUnsavedComposerChanges,
556
+ blurOnSubmit,
555
557
  roomId,
556
558
  children: /* @__PURE__ */ jsxRuntime.jsx(ComposerEditorContainer, {
557
559
  defaultValue,
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.cjs","sources":["../../src/components/Composer.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentAttachment,\n CommentMixedAttachment,\n DM,\n} from \"@liveblocks/core\";\nimport { assertNever, Permission } from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport {\n useCreateRoomComment,\n useCreateRoomThread,\n useEditRoomComment,\n useLayoutEffect,\n useResolveMentionSuggestions,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport type {\n ComponentPropsWithoutRef,\n ComponentType,\n FocusEvent,\n FormEvent,\n ForwardedRef,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\n\nimport { useLiveblocksUiConfig } from \"../config\";\nimport { FLOATING_ELEMENT_SIDE_OFFSET, MENTION_CHARACTER } from \"../constants\";\nimport { AttachmentIcon } from \"../icons/Attachment\";\nimport { BoldIcon } from \"../icons/Bold\";\nimport { CodeIcon } from \"../icons/Code\";\nimport { EmojiIcon } from \"../icons/Emoji\";\nimport { ItalicIcon } from \"../icons/Italic\";\nimport { MentionIcon } from \"../icons/Mention\";\nimport { SendIcon } from \"../icons/Send\";\nimport { StrikethroughIcon } from \"../icons/Strikethrough\";\nimport type { ComposerOverrides, GlobalOverrides } from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport {\n useComposer,\n useComposerAttachmentsContext,\n useComposerEditorContext,\n} from \"../primitives/Composer/contexts\";\nimport type {\n ComposerEditorComponents,\n ComposerEditorLinkProps,\n ComposerEditorMentionProps,\n ComposerEditorMentionSuggestionsProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerMarkToggleProps,\n ComposerSubmitComment,\n} from \"../primitives/Composer/types\";\nimport { useComposerAttachmentsDropArea } from \"../primitives/Composer/utils\";\nimport type { ComposerBodyMark } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\nimport { FileAttachment } from \"./internal/Attachment\";\nimport { Attribution } from \"./internal/Attribution\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport type { EmojiPickerProps } from \"./internal/EmojiPicker\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\ninterface EditorActionProps extends ComponentPropsWithoutRef<\"button\"> {\n label: string;\n tooltipLabel?: string;\n}\n\ninterface EmojiEditorActionProps extends EditorActionProps {\n onPickerOpenChange?: EmojiPickerProps[\"onOpenChange\"];\n}\n\ninterface MarkToggleProps extends ComposerMarkToggleProps {\n icon?: ReactNode;\n shortcut?: string;\n}\n\ntype ComposerCreateThreadProps<M extends BaseMetadata> = {\n threadId?: never;\n commentId?: never;\n\n /**\n * The metadata of the thread to create.\n */\n metadata?: M;\n};\n\ntype ComposerCreateCommentProps = {\n /**\n * The ID of the thread to reply to.\n */\n threadId: string;\n commentId?: never;\n metadata?: never;\n};\n\ntype ComposerEditCommentProps = {\n /**\n * The ID of the thread to edit a comment in.\n */\n threadId: string;\n\n /**\n * The ID of the comment to edit.\n */\n commentId: string;\n metadata?: never;\n};\n\nexport type ComposerProps<M extends BaseMetadata = DM> = Omit<\n ComponentPropsWithoutRef<\"form\">,\n \"defaultValue\"\n> &\n (\n | ComposerCreateThreadProps<M>\n | ComposerCreateCommentProps\n | ComposerEditCommentProps\n ) & {\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n comment: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => Promise<void> | void;\n\n /**\n * The composer's initial value.\n */\n defaultValue?: ComposerEditorProps[\"defaultValue\"];\n\n /**\n * The composer's initial attachments.\n */\n defaultAttachments?: CommentAttachment[];\n\n /**\n * Whether the composer is collapsed. Setting a value will make the composer controlled.\n */\n collapsed?: boolean;\n\n /**\n * The event handler called when the collapsed state of the composer changes.\n */\n onCollapsedChange?: (collapsed: boolean) => void;\n\n /**\n * Whether the composer is initially collapsed. Setting a value will make the composer uncontrolled.\n */\n defaultCollapsed?: boolean;\n\n /**\n * Whether to show and allow adding attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to show formatting controls (e.g. a floating toolbar with formatting toggles when selecting text)\n */\n showFormattingControls?: boolean;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: ComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: ComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n showAttribution?: boolean;\n\n /**\n * @internal\n */\n roomId?: string;\n };\n\ninterface ComposerEditorContainerProps\n extends Pick<\n ComposerProps,\n | \"defaultValue\"\n | \"showAttachments\"\n | \"showFormattingControls\"\n | \"showAttribution\"\n | \"overrides\"\n | \"actions\"\n | \"autoFocus\"\n | \"disabled\"\n > {\n isCollapsed: boolean | undefined;\n onEmptyChange: (isEmpty: boolean) => void;\n hasResolveMentionSuggestions: boolean;\n onEmojiPickerOpenChange: (isOpen: boolean) => void;\n onEditorClick: (event: MouseEvent<HTMLDivElement>) => void;\n}\n\nfunction ComposerInsertMentionEditorAction({\n label,\n tooltipLabel,\n className,\n onClick,\n ...props\n}: EditorActionProps) {\n const { createMention } = useComposer();\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n createMention();\n }\n },\n [createMention, onClick]\n );\n\n return (\n <Tooltip content={tooltipLabel ?? label}>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={handleClick}\n aria-label={label}\n icon={<MentionIcon />}\n {...props}\n />\n </Tooltip>\n );\n}\n\nfunction ComposerInsertEmojiEditorAction({\n label,\n tooltipLabel,\n onPickerOpenChange,\n className,\n ...props\n}: EmojiEditorActionProps) {\n const { insertText } = useComposer();\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <EmojiPicker onEmojiSelect={insertText} onOpenChange={onPickerOpenChange}>\n <Tooltip content={tooltipLabel ?? label}>\n <EmojiPickerTrigger asChild>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n icon={<EmojiIcon />}\n {...props}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n );\n}\n\nfunction ComposerAttachFilesEditorAction({\n label,\n tooltipLabel,\n className,\n ...props\n}: EditorActionProps) {\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <Tooltip content={tooltipLabel ?? label}>\n <ComposerPrimitive.AttachFiles asChild>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n icon={<AttachmentIcon />}\n {...props}\n />\n </ComposerPrimitive.AttachFiles>\n </Tooltip>\n );\n}\n\nfunction ComposerMention({ mention }: ComposerEditorMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return (\n <ComposerPrimitive.Mention className=\"lb-composer-mention\">\n {MENTION_CHARACTER}\n <User userId={mention.id} />\n </ComposerPrimitive.Mention>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n}\n\nfunction ComposerMentionSuggestions({\n mentions,\n}: ComposerEditorMentionSuggestionsProps) {\n return mentions.length > 0 ? (\n <ComposerPrimitive.Suggestions className=\"lb-root lb-portal lb-elevation lb-composer-suggestions lb-composer-mention-suggestions\">\n <ComposerPrimitive.SuggestionsList className=\"lb-composer-suggestions-list lb-composer-mention-suggestions-list\">\n {mentions.map((mention) => {\n switch (mention.kind) {\n case \"user\":\n return (\n <ComposerPrimitive.SuggestionsListItem\n key={mention.id}\n className=\"lb-composer-suggestions-list-item lb-composer-mention-suggestion\"\n value={mention.id}\n >\n <Avatar\n userId={mention.id}\n className=\"lb-composer-mention-suggestion-avatar\"\n />\n <User\n userId={mention.id}\n className=\"lb-composer-mention-suggestion-user\"\n />\n </ComposerPrimitive.SuggestionsListItem>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n })}\n </ComposerPrimitive.SuggestionsList>\n </ComposerPrimitive.Suggestions>\n ) : null;\n}\n\nfunction MarkToggle({\n mark,\n icon,\n shortcut,\n children,\n ...props\n}: MarkToggleProps) {\n const $ = useOverrides();\n const label = useMemo(() => {\n return $.COMPOSER_TOGGLE_MARK(mark);\n }, [$, mark]);\n\n return (\n <ShortcutTooltip\n content={label}\n shortcut={shortcut}\n sideOffset={FLOATING_ELEMENT_SIDE_OFFSET + 2}\n >\n <ComposerPrimitive.MarkToggle mark={mark} asChild {...props}>\n <Button aria-label={label} variant=\"toolbar\" icon={icon}>\n {children}\n </Button>\n </ComposerPrimitive.MarkToggle>\n </ShortcutTooltip>\n );\n}\n\ntype MarkToggles = {\n [K in ComposerBodyMark]: ComponentType<PropsWithChildren>;\n};\n\nconst markToggles: MarkToggles = {\n bold: () => <MarkToggle mark=\"bold\" shortcut=\"Mod-B\" icon={<BoldIcon />} />,\n italic: () => (\n <MarkToggle mark=\"italic\" shortcut=\"Mod-I\" icon={<ItalicIcon />} />\n ),\n strikethrough: () => (\n <MarkToggle\n mark=\"strikethrough\"\n shortcut=\"Mod-Shift-S\"\n icon={<StrikethroughIcon />}\n />\n ),\n code: () => <MarkToggle mark=\"code\" shortcut=\"Mod-E\" icon={<CodeIcon />} />,\n};\n\nconst markTogglesList = Object.entries(markToggles).map(([mark, Toggle]) => (\n <Toggle key={mark} />\n));\n\nfunction ComposerFloatingToolbar() {\n return (\n <ComposerPrimitive.FloatingToolbar className=\"lb-root lb-portal lb-elevation lb-composer-floating-toolbar\">\n {markTogglesList}\n </ComposerPrimitive.FloatingToolbar>\n );\n}\n\nfunction ComposerLink({ href, children }: ComposerEditorLinkProps) {\n return (\n <ComposerPrimitive.Link href={href} className=\"lb-composer-link\">\n {children}\n </ComposerPrimitive.Link>\n );\n}\n\ninterface ComposerAttachmentsProps extends ComponentPropsWithoutRef<\"div\"> {\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n}\n\ninterface ComposerFileAttachmentProps extends ComponentPropsWithoutRef<\"div\"> {\n attachment: CommentMixedAttachment;\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n}\n\nfunction ComposerFileAttachment({\n attachment,\n className,\n overrides,\n ...props\n}: ComposerFileAttachmentProps) {\n const { removeAttachment } = useComposer();\n const { roomId } = useComposerEditorContext();\n\n const handleDeleteClick = useCallback(() => {\n removeAttachment(attachment.id);\n }, [attachment.id, removeAttachment]);\n\n return (\n <FileAttachment\n className={cn(\"lb-composer-attachment\", className)}\n {...props}\n attachment={attachment}\n onDeleteClick={handleDeleteClick}\n preventFocusOnDelete\n overrides={overrides}\n roomId={roomId}\n />\n );\n}\n\nfunction ComposerAttachments({\n overrides,\n className,\n ...props\n}: ComposerAttachmentsProps) {\n const { attachments } = useComposer();\n\n if (attachments.length === 0) {\n return null;\n }\n\n return (\n <div className={cn(\"lb-composer-attachments\", className)} {...props}>\n <div className=\"lb-attachments\">\n {attachments.map((attachment) => {\n return (\n <ComposerFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n />\n );\n })}\n </div>\n </div>\n );\n}\n\nconst editorRequiredComponents: ComposerEditorComponents = {\n Mention: ComposerMention,\n MentionSuggestions: ComposerMentionSuggestions,\n Link: ComposerLink,\n};\n\nfunction ComposerEditorContainer({\n showAttachments = true,\n showFormattingControls = true,\n showAttribution,\n defaultValue,\n isCollapsed,\n overrides,\n actions,\n autoFocus,\n disabled,\n hasResolveMentionSuggestions,\n onEmojiPickerOpenChange,\n onEmptyChange,\n onEditorClick,\n}: ComposerEditorContainerProps) {\n const { isEmpty } = useComposer();\n const { hasMaxAttachments } = useComposerAttachmentsContext();\n const $ = useOverrides(overrides);\n const components = useMemo(() => {\n return {\n ...editorRequiredComponents,\n FloatingToolbar: showFormattingControls\n ? ComposerFloatingToolbar\n : undefined,\n };\n }, [showFormattingControls]);\n\n const [isDraggingOver, dropAreaProps] = useComposerAttachmentsDropArea({\n disabled: disabled || hasMaxAttachments,\n });\n\n useLayoutEffect(() => {\n onEmptyChange(isEmpty);\n }, [isEmpty, onEmptyChange]);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <div className=\"lb-composer-editor-container\" {...dropAreaProps}>\n <ComposerPrimitive.Editor\n className=\"lb-composer-editor\"\n onClick={onEditorClick}\n placeholder={$.COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n autoFocus={autoFocus}\n components={components}\n disabled={disabled}\n dir={$.dir}\n />\n {showAttachments && <ComposerAttachments overrides={overrides} />}\n {(!isCollapsed || isDraggingOver) && (\n <div className=\"lb-composer-footer\">\n <div className=\"lb-composer-editor-actions\">\n {hasResolveMentionSuggestions && (\n <ComposerInsertMentionEditorAction\n label={$.COMPOSER_INSERT_MENTION}\n disabled={disabled}\n />\n )}\n <ComposerInsertEmojiEditorAction\n label={$.COMPOSER_INSERT_EMOJI}\n onPickerOpenChange={onEmojiPickerOpenChange}\n disabled={disabled}\n />\n {showAttachments && (\n <ComposerAttachFilesEditorAction\n label={$.COMPOSER_ATTACH_FILES}\n disabled={disabled}\n />\n )}\n </div>\n {showAttribution && <Attribution />}\n <div className=\"lb-composer-actions\">\n {actions ?? (\n <>\n <ShortcutTooltip content={$.COMPOSER_SEND} shortcut=\"Enter\">\n <ComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-composer-action\"\n variant=\"primary\"\n aria-label={$.COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n )}\n </div>\n </div>\n )}\n {showAttachments && isDraggingOver && (\n <div className=\"lb-composer-attachments-drop-area\">\n <div className=\"lb-composer-attachments-drop-area-label\">\n <AttachmentIcon />\n {$.COMPOSER_ATTACH_FILES}\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport const ComposerRoomIdContext = createContext<string | null>(null);\n\n/**\n * Displays a composer to create comments.\n *\n * @example\n * <Composer />\n */\nexport const Composer = forwardRef(\n <M extends BaseMetadata = DM>(\n {\n threadId,\n commentId,\n metadata,\n defaultValue,\n defaultAttachments,\n onComposerSubmit,\n collapsed: controlledCollapsed,\n defaultCollapsed,\n onCollapsedChange: controlledOnCollapsedChange,\n overrides,\n actions,\n onBlur,\n className,\n onFocus,\n autoFocus,\n disabled,\n showAttachments = true,\n showFormattingControls = true,\n showAttribution,\n roomId: _roomId,\n ...props\n }: ComposerProps<M>,\n forwardedRef: ForwardedRef<HTMLFormElement>\n ) => {\n const room = useRoom({ allowOutsideRoom: true });\n\n const roomId = _roomId !== undefined ? _roomId : room?.id;\n if (roomId === undefined) {\n throw new Error(\n \"Composer must be a descendant of RoomProvider component\"\n );\n }\n\n const createThread = useCreateRoomThread(roomId);\n const createComment = useCreateRoomComment(roomId);\n const editComment = useEditRoomComment(roomId);\n const { preventUnsavedComposerChanges } = useLiveblocksUiConfig();\n const hasResolveMentionSuggestions =\n useResolveMentionSuggestions() !== undefined;\n const isEmptyRef = useRef(true);\n const isEmojiPickerOpenRef = useRef(false);\n const $ = useOverrides(overrides);\n const [isCollapsed, onCollapsedChange] = useControllableState(\n defaultCollapsed ?? false,\n controlledCollapsed,\n controlledOnCollapsedChange\n );\n\n const canCommentFallback = useSyncExternalStore(\n useCallback(\n (callback) => {\n if (room === null) return () => {};\n return room.events.self.subscribeOnce(callback);\n },\n [room]\n ),\n useCallback(() => {\n return room?.getSelf()?.canComment ?? true;\n }, [room]),\n useCallback(() => true, [])\n );\n\n const permissions = useRoomPermissions(roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : canCommentFallback;\n\n const setEmptyRef = useCallback((isEmpty: boolean) => {\n isEmptyRef.current = isEmpty;\n }, []);\n\n const setEmojiPickerOpenRef = useCallback((isEmojiPickerOpen: boolean) => {\n isEmojiPickerOpenRef.current = isEmojiPickerOpen;\n }, []);\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLFormElement>) => {\n onFocus?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n if (isEmptyRef.current && canComment) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, onFocus, canComment]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLFormElement>) => {\n onBlur?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n const isOutside = !event.currentTarget.contains(\n event.relatedTarget ?? document.activeElement\n );\n\n if (isOutside && isEmptyRef.current && !isEmojiPickerOpenRef.current) {\n onCollapsedChange?.(true);\n }\n },\n [onBlur, onCollapsedChange]\n );\n\n const handleEditorClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n event.stopPropagation();\n\n if (isEmptyRef.current && canComment) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, canComment]\n );\n\n const handleComposerSubmit = useCallback(\n (comment: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(comment, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n\n if (commentId && threadId) {\n editComment({\n commentId,\n threadId,\n body: comment.body,\n attachments: comment.attachments,\n });\n } else if (threadId) {\n createComment({\n threadId,\n body: comment.body,\n attachments: comment.attachments,\n });\n } else {\n createThread({\n body: comment.body,\n metadata: metadata ?? {},\n attachments: comment.attachments,\n });\n }\n },\n [\n commentId,\n createComment,\n createThread,\n editComment,\n metadata,\n onComposerSubmit,\n threadId,\n ]\n );\n\n return (\n <TooltipProvider>\n <ComposerPrimitive.Form\n onComposerSubmit={handleComposerSubmit}\n className={cn(\"lb-root lb-composer lb-composer-form\", className)}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n data-collapsed={isCollapsed ? \"\" : undefined}\n onFocus={handleFocus}\n onBlur={handleBlur}\n disabled={disabled || !canComment}\n defaultAttachments={defaultAttachments}\n pasteFilesAsAttachments={showAttachments}\n preventUnsavedChanges={preventUnsavedComposerChanges}\n roomId={roomId}\n >\n <ComposerEditorContainer\n defaultValue={defaultValue}\n actions={actions}\n overrides={overrides}\n isCollapsed={isCollapsed}\n showAttachments={showAttachments}\n showAttribution={showAttribution}\n showFormattingControls={showFormattingControls}\n hasResolveMentionSuggestions={hasResolveMentionSuggestions}\n onEmptyChange={setEmptyRef}\n onEmojiPickerOpenChange={setEmojiPickerOpenRef}\n onEditorClick={handleEditorClick}\n autoFocus={autoFocus}\n disabled={disabled}\n />\n </ComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = DM>(\n props: ComposerProps<M> & RefAttributes<HTMLFormElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoOA;AAA2C;AACzC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AAAoB;AAEhB;AAEA;AACE;AACA;AAAc;AAChB;AACF;AACuB;AAGzB;AACG;AAAiC;AAC/B;AACqD;AACrC;AACN;AACG;AACO;AACf;AACN;AAGN;AAEA;AAAyC;AACvC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAA2B;AAA0B;AACnD;AAAiC;AAC/B;AAA0B;AACxB;AACqD;AACrC;AACN;AACG;AACK;AACb;AACN;AACF;AACF;AAGN;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAiC;AAC/B;AAAqC;AACnC;AACqD;AACrC;AACN;AACG;AACU;AAClB;AACN;AACF;AAGN;AAEA;AACE;AAAsB;AAElB;AACG;AAAoC;AAClC;AAAA;AACA;AAAqB;AAAI;AAAA;AAC5B;AAIF;AAAyD;AAE/D;AAEA;AAAoC;AAEpC;AACE;AACG;AAAwC;AACtC;AAA4C;AAEzC;AAAsB;AAElB;AACG;AAEW;AACK;AAEf;AAAC;AACiB;AACN;AACZ;AACC;AACiB;AACN;AACZ;AAAA;AACF;AAIF;AAAyD;AAC7D;AACD;AACH;AAGN;AAEA;AAAoB;AAClB;AACA;AACA;AACA;AAEF;AACE;AACA;AACE;AAAkC;AAGpC;AACG;AACU;AACT;AAC2C;AAE1C;AAA6B;AAAmB;AAAK;AACnD;AAAmB;AAAe;AAAU;AAC1C;AACH;AACF;AAGN;AAMA;AAAiC;AAClB;AAAgB;AAAgB;AAAwB;AAAI;AAEtE;AAAgB;AAAkB;AAA0B;AAAI;AAGhE;AACM;AACI;AACgB;AAC3B;AAEW;AAAgB;AAAgB;AAAwB;AACvE;AAEA;AAIA;AACE;AACG;AAA4C;AAC1C;AAGP;AAEA;AACE;AACG;AAAuB;AAAsB;AAC3C;AAGP;AAWA;AAAgC;AAC9B;AACA;AACA;AAEF;AACE;AACA;AAEA;AACE;AAA8B;AAGhC;AACG;AACkD;AAC7C;AACJ;AACe;AACK;AACpB;AACA;AAGN;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAO;AAGT;AACG;AAAsD;AAAO;AAC3D;AAAc;AAEX;AACG;AAEC;AACA;AACF;AAEH;AACH;AAGN;AAEA;AAA2D;AAChD;AACW;AAEtB;AAEA;AAAiC;AACb;AACO;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AACA;AACE;AAAO;AACF;AAGC;AACN;AAGF;AAAuE;AAC/C;AAGxB;AACE;AAAqB;AAGvB;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAc;AAAmC;AAChD;AAAC;AACW;AACD;AACM;AACf;AACA;AACA;AACA;AACO;AACT;AACqB;AAAoB;AAAsB;AAE5D;AAAc;AACb;AAAC;AAAc;AACZ;AACE;AACU;AACT;AACF;AAED;AACU;AACW;AACpB;AACF;AAEG;AACU;AACT;AACF;AAAA;AAEJ;AACiC;AAChC;AAAc;AAEX;AACG;AAA2B;AAAwB;AACjD;AAAgC;AAC9B;AACgB;AACN;AACC;AACF;AACM;AACE;AAClB;AACF;AACF;AACF;AAEJ;AAAA;AACF;AAGC;AAAc;AACZ;AAAc;AACb;AAAgB;AACb;AAAA;AACL;AACF;AAAA;AAIR;AAEa;AAQN;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACkB;AACO;AACzB;AACQ;AACL;AAIL;AAEA;AACA;AACE;AAAU;AACR;AACF;AAGF;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AACnB;AACpB;AACA;AAGF;AAA2B;AACzB;AAEI;AAAmB;AAAa;AAChC;AAA8C;AAChD;AACK;AACP;AAEE;AAAsC;AAC/B;AACiB;AAG5B;AACA;AAMA;AACE;AAAqB;AAGvB;AACE;AAA+B;AAGjC;AAAoB;AAEhB;AAEA;AACE;AAAA;AAGF;AACE;AAAyB;AAC3B;AACF;AACuC;AAGzC;AAAmB;AAEf;AAEA;AACE;AAAA;AAGF;AAAuC;AACL;AAGlC;AACE;AAAwB;AAC1B;AACF;AAC0B;AAG5B;AAA0B;AAEtB;AAEA;AACE;AAAyB;AAC3B;AACF;AAC8B;AAGhC;AAA6B;AAEzB;AAEA;AACE;AAAA;AAGF;AAEA;AACE;AAAY;AACV;AACA;AACc;AACO;AACtB;AAED;AAAc;AACZ;AACc;AACO;AACtB;AAED;AAAa;AACG;AACS;AACF;AACtB;AACH;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AACG;AACE;AACmB;AAC6C;AACxD;AACH;AACC;AAC8B;AAC1B;AACD;AACe;AACvB;AACyB;AACF;AACvB;AAEC;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACU;AACV;AACf;AACA;AACF;AACF;AACF;AAGN;;;"}
1
+ {"version":3,"file":"Composer.cjs","sources":["../../src/components/Composer.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentAttachment,\n CommentMixedAttachment,\n DM,\n} from \"@liveblocks/core\";\nimport { assertNever, Permission } from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport {\n useCreateRoomComment,\n useCreateRoomThread,\n useEditRoomComment,\n useLayoutEffect,\n useResolveMentionSuggestions,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport type {\n ComponentPropsWithoutRef,\n ComponentType,\n FocusEvent,\n FormEvent,\n ForwardedRef,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\n\nimport { useLiveblocksUiConfig } from \"../config\";\nimport { FLOATING_ELEMENT_SIDE_OFFSET, MENTION_CHARACTER } from \"../constants\";\nimport { AttachmentIcon } from \"../icons/Attachment\";\nimport { BoldIcon } from \"../icons/Bold\";\nimport { CodeIcon } from \"../icons/Code\";\nimport { EmojiIcon } from \"../icons/Emoji\";\nimport { ItalicIcon } from \"../icons/Italic\";\nimport { MentionIcon } from \"../icons/Mention\";\nimport { SendIcon } from \"../icons/Send\";\nimport { StrikethroughIcon } from \"../icons/Strikethrough\";\nimport type { ComposerOverrides, GlobalOverrides } from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport {\n useComposer,\n useComposerAttachmentsContext,\n useComposerEditorContext,\n} from \"../primitives/Composer/contexts\";\nimport type {\n ComposerEditorComponents,\n ComposerEditorLinkProps,\n ComposerEditorMentionProps,\n ComposerEditorMentionSuggestionsProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerMarkToggleProps,\n ComposerSubmitComment,\n} from \"../primitives/Composer/types\";\nimport { useComposerAttachmentsDropArea } from \"../primitives/Composer/utils\";\nimport type { ComposerBodyMark } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { useControllableState } from \"../utils/use-controllable-state\";\nimport { FileAttachment } from \"./internal/Attachment\";\nimport { Attribution } from \"./internal/Attribution\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport type { EmojiPickerProps } from \"./internal/EmojiPicker\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\ninterface EditorActionProps extends ComponentPropsWithoutRef<\"button\"> {\n label: string;\n tooltipLabel?: string;\n}\n\ninterface EmojiEditorActionProps extends EditorActionProps {\n onPickerOpenChange?: EmojiPickerProps[\"onOpenChange\"];\n}\n\ninterface MarkToggleProps extends ComposerMarkToggleProps {\n icon?: ReactNode;\n shortcut?: string;\n}\n\ntype ComposerCreateThreadProps<M extends BaseMetadata> = {\n threadId?: never;\n commentId?: never;\n\n /**\n * The metadata of the thread to create.\n */\n metadata?: M;\n};\n\ntype ComposerCreateCommentProps = {\n /**\n * The ID of the thread to reply to.\n */\n threadId: string;\n commentId?: never;\n metadata?: never;\n};\n\ntype ComposerEditCommentProps = {\n /**\n * The ID of the thread to edit a comment in.\n */\n threadId: string;\n\n /**\n * The ID of the comment to edit.\n */\n commentId: string;\n metadata?: never;\n};\n\nexport type ComposerProps<M extends BaseMetadata = DM> = Omit<\n ComponentPropsWithoutRef<\"form\">,\n \"defaultValue\"\n> &\n (\n | ComposerCreateThreadProps<M>\n | ComposerCreateCommentProps\n | ComposerEditCommentProps\n ) & {\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n comment: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => Promise<void> | void;\n\n /**\n * The composer's initial value.\n */\n defaultValue?: ComposerEditorProps[\"defaultValue\"];\n\n /**\n * The composer's initial attachments.\n */\n defaultAttachments?: CommentAttachment[];\n\n /**\n * Whether the composer is collapsed. Setting a value will make the composer controlled.\n */\n collapsed?: boolean;\n\n /**\n * The event handler called when the collapsed state of the composer changes.\n */\n onCollapsedChange?: (collapsed: boolean) => void;\n\n /**\n * Whether the composer is initially collapsed. Setting a value will make the composer uncontrolled.\n */\n defaultCollapsed?: boolean;\n\n /**\n * Whether to show and allow adding attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to show formatting controls (e.g. a floating toolbar with formatting toggles when selecting text)\n */\n showFormattingControls?: boolean;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: ComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: ComposerEditorProps[\"autoFocus\"];\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurOnSubmit?: boolean;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n showAttribution?: boolean;\n\n /**\n * @internal\n */\n roomId?: string;\n };\n\ninterface ComposerEditorContainerProps\n extends Pick<\n ComposerProps,\n | \"defaultValue\"\n | \"showAttachments\"\n | \"showFormattingControls\"\n | \"showAttribution\"\n | \"overrides\"\n | \"actions\"\n | \"autoFocus\"\n | \"disabled\"\n > {\n isCollapsed: boolean | undefined;\n onEmptyChange: (isEmpty: boolean) => void;\n hasResolveMentionSuggestions: boolean;\n onEmojiPickerOpenChange: (isOpen: boolean) => void;\n onEditorClick: (event: MouseEvent<HTMLDivElement>) => void;\n}\n\nfunction ComposerInsertMentionEditorAction({\n label,\n tooltipLabel,\n className,\n onClick,\n ...props\n}: EditorActionProps) {\n const { createMention } = useComposer();\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n createMention();\n }\n },\n [createMention, onClick]\n );\n\n return (\n <Tooltip content={tooltipLabel ?? label}>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={handleClick}\n aria-label={label}\n icon={<MentionIcon />}\n {...props}\n />\n </Tooltip>\n );\n}\n\nfunction ComposerInsertEmojiEditorAction({\n label,\n tooltipLabel,\n onPickerOpenChange,\n className,\n ...props\n}: EmojiEditorActionProps) {\n const { insertText } = useComposer();\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <EmojiPicker onEmojiSelect={insertText} onOpenChange={onPickerOpenChange}>\n <Tooltip content={tooltipLabel ?? label}>\n <EmojiPickerTrigger asChild>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n icon={<EmojiIcon />}\n {...props}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n );\n}\n\nfunction ComposerAttachFilesEditorAction({\n label,\n tooltipLabel,\n className,\n ...props\n}: EditorActionProps) {\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <Tooltip content={tooltipLabel ?? label}>\n <ComposerPrimitive.AttachFiles asChild>\n <Button\n className={cn(\"lb-composer-editor-action\", className)}\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n aria-label={label}\n icon={<AttachmentIcon />}\n {...props}\n />\n </ComposerPrimitive.AttachFiles>\n </Tooltip>\n );\n}\n\nfunction ComposerMention({ mention }: ComposerEditorMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return (\n <ComposerPrimitive.Mention className=\"lb-composer-mention\">\n {MENTION_CHARACTER}\n <User userId={mention.id} />\n </ComposerPrimitive.Mention>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n}\n\nfunction ComposerMentionSuggestions({\n mentions,\n}: ComposerEditorMentionSuggestionsProps) {\n return mentions.length > 0 ? (\n <ComposerPrimitive.Suggestions className=\"lb-root lb-portal lb-elevation lb-composer-suggestions lb-composer-mention-suggestions\">\n <ComposerPrimitive.SuggestionsList className=\"lb-composer-suggestions-list lb-composer-mention-suggestions-list\">\n {mentions.map((mention) => {\n switch (mention.kind) {\n case \"user\":\n return (\n <ComposerPrimitive.SuggestionsListItem\n key={mention.id}\n className=\"lb-composer-suggestions-list-item lb-composer-mention-suggestion\"\n value={mention.id}\n >\n <Avatar\n userId={mention.id}\n className=\"lb-composer-mention-suggestion-avatar\"\n />\n <User\n userId={mention.id}\n className=\"lb-composer-mention-suggestion-user\"\n />\n </ComposerPrimitive.SuggestionsListItem>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n })}\n </ComposerPrimitive.SuggestionsList>\n </ComposerPrimitive.Suggestions>\n ) : null;\n}\n\nfunction MarkToggle({\n mark,\n icon,\n shortcut,\n children,\n ...props\n}: MarkToggleProps) {\n const $ = useOverrides();\n const label = useMemo(() => {\n return $.COMPOSER_TOGGLE_MARK(mark);\n }, [$, mark]);\n\n return (\n <ShortcutTooltip\n content={label}\n shortcut={shortcut}\n sideOffset={FLOATING_ELEMENT_SIDE_OFFSET + 2}\n >\n <ComposerPrimitive.MarkToggle mark={mark} asChild {...props}>\n <Button aria-label={label} variant=\"toolbar\" icon={icon}>\n {children}\n </Button>\n </ComposerPrimitive.MarkToggle>\n </ShortcutTooltip>\n );\n}\n\ntype MarkToggles = {\n [K in ComposerBodyMark]: ComponentType<PropsWithChildren>;\n};\n\nconst markToggles: MarkToggles = {\n bold: () => <MarkToggle mark=\"bold\" shortcut=\"Mod-B\" icon={<BoldIcon />} />,\n italic: () => (\n <MarkToggle mark=\"italic\" shortcut=\"Mod-I\" icon={<ItalicIcon />} />\n ),\n strikethrough: () => (\n <MarkToggle\n mark=\"strikethrough\"\n shortcut=\"Mod-Shift-S\"\n icon={<StrikethroughIcon />}\n />\n ),\n code: () => <MarkToggle mark=\"code\" shortcut=\"Mod-E\" icon={<CodeIcon />} />,\n};\n\nconst markTogglesList = Object.entries(markToggles).map(([mark, Toggle]) => (\n <Toggle key={mark} />\n));\n\nfunction ComposerFloatingToolbar() {\n return (\n <ComposerPrimitive.FloatingToolbar className=\"lb-root lb-portal lb-elevation lb-composer-floating-toolbar\">\n {markTogglesList}\n </ComposerPrimitive.FloatingToolbar>\n );\n}\n\nfunction ComposerLink({ href, children }: ComposerEditorLinkProps) {\n return (\n <ComposerPrimitive.Link href={href} className=\"lb-composer-link\">\n {children}\n </ComposerPrimitive.Link>\n );\n}\n\ninterface ComposerAttachmentsProps extends ComponentPropsWithoutRef<\"div\"> {\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n}\n\ninterface ComposerFileAttachmentProps extends ComponentPropsWithoutRef<\"div\"> {\n attachment: CommentMixedAttachment;\n overrides?: Partial<GlobalOverrides & ComposerOverrides>;\n}\n\nfunction ComposerFileAttachment({\n attachment,\n className,\n overrides,\n ...props\n}: ComposerFileAttachmentProps) {\n const { removeAttachment } = useComposer();\n const { roomId } = useComposerEditorContext();\n\n const handleDeleteClick = useCallback(() => {\n removeAttachment(attachment.id);\n }, [attachment.id, removeAttachment]);\n\n return (\n <FileAttachment\n className={cn(\"lb-composer-attachment\", className)}\n {...props}\n attachment={attachment}\n onDeleteClick={handleDeleteClick}\n preventFocusOnDelete\n overrides={overrides}\n roomId={roomId}\n />\n );\n}\n\nfunction ComposerAttachments({\n overrides,\n className,\n ...props\n}: ComposerAttachmentsProps) {\n const { attachments } = useComposer();\n\n if (attachments.length === 0) {\n return null;\n }\n\n return (\n <div className={cn(\"lb-composer-attachments\", className)} {...props}>\n <div className=\"lb-attachments\">\n {attachments.map((attachment) => {\n return (\n <ComposerFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n />\n );\n })}\n </div>\n </div>\n );\n}\n\nconst editorRequiredComponents: ComposerEditorComponents = {\n Mention: ComposerMention,\n MentionSuggestions: ComposerMentionSuggestions,\n Link: ComposerLink,\n};\n\nfunction ComposerEditorContainer({\n showAttachments = true,\n showFormattingControls = true,\n showAttribution,\n defaultValue,\n isCollapsed,\n overrides,\n actions,\n autoFocus,\n disabled,\n hasResolveMentionSuggestions,\n onEmojiPickerOpenChange,\n onEmptyChange,\n onEditorClick,\n}: ComposerEditorContainerProps) {\n const { isEmpty } = useComposer();\n const { hasMaxAttachments } = useComposerAttachmentsContext();\n const $ = useOverrides(overrides);\n const components = useMemo(() => {\n return {\n ...editorRequiredComponents,\n FloatingToolbar: showFormattingControls\n ? ComposerFloatingToolbar\n : undefined,\n };\n }, [showFormattingControls]);\n\n const [isDraggingOver, dropAreaProps] = useComposerAttachmentsDropArea({\n disabled: disabled || hasMaxAttachments,\n });\n\n useLayoutEffect(() => {\n onEmptyChange(isEmpty);\n }, [isEmpty, onEmptyChange]);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return (\n <div className=\"lb-composer-editor-container\" {...dropAreaProps}>\n <ComposerPrimitive.Editor\n className=\"lb-composer-editor\"\n onClick={onEditorClick}\n placeholder={$.COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n autoFocus={autoFocus}\n components={components}\n disabled={disabled}\n dir={$.dir}\n />\n {showAttachments && <ComposerAttachments overrides={overrides} />}\n {(!isCollapsed || isDraggingOver) && (\n <div className=\"lb-composer-footer\">\n <div className=\"lb-composer-editor-actions\">\n {hasResolveMentionSuggestions && (\n <ComposerInsertMentionEditorAction\n label={$.COMPOSER_INSERT_MENTION}\n disabled={disabled}\n />\n )}\n <ComposerInsertEmojiEditorAction\n label={$.COMPOSER_INSERT_EMOJI}\n onPickerOpenChange={onEmojiPickerOpenChange}\n disabled={disabled}\n />\n {showAttachments && (\n <ComposerAttachFilesEditorAction\n label={$.COMPOSER_ATTACH_FILES}\n disabled={disabled}\n />\n )}\n </div>\n {showAttribution && <Attribution />}\n <div className=\"lb-composer-actions\">\n {actions ?? (\n <>\n <ShortcutTooltip content={$.COMPOSER_SEND} shortcut=\"Enter\">\n <ComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-composer-action\"\n variant=\"primary\"\n aria-label={$.COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n )}\n </div>\n </div>\n )}\n {showAttachments && isDraggingOver && (\n <div className=\"lb-composer-attachments-drop-area\">\n <div className=\"lb-composer-attachments-drop-area-label\">\n <AttachmentIcon />\n {$.COMPOSER_ATTACH_FILES}\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport const ComposerRoomIdContext = createContext<string | null>(null);\n\n/**\n * Displays a composer to create comments.\n *\n * @example\n * <Composer />\n */\nexport const Composer = forwardRef(\n <M extends BaseMetadata = DM>(\n {\n threadId,\n commentId,\n metadata,\n defaultValue,\n defaultAttachments,\n onComposerSubmit,\n collapsed: controlledCollapsed,\n defaultCollapsed,\n onCollapsedChange: controlledOnCollapsedChange,\n overrides,\n actions,\n onBlur,\n className,\n onFocus,\n autoFocus,\n disabled,\n blurOnSubmit = true,\n showAttachments = true,\n showFormattingControls = true,\n showAttribution,\n roomId: _roomId,\n ...props\n }: ComposerProps<M>,\n forwardedRef: ForwardedRef<HTMLFormElement>\n ) => {\n const room = useRoom({ allowOutsideRoom: true });\n\n const roomId = _roomId !== undefined ? _roomId : room?.id;\n if (roomId === undefined) {\n throw new Error(\n \"Composer must be a descendant of RoomProvider component\"\n );\n }\n\n const createThread = useCreateRoomThread(roomId);\n const createComment = useCreateRoomComment(roomId);\n const editComment = useEditRoomComment(roomId);\n const { preventUnsavedComposerChanges } = useLiveblocksUiConfig();\n const hasResolveMentionSuggestions =\n useResolveMentionSuggestions() !== undefined;\n const isEmptyRef = useRef(true);\n const isEmojiPickerOpenRef = useRef(false);\n const $ = useOverrides(overrides);\n const [isCollapsed, onCollapsedChange] = useControllableState(\n defaultCollapsed ?? false,\n controlledCollapsed,\n controlledOnCollapsedChange\n );\n\n const canCommentFallback = useSyncExternalStore(\n useCallback(\n (callback) => {\n if (room === null) return () => {};\n return room.events.self.subscribeOnce(callback);\n },\n [room]\n ),\n useCallback(() => {\n return room?.getSelf()?.canComment ?? true;\n }, [room]),\n useCallback(() => true, [])\n );\n\n const permissions = useRoomPermissions(roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : canCommentFallback;\n\n const setEmptyRef = useCallback((isEmpty: boolean) => {\n isEmptyRef.current = isEmpty;\n }, []);\n\n const setEmojiPickerOpenRef = useCallback((isEmojiPickerOpen: boolean) => {\n isEmojiPickerOpenRef.current = isEmojiPickerOpen;\n }, []);\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLFormElement>) => {\n onFocus?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n if (isEmptyRef.current && canComment) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, onFocus, canComment]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLFormElement>) => {\n onBlur?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n const isOutside = !event.currentTarget.contains(\n event.relatedTarget ?? document.activeElement\n );\n\n if (isOutside && isEmptyRef.current && !isEmojiPickerOpenRef.current) {\n onCollapsedChange?.(true);\n }\n },\n [onBlur, onCollapsedChange]\n );\n\n const handleEditorClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n event.stopPropagation();\n\n if (isEmptyRef.current && canComment) {\n onCollapsedChange?.(false);\n }\n },\n [onCollapsedChange, canComment]\n );\n\n const handleComposerSubmit = useCallback(\n (comment: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(comment, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n\n if (commentId && threadId) {\n editComment({\n commentId,\n threadId,\n body: comment.body,\n attachments: comment.attachments,\n });\n } else if (threadId) {\n createComment({\n threadId,\n body: comment.body,\n attachments: comment.attachments,\n });\n } else {\n createThread({\n body: comment.body,\n metadata: metadata ?? {},\n attachments: comment.attachments,\n });\n }\n },\n [\n commentId,\n createComment,\n createThread,\n editComment,\n metadata,\n onComposerSubmit,\n threadId,\n ]\n );\n\n return (\n <TooltipProvider>\n <ComposerPrimitive.Form\n onComposerSubmit={handleComposerSubmit}\n className={cn(\"lb-root lb-composer lb-composer-form\", className)}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n data-collapsed={isCollapsed ? \"\" : undefined}\n onFocus={handleFocus}\n onBlur={handleBlur}\n disabled={disabled || !canComment}\n defaultAttachments={defaultAttachments}\n pasteFilesAsAttachments={showAttachments}\n preventUnsavedChanges={preventUnsavedComposerChanges}\n blurOnSubmit={blurOnSubmit}\n roomId={roomId}\n >\n <ComposerEditorContainer\n defaultValue={defaultValue}\n actions={actions}\n overrides={overrides}\n isCollapsed={isCollapsed}\n showAttachments={showAttachments}\n showAttribution={showAttribution}\n showFormattingControls={showFormattingControls}\n hasResolveMentionSuggestions={hasResolveMentionSuggestions}\n onEmptyChange={setEmptyRef}\n onEmojiPickerOpenChange={setEmojiPickerOpenRef}\n onEditorClick={handleEditorClick}\n autoFocus={autoFocus}\n disabled={disabled}\n />\n </ComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = DM>(\n props: ComposerProps<M> & RefAttributes<HTMLFormElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyOA;AAA2C;AACzC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AAAoB;AAEhB;AAEA;AACE;AACA;AAAc;AAChB;AACF;AACuB;AAGzB;AACG;AAAiC;AAC/B;AACqD;AACrC;AACN;AACG;AACO;AACf;AACN;AAGN;AAEA;AAAyC;AACvC;AACA;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAA2B;AAA0B;AACnD;AAAiC;AAC/B;AAA0B;AACxB;AACqD;AACrC;AACN;AACG;AACK;AACb;AACN;AACF;AACF;AAGN;AAEA;AAAyC;AACvC;AACA;AACA;AAEF;AACE;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAiC;AAC/B;AAAqC;AACnC;AACqD;AACrC;AACN;AACG;AACU;AAClB;AACN;AACF;AAGN;AAEA;AACE;AAAsB;AAElB;AACG;AAAoC;AAClC;AAAA;AACA;AAAqB;AAAI;AAAA;AAC5B;AAIF;AAAyD;AAE/D;AAEA;AAAoC;AAEpC;AACE;AACG;AAAwC;AACtC;AAA4C;AAEzC;AAAsB;AAElB;AACG;AAEW;AACK;AAEf;AAAC;AACiB;AACN;AACZ;AACC;AACiB;AACN;AACZ;AAAA;AACF;AAIF;AAAyD;AAC7D;AACD;AACH;AAGN;AAEA;AAAoB;AAClB;AACA;AACA;AACA;AAEF;AACE;AACA;AACE;AAAkC;AAGpC;AACG;AACU;AACT;AAC2C;AAE1C;AAA6B;AAAmB;AAAK;AACnD;AAAmB;AAAe;AAAU;AAC1C;AACH;AACF;AAGN;AAMA;AAAiC;AAClB;AAAgB;AAAgB;AAAwB;AAAI;AAEtE;AAAgB;AAAkB;AAA0B;AAAI;AAGhE;AACM;AACI;AACgB;AAC3B;AAEW;AAAgB;AAAgB;AAAwB;AACvE;AAEA;AAIA;AACE;AACG;AAA4C;AAC1C;AAGP;AAEA;AACE;AACG;AAAuB;AAAsB;AAC3C;AAGP;AAWA;AAAgC;AAC9B;AACA;AACA;AAEF;AACE;AACA;AAEA;AACE;AAA8B;AAGhC;AACG;AACkD;AAC7C;AACJ;AACe;AACK;AACpB;AACA;AAGN;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAO;AAGT;AACG;AAAsD;AAAO;AAC3D;AAAc;AAEX;AACG;AAEC;AACA;AACF;AAEH;AACH;AAGN;AAEA;AAA2D;AAChD;AACW;AAEtB;AAEA;AAAiC;AACb;AACO;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AACA;AACE;AAAO;AACF;AAGC;AACN;AAGF;AAAuE;AAC/C;AAGxB;AACE;AAAqB;AAGvB;AACE;AAAqB;AAGvB;AACE;AAAsB;AAGxB;AACG;AAAc;AAAmC;AAChD;AAAC;AACW;AACD;AACM;AACf;AACA;AACA;AACA;AACO;AACT;AACqB;AAAoB;AAAsB;AAE5D;AAAc;AACb;AAAC;AAAc;AACZ;AACE;AACU;AACT;AACF;AAED;AACU;AACW;AACpB;AACF;AAEG;AACU;AACT;AACF;AAAA;AAEJ;AACiC;AAChC;AAAc;AAEX;AACG;AAA2B;AAAwB;AACjD;AAAgC;AAC9B;AACgB;AACN;AACC;AACF;AACM;AACE;AAClB;AACF;AACF;AACF;AAEJ;AAAA;AACF;AAGC;AAAc;AACZ;AAAc;AACb;AAAgB;AACb;AAAA;AACL;AACF;AAAA;AAIR;AAEa;AAQN;AAAiB;AAEpB;AACE;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AACmB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACG;AACO;AACzB;AACQ;AACL;AAIL;AAEA;AACA;AACE;AAAU;AACR;AACF;AAGF;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAyC;AACnB;AACpB;AACA;AAGF;AAA2B;AACzB;AAEI;AAAmB;AAAa;AAChC;AAA8C;AAChD;AACK;AACP;AAEE;AAAsC;AAC/B;AACiB;AAG5B;AACA;AAMA;AACE;AAAqB;AAGvB;AACE;AAA+B;AAGjC;AAAoB;AAEhB;AAEA;AACE;AAAA;AAGF;AACE;AAAyB;AAC3B;AACF;AACuC;AAGzC;AAAmB;AAEf;AAEA;AACE;AAAA;AAGF;AAAuC;AACL;AAGlC;AACE;AAAwB;AAC1B;AACF;AAC0B;AAG5B;AAA0B;AAEtB;AAEA;AACE;AAAyB;AAC3B;AACF;AAC8B;AAGhC;AAA6B;AAEzB;AAEA;AACE;AAAA;AAGF;AAEA;AACE;AAAY;AACV;AACA;AACc;AACO;AACtB;AAED;AAAc;AACZ;AACc;AACO;AACtB;AAED;AAAa;AACG;AACS;AACF;AACtB;AACH;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AACG;AACE;AACmB;AAC6C;AACxD;AACH;AACC;AAC8B;AAC1B;AACD;AACe;AACvB;AACyB;AACF;AACvB;AACA;AAEC;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe;AACU;AACV;AACf;AACA;AACF;AACF;AACF;AAGN;;;"}
@@ -413,6 +413,7 @@ const Composer = forwardRef(
413
413
  onFocus,
414
414
  autoFocus,
415
415
  disabled,
416
+ blurOnSubmit = true,
416
417
  showAttachments = true,
417
418
  showFormattingControls = true,
418
419
  showAttribution,
@@ -550,6 +551,7 @@ const Composer = forwardRef(
550
551
  defaultAttachments,
551
552
  pasteFilesAsAttachments: showAttachments,
552
553
  preventUnsavedChanges: preventUnsavedComposerChanges,
554
+ blurOnSubmit,
553
555
  roomId,
554
556
  children: /* @__PURE__ */ jsx(ComposerEditorContainer, {
555
557
  defaultValue,