@liveblocks/react-tiptap 2.17.0-usrnotsettings3 → 2.18.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 (47) hide show
  1. package/dist/LiveblocksExtension.js +116 -21
  2. package/dist/LiveblocksExtension.js.map +1 -1
  3. package/dist/LiveblocksExtension.mjs +114 -19
  4. package/dist/LiveblocksExtension.mjs.map +1 -1
  5. package/dist/ai/AiExtension.js +382 -0
  6. package/dist/ai/AiExtension.js.map +1 -0
  7. package/dist/ai/AiExtension.mjs +378 -0
  8. package/dist/ai/AiExtension.mjs.map +1 -0
  9. package/dist/ai/AiToolbar.js +663 -0
  10. package/dist/ai/AiToolbar.js.map +1 -0
  11. package/dist/ai/AiToolbar.mjs +660 -0
  12. package/dist/ai/AiToolbar.mjs.map +1 -0
  13. package/dist/comments/CommentsExtension.js.map +1 -1
  14. package/dist/comments/CommentsExtension.mjs.map +1 -1
  15. package/dist/comments/FloatingComposer.js +2 -2
  16. package/dist/comments/FloatingComposer.js.map +1 -1
  17. package/dist/comments/FloatingComposer.mjs +2 -2
  18. package/dist/comments/FloatingComposer.mjs.map +1 -1
  19. package/dist/index.d.mts +107 -15
  20. package/dist/index.d.ts +107 -15
  21. package/dist/index.js +2 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +1 -0
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/toolbar/FloatingToolbar.js +7 -0
  26. package/dist/toolbar/FloatingToolbar.js.map +1 -1
  27. package/dist/toolbar/FloatingToolbar.mjs +7 -0
  28. package/dist/toolbar/FloatingToolbar.mjs.map +1 -1
  29. package/dist/toolbar/Toolbar.js +35 -1
  30. package/dist/toolbar/Toolbar.js.map +1 -1
  31. package/dist/toolbar/Toolbar.mjs +36 -2
  32. package/dist/toolbar/Toolbar.mjs.map +1 -1
  33. package/dist/types.js.map +1 -1
  34. package/dist/types.mjs.map +1 -1
  35. package/dist/utils.js +118 -6
  36. package/dist/utils.js.map +1 -1
  37. package/dist/utils.mjs +116 -7
  38. package/dist/utils.mjs.map +1 -1
  39. package/dist/version.js +1 -1
  40. package/dist/version.js.map +1 -1
  41. package/dist/version.mjs +1 -1
  42. package/dist/version.mjs.map +1 -1
  43. package/package.json +7 -6
  44. package/src/styles/index.css +380 -3
  45. package/src/styles/utils.css +6 -0
  46. package/styles.css +1 -1
  47. package/styles.css.map +1 -1
