@datalayer/lexical-loro 0.2.4 → 0.2.5

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 (399) hide show
  1. package/lib/App.d.ts +2 -0
  2. package/lib/App.js +141 -0
  3. package/lib/Editor.d.ts +2 -0
  4. package/lib/Editor.js +115 -0
  5. package/lib/Settings.d.ts +2 -0
  6. package/lib/Settings.js +57 -0
  7. package/lib/appSettings.d.ts +36 -0
  8. package/lib/appSettings.js +48 -0
  9. package/lib/collab/loro/Bindings.d.ts +41 -0
  10. package/lib/collab/loro/Bindings.js +99 -0
  11. package/lib/collab/loro/Debug.d.ts +33 -0
  12. package/lib/collab/loro/Debug.js +452 -0
  13. package/lib/collab/loro/LexicalCollaborationContext.d.ts +19 -0
  14. package/lib/collab/loro/LexicalCollaborationContext.js +52 -0
  15. package/lib/collab/loro/LexicalCollaborationPlugin.d.ts +24 -0
  16. package/lib/collab/loro/LexicalCollaborationPlugin.js +83 -0
  17. package/lib/collab/loro/State.d.ts +53 -0
  18. package/lib/collab/loro/State.js +94 -0
  19. package/lib/collab/loro/components/LoroCollaborationUI.d.ts +13 -0
  20. package/lib/collab/loro/components/LoroCollaborationUI.js +9 -0
  21. package/lib/collab/loro/components/LoroCollaborators.d.ts +8 -0
  22. package/lib/collab/loro/components/LoroCollaborators.js +102 -0
  23. package/lib/collab/loro/components/index.d.ts +2 -0
  24. package/lib/collab/loro/components/index.js +6 -0
  25. package/lib/collab/loro/index.d.ts +6 -0
  26. package/lib/collab/loro/index.js +10 -0
  27. package/lib/collab/loro/integrators/BaseIntegrator.d.ts +14 -0
  28. package/lib/collab/loro/integrators/BaseIntegrator.js +5 -0
  29. package/lib/collab/loro/integrators/CounterIntegrator.d.ts +23 -0
  30. package/lib/collab/loro/integrators/CounterIntegrator.js +44 -0
  31. package/lib/collab/loro/integrators/ListIntegrator.d.ts +23 -0
  32. package/lib/collab/loro/integrators/ListIntegrator.js +53 -0
  33. package/lib/collab/loro/integrators/MapIntegrator.d.ts +24 -0
  34. package/lib/collab/loro/integrators/MapIntegrator.js +235 -0
  35. package/lib/collab/loro/integrators/TextIntegrator.d.ts +25 -0
  36. package/lib/collab/loro/integrators/TextIntegrator.js +55 -0
  37. package/lib/collab/loro/integrators/TreeIntegrator.d.ts +36 -0
  38. package/lib/collab/loro/integrators/TreeIntegrator.js +251 -0
  39. package/lib/collab/loro/nodes/NodeFactory.d.ts +15 -0
  40. package/lib/collab/loro/nodes/NodeFactory.js +101 -0
  41. package/lib/collab/loro/nodes/NodesMapper.d.ts +120 -0
  42. package/lib/collab/loro/nodes/NodesMapper.js +277 -0
  43. package/lib/collab/loro/propagators/DecoratorNodePropagator.d.ts +60 -0
  44. package/lib/collab/loro/propagators/DecoratorNodePropagator.js +306 -0
  45. package/lib/collab/loro/propagators/ElementNodePropagator.d.ts +62 -0
  46. package/lib/collab/loro/propagators/ElementNodePropagator.js +326 -0
  47. package/lib/collab/loro/propagators/LineBreakNodePropagator.d.ts +57 -0
  48. package/lib/collab/loro/propagators/LineBreakNodePropagator.js +200 -0
  49. package/lib/collab/loro/propagators/RootNodePropagator.d.ts +55 -0
  50. package/lib/collab/loro/propagators/RootNodePropagator.js +174 -0
  51. package/lib/collab/loro/propagators/TextNodePropagator.d.ts +60 -0
  52. package/lib/collab/loro/propagators/TextNodePropagator.js +440 -0
  53. package/lib/collab/loro/propagators/index.d.ts +49 -0
  54. package/lib/collab/loro/propagators/index.js +30 -0
  55. package/lib/collab/loro/provider/websocket.d.ts +116 -0
  56. package/lib/collab/loro/provider/websocket.js +911 -0
  57. package/lib/collab/loro/servers/index.d.ts +0 -0
  58. package/lib/collab/loro/servers/index.js +4 -0
  59. package/lib/collab/loro/servers/ws/callback.d.ts +5 -0
  60. package/lib/collab/loro/servers/ws/callback.js +89 -0
  61. package/lib/collab/loro/servers/ws/server.d.ts +2 -0
  62. package/lib/collab/loro/servers/ws/server.js +29 -0
  63. package/lib/collab/loro/servers/ws/utils.d.ts +40 -0
  64. package/lib/collab/loro/servers/ws/utils.js +517 -0
  65. package/lib/collab/loro/sync/SyncCursors.d.ts +32 -0
  66. package/lib/collab/loro/sync/SyncCursors.js +475 -0
  67. package/lib/collab/loro/sync/SyncLexicalToLoro.d.ts +4 -0
  68. package/lib/collab/loro/sync/SyncLexicalToLoro.js +113 -0
  69. package/lib/collab/loro/sync/SyncLoroToLexical.d.ts +5 -0
  70. package/lib/collab/loro/sync/SyncLoroToLexical.js +100 -0
  71. package/lib/collab/loro/types/LexicalNodeData.d.ts +32 -0
  72. package/lib/collab/loro/types/LexicalNodeData.js +75 -0
  73. package/lib/collab/loro/useCollaboration.d.ts +12 -0
  74. package/lib/collab/loro/useCollaboration.js +260 -0
  75. package/lib/collab/loro/utils/InitialContent.d.ts +64 -0
  76. package/lib/collab/loro/utils/InitialContent.js +113 -0
  77. package/lib/collab/loro/utils/LexicalToLoro.d.ts +18 -0
  78. package/lib/collab/loro/utils/LexicalToLoro.js +100 -0
  79. package/lib/collab/loro/utils/Utils.d.ts +44 -0
  80. package/lib/collab/loro/utils/Utils.js +157 -0
  81. package/lib/collab/loro/wsProvider.d.ts +8 -0
  82. package/lib/collab/loro/wsProvider.js +35 -0
  83. package/lib/collab/utils/invariant.d.ts +1 -0
  84. package/lib/collab/utils/invariant.js +15 -0
  85. package/lib/collab/utils/simpleDiffWithCursor.d.ts +5 -0
  86. package/lib/collab/utils/simpleDiffWithCursor.js +35 -0
  87. package/lib/collab/yjs/Bindings.d.ts +23 -0
  88. package/lib/collab/yjs/Bindings.js +26 -0
  89. package/lib/collab/yjs/Debug.d.ts +23 -0
  90. package/lib/collab/yjs/Debug.js +213 -0
  91. package/lib/collab/yjs/LexicalCollaborationContext.d.ts +10 -0
  92. package/lib/collab/yjs/LexicalCollaborationContext.js +37 -0
  93. package/lib/collab/yjs/LexicalCollaborationPlugin.d.ts +21 -0
  94. package/lib/collab/yjs/LexicalCollaborationPlugin.js +63 -0
  95. package/lib/collab/yjs/State.d.ts +51 -0
  96. package/lib/collab/yjs/State.js +35 -0
  97. package/lib/collab/yjs/nodes/AnyCollabNode.d.ts +5 -0
  98. package/lib/collab/yjs/nodes/AnyCollabNode.js +1 -0
  99. package/lib/collab/yjs/nodes/CollabDecoratorNode.d.ts +22 -0
  100. package/lib/collab/yjs/nodes/CollabDecoratorNode.js +64 -0
  101. package/lib/collab/yjs/nodes/CollabElementNode.d.ts +40 -0
  102. package/lib/collab/yjs/nodes/CollabElementNode.js +462 -0
  103. package/lib/collab/yjs/nodes/CollabLineBreakNode.d.ts +19 -0
  104. package/lib/collab/yjs/nodes/CollabLineBreakNode.js +44 -0
  105. package/lib/collab/yjs/nodes/CollabTextNode.d.ts +25 -0
  106. package/lib/collab/yjs/nodes/CollabTextNode.js +103 -0
  107. package/lib/collab/yjs/provider/websocket.d.ts +88 -0
  108. package/lib/collab/yjs/provider/websocket.js +415 -0
  109. package/lib/collab/yjs/servers/index.d.ts +0 -0
  110. package/lib/collab/yjs/servers/index.js +0 -0
  111. package/lib/collab/yjs/servers/ws/callback.d.ts +5 -0
  112. package/lib/collab/yjs/servers/ws/callback.js +72 -0
  113. package/lib/collab/yjs/servers/ws/server.d.ts +2 -0
  114. package/lib/collab/yjs/servers/ws/server.js +25 -0
  115. package/lib/collab/yjs/servers/ws/utils.d.ts +49 -0
  116. package/lib/collab/yjs/servers/ws/utils.js +284 -0
  117. package/lib/collab/yjs/sync/SyncCursors.d.ts +39 -0
  118. package/lib/collab/yjs/sync/SyncCursors.js +351 -0
  119. package/lib/collab/yjs/sync/SyncEditorStates.d.ts +10 -0
  120. package/lib/collab/yjs/sync/SyncEditorStates.js +200 -0
  121. package/lib/collab/yjs/useCollaboration.d.ts +12 -0
  122. package/lib/collab/yjs/useCollaboration.js +255 -0
  123. package/lib/collab/yjs/utils/Utils.d.ts +25 -0
  124. package/lib/collab/yjs/utils/Utils.js +402 -0
  125. package/lib/collab/yjs/wsProvider.d.ts +3 -0
  126. package/lib/collab/yjs/wsProvider.js +21 -0
  127. package/lib/commenting/index.d.ts +41 -0
  128. package/lib/commenting/index.js +328 -0
  129. package/lib/context/FlashMessageContext.d.ts +7 -0
  130. package/lib/context/FlashMessageContext.js +24 -0
  131. package/lib/context/SettingsContext.d.ts +12 -0
  132. package/lib/context/SettingsContext.js +38 -0
  133. package/lib/context/SharedHistoryContext.d.ts +11 -0
  134. package/lib/context/SharedHistoryContext.js +11 -0
  135. package/lib/context/ToolbarContext.d.ts +65 -0
  136. package/lib/context/ToolbarContext.js +84 -0
  137. package/lib/demo.d.ts +12 -0
  138. package/lib/demo.js +45 -0
  139. package/lib/hooks/useFlashMessage.d.ts +2 -0
  140. package/lib/hooks/useFlashMessage.js +8 -0
  141. package/lib/hooks/useModal.d.ts +5 -0
  142. package/lib/hooks/useModal.js +26 -0
  143. package/lib/hooks/useReport.d.ts +1 -0
  144. package/lib/hooks/useReport.js +50 -0
  145. package/lib/index.d.ts +1 -0
  146. package/lib/index.js +5 -0
  147. package/lib/nodes/AutocompleteNode.d.ts +27 -0
  148. package/lib/nodes/AutocompleteNode.js +60 -0
  149. package/lib/nodes/CounterComponent.d.ts +6 -0
  150. package/lib/nodes/CounterComponent.js +137 -0
  151. package/lib/nodes/CounterNode.d.ts +23 -0
  152. package/lib/nodes/CounterNode.js +47 -0
  153. package/lib/nodes/DateTimeNode/DateTimeComponent.d.ts +8 -0
  154. package/lib/nodes/DateTimeNode/DateTimeComponent.js +119 -0
  155. package/lib/nodes/DateTimeNode/DateTimeNode.d.ts +27 -0
  156. package/lib/nodes/DateTimeNode/DateTimeNode.js +82 -0
  157. package/lib/nodes/EmojiNode.d.ts +18 -0
  158. package/lib/nodes/EmojiNode.js +54 -0
  159. package/lib/nodes/EquationComponent.d.ts +9 -0
  160. package/lib/nodes/EquationComponent.js +75 -0
  161. package/lib/nodes/EquationNode.d.ts +26 -0
  162. package/lib/nodes/EquationNode.js +109 -0
  163. package/lib/nodes/ExcalidrawNode/ExcalidrawComponent.d.ts +8 -0
  164. package/lib/nodes/ExcalidrawNode/ExcalidrawComponent.js +110 -0
  165. package/lib/nodes/ExcalidrawNode/ExcalidrawImage.d.ts +50 -0
  166. package/lib/nodes/ExcalidrawNode/ExcalidrawImage.js +55 -0
  167. package/lib/nodes/ExcalidrawNode/index.d.ts +32 -0
  168. package/lib/nodes/ExcalidrawNode/index.js +117 -0
  169. package/lib/nodes/FigmaNode.d.ts +20 -0
  170. package/lib/nodes/FigmaNode.js +52 -0
  171. package/lib/nodes/ImageComponent.d.ts +16 -0
  172. package/lib/nodes/ImageComponent.js +272 -0
  173. package/lib/nodes/ImageNode.d.ts +50 -0
  174. package/lib/nodes/ImageNode.js +151 -0
  175. package/lib/nodes/InlineImageNode/InlineImageComponent.d.ts +26 -0
  176. package/lib/nodes/InlineImageNode/InlineImageComponent.js +161 -0
  177. package/lib/nodes/InlineImageNode/InlineImageNode.d.ts +59 -0
  178. package/lib/nodes/InlineImageNode/InlineImageNode.js +162 -0
  179. package/lib/nodes/KeywordNode.d.ts +14 -0
  180. package/lib/nodes/KeywordNode.js +37 -0
  181. package/lib/nodes/LayoutContainerNode.d.ts +24 -0
  182. package/lib/nodes/LayoutContainerNode.js +95 -0
  183. package/lib/nodes/LayoutItemNode.d.ts +16 -0
  184. package/lib/nodes/LayoutItemNode.js +69 -0
  185. package/lib/nodes/MentionNode.d.ts +20 -0
  186. package/lib/nodes/MentionNode.js +85 -0
  187. package/lib/nodes/PageBreakNode/index.d.ts +17 -0
  188. package/lib/nodes/PageBreakNode/index.js +83 -0
  189. package/lib/nodes/PlaygroundNodes.d.ts +3 -0
  190. package/lib/nodes/PlaygroundNodes.js +75 -0
  191. package/lib/nodes/PollComponent.d.ts +9 -0
  192. package/lib/nodes/PollComponent.js +85 -0
  193. package/lib/nodes/PollNode.d.ts +43 -0
  194. package/lib/nodes/PollNode.js +153 -0
  195. package/lib/nodes/SpecialTextNode.d.ts +24 -0
  196. package/lib/nodes/SpecialTextNode.js +54 -0
  197. package/lib/nodes/StickyComponent.d.ts +10 -0
  198. package/lib/nodes/StickyComponent.js +162 -0
  199. package/lib/nodes/StickyNode.d.ts +31 -0
  200. package/lib/nodes/StickyNode.js +76 -0
  201. package/lib/nodes/TweetNode.d.ts +21 -0
  202. package/lib/nodes/TweetNode.js +119 -0
  203. package/lib/nodes/YouTubeNode.d.ts +22 -0
  204. package/lib/nodes/YouTubeNode.js +84 -0
  205. package/lib/plugins/ActionsPlugin/index.d.ts +5 -0
  206. package/lib/plugins/ActionsPlugin/index.js +168 -0
  207. package/lib/plugins/AutoEmbedPlugin/index.d.ts +19 -0
  208. package/lib/plugins/AutoEmbedPlugin/index.js +158 -0
  209. package/lib/plugins/AutoLinkPlugin/index.d.ts +2 -0
  210. package/lib/plugins/AutoLinkPlugin/index.js +15 -0
  211. package/lib/plugins/AutocompletePlugin/index.d.ts +10 -0
  212. package/lib/plugins/AutocompletePlugin/index.js +2477 -0
  213. package/lib/plugins/CodeActionMenuPlugin/components/CopyButton/index.d.ts +7 -0
  214. package/lib/plugins/CodeActionMenuPlugin/components/CopyButton/index.js +46 -0
  215. package/lib/plugins/CodeActionMenuPlugin/components/PrettierButton/index.d.ts +17 -0
  216. package/lib/plugins/CodeActionMenuPlugin/components/PrettierButton/index.js +115 -0
  217. package/lib/plugins/CodeActionMenuPlugin/index.d.ts +5 -0
  218. package/lib/plugins/CodeActionMenuPlugin/index.js +104 -0
  219. package/lib/plugins/CodeActionMenuPlugin/utils.d.ts +1 -0
  220. package/lib/plugins/CodeActionMenuPlugin/utils.js +22 -0
  221. package/lib/plugins/CodeHighlightPrismPlugin/index.d.ts +2 -0
  222. package/lib/plugins/CodeHighlightPrismPlugin/index.js +14 -0
  223. package/lib/plugins/CodeHighlightShikiPlugin/index.d.ts +2 -0
  224. package/lib/plugins/CodeHighlightShikiPlugin/index.js +14 -0
  225. package/lib/plugins/CollapsiblePlugin/CollapsibleContainerNode.d.ts +25 -0
  226. package/lib/plugins/CollapsiblePlugin/CollapsibleContainerNode.js +135 -0
  227. package/lib/plugins/CollapsiblePlugin/CollapsibleContentNode.d.ts +16 -0
  228. package/lib/plugins/CollapsiblePlugin/CollapsibleContentNode.js +83 -0
  229. package/lib/plugins/CollapsiblePlugin/CollapsibleTitleNode.d.ts +16 -0
  230. package/lib/plugins/CollapsiblePlugin/CollapsibleTitleNode.js +85 -0
  231. package/lib/plugins/CollapsiblePlugin/CollapsibleUtils.d.ts +2 -0
  232. package/lib/plugins/CollapsiblePlugin/CollapsibleUtils.js +12 -0
  233. package/lib/plugins/CollapsiblePlugin/index.d.ts +3 -0
  234. package/lib/plugins/CollapsiblePlugin/index.js +132 -0
  235. package/lib/plugins/CommentPlugin/index.d.ts +9 -0
  236. package/lib/plugins/CommentPlugin/index.js +460 -0
  237. package/lib/plugins/ComponentPickerPlugin/index.d.ts +2 -0
  238. package/lib/plugins/ComponentPickerPlugin/index.js +276 -0
  239. package/lib/plugins/ContextMenuPlugin/index.d.ts +2 -0
  240. package/lib/plugins/ContextMenuPlugin/index.js +112 -0
  241. package/lib/plugins/CounterPlugin/index.d.ts +3 -0
  242. package/lib/plugins/CounterPlugin/index.js +24 -0
  243. package/lib/plugins/DateTimePlugin/index.d.ts +8 -0
  244. package/lib/plugins/DateTimePlugin/index.js +28 -0
  245. package/lib/plugins/DebugPlugin/index.d.ts +3 -0
  246. package/lib/plugins/DebugPlugin/index.js +219 -0
  247. package/lib/plugins/DocsPlugin/index.d.ts +2 -0
  248. package/lib/plugins/DocsPlugin/index.js +4 -0
  249. package/lib/plugins/DragDropPastePlugin/index.d.ts +1 -0
  250. package/lib/plugins/DragDropPastePlugin/index.js +37 -0
  251. package/lib/plugins/DraggableBlockPlugin/index.d.ts +12 -0
  252. package/lib/plugins/DraggableBlockPlugin/index.js +36 -0
  253. package/lib/plugins/EmojiPickerPlugin/index.d.ts +1 -0
  254. package/lib/plugins/EmojiPickerPlugin/index.js +84 -0
  255. package/lib/plugins/EmojisPlugin/index.d.ts +2 -0
  256. package/lib/plugins/EmojisPlugin/index.js +56 -0
  257. package/lib/plugins/EquationsPlugin/index.d.ts +14 -0
  258. package/lib/plugins/EquationsPlugin/index.js +34 -0
  259. package/lib/plugins/ExcalidrawPlugin/index.d.ts +5 -0
  260. package/lib/plugins/ExcalidrawPlugin/index.js +44 -0
  261. package/lib/plugins/FigmaPlugin/index.d.ts +4 -0
  262. package/lib/plugins/FigmaPlugin/index.js +24 -0
  263. package/lib/plugins/FloatingLinkEditorPlugin/index.d.ts +15 -0
  264. package/lib/plugins/FloatingLinkEditorPlugin/index.js +280 -0
  265. package/lib/plugins/FloatingTextFormatToolbarPlugin/index.d.ts +7 -0
  266. package/lib/plugins/FloatingTextFormatToolbarPlugin/index.js +219 -0
  267. package/lib/plugins/ImagesPlugin/index.d.ts +24 -0
  268. package/lib/plugins/ImagesPlugin/index.js +195 -0
  269. package/lib/plugins/InlineImagePlugin/index.d.ts +17 -0
  270. package/lib/plugins/InlineImagePlugin/index.js +180 -0
  271. package/lib/plugins/KeywordsPlugin/index.d.ts +2 -0
  272. package/lib/plugins/KeywordsPlugin/index.js +35 -0
  273. package/lib/plugins/LayoutPlugin/InsertLayoutDialog.d.ts +6 -0
  274. package/lib/plugins/LayoutPlugin/InsertLayoutDialog.js +21 -0
  275. package/lib/plugins/LayoutPlugin/LayoutPlugin.d.ts +7 -0
  276. package/lib/plugins/LayoutPlugin/LayoutPlugin.js +135 -0
  277. package/lib/plugins/LinkPlugin/index.d.ts +6 -0
  278. package/lib/plugins/LinkPlugin/index.js +11 -0
  279. package/lib/plugins/MarkdownShortcutPlugin/index.d.ts +2 -0
  280. package/lib/plugins/MarkdownShortcutPlugin/index.js +6 -0
  281. package/lib/plugins/MarkdownTransformers/index.d.ts +8 -0
  282. package/lib/plugins/MarkdownTransformers/index.js +238 -0
  283. package/lib/plugins/MaxLengthPlugin/index.d.ts +3 -0
  284. package/lib/plugins/MaxLengthPlugin/index.js +41 -0
  285. package/lib/plugins/MentionsPlugin/index.d.ts +2 -0
  286. package/lib/plugins/MentionsPlugin/index.js +564 -0
  287. package/lib/plugins/PageBreakPlugin/index.d.ts +4 -0
  288. package/lib/plugins/PageBreakPlugin/index.js +31 -0
  289. package/lib/plugins/PasteLogPlugin/index.d.ts +2 -0
  290. package/lib/plugins/PasteLogPlugin/index.js +27 -0
  291. package/lib/plugins/PollPlugin/index.d.ts +8 -0
  292. package/lib/plugins/PollPlugin/index.js +38 -0
  293. package/lib/plugins/ShortcutsPlugin/index.d.ts +6 -0
  294. package/lib/plugins/ShortcutsPlugin/index.js +116 -0
  295. package/lib/plugins/ShortcutsPlugin/shortcuts.d.ts +59 -0
  296. package/lib/plugins/ShortcutsPlugin/shortcuts.js +173 -0
  297. package/lib/plugins/SpecialTextPlugin/index.d.ts +2 -0
  298. package/lib/plugins/SpecialTextPlugin/index.js +50 -0
  299. package/lib/plugins/SpeechToTextPlugin/index.d.ts +5 -0
  300. package/lib/plugins/SpeechToTextPlugin/index.js +86 -0
  301. package/lib/plugins/StickyPlugin/index.d.ts +2 -0
  302. package/lib/plugins/StickyPlugin/index.js +16 -0
  303. package/lib/plugins/TabFocusPlugin/index.d.ts +1 -0
  304. package/lib/plugins/TabFocusPlugin/index.js +38 -0
  305. package/lib/plugins/TableActionMenuPlugin/index.d.ts +5 -0
  306. package/lib/plugins/TableActionMenuPlugin/index.js +492 -0
  307. package/lib/plugins/TableCellResizer/index.d.ts +3 -0
  308. package/lib/plugins/TableCellResizer/index.js +297 -0
  309. package/lib/plugins/TableHoverActionsPlugin/index.d.ts +4 -0
  310. package/lib/plugins/TableHoverActionsPlugin/index.js +188 -0
  311. package/lib/plugins/TableOfContentsPlugin/index.d.ts +2 -0
  312. package/lib/plugins/TableOfContentsPlugin/index.js +116 -0
  313. package/lib/plugins/TablePlugin.d.ts +31 -0
  314. package/lib/plugins/TablePlugin.js +63 -0
  315. package/lib/plugins/TestRecorderPlugin/index.d.ts +3 -0
  316. package/lib/plugins/TestRecorderPlugin/index.js +346 -0
  317. package/lib/plugins/ToolbarPlugin/fontSize.d.ts +9 -0
  318. package/lib/plugins/ToolbarPlugin/fontSize.js +84 -0
  319. package/lib/plugins/ToolbarPlugin/index.d.ts +9 -0
  320. package/lib/plugins/ToolbarPlugin/index.js +500 -0
  321. package/lib/plugins/ToolbarPlugin/utils.d.ts +26 -0
  322. package/lib/plugins/ToolbarPlugin/utils.js +247 -0
  323. package/lib/plugins/TreeViewPlugin/index.d.ts +2 -0
  324. package/lib/plugins/TreeViewPlugin/index.js +7 -0
  325. package/lib/plugins/TwitterPlugin/index.d.ts +4 -0
  326. package/lib/plugins/TwitterPlugin/index.js +24 -0
  327. package/lib/plugins/TypingPerfPlugin/index.d.ts +2 -0
  328. package/lib/plugins/TypingPerfPlugin/index.js +97 -0
  329. package/lib/plugins/YouTubePlugin/index.d.ts +4 -0
  330. package/lib/plugins/YouTubePlugin/index.js +24 -0
  331. package/lib/server/validation.d.ts +1 -0
  332. package/lib/server/validation.js +115 -0
  333. package/lib/setupEnv.d.ts +2 -0
  334. package/lib/setupEnv.js +29 -0
  335. package/lib/themes/CommentEditorTheme.d.ts +4 -0
  336. package/lib/themes/CommentEditorTheme.js +11 -0
  337. package/lib/themes/PlaygroundEditorTheme.d.ts +4 -0
  338. package/lib/themes/PlaygroundEditorTheme.js +124 -0
  339. package/lib/themes/StickyEditorTheme.d.ts +4 -0
  340. package/lib/themes/StickyEditorTheme.js +11 -0
  341. package/lib/tyes.dt.d.ts +12 -0
  342. package/lib/tyes.dt.js +4 -0
  343. package/lib/ui/Button.d.ts +12 -0
  344. package/lib/ui/Button.js +6 -0
  345. package/lib/ui/ColorPicker.d.ts +14 -0
  346. package/lib/ui/ColorPicker.js +219 -0
  347. package/lib/ui/ContentEditable.d.ts +9 -0
  348. package/lib/ui/ContentEditable.js +6 -0
  349. package/lib/ui/Dialog.d.ts +10 -0
  350. package/lib/ui/Dialog.js +8 -0
  351. package/lib/ui/DropDown.d.ts +18 -0
  352. package/lib/ui/DropDown.js +133 -0
  353. package/lib/ui/DropdownColorPicker.d.ts +13 -0
  354. package/lib/ui/DropdownColorPicker.js +6 -0
  355. package/lib/ui/EquationEditor.d.ts +8 -0
  356. package/lib/ui/EquationEditor.js +11 -0
  357. package/lib/ui/ExcalidrawModal.d.ts +42 -0
  358. package/lib/ui/ExcalidrawModal.js +103 -0
  359. package/lib/ui/FileInput.d.ts +10 -0
  360. package/lib/ui/FileInput.js +5 -0
  361. package/lib/ui/FlashMessage.d.ts +7 -0
  362. package/lib/ui/FlashMessage.js +6 -0
  363. package/lib/ui/ImageResizer.d.ts +17 -0
  364. package/lib/ui/ImageResizer.js +171 -0
  365. package/lib/ui/KatexEquationAlterer.d.ts +8 -0
  366. package/lib/ui/KatexEquationAlterer.js +23 -0
  367. package/lib/ui/KatexRenderer.d.ts +6 -0
  368. package/lib/ui/KatexRenderer.js +24 -0
  369. package/lib/ui/Modal.d.ts +9 -0
  370. package/lib/ui/Modal.js +48 -0
  371. package/lib/ui/Select.d.ts +8 -0
  372. package/lib/ui/Select.js +5 -0
  373. package/lib/ui/Switch.d.ts +8 -0
  374. package/lib/ui/Switch.js +6 -0
  375. package/lib/ui/TextInput.d.ts +13 -0
  376. package/lib/ui/TextInput.js +7 -0
  377. package/lib/utils/docSerialization.d.ts +3 -0
  378. package/lib/utils/docSerialization.js +60 -0
  379. package/lib/utils/emoji-list.d.ts +20 -0
  380. package/lib/utils/emoji-list.js +16609 -0
  381. package/lib/utils/getDOMRangeRect.d.ts +8 -0
  382. package/lib/utils/getDOMRangeRect.js +26 -0
  383. package/lib/utils/getSelectedNode.d.ts +2 -0
  384. package/lib/utils/getSelectedNode.js +28 -0
  385. package/lib/utils/getThemeSelector.d.ts +2 -0
  386. package/lib/utils/getThemeSelector.js +14 -0
  387. package/lib/utils/isMobileWidth.d.ts +7 -0
  388. package/lib/utils/isMobileWidth.js +11 -0
  389. package/lib/utils/joinClasses.d.ts +1 -0
  390. package/lib/utils/joinClasses.js +7 -0
  391. package/lib/utils/setFloatingElemPosition.d.ts +1 -0
  392. package/lib/utils/setFloatingElemPosition.js +59 -0
  393. package/lib/utils/setFloatingElemPositionForLinkEditor.d.ts +1 -0
  394. package/lib/utils/setFloatingElemPositionForLinkEditor.js +36 -0
  395. package/lib/utils/swipe.d.ts +4 -0
  396. package/lib/utils/swipe.js +94 -0
  397. package/lib/utils/url.d.ts +2 -0
  398. package/lib/utils/url.js +31 -0
  399. package/package.json +1 -1
