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