@liveblocks/react-tiptap 0.0.1 → 2.8.3-tiptap1

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 (69) hide show
  1. package/dist/LiveblocksExtension.js +144 -0
  2. package/dist/LiveblocksExtension.js.map +1 -0
  3. package/dist/LiveblocksExtension.mjs +142 -0
  4. package/dist/LiveblocksExtension.mjs.map +1 -0
  5. package/dist/classnames.js +8 -0
  6. package/dist/classnames.js.map +1 -0
  7. package/dist/classnames.mjs +6 -0
  8. package/dist/classnames.mjs.map +1 -0
  9. package/dist/comments/AnchoredThreads.js +178 -0
  10. package/dist/comments/AnchoredThreads.js.map +1 -0
  11. package/dist/comments/AnchoredThreads.mjs +176 -0
  12. package/dist/comments/AnchoredThreads.mjs.map +1 -0
  13. package/dist/comments/CommentsExtension.js +207 -0
  14. package/dist/comments/CommentsExtension.js.map +1 -0
  15. package/dist/comments/CommentsExtension.mjs +205 -0
  16. package/dist/comments/CommentsExtension.mjs.map +1 -0
  17. package/dist/comments/FloatingComposer.js +103 -0
  18. package/dist/comments/FloatingComposer.js.map +1 -0
  19. package/dist/comments/FloatingComposer.mjs +100 -0
  20. package/dist/comments/FloatingComposer.mjs.map +1 -0
  21. package/dist/comments/FloatingThreads.js +154 -0
  22. package/dist/comments/FloatingThreads.js.map +1 -0
  23. package/dist/comments/FloatingThreads.mjs +151 -0
  24. package/dist/comments/FloatingThreads.mjs.map +1 -0
  25. package/dist/index.d.mts +65 -0
  26. package/dist/index.d.ts +65 -0
  27. package/dist/index.js +18 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/index.mjs +10 -0
  30. package/dist/index.mjs.map +1 -0
  31. package/dist/mentions/Avatar.js +53 -0
  32. package/dist/mentions/Avatar.js.map +1 -0
  33. package/dist/mentions/Avatar.mjs +51 -0
  34. package/dist/mentions/Avatar.mjs.map +1 -0
  35. package/dist/mentions/Mention.js +24 -0
  36. package/dist/mentions/Mention.js.map +1 -0
  37. package/dist/mentions/Mention.mjs +22 -0
  38. package/dist/mentions/Mention.mjs.map +1 -0
  39. package/dist/mentions/MentionExtension.js +221 -0
  40. package/dist/mentions/MentionExtension.js.map +1 -0
  41. package/dist/mentions/MentionExtension.mjs +219 -0
  42. package/dist/mentions/MentionExtension.mjs.map +1 -0
  43. package/dist/mentions/MentionsList.js +123 -0
  44. package/dist/mentions/MentionsList.js.map +1 -0
  45. package/dist/mentions/MentionsList.mjs +119 -0
  46. package/dist/mentions/MentionsList.mjs.map +1 -0
  47. package/dist/types.js +33 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/types.mjs +24 -0
  50. package/dist/types.mjs.map +1 -0
  51. package/dist/utils.js +47 -0
  52. package/dist/utils.js.map +1 -0
  53. package/dist/utils.mjs +43 -0
  54. package/dist/utils.mjs.map +1 -0
  55. package/dist/version-history/HistoryVersionPreview.js +79 -0
  56. package/dist/version-history/HistoryVersionPreview.js.map +1 -0
  57. package/dist/version-history/HistoryVersionPreview.mjs +77 -0
  58. package/dist/version-history/HistoryVersionPreview.mjs.map +1 -0
  59. package/dist/version.js +10 -0
  60. package/dist/version.js.map +1 -0
  61. package/dist/version.mjs +6 -0
  62. package/dist/version.mjs.map +1 -0
  63. package/package.json +77 -1
  64. package/src/styles/constants.css +9 -0
  65. package/src/styles/index.css +247 -0
  66. package/src/styles/utils.css +6 -0
  67. package/styles.css +1 -0
  68. package/styles.css.d.ts +1 -0
  69. package/styles.css.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentsExtension.mjs","sources":["../../src/comments/CommentsExtension.ts"],"sourcesContent":["import { Extension, Mark, mergeAttributes } from \"@tiptap/core\";\nimport type { Node } from \"@tiptap/pm/model\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport { Plugin, TextSelection } from \"@tiptap/pm/state\";\nimport { Decoration, DecorationSet } from \"@tiptap/pm/view\";\nimport { ySyncPluginKey } from \"y-prosemirror\";\n\nimport type { CommentsExtensionStorage, ThreadPluginState } from \"../types\";\nimport {\n ACTIVE_SELECTION_PLUGIN,\n LIVEBLOCKS_COMMENT_MARK_TYPE,\n ThreadPluginActions,\n THREADS_PLUGIN_KEY,\n} from \"../types\";\n\ntype ThreadPluginAction = {\n name: ThreadPluginActions;\n data: string | null;\n};\n\n/**\n * Known issues: Overlapping marks are merged when reloading the doc. May be related:\n * https://github.com/ueberdosis/tiptap/issues/4339\n * https://github.com/yjs/y-prosemirror/issues/47\n */\nconst Comment = Mark.create({\n name: LIVEBLOCKS_COMMENT_MARK_TYPE,\n excludes: \"\",\n inclusive: false,\n keepOnSplit: true,\n addAttributes() {\n // Return an object with attribute configuration\n return {\n threadId: {\n parseHTML: (element) => element.getAttribute(\"data-lb-thread-id\"),\n renderHTML: (attributes) => {\n return {\n \"data-lb-thread-id\": (attributes as { threadId: string }).threadId,\n };\n },\n default: \"\",\n },\n };\n },\n\n renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, any> }) {\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n class: \"lb-root lb-tiptap-thread-mark\",\n }),\n ];\n },\n\n /**\n * This plugin tracks the (first) position of each thread mark in the doc and creates a decoration for the selected thread\n */\n addProseMirrorPlugins() {\n const updateState = (doc: Node, selectedThreadId: string | null) => {\n const threadPositions = new Map<string, { from: number; to: number }>();\n const decorations: Decoration[] = [];\n // find all thread marks and store their position + create decoration for selected thread\n doc.descendants((node, pos) => {\n node.marks.forEach((mark) => {\n if (mark.type === this.type) {\n const thisThreadId = (\n mark.attrs as { threadId: string | undefined }\n ).threadId;\n if (!thisThreadId) {\n return;\n }\n const from = pos;\n const to = from + node.nodeSize;\n\n // FloatingThreads component uses \"to\" as the position, so always store the largest \"to\" found\n // AnchoredThreads component uses \"from\" as the position, so always store the smallest \"from\" found\n const currentPosition = threadPositions.get(thisThreadId) ?? {\n from: Infinity,\n to: 0,\n };\n threadPositions.set(thisThreadId, {\n from: Math.min(from, currentPosition.from),\n to: Math.max(to, currentPosition.to),\n });\n\n if (selectedThreadId === thisThreadId) {\n decorations.push(\n Decoration.inline(from, to, {\n class: \"lb-root lb-tiptap-thread-mark-selected\",\n })\n );\n }\n }\n });\n });\n return {\n decorations: DecorationSet.create(doc, decorations),\n selectedThreadId,\n threadPositions,\n selectedThreadPos:\n selectedThreadId !== null\n ? threadPositions.get(selectedThreadId)?.to ?? null\n : null,\n };\n };\n\n return [\n new Plugin({\n key: THREADS_PLUGIN_KEY,\n state: {\n init() {\n return {\n threadPositions: new Map<string, { from: number; to: number }>(),\n selectedThreadId: null,\n selectedThreadPos: null,\n decorations: DecorationSet.empty,\n } as ThreadPluginState;\n },\n apply(tr, state) {\n const action = tr.getMeta(THREADS_PLUGIN_KEY) as ThreadPluginAction;\n if (!tr.docChanged && !action) {\n return state;\n }\n\n if (!action) {\n // Doc changed, but no action, just update rects\n return updateState(tr.doc, state.selectedThreadId);\n }\n // handle actions, possibly support more actions\n if (\n action.name === ThreadPluginActions.SET_SELECTED_THREAD_ID &&\n state.selectedThreadId !== action.data\n ) {\n return updateState(tr.doc, action.data);\n }\n\n return state;\n },\n },\n props: {\n decorations: (state) => {\n return (\n THREADS_PLUGIN_KEY.getState(state)?.decorations ??\n DecorationSet.empty\n );\n },\n handleClick: (view, pos, event) => {\n if (event.button !== 0) {\n return;\n }\n\n const selectThread = (threadId: string | null) => {\n view.dispatch(\n view.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_SELECTED_THREAD_ID,\n data: threadId,\n })\n );\n };\n\n const node = view.state.doc.nodeAt(pos);\n if (!node) {\n selectThread(null);\n return;\n }\n const threadId = node.marks.find((mark) => mark.type === this.type)\n ?.attrs.threadId as string | undefined;\n selectThread(threadId ?? null);\n },\n },\n }),\n ];\n },\n});\n\nexport const CommentsExtension = Extension.create<\n never,\n CommentsExtensionStorage\n>({\n name: \"liveblocksComments\",\n addExtensions() {\n return [Comment];\n },\n\n addStorage() {\n return {\n pendingCommentSelection: null,\n };\n },\n\n addCommands() {\n return {\n addPendingComment: () => () => {\n if (this.editor.state.selection.empty) {\n return false;\n }\n // unselect any open threads\n this.editor.view.dispatch(\n this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_SELECTED_THREAD_ID,\n data: null,\n })\n );\n this.storage.pendingCommentSelection = new TextSelection(\n this.editor.state.selection.$anchor,\n this.editor.state.selection.$head\n );\n return true;\n },\n selectThread: (id: string | null) => () => {\n this.editor.view.dispatch(\n this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_SELECTED_THREAD_ID,\n data: id,\n })\n );\n return true;\n },\n addComment:\n (id: string) =>\n ({ commands }) => {\n if (!this.storage.pendingCommentSelection) {\n return false;\n }\n this.editor.state.selection = this.storage.pendingCommentSelection;\n commands.setMark(LIVEBLOCKS_COMMENT_MARK_TYPE, { threadId: id });\n this.storage.pendingCommentSelection = null;\n\n return true;\n },\n };\n },\n\n //@ts-expect-error - this is incorrectly typed upstream in Mark.ts of TipTap. This event does include transaction\n // correct: https://github.com/ueberdosis/tiptap/blob/2ff327ced84df6865b4ef98947b667aa79992292/packages/core/src/types.ts#L60\n // incorrect: https://github.com/ueberdosis/tiptap/blob/2ff327ced84df6865b4ef98947b667aa79992292/packages/core/src/Mark.ts#L330\n onSelectionUpdate(\n this: { storage: Storage }, // NOTE: there are more types here I didn't override, this gets removed after submitting PR to tiptap\n { transaction }: { transaction: Transaction } // TODO: remove this after submitting PR to tiptap\n ) {\n // ignore changes made by yjs\n if (\n !this.storage.pendingCommentSelection ||\n transaction.getMeta(ySyncPluginKey)\n ) {\n return;\n }\n this.storage.pendingCommentSelection = null;\n },\n // TODO: this.storage.pendingCommentSelection needs to be a Yjs Relative Position that gets translated back to absolute position.\n // Commit: eba949d32d6010a3d8b3f7967d73d4deb015b02a has code that can help with this.\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: ACTIVE_SELECTION_PLUGIN,\n props: {\n decorations: ({ doc }) => {\n const active = this.storage.pendingCommentSelection !== null;\n if (!active) {\n return DecorationSet.create(doc, []);\n }\n const { from, to } = this.storage\n .pendingCommentSelection as TextSelection;\n const decorations: Decoration[] = [\n Decoration.inline(from, to, {\n class: \"lb-root lb-tiptap-active-selection\",\n }),\n ];\n return DecorationSet.create(doc, decorations);\n },\n },\n }),\n ];\n },\n});\n"],"names":["threadId"],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ 'use strict';