@@ -0,0 +1,475 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the MIT License.
4
+ */
5
+ import { $getNodeByKey, $getSelection, $isElementNode, $isLineBreakNode, $isRangeSelection, $isTextNode, } from 'lexical';
6
+ import { createDOMRange, createRectsFromDOMRange } from '@lexical/selection';
7
+ /*****************************************************************************/
8
+ // Helper function to find Loro tree node for a Lexical NodeKey (read-only lookup)
9
+ function findLoroTreeNodeForLexicalKey(nodeKey, binding) {
10
+ try {
11
+ const nodeMapper = binding.nodeMapper;
12
+ const treeId = nodeMapper.getTreeIDByLexicalKey(nodeKey);
13
+ if (!treeId) {
14
+ return null;
15
+ }
16
+ const tree = binding.tree;
17
+ if (tree.has(treeId)) {
18
+ return tree.getNodeByID(treeId) || null;
19
+ }
20
+ return null;
21
+ }
22
+ catch (error) {
23
+ console.warn('Failed to find Loro tree node for Lexical key:', nodeKey, error);
24
+ return null;
25
+ }
26
+ }
27
+ // Helper function to convert a Lexical Point to a Loro Cursor.
28
+ // The cursor data uses TreeIDs (stable across all clients) instead of NodeKeys
29
+ // (local to each editor instance) to ensure correct cross-client resolution.
30
+ function convertLexicalPointToCursor(point, binding) {
31
+ try {
32
+ const node = $getNodeByKey(point.key);
33
+ if (!node) {
34
+ return null;
35
+ }
36
+ const nodeMapper = binding.nodeMapper;
37
+ // --- Text node ---
38
+ if ($isTextNode(node)) {
39
+ const treeId = nodeMapper.getTreeIDByLexicalKey(point.key);
40
+ if (!treeId) {
41
+ return null;
42
+ }
43
+ const textContent = node.getTextContent();
44
+ const offset = Math.min(point.offset, textContent.length);
45
+ return {
46
+ treeId,
47
+ offset,
48
+ pointType: 'text',
49
+ };
50
+ }
51
+ // --- Element node (paragraphs, headings, table cells, …) ---
52
+ if ($isElementNode(node)) {
53
+ const children = node.getChildren();
54
+ if (children.length === 0) {
55
+ // Empty element (e.g. freshly-created paragraph after Enter)
56
+ const treeId = nodeMapper.getTreeIDByLexicalKey(point.key);
57
+ if (!treeId)
58
+ return null;
59
+ return { treeId, offset: point.offset, pointType: 'element' };
60
+ }
61
+ // Try to resolve the child at `point.offset`
62
+ if (point.offset < children.length) {
63
+ const child = children[point.offset];
64
+ if (child) {
65
+ const childTreeId = nodeMapper.getTreeIDByLexicalKey(child.getKey());
66
+ if (childTreeId) {
67
+ return {
68
+ treeId: childTreeId,
69
+ offset: 0,
70
+ pointType: $isTextNode(child) ? 'text' : 'element',
71
+ };
72
+ }
73
+ }
74
+ }
75
+ // Fallback: cursor is at/past the end of this element's children
76
+ const treeId = nodeMapper.getTreeIDByLexicalKey(point.key);
77
+ if (!treeId)
78
+ return null;
79
+ return { treeId, offset: point.offset, pointType: 'element' };
80
+ }
81
+ // --- Any other node type (decorator, linebreak, …) ---
82
+ const treeId = nodeMapper.getTreeIDByLexicalKey(point.key);
83
+ if (!treeId)
84
+ return null;
85
+ return { treeId, offset: point.offset, pointType: 'element' };
86
+ }
87
+ catch (error) {
88
+ console.warn('Failed to convert Lexical point to Loro cursor:', error);
89
+ return null;
90
+ }
91
+ }
92
+ // Helper function to convert Loro cursor data back to Lexical selection.
93
+ // Always resolves via TreeID → NodeMapper → local NodeKey.
94
+ // NodeKeys are never transmitted between clients because they are
95
+ // editor-instance-local and would map to wrong nodes on the remote side.
96
+ function convertLoroSelectionToLexical(anchorCursor, focusCursor, binding) {
97
+ try {
98
+ if (!anchorCursor || !focusCursor) {
99
+ return null;
100
+ }
101
+ const anchor = anchorCursor;
102
+ const focus = focusCursor;
103
+ const nodeMapper = binding.nodeMapper;
104
+ let anchorKey = null;
105
+ let focusKey = null;
106
+ if (anchor.treeId) {
107
+ anchorKey = nodeMapper.getLexicalKeyByLoroId(anchor.treeId);
108
+ }
109
+ if (focus.treeId) {
110
+ focusKey = nodeMapper.getLexicalKeyByLoroId(focus.treeId);
111
+ }
112
+ if (!anchorKey || !focusKey) {
113
+ // Mapping not yet available — tree operations may not have arrived yet.
114
+ // Cursors will be re-synced after the next tree integration.
115
+ return null;
116
+ }
117
+ return {
118
+ anchorKey,
119
+ anchorOffset: anchor.offset || 0,
120
+ focusKey,
121
+ focusOffset: focus.offset || 0,
122
+ };
123
+ }
124
+ catch (error) {
125
+ console.warn('Failed to convert Loro cursors to Lexical selection:', error);
126
+ return null;
127
+ }
128
+ }
129
+ // Helper function to set a point in Lexical selection
130
+ function $setPoint(point, key, offset) {
131
+ if (point.key !== key || point.offset !== offset) {
132
+ let anchorNode = $getNodeByKey(key);
133
+ if (anchorNode !== null &&
134
+ !$isElementNode(anchorNode) &&
135
+ !$isTextNode(anchorNode)) {
136
+ const parent = anchorNode.getParentOrThrow();
137
+ key = parent.getKey();
138
+ offset = anchorNode.getIndexWithinParent();
139
+ anchorNode = parent;
140
+ }
141
+ point.set(key, offset, $isElementNode(anchorNode) ? 'element' : 'text');
142
+ }
143
+ }
144
+ // Helper functions for cursor UI management
145
+ function createCollabCursor(name, color) {
146
+ return {
147
+ color: color,
148
+ name: name,
149
+ selection: null,
150
+ };
151
+ }
152
+ function createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset, isCurrentUser = false) {
153
+ const color = cursor.color;
154
+ // Helper function to convert color to rgba with opacity
155
+ const getColorWithOpacity = (color, opacity) => {
156
+ if (color.startsWith('#')) {
157
+ const hex = color.slice(1);
158
+ const r = parseInt(hex.slice(0, 2), 16);
159
+ const g = parseInt(hex.slice(2, 4), 16);
160
+ const b = parseInt(hex.slice(4, 6), 16);
161
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
162
+ }
163
+ return color;
164
+ };
165
+ const caretColor = isCurrentUser ? getColorWithOpacity(color, 0.6) : color;
166
+ const nameBackgroundColor = isCurrentUser ? getColorWithOpacity(color, 0.7) : color;
167
+ const caret = document.createElement('span');
168
+ caret.style.cssText = `position:absolute;top:0;bottom:0;right:-1px;width:2px;background-color:${caretColor};z-index:10;${isCurrentUser ? 'opacity:0.8;' : ''}`;
169
+ const name = document.createElement('span');
170
+ name.textContent = cursor.name;
171
+ name.style.cssText = `position:absolute;left:-2px;top:-16px;background-color:${nameBackgroundColor};color:#fff;line-height:12px;font-size:12px;padding:2px;font-family:Arial;font-weight:bold;white-space:nowrap;${isCurrentUser ? 'opacity:0.9;' : ''}`;
172
+ caret.appendChild(name);
173
+ return {
174
+ anchor: { key: anchorKey, offset: anchorOffset },
175
+ focus: { key: focusKey, offset: focusOffset },
176
+ caret,
177
+ color: cursor.color,
178
+ name,
179
+ selections: [],
180
+ };
181
+ }
182
+ function destroyCursor(binding, cursor) {
183
+ const selection = cursor.selection;
184
+ if (selection !== null) {
185
+ destroySelection(binding, selection);
186
+ cursor.selection = null;
187
+ }
188
+ }
189
+ function destroySelection(binding, selection) {
190
+ const cursorsContainer = binding.cursorsContainer;
191
+ if (cursorsContainer !== null) {
192
+ const selections = selection.selections;
193
+ const selectionsLength = selections.length;
194
+ for (let i = 0; i < selectionsLength; i++) {
195
+ cursorsContainer.removeChild(selections[i]);
196
+ }
197
+ }
198
+ }
199
+ /*****************************************************************************/
200
+ export function syncCursorPositions(binding, provider, options) {
201
+ const { getAwarenessStates = getAwarenessStatesDefault } = options ?? {};
202
+ const awarenessStates = Array.from(getAwarenessStates(binding, provider));
203
+ const localClientID = binding.clientID;
204
+ const cursors = binding.cursors;
205
+ const editor = binding.editor;
206
+ const nodeMap = editor._editorState._nodeMap;
207
+ const visitedClientIDs = new Set();
208
+ // Process all cursor positions from awareness (including local user)
209
+ for (let i = 0; i < awarenessStates.length; i++) {
210
+ const awarenessState = awarenessStates[i];
211
+ const [clientID, awareness] = awarenessState;
212
+ visitedClientIDs.add(clientID);
213
+ const { name, color, focusing } = awareness;
214
+ const isCurrentUser = clientID === localClientID;
215
+ let selection = null;
216
+ let cursor = cursors.get(clientID);
217
+ if (cursor === undefined) {
218
+ // Add "(Me)" label for current user's cursor
219
+ const cursorName = isCurrentUser ? `${name} (Me)` : name;
220
+ cursor = createCollabCursor(cursorName, color);
221
+ cursors.set(clientID, cursor);
222
+ }
223
+ // Render cursor/selection whenever valid anchorPos/focusPos exist.
224
+ // We intentionally do NOT gate on `focusing` because the FOCUS_COMMAND
225
+ // and selection-update can race — the selection update may broadcast
226
+ // before FOCUS_COMMAND fires, leaving `focusing: false` even though
227
+ // the user clearly has the editor focused (they are selecting text).
228
+ const { anchorPos, focusPos } = awareness;
229
+ if (anchorPos !== null && focusPos !== null) {
230
+ const selectionInfo = convertLoroSelectionToLexical(anchorPos, focusPos, binding);
231
+ if (selectionInfo) {
232
+ const { anchorKey, anchorOffset, focusKey, focusOffset } = selectionInfo;
233
+ selection = cursor.selection;
234
+ if (selection === null) {
235
+ selection = createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset, isCurrentUser);
236
+ }
237
+ else {
238
+ // Update existing selection
239
+ const anchor = selection.anchor;
240
+ const focus = selection.focus;
241
+ anchor.key = anchorKey;
242
+ anchor.offset = anchorOffset;
243
+ focus.key = focusKey;
244
+ focus.offset = focusOffset;
245
+ }
246
+ }
247
+ }
248
+ updateCursor(binding, cursor, selection, nodeMap, isCurrentUser);
249
+ }
250
+ // Clean up cursors for clients that are no longer present
251
+ const allClientIDs = Array.from(cursors.keys());
252
+ for (let i = 0; i < allClientIDs.length; i++) {
253
+ const clientID = allClientIDs[i];
254
+ if (!visitedClientIDs.has(clientID)) {
255
+ const cursor = cursors.get(clientID);
256
+ if (cursor !== undefined) {
257
+ destroyCursor(binding, cursor);
258
+ cursors.delete(clientID);
259
+ }
260
+ }
261
+ }
262
+ }
263
+ // Default function to get awareness states from provider
264
+ function getAwarenessStatesDefault(binding, provider) {
265
+ return provider.awareness.getStates();
266
+ }
267
+ // Function to update cursor visualization in the DOM
268
+ function updateCursor(binding, cursor, nextSelection, nodeMap, // LexicalEditor NodeMap type
269
+ isCurrentUser = false) {
270
+ const editor = binding.editor;
271
+ const rootElement = editor.getRootElement();
272
+ const cursorsContainer = binding.cursorsContainer;
273
+ if (cursorsContainer === null || rootElement === null) {
274
+ return;
275
+ }
276
+ // The cursorsContainer is a position:fixed overlay covering the viewport.
277
+ // Use its own getBoundingClientRect() as the coordinate reference,
278
+ // so that absolute children are positioned correctly relative to it.
279
+ const containerRect = cursorsContainer.getBoundingClientRect();
280
+ const prevSelection = cursor.selection;
281
+ if (nextSelection === null) {
282
+ if (prevSelection === null) {
283
+ return;
284
+ }
285
+ else {
286
+ cursor.selection = null;
287
+ destroySelection(binding, prevSelection);
288
+ return;
289
+ }
290
+ }
291
+ else {
292
+ cursor.selection = nextSelection;
293
+ }
294
+ const caret = nextSelection.caret;
295
+ const color = nextSelection.color;
296
+ const selections = nextSelection.selections;
297
+ const anchor = nextSelection.anchor;
298
+ const focus = nextSelection.focus;
299
+ const anchorKey = anchor.key;
300
+ const focusKey = focus.key;
301
+ const anchorNode = nodeMap.get(anchorKey);
302
+ const focusNode = nodeMap.get(focusKey);
303
+ if (anchorNode == null || focusNode == null) {
304
+ return;
305
+ }
306
+ // Determine if the selection is collapsed (cursor only) or expanded (text selected)
307
+ const isCollapsed = anchorKey === focusKey && anchor.offset === focus.offset;
308
+ let selectionRects;
309
+ // Handle collapsed selection on a linebreak
310
+ if (anchorNode === focusNode && $isLineBreakNode(anchorNode)) {
311
+ const brElement = editor.getElementByKey(anchorKey);
312
+ if (brElement) {
313
+ const brRect = brElement.getBoundingClientRect();
314
+ selectionRects = [brRect];
315
+ }
316
+ else {
317
+ selectionRects = [];
318
+ }
319
+ }
320
+ else {
321
+ const range = createDOMRange(editor, anchorNode, anchor.offset, focusNode, focus.offset);
322
+ if (range === null) {
323
+ return;
324
+ }
325
+ if (isCollapsed) {
326
+ // For collapsed cursors, get all rects (including zero-width) to position the caret.
327
+ // createRectsFromDOMRange filters out rects with width < 1, so use the raw
328
+ // range rect as a fallback for caret positioning.
329
+ selectionRects = createRectsFromDOMRange(editor, range);
330
+ if (selectionRects.length === 0) {
331
+ // Fallback: use the bounding rect of the collapsed range
332
+ const boundingRect = range.getBoundingClientRect();
333
+ if (boundingRect.height > 0) {
334
+ selectionRects = [boundingRect];
335
+ }
336
+ }
337
+ }
338
+ else {
339
+ selectionRects = createRectsFromDOMRange(editor, range);
340
+ }
341
+ }
342
+ const selectionsLength = selections.length;
343
+ const selectionRectsLength = selectionRects.length;
344
+ for (let i = 0; i < selectionRectsLength; i++) {
345
+ const selectionRect = selectionRects[i];
346
+ let selection = selections[i];
347
+ if (selection === undefined) {
348
+ selection = document.createElement('span');
349
+ selections[i] = selection;
350
+ const selectionBg = document.createElement('span');
351
+ selection.appendChild(selectionBg);
352
+ cursorsContainer.appendChild(selection);
353
+ }
354
+ const top = selectionRect.top - containerRect.top;
355
+ const left = selectionRect.left - containerRect.left;
356
+ // For collapsed cursors, give the wrapper a minimum width so the caret
357
+ // (positioned inside) is not clipped by some browsers.
358
+ const effectiveWidth = isCollapsed ? Math.max(selectionRect.width, 4) : selectionRect.width;
359
+ const style = `position:absolute;top:${top}px;left:${left}px;height:${selectionRect.height}px;width:${effectiveWidth}px;pointer-events:none;z-index:10;overflow:visible;`;
360
+ selection.style.cssText = style;
361
+ if (isCollapsed) {
362
+ // Collapsed cursor: hide the background span.
363
+ selection.firstChild.style.cssText =
364
+ `position:absolute;left:0;top:0;width:0;height:0;`;
365
+ // Make the caret clearly visible with explicit height and a bright border-left for contrast.
366
+ caret.style.cssText = `position:absolute;top:0;left:0;width:2px;height:${selectionRect.height}px;background-color:${color};z-index:10;`;
367
+ }
368
+ else if (isCurrentUser) {
369
+ // Current user's expanded selection: very subtle since the browser already
370
+ // shows the native selection highlight.
371
+ selection.firstChild.style.cssText =
372
+ `position:absolute;left:0;top:0;width:${selectionRect.width}px;height:${selectionRect.height}px;` +
373
+ `background-color:${color};opacity:0.08;border-radius:2px;pointer-events:none;`;
374
+ }
375
+ else {
376
+ // Remote user's expanded selection: clearly visible with transparency.
377
+ // The user's color is shown as a translucent overlay so the text beneath
378
+ // remains readable.
379
+ selection.firstChild.style.cssText =
380
+ `position:absolute;left:0;top:0;width:${selectionRect.width}px;height:${selectionRect.height}px;` +
381
+ `background-color:${color};opacity:0.25;border-radius:2px;pointer-events:none;`;
382
+ }
383
+ if (i === selectionRectsLength - 1) {
384
+ if (caret.parentNode !== selection) {
385
+ selection.appendChild(caret);
386
+ }
387
+ }
388
+ }
389
+ for (let i = selectionsLength - 1; i >= selectionRectsLength; i--) {
390
+ const selection = selections[i];
391
+ cursorsContainer.removeChild(selection);
392
+ selections.pop();
393
+ }
394
+ }
395
+ export function syncLexicalSelectionToLoro(binding, provider, prevSelection, nextSelection) {
396
+ const awareness = provider.awareness;
397
+ const localState = awareness.getLocalState();
398
+ if (localState === null) {
399
+ return;
400
+ }
401
+ const { anchorPos: currentAnchorPos, focusPos: currentFocusPos, name, color, focusing, awarenessData, } = localState;
402
+ let anchorPos = null;
403
+ let focusPos = null;
404
+ // Convert Lexical selection to Loro cursors if we have a range selection
405
+ if ($isRangeSelection(nextSelection)) {
406
+ anchorPos = convertLexicalPointToCursor(nextSelection.anchor, binding);
407
+ focusPos = convertLexicalPointToCursor(nextSelection.focus, binding);
408
+ }
409
+ else if (nextSelection === null) {
410
+ // Selection cleared — broadcast null positions
411
+ }
412
+ else {
413
+ // Not a range selection (e.g. NodeSelection) — skip
414
+ return;
415
+ }
416
+ // Always broadcast to keep remote cursors in sync.
417
+ // The shouldUpdatePosition check prevents redundant updates for unchanged positions.
418
+ const shouldUpdate = shouldUpdatePosition(currentAnchorPos, anchorPos) ||
419
+ shouldUpdatePosition(currentFocusPos, focusPos);
420
+ if (!shouldUpdate) {
421
+ return;
422
+ }
423
+ // When we have a valid selection to broadcast, the editor IS focused —
424
+ // force `focusing: true` to prevent the FOCUS_COMMAND race condition
425
+ // where the selection update fires before FOCUS_COMMAND sets the flag.
426
+ const effectiveFocusing = (anchorPos !== null && focusPos !== null) ? true : focusing;
427
+ // Update the local state in EphemeralStore via awareness
428
+ awareness.setLocalState({
429
+ ...localState,
430
+ anchorPos,
431
+ awarenessData,
432
+ color,
433
+ focusPos,
434
+ focusing: effectiveFocusing,
435
+ name,
436
+ });
437
+ }
438
+ // Helper function to determine if cursor position should be updated
439
+ function shouldUpdatePosition(currentPos, pos) {
440
+ if (currentPos == null) {
441
+ return pos != null;
442
+ }
443
+ else if (pos == null) {
444
+ return true;
445
+ }
446
+ else {
447
+ // Compare using treeId (stable across clients) and offset
448
+ const current = currentPos;
449
+ const next = pos;
450
+ return current.treeId !== next.treeId || current.offset !== next.offset || current.pointType !== next.pointType;
451
+ }
452
+ }
453
+ /*****************************************************************************/
454
+ export function $syncLocalCursorPosition(binding, provider) {
455
+ const awareness = provider.awareness;
456
+ const localState = awareness.getLocalState();
457
+ if (localState === null) {
458
+ return;
459
+ }
460
+ const { anchorPos, focusPos } = localState;
461
+ // Convert Loro cursors back to Lexical selection
462
+ if (anchorPos !== null && focusPos !== null) {
463
+ const selectionInfo = convertLoroSelectionToLexical(anchorPos, focusPos, binding);
464
+ if (selectionInfo) {
465
+ const { anchorKey, anchorOffset, focusKey, focusOffset } = selectionInfo;
466
+ const selection = $getSelection();
467
+ if (!$isRangeSelection(selection)) {
468
+ return;
469
+ }
470
+ // Update the Lexical selection to match the cursor positions
471
+ $setPoint(selection.anchor, anchorKey, anchorOffset);
472
+ $setPoint(selection.focus, focusKey, focusOffset);
473
+ }
474
+ }
475
+ }
@@ -0,0 +1,4 @@
1
+ import { UpdateListenerPayload } from 'lexical';
2
+ import { Binding } from '../Bindings';
3
+ import { Provider } from '../State';
4
+ export declare function syncLexicalToLoro(binding: Binding, provider: Provider, update: UpdateListenerPayload): void;
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the MIT License.
4
+ */
5
+ import { RootNode, ElementNode, TextNode, LineBreakNode, DecoratorNode, $getSelection, $getNodeByKey } from 'lexical';
6
+ import { propagateRootNode } from '../propagators/RootNodePropagator';
7
+ import { propagateLineBreakNode } from '../propagators/LineBreakNodePropagator';
8
+ import { propagateElementNode } from '../propagators/ElementNodePropagator';
9
+ import { propagateTextNode } from '../propagators/TextNodePropagator';
10
+ import { propagateDecoratorNode } from '../propagators/DecoratorNodePropagator';
11
+ import { isClassExtending, generateClientID } from '../utils/Utils';
12
+ import { syncLexicalSelectionToLoro } from './SyncCursors';
13
+ // import { scheduleAsyncCommit } from '../Bindings';
14
+ // import { syncCursorPositions, SyncCursorPositionsFn } from './SyncCursors';
15
+ export function syncLexicalToLoro(binding, provider, update) {
16
+ const { mutatedNodes, prevEditorState, editorState: currEditorState, } = update;
17
+ // Process node mutations if present.
18
+ // NOTE: mutatedNodes is null for selection-only updates (no DOM mutations).
19
+ // See Lexical docs: "Will be null if no DOM was mutated, such as when only
20
+ // the selection changed."
21
+ if (mutatedNodes) {
22
+ const tree = binding.tree;
23
+ // Ensure we have a numeric peerId for TreeID format
24
+ const peerId = generateClientID(binding.doc);
25
+ // Create options object for mutators
26
+ const mutatorOptions = {
27
+ binding,
28
+ tree,
29
+ peerId
30
+ };
31
+ // Process mutations in proper dependency order:
32
+ // 1. RootNode first
33
+ // 2. ElementNodes sorted by tree depth (parents before children)
34
+ // 3. TextNodes, LineBreakNodes, DecoratorNodes (leaf children)
35
+ //
36
+ // Depth sorting is critical: when a table is inserted, the mutations for
37
+ // table, tablerow, tablecell, and paragraph-inside-cell ALL fire in the
38
+ // same update. Without sorting, a tablecell may be propagated before its
39
+ // parent tablerow — causing `getTreeIDByLexicalKey(parent)` to return
40
+ // undefined and the cell to be created at the Loro tree root.
41
+ // Check if any nodes actually mutated (Map could be empty for selection-only changes)
42
+ let hasMutations = false;
43
+ mutatedNodes.forEach((nodeMap) => {
44
+ if (nodeMap.size > 0)
45
+ hasMutations = true;
46
+ });
47
+ if (hasMutations) {
48
+ // Phase 1: Process RootNode mutations
49
+ mutatedNodes.forEach((nodeMap, Klass) => {
50
+ if (isClassExtending(Klass, RootNode)) {
51
+ nodeMap.forEach((mutation, nodeKey) => {
52
+ propagateRootNode(update, mutation, nodeKey, mutatorOptions);
53
+ });
54
+ }
55
+ });
56
+ // Phase 2: Collect all ElementNode mutations, sort by depth, then propagate
57
+ const elementMutations = [];
58
+ mutatedNodes.forEach((nodeMap, Klass) => {
59
+ if (isClassExtending(Klass, ElementNode) && !isClassExtending(Klass, RootNode)) {
60
+ nodeMap.forEach((mutation, nodeKey) => {
61
+ // Compute depth in Lexical tree (root=0, paragraph=1, tablecell=3, etc.)
62
+ let depth = 0;
63
+ currEditorState.read(() => {
64
+ const node = $getNodeByKey(nodeKey);
65
+ if (node) {
66
+ let current = node.getParent();
67
+ while (current) {
68
+ depth++;
69
+ current = current.getParent();
70
+ }
71
+ }
72
+ });
73
+ elementMutations.push({ mutation, nodeKey, depth });
74
+ });
75
+ }
76
+ });
77
+ // Sort by depth ascending → parents (depth 1) are propagated before children (depth 2, 3, …)
78
+ elementMutations.sort((a, b) => a.depth - b.depth);
79
+ for (const { mutation, nodeKey } of elementMutations) {
80
+ propagateElementNode(update, mutation, nodeKey, mutatorOptions);
81
+ }
82
+ // Phase 3: Process leaf children (their parents are now guaranteed to be mapped)
83
+ mutatedNodes.forEach((nodeMap, Klass) => {
84
+ if (isClassExtending(Klass, TextNode)) {
85
+ nodeMap.forEach((mutation, nodeKey) => {
86
+ propagateTextNode(update, mutation, nodeKey, mutatorOptions);
87
+ });
88
+ }
89
+ else if (isClassExtending(Klass, LineBreakNode)) {
90
+ nodeMap.forEach((mutation, nodeKey) => {
91
+ propagateLineBreakNode(update, mutation, nodeKey, mutatorOptions);
92
+ });
93
+ }
94
+ else if (isClassExtending(Klass, DecoratorNode)) {
95
+ nodeMap.forEach((mutation, nodeKey) => {
96
+ propagateDecoratorNode(update, mutation, nodeKey, mutatorOptions);
97
+ });
98
+ }
99
+ });
100
+ // Commit only when there were actual node mutations (not selection-only changes)
101
+ binding.doc.commit({ origin: binding.doc.peerIdStr });
102
+ }
103
+ }
104
+ // Always sync selection/cursor state — even for selection-only changes
105
+ // (e.g. user clicks or drags to select text without editing).
106
+ // This MUST be outside the `if (mutatedNodes)` block because Lexical sets
107
+ // mutatedNodes to null when only the selection changed (no DOM mutations).
108
+ currEditorState.read(() => {
109
+ const selection = $getSelection();
110
+ const prevSelection = prevEditorState._selection;
111
+ syncLexicalSelectionToLoro(binding, provider, prevSelection, selection);
112
+ });
113
+ }
@@ -0,0 +1,5 @@
1
+ import { LoroEventBatch } from 'loro-crdt';
2
+ import { Binding } from '../Bindings';
3
+ import { Provider } from '../State';
4
+ import { SyncCursorPositionsFn } from './SyncCursors';
5
+ export declare function syncLoroToLexical(binding: Binding, provider: Provider, eventBatch: LoroEventBatch, isFromUndoManger: boolean, syncCursorPositionsFn?: SyncCursorPositionsFn): void;