@@ -0,0 +1,660 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { hide, offset, shift, limitShift, autoUpdate, useFloating } from '@floating-ui/react-dom';
3
+ import { useLayoutEffect } from '@liveblocks/react/_private';
4
+ import { CheckIcon, UndoIcon, CrossIcon, ArrowCornerDownRightIcon, SparklesIcon, ShortcutTooltip, Button, SendIcon, WarningIcon, EditIcon, ShortenIcon, LengthenIcon, SparklesTextIcon, QuestionMarkIcon, useRefs, TooltipProvider } from '@liveblocks/react-ui/_private';
5
+ import { useEditorState } from '@tiptap/react';
6
+ import { Command, useCommandState } from 'cmdk';
7
+ import { createContext, useContext, forwardRef, useCallback, useRef, useMemo, useEffect, useState } from 'react';
8
+ import { createPortal } from 'react-dom';
9
+ import { classNames } from '../classnames.mjs';
10
+ import { useCurrentEditor, EditorProvider } from '../context.mjs';
11
+ import { getDomRangeFromSelection } from '../utils.mjs';
12
+ import { isContextualPromptDiffResponse, DEFAULT_STATE } from './AiExtension.mjs';
13
+
14
+ const AI_TOOLBAR_COLLISION_PADDING = 10;
15
+ const AiToolbarContext = createContext(null);
16
+ function useAiToolbarContext() {
17
+ const context = useContext(AiToolbarContext);
18
+ if (!context) {
19
+ throw new Error("useAiToolbarContext must be used within an AiToolbar");
20
+ }
21
+ return context;
22
+ }
23
+ function tiptapFloating(editor) {
24
+ return {
25
+ name: "tiptap",
26
+ options: editor,
27
+ fn({ elements }) {
28
+ if (!editor) {
29
+ return {};
30
+ }
31
+ const editorRect = editor.view.dom.getBoundingClientRect();
32
+ elements.floating.style.setProperty(
33
+ "--lb-tiptap-editor-width",
34
+ `${editorRect.width}px`
35
+ );
36
+ elements.floating.style.setProperty(
37
+ "--lb-tiptap-editor-height",
38
+ `${editorRect.height}px`
39
+ );
40
+ return {
41
+ x: editorRect.x
42
+ };
43
+ }
44
+ };
45
+ }
46
+ function flipToolbar() {
47
+ return {
48
+ name: "flipToolbar",
49
+ fn({ elements, middlewareData, rects }) {
50
+ const shiftOffsetY = middlewareData.shift?.y ?? 0;
51
+ if (Math.abs(shiftOffsetY) >= rects.floating.height) {
52
+ elements.floating.setAttribute("data-liveblocks-ai-toolbar-flip", "");
53
+ } else {
54
+ elements.floating.removeAttribute("data-liveblocks-ai-toolbar-flip");
55
+ }
56
+ return {};
57
+ }
58
+ };
59
+ }
60
+ const AiToolbarDropdownSeparator = forwardRef(({ className, ...props }, forwardedRef) => {
61
+ return /* @__PURE__ */ jsx(Command.Separator, {
62
+ className: classNames("lb-dropdown-separator", className),
63
+ ...props,
64
+ ref: forwardedRef
65
+ });
66
+ });
67
+ const AiToolbarSuggestionsSeparator = forwardRef((props, forwardedRef) => {
68
+ return /* @__PURE__ */ jsx(AiToolbarDropdownSeparator, {
69
+ ref: forwardedRef,
70
+ ...props
71
+ });
72
+ });
73
+ const AiToolbarDropdownItem = forwardRef(({ children, onSelect, icon, className, ...props }, forwardedRef) => {
74
+ return /* @__PURE__ */ jsxs(Command.Item, {
75
+ className: classNames("lb-dropdown-item", className),
76
+ onSelect,
77
+ ...props,
78
+ ref: forwardedRef,
79
+ children: [
80
+ icon ? /* @__PURE__ */ jsx("span", {
81
+ className: "lb-icon-container",
82
+ children: icon
83
+ }) : null,
84
+ children ? /* @__PURE__ */ jsx("span", {
85
+ className: "lb-dropdown-item-label",
86
+ children
87
+ }) : null
88
+ ]
89
+ });
90
+ });
91
+ const AiToolbarSuggestionsLabel = forwardRef(({ children, className, ...props }, forwardedRef) => {
92
+ return /* @__PURE__ */ jsx("span", {
93
+ ref: forwardedRef,
94
+ className: classNames("lb-dropdown-label", className),
95
+ ...props,
96
+ children
97
+ });
98
+ });
99
+ const AiToolbarSuggestion = forwardRef(({ prompt: manualPrompt, ...props }, forwardedRef) => {
100
+ const editor = useCurrentEditor("Suggestion", "AiToolbar");
101
+ const handleSelect = useCallback(
102
+ (prompt) => {
103
+ editor.commands.$startAiToolbarThinking(
104
+ manualPrompt ?? prompt
105
+ );
106
+ },
107
+ [editor, manualPrompt]
108
+ );
109
+ return /* @__PURE__ */ jsx(AiToolbarDropdownItem, {
110
+ ...props,
111
+ onSelect: handleSelect,
112
+ ref: forwardedRef
113
+ });
114
+ });
115
+ function AiToolbarReviewingSuggestions() {
116
+ const editor = useCurrentEditor("ReviewingSuggestions", "AiToolbar");
117
+ const { state } = useAiToolbarContext();
118
+ const { response } = state;
119
+ if (isContextualPromptDiffResponse(response)) {
120
+ return /* @__PURE__ */ jsxs(Fragment, {
121
+ children: [
122
+ /* @__PURE__ */ jsx(AiToolbarDropdownItem, {
123
+ icon: /* @__PURE__ */ jsx(CheckIcon, {}),
124
+ onSelect: editor.commands.$acceptAiToolbarResponse,
125
+ children: "Accept"
126
+ }),
127
+ /* @__PURE__ */ jsx(AiToolbarDropdownItem, {
128
+ icon: /* @__PURE__ */ jsx(UndoIcon, {}),
129
+ onSelect: editor.commands.$startAiToolbarThinking,
130
+ children: "Try again"
131
+ }),
132
+ /* @__PURE__ */ jsx(AiToolbarDropdownItem, {
133
+ icon: /* @__PURE__ */ jsx(CrossIcon, {}),
134
+ onSelect: editor.commands.$closeAiToolbar,
135
+ children: "Discard"
136
+ })
137
+ ]
138
+ });
139
+ } else {
140
+ return /* @__PURE__ */ jsxs(Fragment, {
141
+ children: [
142
+ /* @__PURE__ */ jsx(AiToolbarDropdownItem, {
143
+ icon: /* @__PURE__ */ jsx(ArrowCornerDownRightIcon, {}),
144
+ onSelect: editor.commands.$acceptAiToolbarResponse,
145
+ children: "Insert below"
146
+ }),
147
+ /* @__PURE__ */ jsx(AiToolbarDropdownItem, {
148
+ icon: /* @__PURE__ */ jsx(UndoIcon, {}),
149
+ onSelect: editor.commands.$startAiToolbarThinking,
150
+ children: "Try again"
151
+ }),
152
+ /* @__PURE__ */ jsx(AiToolbarDropdownItem, {
153
+ icon: /* @__PURE__ */ jsx(CrossIcon, {}),
154
+ onSelect: editor.commands.$closeAiToolbar,
155
+ children: "Discard"
156
+ })
157
+ ]
158
+ });
159
+ }
160
+ }
161
+ function AiToolbarCustomPromptContent() {
162
+ const editor = useCurrentEditor("CustomPromptContent", "AiToolbar");
163
+ const aiName = editor.storage.liveblocksAi.name;
164
+ const textAreaRef = useRef(null);
165
+ const { state, dropdownRef, isDropdownHidden } = useAiToolbarContext();
166
+ const { customPrompt } = state;
167
+ const isCustomPromptEmpty = useMemo(
168
+ () => customPrompt.trim() === "",
169
+ [customPrompt]
170
+ );
171
+ useLayoutEffect(
172
+ () => {
173
+ setTimeout(() => {
174
+ const textArea = textAreaRef.current;
175
+ if (!textArea) {
176
+ return;
177
+ }
178
+ textArea.focus();
179
+ textArea.setSelectionRange(
180
+ textArea.value.length,
181
+ textArea.value.length
182
+ );
183
+ }, 0);
184
+ },
185
+ []
186
+ );
187
+ const handlePromptKeyDown = (event) => {
188
+ if (event.key === "Enter") {
189
+ event.preventDefault();
190
+ event.stopPropagation();
191
+ if (event.shiftKey) {
192
+ editor.commands._updateAiToolbarCustomPrompt(
193
+ (customPrompt2) => customPrompt2 + "\n"
194
+ );
195
+ } else {
196
+ const selectedDropdownItem = dropdownRef.current?.querySelector(
197
+ "[role='option'][data-selected='true']"
198
+ );
199
+ if (!isDropdownHidden && selectedDropdownItem) {
200
+ selectedDropdownItem.click();
201
+ } else if (!isCustomPromptEmpty) {
202
+ editor.commands.$startAiToolbarThinking(
203
+ customPrompt,
204
+ state.phase === "reviewing"
205
+ );
206
+ }
207
+ }
208
+ }
209
+ };
210
+ const handleCustomPromptChange = useCallback(
211
+ (customPrompt2) => {
212
+ editor.commands._updateAiToolbarCustomPrompt(
213
+ customPrompt2
214
+ );
215
+ },
216
+ [editor]
217
+ );
218
+ const handleSendClick = useCallback(() => {
219
+ if (isCustomPromptEmpty) {
220
+ return;
221
+ }
222
+ editor.commands.$startAiToolbarThinking(
223
+ customPrompt,
224
+ state.phase === "reviewing"
225
+ );
226
+ }, [editor, customPrompt, isCustomPromptEmpty, state.phase]);
227
+ return /* @__PURE__ */ jsxs("div", {
228
+ className: "lb-tiptap-ai-toolbar-content",
229
+ children: [
230
+ /* @__PURE__ */ jsx("span", {
231
+ className: "lb-icon-container lb-tiptap-ai-toolbar-icon-container",
232
+ children: /* @__PURE__ */ jsx(SparklesIcon, {})
233
+ }),
234
+ /* @__PURE__ */ jsx("div", {
235
+ className: "lb-tiptap-ai-toolbar-custom-prompt-container",
236
+ "data-value": customPrompt,
237
+ children: /* @__PURE__ */ jsx(Command.Input, {
238
+ value: customPrompt,
239
+ onValueChange: handleCustomPromptChange,
240
+ asChild: true,
241
+ children: /* @__PURE__ */ jsx("textarea", {
242
+ ref: textAreaRef,
243
+ className: "lb-tiptap-ai-toolbar-custom-prompt",
244
+ placeholder: `Ask ${aiName} anything\u2026`,
245
+ onKeyDown: handlePromptKeyDown,
246
+ rows: 1,
247
+ autoFocus: true
248
+ })
249
+ })
250
+ }),
251
+ /* @__PURE__ */ jsx("div", {
252
+ className: "lb-tiptap-ai-toolbar-actions",
253
+ children: /* @__PURE__ */ jsx(ShortcutTooltip, {
254
+ content: `Ask ${aiName}`,
255
+ shortcut: "Enter",
256
+ children: /* @__PURE__ */ jsx(Button, {
257
+ className: "lb-tiptap-ai-toolbar-action",
258
+ variant: "primary",
259
+ "aria-label": `Ask ${aiName}`,
260
+ icon: /* @__PURE__ */ jsx(SendIcon, {}),
261
+ disabled: isCustomPromptEmpty,
262
+ onClick: handleSendClick
263
+ })
264
+ })
265
+ })
266
+ ]
267
+ });
268
+ }
269
+ function AiToolbarAsking() {
270
+ const { state } = useAiToolbarContext();
271
+ const { error } = state;
272
+ return /* @__PURE__ */ jsxs(Fragment, {
273
+ children: [
274
+ /* @__PURE__ */ jsx(AiToolbarCustomPromptContent, {}),
275
+ error ? /* @__PURE__ */ jsxs("div", {
276
+ className: "lb-tiptap-ai-toolbar-error",
277
+ children: [
278
+ /* @__PURE__ */ jsx("span", {
279
+ className: "lb-icon-container",
280
+ children: /* @__PURE__ */ jsx(WarningIcon, {})
281
+ }),
282
+ "There was a problem with your request."
283
+ ]
284
+ }) : null
285
+ ]
286
+ });
287
+ }
288
+ function AiToolbarThinking() {
289
+ const editor = useCurrentEditor("AiToolbarThinking", "AiToolbar");
290
+ const contentRef = useRef(null);
291
+ const aiName = editor.storage.liveblocksAi.name;
292
+ const handleCancel = useCallback(() => {
293
+ editor.commands.$cancelAiToolbarThinking();
294
+ }, [editor]);
295
+ useLayoutEffect(() => {
296
+ contentRef.current?.focus();
297
+ window.getSelection()?.removeAllRanges();
298
+ }, []);
299
+ return /* @__PURE__ */ jsx(Fragment, {
300
+ children: /* @__PURE__ */ jsxs("div", {
301
+ className: "lb-tiptap-ai-toolbar-content",
302
+ tabIndex: 0,
303
+ ref: contentRef,
304
+ children: [
305
+ /* @__PURE__ */ jsx("span", {
306
+ className: "lb-icon-container lb-tiptap-ai-toolbar-icon-container",
307
+ children: /* @__PURE__ */ jsx(SparklesIcon, {})
308
+ }),
309
+ /* @__PURE__ */ jsxs("div", {
310
+ className: "lb-tiptap-ai-toolbar-thinking",
311
+ children: [
312
+ aiName,
313
+ " is thinking\u2026"
314
+ ]
315
+ }),
316
+ /* @__PURE__ */ jsx("div", {
317
+ className: "lb-tiptap-ai-toolbar-actions",
318
+ children: /* @__PURE__ */ jsx(ShortcutTooltip, {
319
+ content: "Cancel",
320
+ shortcut: "Escape",
321
+ children: /* @__PURE__ */ jsx(Button, {
322
+ className: "lb-tiptap-ai-toolbar-action",
323
+ variant: "secondary",
324
+ "aria-label": "Cancel",
325
+ icon: /* @__PURE__ */ jsx(UndoIcon, {}),
326
+ onClick: handleCancel
327
+ })
328
+ })
329
+ })
330
+ ]
331
+ })
332
+ });
333
+ }
334
+ function AiToolbarReviewing() {
335
+ const { state } = useAiToolbarContext();
336
+ const { response } = state;
337
+ return /* @__PURE__ */ jsxs(Fragment, {
338
+ children: [
339
+ response.type === "other" ? /* @__PURE__ */ jsx("div", {
340
+ className: "lb-tiptap-ai-toolbar-response-container",
341
+ children: /* @__PURE__ */ jsx("div", {
342
+ className: "lb-tiptap-ai-toolbar-response",
343
+ children: response.text
344
+ })
345
+ }) : null,
346
+ /* @__PURE__ */ jsx(AiToolbarCustomPromptContent, {})
347
+ ]
348
+ });
349
+ }
350
+ function AiToolbarContainer({
351
+ state,
352
+ toolbarRef,
353
+ dropdownRef,
354
+ children
355
+ }) {
356
+ const editor = useCurrentEditor("AiToolbarContainer", "AiToolbar");
357
+ const customPrompt = state.customPrompt;
358
+ const isCustomPromptMultiline = useMemo(
359
+ () => customPrompt?.includes("\n"),
360
+ [customPrompt]
361
+ );
362
+ const hasDropdownItems = useCommandState(
363
+ (state2) => state2.filtered.count > 0
364
+ );
365
+ const isDropdownHidden = isCustomPromptMultiline || !hasDropdownItems;
366
+ useEffect(() => {
367
+ if (!editor) {
368
+ return;
369
+ }
370
+ const handleKeyDown = (event) => {
371
+ if (!event.defaultPrevented && event.key === "Escape") {
372
+ event.preventDefault();
373
+ event.stopPropagation();
374
+ if (state.phase === "thinking") {
375
+ editor.commands.$cancelAiToolbarThinking();
376
+ } else {
377
+ editor.chain().$closeAiToolbar().focus().run();
378
+ }
379
+ }
380
+ };
381
+ document.addEventListener("keydown", handleKeyDown);
382
+ return () => {
383
+ document.removeEventListener("keydown", handleKeyDown);
384
+ };
385
+ }, [editor, state.phase]);
386
+ return /* @__PURE__ */ jsxs(AiToolbarContext.Provider, {
387
+ value: {
388
+ state,
389
+ toolbarRef,
390
+ dropdownRef,
391
+ isDropdownHidden
392
+ },
393
+ children: [
394
+ /* @__PURE__ */ jsxs("div", {
395
+ className: "lb-tiptap-ai-toolbar-container",
396
+ children: [
397
+ /* @__PURE__ */ jsx("div", {
398
+ className: "lb-elevation lb-tiptap-ai-toolbar",
399
+ children: state.phase === "asking" ? /* @__PURE__ */ jsx(AiToolbarAsking, {}) : state.phase === "thinking" ? /* @__PURE__ */ jsx(AiToolbarThinking, {}) : state.phase === "reviewing" ? /* @__PURE__ */ jsx(AiToolbarReviewing, {}) : null
400
+ }),
401
+ /* @__PURE__ */ jsxs("div", {
402
+ className: "lb-tiptap-ai-toolbar-halo",
403
+ "data-active": state.phase === "thinking" ? "" : void 0,
404
+ "aria-hidden": true,
405
+ children: [
406
+ /* @__PURE__ */ jsx("div", {
407
+ className: "lb-tiptap-ai-toolbar-halo-horizontal"
408
+ }),
409
+ /* @__PURE__ */ jsx("div", {
410
+ className: "lb-tiptap-ai-toolbar-halo-vertical"
411
+ })
412
+ ]
413
+ })
414
+ ]
415
+ }),
416
+ state.phase === "asking" || state.phase === "reviewing" ? /* @__PURE__ */ jsx(Command.List, {
417
+ className: "lb-elevation lb-dropdown lb-tiptap-ai-toolbar-dropdown",
418
+ "data-hidden": isDropdownHidden ? "" : void 0,
419
+ ref: dropdownRef,
420
+ children: state.phase === "reviewing" ? /* @__PURE__ */ jsx(AiToolbarReviewingSuggestions, {}) : children
421
+ }) : null
422
+ ]
423
+ });
424
+ }
425
+ const defaultSuggestions = /* @__PURE__ */ jsxs(Fragment, {
426
+ children: [
427
+ /* @__PURE__ */ jsx(AiToolbarSuggestion, {
428
+ icon: /* @__PURE__ */ jsx(EditIcon, {}),
429
+ prompt: "Improve the quality of the text",
430
+ children: "Improve writing"
431
+ }),
432
+ /* @__PURE__ */ jsx(AiToolbarSuggestion, {
433
+ icon: /* @__PURE__ */ jsx(CheckIcon, {}),
434
+ prompt: "Fix spelling & grammar errors in the text",
435
+ children: "Fix mistakes"
436
+ }),
437
+ /* @__PURE__ */ jsx(AiToolbarSuggestion, {
438
+ icon: /* @__PURE__ */ jsx(ShortenIcon, {}),
439
+ prompt: "Shorten the text, simplifying it",
440
+ children: "Simplify"
441
+ }),
442
+ /* @__PURE__ */ jsx(AiToolbarSuggestion, {
443
+ icon: /* @__PURE__ */ jsx(LengthenIcon, {}),
444
+ prompt: "Lengthen the text, going into more detail",
445
+ children: "Add more detail"
446
+ }),
447
+ /* @__PURE__ */ jsx(AiToolbarSuggestionsSeparator, {}),
448
+ /* @__PURE__ */ jsx(AiToolbarSuggestion, {
449
+ icon: /* @__PURE__ */ jsx(SparklesTextIcon, {}),
450
+ prompt: "Continue writing from the text's end",
451
+ children: "Continue writing"
452
+ }),
453
+ /* @__PURE__ */ jsx(AiToolbarSuggestion, {
454
+ icon: /* @__PURE__ */ jsx(QuestionMarkIcon, {}),
455
+ prompt: "Explain what the text is about",
456
+ children: "Explain"
457
+ })
458
+ ]
459
+ });
460
+ const AiToolbar = Object.assign(
461
+ forwardRef(
462
+ ({
463
+ offset: sideOffset = 6,
464
+ editor,
465
+ className,
466
+ suggestions: Suggestions = defaultSuggestions,
467
+ ...props
468
+ }, forwardedRef) => {
469
+ const state = useEditorState({
470
+ editor,
471
+ selector: (ctx) => {
472
+ return ctx.editor?.storage.liveblocksAi?.state;
473
+ }
474
+ }) ?? DEFAULT_STATE;
475
+ const selection = editor?.state.selection;
476
+ const floatingOptions = useMemo(() => {
477
+ const detectOverflowOptions = {
478
+ padding: AI_TOOLBAR_COLLISION_PADDING
479
+ };
480
+ return {
481
+ strategy: "fixed",
482
+ placement: "bottom",
483
+ middleware: [
484
+ tiptapFloating(editor),
485
+ hide(detectOverflowOptions),
486
+ offset(sideOffset),
487
+ shift({
488
+ ...detectOverflowOptions,
489
+ mainAxis: false,
490
+ crossAxis: true,
491
+ limiter: limitShift()
492
+ }),
493
+ flipToolbar()
494
+ ],
495
+ whileElementsMounted: (...args) => {
496
+ return autoUpdate(...args, {
497
+ animationFrame: true
498
+ });
499
+ }
500
+ };
501
+ }, [editor, sideOffset]);
502
+ const isOpen = selection !== void 0 && state.phase !== "closed";
503
+ const {
504
+ refs: { setReference, setFloating },
505
+ strategy,
506
+ x,
507
+ y,
508
+ isPositioned
509
+ } = useFloating({
510
+ ...floatingOptions,
511
+ open: isOpen
512
+ });
513
+ const toolbarRef = useRef(null);
514
+ const mergedRefs = useRefs(forwardedRef, toolbarRef, setFloating);
515
+ const dropdownRef = useRef(null);
516
+ const [selectedDropdownValue, setSelectedDropdownValue] = useState("");
517
+ useEffect(() => {
518
+ if (state.phase === "closed") {
519
+ setSelectedDropdownValue("");
520
+ }
521
+ }, [state.phase]);
522
+ useEffect(() => {
523
+ if (state.phase === "closed") {
524
+ setSelectedDropdownValue("");
525
+ return;
526
+ }
527
+ const selectedDropdownItem = dropdownRef.current?.querySelector(
528
+ "[role='option'][data-selected='true']"
529
+ );
530
+ if (selectedDropdownItem) {
531
+ return;
532
+ }
533
+ const firstDropdownItem = dropdownRef.current?.querySelector("[role='option']");
534
+ setSelectedDropdownValue(
535
+ firstDropdownItem?.dataset.value ?? ""
536
+ );
537
+ }, [state.phase, dropdownRef, setSelectedDropdownValue]);
538
+ useEffect(() => {
539
+ if (!editor) {
540
+ return;
541
+ }
542
+ if (!selection && state.phase !== "closed") {
543
+ editor.commands.$closeAiToolbar();
544
+ }
545
+ }, [state.phase, editor, selection]);
546
+ useLayoutEffect(() => {
547
+ if (!editor || !isOpen) {
548
+ return;
549
+ }
550
+ setReference(null);
551
+ setTimeout(() => {
552
+ if (state.phase === "reviewing" && isContextualPromptDiffResponse(state.response)) {
553
+ const changes = editor.view.dom.querySelectorAll(
554
+ "ychange[data-liveblocks]"
555
+ );
556
+ setReference({
557
+ getBoundingClientRect: () => {
558
+ const rects = [];
559
+ changes.forEach((change) => {
560
+ rects.push(change.getBoundingClientRect());
561
+ });
562
+ const minX = Math.min(...rects.map((rect) => rect.left));
563
+ const minY = Math.min(...rects.map((rect) => rect.top));
564
+ const maxX = Math.max(...rects.map((rect) => rect.right));
565
+ const maxY = Math.max(...rects.map((rect) => rect.bottom));
566
+ return {
567
+ x: minX,
568
+ y: minY,
569
+ width: maxX - minX,
570
+ height: maxY - minY,
571
+ top: minY,
572
+ left: minX,
573
+ bottom: maxY,
574
+ right: maxX
575
+ };
576
+ }
577
+ });
578
+ } else if (selection) {
579
+ const domRange = getDomRangeFromSelection(editor, selection);
580
+ setReference(domRange);
581
+ } else {
582
+ setReference(null);
583
+ }
584
+ }, 0);
585
+ }, [
586
+ selection,
587
+ editor,
588
+ isOpen,
589
+ setReference,
590
+ state.phase,
591
+ state.response
592
+ ]);
593
+ useEffect(() => {
594
+ if (!editor || !isOpen) {
595
+ return;
596
+ }
597
+ const handleOutsideEvent = (event) => {
598
+ if (!toolbarRef.current) {
599
+ return;
600
+ }
601
+ if (event.target && !toolbarRef.current.contains(event.target) && (dropdownRef.current ? !dropdownRef.current.contains(event.target) : true)) {
602
+ editor.commands.$closeAiToolbar();
603
+ }
604
+ };
605
+ setTimeout(() => {
606
+ document.addEventListener("pointerdown", handleOutsideEvent);
607
+ }, 0);
608
+ return () => {
609
+ document.removeEventListener("pointerdown", handleOutsideEvent);
610
+ };
611
+ }, [editor, isOpen]);
612
+ if (!editor || !isOpen) {
613
+ return null;
614
+ }
615
+ return createPortal(
616
+ /* @__PURE__ */ jsx(TooltipProvider, {
617
+ children: /* @__PURE__ */ jsx(EditorProvider, {
618
+ editor,
619
+ children: /* @__PURE__ */ jsx(Command, {
620
+ role: "toolbar",
621
+ label: "AI toolbar",
622
+ "aria-orientation": "horizontal",
623
+ className: classNames(
624
+ "lb-root lb-portal lb-tiptap-ai-toolbar-portal",
625
+ className
626
+ ),
627
+ ref: mergedRefs,
628
+ style: {
629
+ position: strategy,
630
+ top: 0,
631
+ left: 0,
632
+ transform: isPositioned ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)` : "translate3d(0, -200%, 0)"
633
+ },
634
+ value: selectedDropdownValue,
635
+ onValueChange: setSelectedDropdownValue,
636
+ ...props,
637
+ children: /* @__PURE__ */ jsx(AiToolbarContainer, {
638
+ state,
639
+ dropdownRef,
640
+ toolbarRef,
641
+ children: typeof Suggestions === "function" ? /* @__PURE__ */ jsx(Suggestions, {
642
+ children: defaultSuggestions
643
+ }) : Suggestions
644
+ })
645
+ })
646
+ })
647
+ }),
648
+ document.body
649
+ );
650
+ }
651
+ ),
652
+ {
653
+ Suggestion: AiToolbarSuggestion,
654
+ SuggestionsLabel: AiToolbarSuggestionsLabel,
655
+ SuggestionsSeparator: AiToolbarSuggestionsSeparator
656
+ }
657
+ );
658
+
659
+ export { AI_TOOLBAR_COLLISION_PADDING, AiToolbar };
660
+ //# sourceMappingURL=AiToolbar.mjs.map