2
+
3
+ var reactDom = require('@floating-ui/react-dom');
4
+ var react = require('@liveblocks/react');
5
+ var reactUi = require('@liveblocks/react-ui');
6
+ var React = require('react');
7
+ var utils = require('../utils.js');
8
+
9
+ const FLOATING_COMPOSER_COLLISION_PADDING = 10;
10
+ const FloatingComposer = React.forwardRef(function FloatingComposer2(props, forwardedRef) {
11
+ const createThread = react.useCreateThread();
12
+ const { editor } = props;
13
+ const storage = editor?.storage.liveblocksComments;
14
+ const showComposer = !!storage?.pendingCommentSelection;
15
+ const {
16
+ refs: { setReference, setFloating },
17
+ strategy,
18
+ x,
19
+ y
20
+ } = reactDom.useFloating({
21
+ strategy: "fixed",
22
+ placement: "bottom",
23
+ middleware: [
24
+ reactDom.flip({ padding: FLOATING_COMPOSER_COLLISION_PADDING, crossAxis: false }),
25
+ reactDom.offset(10),
26
+ reactDom.hide({ padding: FLOATING_COMPOSER_COLLISION_PADDING }),
27
+ reactDom.shift({
28
+ padding: FLOATING_COMPOSER_COLLISION_PADDING,
29
+ limiter: reactDom.limitShift()
30
+ }),
31
+ reactDom.size({ padding: FLOATING_COMPOSER_COLLISION_PADDING })
32
+ ],
33
+ whileElementsMounted: (...args) => {
34
+ return reactDom.autoUpdate(...args, {
35
+ animationFrame: true
36
+ });
37
+ }
38
+ });
39
+ React.useEffect(() => {
40
+ if (!editor || !showComposer) {
41
+ return;
42
+ }
43
+ const updateRect = () => {
44
+ if (!storage) {
45
+ return;
46
+ }
47
+ const seclection = storage.pendingCommentSelection;
48
+ if (!seclection) {
49
+ return;
50
+ }
51
+ const coords = editor.view.coordsAtPos(Math.min(seclection.from, editor.state.doc.content.size - 1));
52
+ if (coords) {
53
+ setReference({
54
+ getBoundingClientRect: () => utils.getRectFromCoords(coords)
55
+ });
56
+ }
57
+ };
58
+ updateRect();
59
+ editor.on("update", updateRect);
60
+ return () => {
61
+ editor.off("update", updateRect);
62
+ };
63
+ }, [editor, setReference, showComposer]);
64
+ const handleComposerSubmit = React.useCallback(
65
+ ({ body }, event) => {
66
+ if (!editor) {
67
+ return;
68
+ }
69
+ event.preventDefault();
70
+ const thread = createThread({
71
+ body
72
+ });
73
+ editor.commands.addComment(thread.id);
74
+ },
75
+ [editor, createThread]
76
+ );
77
+ if (!showComposer || !editor) {
78
+ return null;
79
+ }
80
+ return /* @__PURE__ */ React.createElement("div", {
81
+ className: "lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-composer",
82
+ ref: setFloating,
83
+ style: {
84
+ position: strategy,
85
+ top: 0,
86
+ left: 0,
87
+ transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,
88
+ minWidth: "max-content"
89
+ }
90
+ }, /* @__PURE__ */ React.createElement(reactUi.Composer, {
91
+ ref: forwardedRef,
92
+ ...props,
93
+ onComposerSubmit: handleComposerSubmit,
94
+ onClick: (e) => {
95
+ e.stopPropagation();
96
+ },
97
+ autoFocus: true
98
+ }));
99
+ });
100
+
101
+ exports.FLOATING_COMPOSER_COLLISION_PADDING = FLOATING_COMPOSER_COLLISION_PADDING;
102
+ exports.FloatingComposer = FloatingComposer;
103
+ //# sourceMappingURL=FloatingComposer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloatingComposer.js","sources":["../../src/comments/FloatingComposer.tsx"],"sourcesContent":["import { autoUpdate, flip, hide, limitShift, offset, shift, size, useFloating } from \"@floating-ui/react-dom\";\nimport type { BaseMetadata } from \"@liveblocks/client\";\nimport type { DM } from \"@liveblocks/core\";\nimport { useCreateThread } from \"@liveblocks/react\";\nimport type { ComposerProps, ComposerSubmitComment } from \"@liveblocks/react-ui\";\nimport { Composer } from \"@liveblocks/react-ui\";\nimport type { Editor } from \"@tiptap/react\";\nimport type { ComponentRef, FormEvent } from \"react\";\nimport React, { forwardRef, useCallback, useEffect } from \"react\";\n\nimport type { CommentsExtensionStorage } from \"../types\";\nimport { getRectFromCoords } from \"../utils\";\n\nexport type FloatingComposerProps<M extends BaseMetadata = DM> = Omit<\n ComposerProps<M>,\n \"threadId\" | \"commentId\"\n> & {\n editor: Editor | null;\n};\n\ntype ComposerElement = ComponentRef<typeof Composer>;\n\nexport const FLOATING_COMPOSER_COLLISION_PADDING = 10;\n\nexport const FloatingComposer = forwardRef<\n ComposerElement,\n FloatingComposerProps\n>(function FloatingComposer(props, forwardedRef) {\n const createThread = useCreateThread();\n const { editor } = props;\n\n const storage = editor?.storage.liveblocksComments as CommentsExtensionStorage | undefined;\n\n const showComposer = !!storage?.pendingCommentSelection;\n\n const {\n refs: { setReference, setFloating },\n strategy,\n x,\n y,\n } = useFloating({\n strategy: \"fixed\",\n placement: \"bottom\",\n middleware: [\n flip({ padding: FLOATING_COMPOSER_COLLISION_PADDING, crossAxis: false }),\n offset(10),\n hide({ padding: FLOATING_COMPOSER_COLLISION_PADDING }),\n shift({\n padding: FLOATING_COMPOSER_COLLISION_PADDING,\n limiter: limitShift(),\n }),\n size({ padding: FLOATING_COMPOSER_COLLISION_PADDING }),\n ],\n whileElementsMounted: (...args) => {\n return autoUpdate(...args, {\n animationFrame: true,\n });\n },\n });\n\n useEffect(() => {\n if (!editor || !showComposer) {\n return;\n }\n const updateRect = () => {\n if (!storage) {\n return;\n }\n const seclection = storage.pendingCommentSelection;\n if (!seclection) {\n return;\n }\n const coords = editor.view.coordsAtPos(Math.min(seclection.from, editor.state.doc.content.size - 1));\n if (coords) {\n setReference({\n getBoundingClientRect: () => getRectFromCoords(coords)\n });\n }\n }\n updateRect();\n editor.on(\"update\", updateRect);\n return () => {\n editor.off(\"update\", updateRect);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor, setReference, showComposer]); // don't need storage explicitly here\n\n\n // Submit a new thread and update the comment highlight to show a completed highlight\n const handleComposerSubmit = useCallback(\n ({ body }: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n if (!editor) {\n return;\n }\n event.preventDefault();\n\n const thread = createThread({\n body,\n });\n editor.commands.addComment(thread.id);\n\n },\n [editor, createThread]\n );\n\n if (!showComposer || !editor) {\n return null;\n }\n\n return (\n <div\n className=\"lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-composer\"\n ref={setFloating} style={{\n position: strategy,\n top: 0,\n left: 0,\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n minWidth: \"max-content\",\n }}>\n <Composer\n ref={forwardedRef}\n {...props}\n onComposerSubmit={handleComposerSubmit}\n onClick={(e) => {\n // Don't send up a click event from emoji popout and close the composer\n e.stopPropagation();\n }}\n autoFocus={true}\n />\n </div>\n );\n});"],"names":["forwardRef","FloatingComposer","useCreateThread","useFloating","flip","offset","hide","shift","limitShift","size","autoUpdate","useEffect","getRectFromCoords","useCallback","Composer"],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ import { useFloating, flip, offset, hide, shift, limitShift, size, autoUpdate } from '@floating-ui/react-dom';
2
+ import { useCreateThread } from '@liveblocks/react';
3
+ import { Composer } from '@liveblocks/react-ui';
4
+ import React, { forwardRef, useEffect, useCallback } from 'react';
5
+ import { getRectFromCoords } from '../utils.mjs';
6
+
7
+ const FLOATING_COMPOSER_COLLISION_PADDING = 10;
8
+ const FloatingComposer = forwardRef(function FloatingComposer2(props, forwardedRef) {
9
+ const createThread = useCreateThread();
10
+ const { editor } = props;
11
+ const storage = editor?.storage.liveblocksComments;
12
+ const showComposer = !!storage?.pendingCommentSelection;
13
+ const {
14
+ refs: { setReference, setFloating },
15
+ strategy,
16
+ x,
17
+ y
18
+ } = useFloating({
19
+ strategy: "fixed",
20
+ placement: "bottom",
21
+ middleware: [
22
+ flip({ padding: FLOATING_COMPOSER_COLLISION_PADDING, crossAxis: false }),
23
+ offset(10),
24
+ hide({ padding: FLOATING_COMPOSER_COLLISION_PADDING }),
25
+ shift({
26
+ padding: FLOATING_COMPOSER_COLLISION_PADDING,
27
+ limiter: limitShift()
28
+ }),
29
+ size({ padding: FLOATING_COMPOSER_COLLISION_PADDING })
30
+ ],
31
+ whileElementsMounted: (...args) => {
32
+ return autoUpdate(...args, {
33
+ animationFrame: true
34
+ });
35
+ }
36
+ });
37
+ useEffect(() => {
38
+ if (!editor || !showComposer) {
39
+ return;
40
+ }
41
+ const updateRect = () => {
42
+ if (!storage) {
43
+ return;
44
+ }
45
+ const seclection = storage.pendingCommentSelection;
46
+ if (!seclection) {
47
+ return;
48
+ }
49
+ const coords = editor.view.coordsAtPos(Math.min(seclection.from, editor.state.doc.content.size - 1));
50
+ if (coords) {
51
+ setReference({
52
+ getBoundingClientRect: () => getRectFromCoords(coords)
53
+ });
54
+ }
55
+ };
56
+ updateRect();
57
+ editor.on("update", updateRect);
58
+ return () => {
59
+ editor.off("update", updateRect);
60
+ };
61
+ }, [editor, setReference, showComposer]);
62
+ const handleComposerSubmit = useCallback(
63
+ ({ body }, event) => {
64
+ if (!editor) {
65
+ return;
66
+ }
67
+ event.preventDefault();
68
+ const thread = createThread({
69
+ body
70
+ });
71
+ editor.commands.addComment(thread.id);
72
+ },
73
+ [editor, createThread]
74
+ );
75
+ if (!showComposer || !editor) {
76
+ return null;
77
+ }
78
+ return /* @__PURE__ */ React.createElement("div", {
79
+ className: "lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-composer",
80
+ ref: setFloating,
81
+ style: {
82
+ position: strategy,
83
+ top: 0,
84
+ left: 0,
85
+ transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,
86
+ minWidth: "max-content"
87
+ }
88
+ }, /* @__PURE__ */ React.createElement(Composer, {
89
+ ref: forwardedRef,
90
+ ...props,
91
+ onComposerSubmit: handleComposerSubmit,
92
+ onClick: (e) => {
93
+ e.stopPropagation();
94
+ },
95
+ autoFocus: true
96
+ }));
97
+ });
98
+
99
+ export { FLOATING_COMPOSER_COLLISION_PADDING, FloatingComposer };
100
+ //# sourceMappingURL=FloatingComposer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloatingComposer.mjs","sources":["../../src/comments/FloatingComposer.tsx"],"sourcesContent":["import { autoUpdate, flip, hide, limitShift, offset, shift, size, useFloating } from \"@floating-ui/react-dom\";\nimport type { BaseMetadata } from \"@liveblocks/client\";\nimport type { DM } from \"@liveblocks/core\";\nimport { useCreateThread } from \"@liveblocks/react\";\nimport type { ComposerProps, ComposerSubmitComment } from \"@liveblocks/react-ui\";\nimport { Composer } from \"@liveblocks/react-ui\";\nimport type { Editor } from \"@tiptap/react\";\nimport type { ComponentRef, FormEvent } from \"react\";\nimport React, { forwardRef, useCallback, useEffect } from \"react\";\n\nimport type { CommentsExtensionStorage } from \"../types\";\nimport { getRectFromCoords } from \"../utils\";\n\nexport type FloatingComposerProps<M extends BaseMetadata = DM> = Omit<\n ComposerProps<M>,\n \"threadId\" | \"commentId\"\n> & {\n editor: Editor | null;\n};\n\ntype ComposerElement = ComponentRef<typeof Composer>;\n\nexport const FLOATING_COMPOSER_COLLISION_PADDING = 10;\n\nexport const FloatingComposer = forwardRef<\n ComposerElement,\n FloatingComposerProps\n>(function FloatingComposer(props, forwardedRef) {\n const createThread = useCreateThread();\n const { editor } = props;\n\n const storage = editor?.storage.liveblocksComments as CommentsExtensionStorage | undefined;\n\n const showComposer = !!storage?.pendingCommentSelection;\n\n const {\n refs: { setReference, setFloating },\n strategy,\n x,\n y,\n } = useFloating({\n strategy: \"fixed\",\n placement: \"bottom\",\n middleware: [\n flip({ padding: FLOATING_COMPOSER_COLLISION_PADDING, crossAxis: false }),\n offset(10),\n hide({ padding: FLOATING_COMPOSER_COLLISION_PADDING }),\n shift({\n padding: FLOATING_COMPOSER_COLLISION_PADDING,\n limiter: limitShift(),\n }),\n size({ padding: FLOATING_COMPOSER_COLLISION_PADDING }),\n ],\n whileElementsMounted: (...args) => {\n return autoUpdate(...args, {\n animationFrame: true,\n });\n },\n });\n\n useEffect(() => {\n if (!editor || !showComposer) {\n return;\n }\n const updateRect = () => {\n if (!storage) {\n return;\n }\n const seclection = storage.pendingCommentSelection;\n if (!seclection) {\n return;\n }\n const coords = editor.view.coordsAtPos(Math.min(seclection.from, editor.state.doc.content.size - 1));\n if (coords) {\n setReference({\n getBoundingClientRect: () => getRectFromCoords(coords)\n });\n }\n }\n updateRect();\n editor.on(\"update\", updateRect);\n return () => {\n editor.off(\"update\", updateRect);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor, setReference, showComposer]); // don't need storage explicitly here\n\n\n // Submit a new thread and update the comment highlight to show a completed highlight\n const handleComposerSubmit = useCallback(\n ({ body }: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n if (!editor) {\n return;\n }\n event.preventDefault();\n\n const thread = createThread({\n body,\n });\n editor.commands.addComment(thread.id);\n\n },\n [editor, createThread]\n );\n\n if (!showComposer || !editor) {\n return null;\n }\n\n return (\n <div\n className=\"lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-composer\"\n ref={setFloating} style={{\n position: strategy,\n top: 0,\n left: 0,\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n minWidth: \"max-content\",\n }}>\n <Composer\n ref={forwardedRef}\n {...props}\n onComposerSubmit={handleComposerSubmit}\n onClick={(e) => {\n // Don't send up a click event from emoji popout and close the composer\n e.stopPropagation();\n }}\n autoFocus={true}\n />\n </div>\n );\n});"],"names":["FloatingComposer"],"mappings":"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"}
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ var reactDom = require('@floating-ui/react-dom');
4
+ var reactUi = require('@liveblocks/react-ui');
5
+ var React = require('react');
6
+ var reactDom$1 = require('react-dom');
7
+ var classnames = require('../classnames.js');
8
+ var types = require('../types.js');
9
+ var utils = require('../utils.js');
10
+
11
+ function FloatingThreads({
12
+ threads,
13
+ components,
14
+ editor,
15
+ ...props
16
+ }) {
17
+ const Thread = components?.Thread ?? reactUi.Thread;
18
+ const pluginState = editor ? types.THREADS_PLUGIN_KEY.getState(editor.state) : null;
19
+ const [activeThreads, setActiveThreads] = React.useState(null);
20
+ React.useEffect(() => {
21
+ if (!editor || !pluginState) {
22
+ setActiveThreads(null);
23
+ return;
24
+ }
25
+ const { selectedThreadId, selectedThreadPos } = pluginState;
26
+ if (selectedThreadId === null || selectedThreadPos === null) {
27
+ setActiveThreads(null);
28
+ return;
29
+ }
30
+ const coords = editor.view.coordsAtPos(Math.min(selectedThreadPos, editor.state.doc.content.size - 1));
31
+ const active = (threads ?? []).filter(
32
+ (thread) => selectedThreadId === thread.id
33
+ );
34
+ setActiveThreads({
35
+ rect: utils.getRectFromCoords(coords),
36
+ threads: active
37
+ });
38
+ }, [editor, pluginState, threads]);
39
+ const handleEscapeKeydown = React.useCallback(() => {
40
+ if (!editor || activeThreads === null)
41
+ return false;
42
+ editor.commands.selectThread(null);
43
+ return true;
44
+ }, [activeThreads, editor]);
45
+ if (!activeThreads)
46
+ return null;
47
+ return /* @__PURE__ */ React.createElement(FloatingThreadPortal, {
48
+ rect: activeThreads.rect,
49
+ container: document.body,
50
+ ...props
51
+ }, activeThreads.threads.map((thread) => /* @__PURE__ */ React.createElement(ThreadWrapper, {
52
+ key: thread.id,
53
+ thread,
54
+ Thread,
55
+ onEscapeKeydown: handleEscapeKeydown,
56
+ className: "lb-tiptap-floating-threads-thread"
57
+ })));
58
+ }
59
+ const FLOATING_THREAD_COLLISION_PADDING = 10;
60
+ function FloatingThreadPortal({
61
+ container,
62
+ rect,
63
+ children,
64
+ className,
65
+ style,
66
+ ...props
67
+ }) {
68
+ const {
69
+ refs: { setReference, setFloating },
70
+ strategy,
71
+ x,
72
+ y
73
+ } = reactDom.useFloating({
74
+ strategy: "absolute",
75
+ placement: "bottom",
76
+ middleware: [
77
+ reactDom.flip({ padding: FLOATING_THREAD_COLLISION_PADDING, crossAxis: false }),
78
+ reactDom.offset(10),
79
+ reactDom.hide({ padding: FLOATING_THREAD_COLLISION_PADDING }),
80
+ reactDom.shift({
81
+ padding: FLOATING_THREAD_COLLISION_PADDING,
82
+ limiter: reactDom.limitShift()
83
+ }),
84
+ reactDom.size({
85
+ padding: FLOATING_THREAD_COLLISION_PADDING,
86
+ apply({ availableWidth, availableHeight, elements }) {
87
+ elements.floating.style.setProperty(
88
+ "--lb-tiptap-floating-threads-available-width",
89
+ `${availableWidth}px`
90
+ );
91
+ elements.floating.style.setProperty(
92
+ "--lb-tiptap-floating-threads-available-height",
93
+ `${availableHeight}px`
94
+ );
95
+ }
96
+ })
97
+ ],
98
+ whileElementsMounted: (...args) => {
99
+ return reactDom.autoUpdate(...args, {
100
+ animationFrame: true
101
+ });
102
+ }
103
+ });
104
+ React.useLayoutEffect(() => {
105
+ setReference({
106
+ getBoundingClientRect: () => rect
107
+ });
108
+ }, [setReference, rect]);
109
+ return reactDom$1.createPortal(
110
+ /* @__PURE__ */ React.createElement("div", {
111
+ ref: setFloating,
112
+ ...props,
113
+ style: {
114
+ ...style,
115
+ position: strategy,
116
+ top: 0,
117
+ left: 0,
118
+ transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,
119
+ minWidth: "max-content"
120
+ },
121
+ className: classnames.classNames(
122
+ "lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-threads",
123
+ className
124
+ )
125
+ }, children),
126
+ container
127
+ );
128
+ }
129
+ function ThreadWrapper({
130
+ thread,
131
+ Thread,
132
+ onEscapeKeydown,
133
+ onKeyDown,
134
+ ...threadProps
135
+ }) {
136
+ const handleKeyDown = React.useCallback(
137
+ (event) => {
138
+ onKeyDown?.(event);
139
+ if (event.key === "Escape") {
140
+ onEscapeKeydown();
141
+ }
142
+ },
143
+ [onEscapeKeydown, onKeyDown]
144
+ );
145
+ return /* @__PURE__ */ React.createElement(Thread, {
146
+ thread,
147
+ onKeyDown: handleKeyDown,
148
+ ...threadProps
149
+ });
150
+ }
151
+
152
+ exports.FLOATING_THREAD_COLLISION_PADDING = FLOATING_THREAD_COLLISION_PADDING;
153
+ exports.FloatingThreads = FloatingThreads;
154
+ //# sourceMappingURL=FloatingThreads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloatingThreads.js","sources":["../../src/comments/FloatingThreads.tsx"],"sourcesContent":["import type {\n ClientRectObject\n} from \"@floating-ui/react-dom\";\nimport {\n autoUpdate,\n flip,\n hide,\n limitShift,\n offset,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport type { BaseMetadata, DM, ThreadData } from \"@liveblocks/core\";\nimport {\n Thread as DefaultThread,\n type ThreadProps,\n} from \"@liveblocks/react-ui\";\nimport type { Editor } from \"@tiptap/react\";\nimport React, {\n type ComponentType,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useLayoutEffect,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { classNames } from \"../classnames\";\nimport type { ThreadPluginState } from \"../types\";\nimport { THREADS_PLUGIN_KEY } from \"../types\";\nimport { getRectFromCoords } from \"../utils\";\n\ntype ThreadPanelComponents = {\n Thread: ComponentType<ThreadProps>;\n};\n\nexport interface FloatingThreadsProps<M extends BaseMetadata = DM>\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /**\n * The threads to display.\n */\n threads: ThreadData<M>[];\n\n /**\n * Override the component's components.\n */\n components?: Partial<ThreadPanelComponents>;\n\n /**\n * The tiptap editor\n */\n editor: Editor | null;\n}\n\nexport function FloatingThreads({\n threads,\n components,\n editor,\n ...props\n}: FloatingThreadsProps) {\n\n const Thread = components?.Thread ?? DefaultThread;\n const pluginState = editor ? THREADS_PLUGIN_KEY.getState(editor.state) as ThreadPluginState : null;\n\n const [activeThreads, setActiveThreads] = useState<{\n rect: ClientRectObject;\n threads: ThreadData[];\n } | null>(null);\n\n\n useEffect(() => {\n if (!editor || !pluginState) {\n setActiveThreads(null);\n return;\n }\n const { selectedThreadId, selectedThreadPos } = pluginState;\n if (selectedThreadId === null || selectedThreadPos === null) {\n setActiveThreads(null);\n return;\n }\n const coords = editor.view.coordsAtPos(Math.min(selectedThreadPos, editor.state.doc.content.size - 1));\n const active = (threads ?? []).filter((thread) =>\n selectedThreadId === thread.id\n );\n setActiveThreads({\n rect: getRectFromCoords(coords), threads: active\n });\n }, [editor, pluginState, threads]);\n\n const handleEscapeKeydown = useCallback((): boolean => {\n if (!editor || activeThreads === null) return false;\n editor.commands.selectThread(null);\n return true;\n }, [activeThreads, editor]);\n\n if (!activeThreads) return null;\n\n return (\n <FloatingThreadPortal\n rect={activeThreads.rect}\n container={document.body}\n {...props}\n >\n {activeThreads.threads.map((thread) => (\n <ThreadWrapper\n key={thread.id}\n thread={thread}\n Thread={Thread}\n onEscapeKeydown={handleEscapeKeydown}\n className=\"lb-tiptap-floating-threads-thread\"\n />\n ))}\n </FloatingThreadPortal>\n );\n}\n\ninterface FloatingThreadPortalProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n rect: ClientRectObject;\n container: HTMLElement;\n children: ReactNode;\n}\n\nexport const FLOATING_THREAD_COLLISION_PADDING = 10;\n\nfunction FloatingThreadPortal({\n container,\n rect,\n children,\n className,\n style,\n ...props\n}: FloatingThreadPortalProps) {\n const {\n refs: { setReference, setFloating },\n strategy,\n x,\n y,\n } = useFloating({\n strategy: \"absolute\",\n placement: \"bottom\",\n middleware: [\n flip({ padding: FLOATING_THREAD_COLLISION_PADDING, crossAxis: false }),\n offset(10),\n hide({ padding: FLOATING_THREAD_COLLISION_PADDING }),\n shift({\n padding: FLOATING_THREAD_COLLISION_PADDING,\n limiter: limitShift(),\n }),\n size({\n padding: FLOATING_THREAD_COLLISION_PADDING,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-tiptap-floating-threads-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-tiptap-floating-threads-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: (...args) => {\n return autoUpdate(...args, {\n animationFrame: true,\n });\n },\n });\n\n useLayoutEffect(() => {\n setReference({\n getBoundingClientRect: () => rect,\n });\n }, [setReference, rect]);\n\n return createPortal(\n <div\n ref={setFloating}\n {...props}\n style={{\n ...style,\n position: strategy,\n top: 0,\n left: 0,\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n minWidth: \"max-content\",\n }}\n className={classNames(\n \"lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-threads\",\n className\n )}\n >\n {children}\n </div>,\n container\n );\n}\n\ninterface ThreadWrapperProps extends ThreadProps {\n thread: ThreadData;\n Thread: ComponentType<ThreadProps>;\n onEscapeKeydown: () => void;\n}\n\nfunction ThreadWrapper({\n thread,\n Thread,\n onEscapeKeydown,\n onKeyDown,\n ...threadProps\n}: ThreadWrapperProps) {\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n // TODO: Add ability to preventDefault on keydown to override the default behavior, e.g. to show an alert dialog\n if (event.key === \"Escape\") {\n onEscapeKeydown();\n }\n },\n [onEscapeKeydown, onKeyDown]\n );\n\n return <Thread thread={thread} onKeyDown={handleKeyDown} {...threadProps} />;\n}\n\n"],"names":["DefaultThread","THREADS_PLUGIN_KEY","useState","useEffect","getRectFromCoords","useCallback","useFloating","flip","offset","hide","shift","limitShift","size","autoUpdate","useLayoutEffect","createPortal","classNames"],"mappings":"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"}
@@ -0,0 +1,151 @@
1
+ import { useFloating, flip, offset, hide, shift, limitShift, size, autoUpdate } from '@floating-ui/react-dom';
2
+ import { Thread } from '@liveblocks/react-ui';
3
+ import React, { useState, useEffect, useCallback, useLayoutEffect } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { classNames } from '../classnames.mjs';
6
+ import { THREADS_PLUGIN_KEY } from '../types.mjs';
7
+ import { getRectFromCoords } from '../utils.mjs';
8
+
9
+ function FloatingThreads({
10
+ threads,
11
+ components,
12
+ editor,
13
+ ...props
14
+ }) {
15
+ const Thread$1 = components?.Thread ?? Thread;
16
+ const pluginState = editor ? THREADS_PLUGIN_KEY.getState(editor.state) : null;
17
+ const [activeThreads, setActiveThreads] = useState(null);
18
+ useEffect(() => {
19
+ if (!editor || !pluginState) {
20
+ setActiveThreads(null);
21
+ return;
22
+ }
23
+ const { selectedThreadId, selectedThreadPos } = pluginState;
24
+ if (selectedThreadId === null || selectedThreadPos === null) {
25
+ setActiveThreads(null);
26
+ return;
27
+ }
28
+ const coords = editor.view.coordsAtPos(Math.min(selectedThreadPos, editor.state.doc.content.size - 1));
29
+ const active = (threads ?? []).filter(
30
+ (thread) => selectedThreadId === thread.id
31
+ );
32
+ setActiveThreads({
33
+ rect: getRectFromCoords(coords),
34
+ threads: active
35
+ });
36
+ }, [editor, pluginState, threads]);
37
+ const handleEscapeKeydown = useCallback(() => {
38
+ if (!editor || activeThreads === null)
39
+ return false;
40
+ editor.commands.selectThread(null);
41
+ return true;
42
+ }, [activeThreads, editor]);
43
+ if (!activeThreads)
44
+ return null;
45
+ return /* @__PURE__ */ React.createElement(FloatingThreadPortal, {
46
+ rect: activeThreads.rect,
47
+ container: document.body,
48
+ ...props
49
+ }, activeThreads.threads.map((thread) => /* @__PURE__ */ React.createElement(ThreadWrapper, {
50
+ key: thread.id,
51
+ thread,
52
+ Thread: Thread$1,
53
+ onEscapeKeydown: handleEscapeKeydown,
54
+ className: "lb-tiptap-floating-threads-thread"
55
+ })));
56
+ }
57
+ const FLOATING_THREAD_COLLISION_PADDING = 10;
58
+ function FloatingThreadPortal({
59
+ container,
60
+ rect,
61
+ children,
62
+ className,
63
+ style,
64
+ ...props
65
+ }) {
66
+ const {
67
+ refs: { setReference, setFloating },
68
+ strategy,
69
+ x,
70
+ y
71
+ } = useFloating({
72
+ strategy: "absolute",
73
+ placement: "bottom",
74
+ middleware: [
75
+ flip({ padding: FLOATING_THREAD_COLLISION_PADDING, crossAxis: false }),
76
+ offset(10),
77
+ hide({ padding: FLOATING_THREAD_COLLISION_PADDING }),
78
+ shift({
79
+ padding: FLOATING_THREAD_COLLISION_PADDING,
80
+ limiter: limitShift()
81
+ }),
82
+ size({
83
+ padding: FLOATING_THREAD_COLLISION_PADDING,
84
+ apply({ availableWidth, availableHeight, elements }) {
85
+ elements.floating.style.setProperty(
86
+ "--lb-tiptap-floating-threads-available-width",
87
+ `${availableWidth}px`
88
+ );
89
+ elements.floating.style.setProperty(
90
+ "--lb-tiptap-floating-threads-available-height",
91
+ `${availableHeight}px`
92
+ );
93
+ }
94
+ })
95
+ ],
96
+ whileElementsMounted: (...args) => {
97
+ return autoUpdate(...args, {
98
+ animationFrame: true
99
+ });
100
+ }
101
+ });
102
+ useLayoutEffect(() => {
103
+ setReference({
104
+ getBoundingClientRect: () => rect
105
+ });
106
+ }, [setReference, rect]);
107
+ return createPortal(
108
+ /* @__PURE__ */ React.createElement("div", {
109
+ ref: setFloating,
110
+ ...props,
111
+ style: {
112
+ ...style,
113
+ position: strategy,
114
+ top: 0,
115
+ left: 0,
116
+ transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,
117
+ minWidth: "max-content"
118
+ },
119
+ className: classNames(
120
+ "lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-threads",
121
+ className
122
+ )
123
+ }, children),
124
+ container
125
+ );
126
+ }
127
+ function ThreadWrapper({
128
+ thread,
129
+ Thread,
130
+ onEscapeKeydown,
131
+ onKeyDown,
132
+ ...threadProps
133
+ }) {
134
+ const handleKeyDown = useCallback(
135
+ (event) => {
136
+ onKeyDown?.(event);
137
+ if (event.key === "Escape") {
138
+ onEscapeKeydown();
139
+ }
140
+ },
141
+ [onEscapeKeydown, onKeyDown]
142
+ );
143
+ return /* @__PURE__ */ React.createElement(Thread, {
144
+ thread,
145
+ onKeyDown: handleKeyDown,
146
+ ...threadProps
147
+ });
148
+ }
149
+
150
+ export { FLOATING_THREAD_COLLISION_PADDING, FloatingThreads };
151
+ //# sourceMappingURL=FloatingThreads.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloatingThreads.mjs","sources":["../../src/comments/FloatingThreads.tsx"],"sourcesContent":["import type {\n ClientRectObject\n} from \"@floating-ui/react-dom\";\nimport {\n autoUpdate,\n flip,\n hide,\n limitShift,\n offset,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport type { BaseMetadata, DM, ThreadData } from \"@liveblocks/core\";\nimport {\n Thread as DefaultThread,\n type ThreadProps,\n} from \"@liveblocks/react-ui\";\nimport type { Editor } from \"@tiptap/react\";\nimport React, {\n type ComponentType,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useLayoutEffect,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { classNames } from \"../classnames\";\nimport type { ThreadPluginState } from \"../types\";\nimport { THREADS_PLUGIN_KEY } from \"../types\";\nimport { getRectFromCoords } from \"../utils\";\n\ntype ThreadPanelComponents = {\n Thread: ComponentType<ThreadProps>;\n};\n\nexport interface FloatingThreadsProps<M extends BaseMetadata = DM>\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /**\n * The threads to display.\n */\n threads: ThreadData<M>[];\n\n /**\n * Override the component's components.\n */\n components?: Partial<ThreadPanelComponents>;\n\n /**\n * The tiptap editor\n */\n editor: Editor | null;\n}\n\nexport function FloatingThreads({\n threads,\n components,\n editor,\n ...props\n}: FloatingThreadsProps) {\n\n const Thread = components?.Thread ?? DefaultThread;\n const pluginState = editor ? THREADS_PLUGIN_KEY.getState(editor.state) as ThreadPluginState : null;\n\n const [activeThreads, setActiveThreads] = useState<{\n rect: ClientRectObject;\n threads: ThreadData[];\n } | null>(null);\n\n\n useEffect(() => {\n if (!editor || !pluginState) {\n setActiveThreads(null);\n return;\n }\n const { selectedThreadId, selectedThreadPos } = pluginState;\n if (selectedThreadId === null || selectedThreadPos === null) {\n setActiveThreads(null);\n return;\n }\n const coords = editor.view.coordsAtPos(Math.min(selectedThreadPos, editor.state.doc.content.size - 1));\n const active = (threads ?? []).filter((thread) =>\n selectedThreadId === thread.id\n );\n setActiveThreads({\n rect: getRectFromCoords(coords), threads: active\n });\n }, [editor, pluginState, threads]);\n\n const handleEscapeKeydown = useCallback((): boolean => {\n if (!editor || activeThreads === null) return false;\n editor.commands.selectThread(null);\n return true;\n }, [activeThreads, editor]);\n\n if (!activeThreads) return null;\n\n return (\n <FloatingThreadPortal\n rect={activeThreads.rect}\n container={document.body}\n {...props}\n >\n {activeThreads.threads.map((thread) => (\n <ThreadWrapper\n key={thread.id}\n thread={thread}\n Thread={Thread}\n onEscapeKeydown={handleEscapeKeydown}\n className=\"lb-tiptap-floating-threads-thread\"\n />\n ))}\n </FloatingThreadPortal>\n );\n}\n\ninterface FloatingThreadPortalProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n rect: ClientRectObject;\n container: HTMLElement;\n children: ReactNode;\n}\n\nexport const FLOATING_THREAD_COLLISION_PADDING = 10;\n\nfunction FloatingThreadPortal({\n container,\n rect,\n children,\n className,\n style,\n ...props\n}: FloatingThreadPortalProps) {\n const {\n refs: { setReference, setFloating },\n strategy,\n x,\n y,\n } = useFloating({\n strategy: \"absolute\",\n placement: \"bottom\",\n middleware: [\n flip({ padding: FLOATING_THREAD_COLLISION_PADDING, crossAxis: false }),\n offset(10),\n hide({ padding: FLOATING_THREAD_COLLISION_PADDING }),\n shift({\n padding: FLOATING_THREAD_COLLISION_PADDING,\n limiter: limitShift(),\n }),\n size({\n padding: FLOATING_THREAD_COLLISION_PADDING,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-tiptap-floating-threads-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-tiptap-floating-threads-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: (...args) => {\n return autoUpdate(...args, {\n animationFrame: true,\n });\n },\n });\n\n useLayoutEffect(() => {\n setReference({\n getBoundingClientRect: () => rect,\n });\n }, [setReference, rect]);\n\n return createPortal(\n <div\n ref={setFloating}\n {...props}\n style={{\n ...style,\n position: strategy,\n top: 0,\n left: 0,\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n minWidth: \"max-content\",\n }}\n className={classNames(\n \"lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-threads\",\n className\n )}\n >\n {children}\n </div>,\n container\n );\n}\n\ninterface ThreadWrapperProps extends ThreadProps {\n thread: ThreadData;\n Thread: ComponentType<ThreadProps>;\n onEscapeKeydown: () => void;\n}\n\nfunction ThreadWrapper({\n thread,\n Thread,\n onEscapeKeydown,\n onKeyDown,\n ...threadProps\n}: ThreadWrapperProps) {\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n // TODO: Add ability to preventDefault on keydown to override the default behavior, e.g. to show an alert dialog\n if (event.key === \"Escape\") {\n onEscapeKeydown();\n }\n },\n [onEscapeKeydown, onKeyDown]\n );\n\n return <Thread thread={thread} onKeyDown={handleKeyDown} {...threadProps} />;\n}\n\n"],"names":["Thread","DefaultThread"],"mappings":"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"}