@react-x11/components 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +295 -51
  2. package/dist/charts/node.d.ts +1 -1
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +6 -2
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/color-picker/ColorPicker.d.ts.map +1 -1
  7. package/dist/color-picker/ColorPicker.js +43 -33
  8. package/dist/color-picker/ColorPicker.js.map +1 -1
  9. package/dist/embed/client.d.ts +48 -3
  10. package/dist/embed/client.d.ts.map +1 -1
  11. package/dist/embed/client.js +79 -5
  12. package/dist/embed/client.js.map +1 -1
  13. package/dist/embed/index.d.ts +1 -1
  14. package/dist/embed/index.d.ts.map +1 -1
  15. package/dist/embed/index.js +1 -1
  16. package/dist/embed/index.js.map +1 -1
  17. package/dist/html/node.d.ts +3 -1
  18. package/dist/html/node.d.ts.map +1 -1
  19. package/dist/html/node.js +7 -5
  20. package/dist/html/node.js.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
  26. package/dist/internal/markdown/ast.d.ts.map +1 -0
  27. package/dist/internal/markdown/ast.js +22 -0
  28. package/dist/internal/markdown/ast.js.map +1 -0
  29. package/dist/internal/markdown/parse.d.ts.map +1 -0
  30. package/dist/{markdown → internal/markdown}/parse.js +112 -22
  31. package/dist/internal/markdown/parse.js.map +1 -0
  32. package/dist/internal/markdown/stringify.d.ts +4 -0
  33. package/dist/internal/markdown/stringify.d.ts.map +1 -0
  34. package/dist/internal/markdown/stringify.js +684 -0
  35. package/dist/internal/markdown/stringify.js.map +1 -0
  36. package/dist/internal/markdown/tags.d.ts.map +1 -0
  37. package/dist/internal/markdown/tags.js.map +1 -0
  38. package/dist/internal/scroll.d.ts +3 -2
  39. package/dist/internal/scroll.d.ts.map +1 -1
  40. package/dist/internal/scroll.js +33 -15
  41. package/dist/internal/scroll.js.map +1 -1
  42. package/dist/internal/units.d.ts +2 -0
  43. package/dist/internal/units.d.ts.map +1 -0
  44. package/dist/internal/units.js +35 -0
  45. package/dist/internal/units.js.map +1 -0
  46. package/dist/internal/window.d.ts.map +1 -1
  47. package/dist/internal/window.js +4 -2
  48. package/dist/internal/window.js.map +1 -1
  49. package/dist/maps/anchors.d.ts +98 -0
  50. package/dist/maps/anchors.d.ts.map +1 -0
  51. package/dist/maps/anchors.js +531 -0
  52. package/dist/maps/anchors.js.map +1 -0
  53. package/dist/maps/controller.d.ts +283 -0
  54. package/dist/maps/controller.d.ts.map +1 -0
  55. package/dist/maps/controller.js +743 -0
  56. package/dist/maps/controller.js.map +1 -0
  57. package/dist/maps/gl/buckets.d.ts +95 -0
  58. package/dist/maps/gl/buckets.d.ts.map +1 -0
  59. package/dist/maps/gl/buckets.js +362 -0
  60. package/dist/maps/gl/buckets.js.map +1 -0
  61. package/dist/maps/gl/build-worker.d.ts +13 -0
  62. package/dist/maps/gl/build-worker.d.ts.map +1 -0
  63. package/dist/maps/gl/build-worker.js +44 -0
  64. package/dist/maps/gl/build-worker.js.map +1 -0
  65. package/dist/maps/gl/color.d.ts +8 -0
  66. package/dist/maps/gl/color.d.ts.map +1 -0
  67. package/dist/maps/gl/color.js +57 -0
  68. package/dist/maps/gl/color.js.map +1 -0
  69. package/dist/maps/gl/cover.d.ts +51 -0
  70. package/dist/maps/gl/cover.d.ts.map +1 -0
  71. package/dist/maps/gl/cover.js +124 -0
  72. package/dist/maps/gl/cover.js.map +1 -0
  73. package/dist/maps/gl/index.d.ts +3 -0
  74. package/dist/maps/gl/index.d.ts.map +1 -0
  75. package/dist/maps/gl/index.js +20 -0
  76. package/dist/maps/gl/index.js.map +1 -0
  77. package/dist/maps/gl/markers.d.ts +28 -0
  78. package/dist/maps/gl/markers.d.ts.map +1 -0
  79. package/dist/maps/gl/markers.js +60 -0
  80. package/dist/maps/gl/markers.js.map +1 -0
  81. package/dist/maps/gl/overlays.d.ts +68 -0
  82. package/dist/maps/gl/overlays.d.ts.map +1 -0
  83. package/dist/maps/gl/overlays.js +300 -0
  84. package/dist/maps/gl/overlays.js.map +1 -0
  85. package/dist/maps/gl/pane.d.ts +12 -0
  86. package/dist/maps/gl/pane.d.ts.map +1 -0
  87. package/dist/maps/gl/pane.js +37 -0
  88. package/dist/maps/gl/pane.js.map +1 -0
  89. package/dist/maps/gl/placement.d.ts +111 -0
  90. package/dist/maps/gl/placement.d.ts.map +1 -0
  91. package/dist/maps/gl/placement.js +567 -0
  92. package/dist/maps/gl/placement.js.map +1 -0
  93. package/dist/maps/gl/renderer.d.ts +352 -0
  94. package/dist/maps/gl/renderer.d.ts.map +1 -0
  95. package/dist/maps/gl/renderer.js +1127 -0
  96. package/dist/maps/gl/renderer.js.map +1 -0
  97. package/dist/maps/gl/shaders.d.ts +120 -0
  98. package/dist/maps/gl/shaders.d.ts.map +1 -0
  99. package/dist/maps/gl/shaders.js +451 -0
  100. package/dist/maps/gl/shaders.js.map +1 -0
  101. package/dist/maps/gl/store.d.ts +115 -0
  102. package/dist/maps/gl/store.d.ts.map +1 -0
  103. package/dist/maps/gl/store.js +469 -0
  104. package/dist/maps/gl/store.js.map +1 -0
  105. package/dist/maps/gl/text.d.ts +154 -0
  106. package/dist/maps/gl/text.d.ts.map +1 -0
  107. package/dist/maps/gl/text.js +382 -0
  108. package/dist/maps/gl/text.js.map +1 -0
  109. package/dist/maps/gl/view.d.ts +54 -0
  110. package/dist/maps/gl/view.d.ts.map +1 -0
  111. package/dist/maps/gl/view.js +974 -0
  112. package/dist/maps/gl/view.js.map +1 -0
  113. package/dist/maps/index.d.ts +1 -1
  114. package/dist/maps/index.d.ts.map +1 -1
  115. package/dist/maps/index.js +216 -52
  116. package/dist/maps/index.js.map +1 -1
  117. package/dist/maps/labels.d.ts +37 -16
  118. package/dist/maps/labels.d.ts.map +1 -1
  119. package/dist/maps/labels.js +188 -119
  120. package/dist/maps/labels.js.map +1 -1
  121. package/dist/maps/node.d.ts +162 -56
  122. package/dist/maps/node.d.ts.map +1 -1
  123. package/dist/maps/node.js +607 -515
  124. package/dist/maps/node.js.map +1 -1
  125. package/dist/maps/overlay.d.ts +72 -9
  126. package/dist/maps/overlay.d.ts.map +1 -1
  127. package/dist/maps/overlay.js +108 -28
  128. package/dist/maps/overlay.js.map +1 -1
  129. package/dist/maps/paint.d.ts +3 -0
  130. package/dist/maps/paint.d.ts.map +1 -1
  131. package/dist/maps/paint.js.map +1 -1
  132. package/dist/maps/proj.d.ts +14 -0
  133. package/dist/maps/proj.d.ts.map +1 -1
  134. package/dist/maps/proj.js +28 -0
  135. package/dist/maps/proj.js.map +1 -1
  136. package/dist/maps/renderer.d.ts +69 -0
  137. package/dist/maps/renderer.d.ts.map +1 -0
  138. package/dist/maps/renderer.js +66 -0
  139. package/dist/maps/renderer.js.map +1 -0
  140. package/dist/maps/sources.d.ts +25 -3
  141. package/dist/maps/sources.d.ts.map +1 -1
  142. package/dist/maps/sources.js +17 -0
  143. package/dist/maps/sources.js.map +1 -1
  144. package/dist/maps/theme.d.ts +19 -0
  145. package/dist/maps/theme.d.ts.map +1 -0
  146. package/dist/maps/theme.js +44 -0
  147. package/dist/maps/theme.js.map +1 -0
  148. package/dist/maps/tiles.d.ts +117 -17
  149. package/dist/maps/tiles.d.ts.map +1 -1
  150. package/dist/maps/tiles.js +163 -30
  151. package/dist/maps/tiles.js.map +1 -1
  152. package/dist/maps/types.d.ts +188 -20
  153. package/dist/maps/types.d.ts.map +1 -1
  154. package/dist/markdown/expressions.d.ts +1 -1
  155. package/dist/markdown/expressions.d.ts.map +1 -1
  156. package/dist/markdown/expressions.js.map +1 -1
  157. package/dist/markdown/index.d.ts +7 -6
  158. package/dist/markdown/index.d.ts.map +1 -1
  159. package/dist/markdown/index.js +9 -7
  160. package/dist/markdown/index.js.map +1 -1
  161. package/dist/markdown/spans.d.ts +1 -1
  162. package/dist/markdown/spans.d.ts.map +1 -1
  163. package/dist/media-player/index.d.ts +11 -3
  164. package/dist/media-player/index.d.ts.map +1 -1
  165. package/dist/media-player/index.js +13 -2
  166. package/dist/media-player/index.js.map +1 -1
  167. package/dist/rich-text-editor/clipboard.d.ts +23 -0
  168. package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
  169. package/dist/rich-text-editor/clipboard.js +307 -0
  170. package/dist/rich-text-editor/clipboard.js.map +1 -0
  171. package/dist/rich-text-editor/collab.d.ts +32 -0
  172. package/dist/rich-text-editor/collab.d.ts.map +1 -0
  173. package/dist/rich-text-editor/collab.js +55 -0
  174. package/dist/rich-text-editor/collab.js.map +1 -0
  175. package/dist/rich-text-editor/commands.d.ts +50 -0
  176. package/dist/rich-text-editor/commands.d.ts.map +1 -0
  177. package/dist/rich-text-editor/commands.js +376 -0
  178. package/dist/rich-text-editor/commands.js.map +1 -0
  179. package/dist/rich-text-editor/drag.d.ts +68 -0
  180. package/dist/rich-text-editor/drag.d.ts.map +1 -0
  181. package/dist/rich-text-editor/drag.js +148 -0
  182. package/dist/rich-text-editor/drag.js.map +1 -0
  183. package/dist/rich-text-editor/html.d.ts +145 -0
  184. package/dist/rich-text-editor/html.d.ts.map +1 -0
  185. package/dist/rich-text-editor/html.js +516 -0
  186. package/dist/rich-text-editor/html.js.map +1 -0
  187. package/dist/rich-text-editor/index.d.ts +263 -0
  188. package/dist/rich-text-editor/index.d.ts.map +1 -0
  189. package/dist/rich-text-editor/index.js +822 -0
  190. package/dist/rich-text-editor/index.js.map +1 -0
  191. package/dist/rich-text-editor/inline.d.ts +94 -0
  192. package/dist/rich-text-editor/inline.d.ts.map +1 -0
  193. package/dist/rich-text-editor/inline.js +252 -0
  194. package/dist/rich-text-editor/inline.js.map +1 -0
  195. package/dist/rich-text-editor/keymap.d.ts +71 -0
  196. package/dist/rich-text-editor/keymap.d.ts.map +1 -0
  197. package/dist/rich-text-editor/keymap.js +393 -0
  198. package/dist/rich-text-editor/keymap.js.map +1 -0
  199. package/dist/rich-text-editor/keys.d.ts +29 -0
  200. package/dist/rich-text-editor/keys.d.ts.map +1 -0
  201. package/dist/rich-text-editor/keys.js +93 -0
  202. package/dist/rich-text-editor/keys.js.map +1 -0
  203. package/dist/rich-text-editor/look.d.ts +71 -0
  204. package/dist/rich-text-editor/look.d.ts.map +1 -0
  205. package/dist/rich-text-editor/look.js +342 -0
  206. package/dist/rich-text-editor/look.js.map +1 -0
  207. package/dist/rich-text-editor/markdown.d.ts +18 -0
  208. package/dist/rich-text-editor/markdown.d.ts.map +1 -0
  209. package/dist/rich-text-editor/markdown.js +496 -0
  210. package/dist/rich-text-editor/markdown.js.map +1 -0
  211. package/dist/rich-text-editor/nodes.d.ts +135 -0
  212. package/dist/rich-text-editor/nodes.d.ts.map +1 -0
  213. package/dist/rich-text-editor/nodes.js +397 -0
  214. package/dist/rich-text-editor/nodes.js.map +1 -0
  215. package/dist/rich-text-editor/render.d.ts +68 -0
  216. package/dist/rich-text-editor/render.d.ts.map +1 -0
  217. package/dist/rich-text-editor/render.js +500 -0
  218. package/dist/rich-text-editor/render.js.map +1 -0
  219. package/dist/rich-text-editor/schema.d.ts +16 -0
  220. package/dist/rich-text-editor/schema.d.ts.map +1 -0
  221. package/dist/rich-text-editor/schema.js +386 -0
  222. package/dist/rich-text-editor/schema.js.map +1 -0
  223. package/dist/rich-text-editor/store.d.ts +11 -0
  224. package/dist/rich-text-editor/store.d.ts.map +1 -0
  225. package/dist/rich-text-editor/store.js +43 -0
  226. package/dist/rich-text-editor/store.js.map +1 -0
  227. package/dist/rich-text-editor/suggest.d.ts +127 -0
  228. package/dist/rich-text-editor/suggest.d.ts.map +1 -0
  229. package/dist/rich-text-editor/suggest.js +0 -0
  230. package/dist/rich-text-editor/suggest.js.map +1 -0
  231. package/dist/rich-text-editor/tables.d.ts +41 -0
  232. package/dist/rich-text-editor/tables.d.ts.map +1 -0
  233. package/dist/rich-text-editor/tables.js +204 -0
  234. package/dist/rich-text-editor/tables.js.map +1 -0
  235. package/dist/rich-text-editor/toolbar.d.ts +58 -0
  236. package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
  237. package/dist/rich-text-editor/toolbar.js +548 -0
  238. package/dist/rich-text-editor/toolbar.js.map +1 -0
  239. package/dist/rich-text-editor/view.d.ts +368 -0
  240. package/dist/rich-text-editor/view.d.ts.map +1 -0
  241. package/dist/rich-text-editor/view.js +1968 -0
  242. package/dist/rich-text-editor/view.js.map +1 -0
  243. package/dist/rich-text-editor/virtual.d.ts +54 -0
  244. package/dist/rich-text-editor/virtual.d.ts.map +1 -0
  245. package/dist/rich-text-editor/virtual.js +262 -0
  246. package/dist/rich-text-editor/virtual.js.map +1 -0
  247. package/dist/richtext/node.d.ts +25 -2
  248. package/dist/richtext/node.d.ts.map +1 -1
  249. package/dist/richtext/node.js +46 -24
  250. package/dist/richtext/node.js.map +1 -1
  251. package/dist/table/index.d.ts.map +1 -1
  252. package/dist/table/index.js +14 -5
  253. package/dist/table/index.js.map +1 -1
  254. package/dist/terminal/backends.d.ts +4 -1
  255. package/dist/terminal/backends.d.ts.map +1 -1
  256. package/dist/terminal/backends.js.map +1 -1
  257. package/dist/terminal/index.d.ts +18 -7
  258. package/dist/terminal/index.d.ts.map +1 -1
  259. package/dist/terminal/index.js +28 -11
  260. package/dist/terminal/index.js.map +1 -1
  261. package/dist/terminal/vt/node.d.ts +56 -3
  262. package/dist/terminal/vt/node.d.ts.map +1 -1
  263. package/dist/terminal/vt/node.js +106 -26
  264. package/dist/terminal/vt/node.js.map +1 -1
  265. package/dist/terminal/vt/renderer.d.ts +4 -0
  266. package/dist/terminal/vt/renderer.d.ts.map +1 -1
  267. package/dist/terminal/vt/renderer.js +14 -5
  268. package/dist/terminal/vt/renderer.js.map +1 -1
  269. package/dist/tree/index.d.ts.map +1 -1
  270. package/dist/tree/index.js +6 -4
  271. package/dist/tree/index.js.map +1 -1
  272. package/package.json +23 -4
  273. package/src/charts/node.ts +6 -2
  274. package/src/color-picker/ColorPicker.ts +107 -81
  275. package/src/embed/client.ts +102 -8
  276. package/src/embed/index.ts +2 -0
  277. package/src/html/node.ts +7 -5
  278. package/src/index.ts +2 -0
  279. package/src/{markdown → internal/markdown}/ast.ts +24 -7
  280. package/src/{markdown → internal/markdown}/parse.ts +134 -24
  281. package/src/internal/markdown/stringify.ts +772 -0
  282. package/src/internal/scroll.ts +39 -18
  283. package/src/internal/units.ts +34 -0
  284. package/src/internal/window.ts +4 -2
  285. package/src/maps/anchors.ts +607 -0
  286. package/src/maps/controller.ts +999 -0
  287. package/src/maps/gl/buckets.ts +457 -0
  288. package/src/maps/gl/build-worker.ts +55 -0
  289. package/src/maps/gl/color.ts +62 -0
  290. package/src/maps/gl/cover.ts +170 -0
  291. package/src/maps/gl/index.ts +23 -0
  292. package/src/maps/gl/markers.ts +83 -0
  293. package/src/maps/gl/overlays.ts +379 -0
  294. package/src/maps/gl/pane.ts +42 -0
  295. package/src/maps/gl/placement.ts +816 -0
  296. package/src/maps/gl/renderer.ts +1576 -0
  297. package/src/maps/gl/shaders.ts +486 -0
  298. package/src/maps/gl/store.ts +566 -0
  299. package/src/maps/gl/text.ts +495 -0
  300. package/src/maps/gl/view.ts +1224 -0
  301. package/src/maps/index.ts +270 -81
  302. package/src/maps/labels.ts +214 -121
  303. package/src/maps/node.ts +763 -629
  304. package/src/maps/overlay.ts +148 -43
  305. package/src/maps/paint.ts +3 -0
  306. package/src/maps/proj.ts +31 -0
  307. package/src/maps/renderer.ts +155 -0
  308. package/src/maps/sources.ts +38 -3
  309. package/src/maps/theme.ts +51 -0
  310. package/src/maps/tiles.ts +191 -33
  311. package/src/maps/types.ts +188 -20
  312. package/src/markdown/expressions.ts +5 -1
  313. package/src/markdown/index.ts +14 -11
  314. package/src/markdown/spans.ts +1 -1
  315. package/src/media-player/index.ts +18 -4
  316. package/src/rich-text-editor/clipboard.ts +439 -0
  317. package/src/rich-text-editor/collab.ts +91 -0
  318. package/src/rich-text-editor/commands.ts +448 -0
  319. package/src/rich-text-editor/dom-globals.d.ts +23 -0
  320. package/src/rich-text-editor/drag.ts +211 -0
  321. package/src/rich-text-editor/html.ts +619 -0
  322. package/src/rich-text-editor/index.ts +1331 -0
  323. package/src/rich-text-editor/inline.ts +345 -0
  324. package/src/rich-text-editor/keymap.ts +510 -0
  325. package/src/rich-text-editor/keys.ts +119 -0
  326. package/src/rich-text-editor/look.ts +427 -0
  327. package/src/rich-text-editor/markdown.ts +653 -0
  328. package/src/rich-text-editor/nodes.ts +497 -0
  329. package/src/rich-text-editor/render.ts +800 -0
  330. package/src/rich-text-editor/schema.ts +431 -0
  331. package/src/rich-text-editor/store.ts +39 -0
  332. package/src/rich-text-editor/suggest.ts +597 -0
  333. package/src/rich-text-editor/tables.ts +272 -0
  334. package/src/rich-text-editor/toolbar.ts +724 -0
  335. package/src/rich-text-editor/view.ts +2287 -0
  336. package/src/rich-text-editor/virtual.ts +347 -0
  337. package/src/richtext/node.ts +59 -30
  338. package/src/table/index.ts +14 -5
  339. package/src/terminal/backends.ts +4 -1
  340. package/src/terminal/index.ts +43 -15
  341. package/src/terminal/vt/node.ts +128 -29
  342. package/src/terminal/vt/renderer.ts +27 -5
  343. package/src/tree/index.ts +6 -4
  344. package/dist/markdown/ast.d.ts.map +0 -1
  345. package/dist/markdown/ast.js +0 -17
  346. package/dist/markdown/ast.js.map +0 -1
  347. package/dist/markdown/parse.d.ts.map +0 -1
  348. package/dist/markdown/parse.js.map +0 -1
  349. package/dist/markdown/tags.d.ts.map +0 -1
  350. package/dist/markdown/tags.js.map +0 -1
  351. /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
  352. /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
  353. /package/dist/{markdown → internal/markdown}/tags.js +0 -0
  354. /package/src/{markdown → internal/markdown}/tags.ts +0 -0
@@ -0,0 +1,1331 @@
1
+ // <RichTextEditor> — a WYSIWYG editor: ProseMirror's document model and
2
+ // plugin system, drawn and driven by react-x11.
3
+ //
4
+ // The ladder, one element all the way up (docs/prd-rich-text-editor.md):
5
+ //
6
+ // 1. `defaultValue` / `value` + `onChange` — markdown in and out, and a
7
+ // caller never meets ProseMirror;
8
+ // 2. `toolbar`, `placeholder`, `readOnly`, `onSubmit`, `suggestions` — the
9
+ // chrome and the behaviours an app would otherwise wire by hand;
10
+ // 3. `format="html" | "text"`, `markStyles`, `renderImage`, `nodeViews` —
11
+ // what the document is written in, and how its parts look;
12
+ // 4. `plugins`, `editorProps`, `schema` — ProseMirror's own seams, so the
13
+ // ecosystem's plugins and schemas plug in as they are;
14
+ // 5. `state` + `dispatchTransaction` — the app owns the EditorState outright.
15
+ //
16
+ // **Registration happens when this module is evaluated** — the editor's two
17
+ // elements, and `<richtext>` for the toolbar's labels — which is the one side
18
+ // effect the package's tree-shaking contract allows (AGENTS.md).
19
+ //
20
+ // **Not in the barrel.** `src/index.ts` re-exports every other component;
21
+ // this one is imported from `@react-x11/components/rich-text-editor`.
22
+ // ProseMirror's declarations name DOM globals (`dom-globals.d.ts` has the
23
+ // four this repository supplies for itself), and an app importing *anything*
24
+ // from the barrel loads every re-exported module's declarations — so every
25
+ // app would inherit that requirement to use a calendar. The subpath keeps it
26
+ // with the apps that use the editor.
27
+ import React, {
28
+ useCallback,
29
+ useEffect,
30
+ useImperativeHandle,
31
+ useLayoutEffect,
32
+ useMemo,
33
+ useRef,
34
+ useState,
35
+ useSyncExternalStore,
36
+ } from 'react';
37
+ import type { ComponentType, ReactElement, ReactNode, Ref } from 'react';
38
+ import { useApp, useClipboard, useTheme } from 'react-x11';
39
+ import type {
40
+ DragEndEvent,
41
+ DragEvent,
42
+ DragSourceEvent,
43
+ DrawnNode,
44
+ DropEvent,
45
+ FocusEvent,
46
+ KeyboardEvent,
47
+ MouseEvent as X11MouseEvent,
48
+ ScrollableNode,
49
+ } from 'react-x11';
50
+ import { XK_ESCAPE, XK_KP_ENTER, XK_RETURN } from 'react-x11/keysyms';
51
+ import { tint } from 'react-x11/style';
52
+ import type { Style } from 'react-x11/style';
53
+ import { toggleMark } from 'prosemirror-commands';
54
+ import { redo, undo } from 'prosemirror-history';
55
+ import { Fragment, Slice } from 'prosemirror-model';
56
+ import type { Node as PMNode, Schema } from 'prosemirror-model';
57
+ import { EditorState, Selection } from 'prosemirror-state';
58
+ import type { Command, Plugin, Transaction } from 'prosemirror-state';
59
+ import type { EditorProps, EditorView } from 'prosemirror-view';
60
+
61
+ // Loads the module the JSX augmentation at the bottom targets — nothing in
62
+ // `src/` writes JSX, so the build has no other reason to resolve it.
63
+ import type {} from 'react-x11/jsx-runtime';
64
+
65
+ import { hx } from '../internal/hx.js';
66
+ import { registerRichText } from '../richtext/index.js';
67
+ import type { TextRun } from '../richtext/index.js';
68
+ import type { Language } from '../code-language/index.js';
69
+ import {
70
+ isBlockActive,
71
+ isMarkActive,
72
+ markAttrs,
73
+ setLink,
74
+ toggleBlockType,
75
+ toggleList,
76
+ toggleWrap,
77
+ } from './commands.js';
78
+ import { DROP_TYPES, SLICE_TYPE } from './drag.js';
79
+ import type { DropAnswer } from './drag.js';
80
+ import { docFromHTML, htmlFromContent } from './html.js';
81
+ import { defaultPlugins } from './keymap.js';
82
+ import { deriveLook } from './look.js';
83
+ import type { MarkStyle } from './look.js';
84
+ import { docFromText, markdownCodec, textFromDoc } from './markdown.js';
85
+ import type { MarkdownCodec } from './markdown.js';
86
+ import { registerEditorElements, ROOT_ELEMENT, TEXT_ELEMENT } from './nodes.js';
87
+ import { renderBlockRange, renderBlocks } from './render.js';
88
+ import type { ImageInfo, NodeViewProps, RenderContext } from './render.js';
89
+ import type { RunStyle } from './inline.js';
90
+ import { schema as defaultSchema } from './schema.js';
91
+ import {
92
+ acceptSuggestion,
93
+ SUGGESTION_PAGE,
94
+ suggesterFor,
95
+ suggestionState,
96
+ suggestions as suggestionPlugin,
97
+ } from './suggest.js';
98
+ import type { Suggester } from './suggest.js';
99
+ import {
100
+ DEFAULT_TOOLBAR,
101
+ resolveToolbar,
102
+ Toolbar,
103
+ toolbarItems,
104
+ } from './toolbar.js';
105
+ import type { ToolbarEntry } from './toolbar.js';
106
+ import { RichEditorView } from './view.js';
107
+ import type { ViewConfig, ViewProps } from './view.js';
108
+ import { useBlockWindow } from './virtual.js';
109
+
110
+ registerRichText();
111
+ registerEditorElements();
112
+
113
+ const h = React.createElement;
114
+
115
+ /** What a drag out of the editor offers to do: an editable one moves or
116
+ * copies, a read-only one only copies. */
117
+ const MOVE_OR_COPY: Array<'move' | 'copy'> = ['move', 'copy'];
118
+ const COPY_ONLY: Array<'move' | 'copy'> = ['copy'];
119
+
120
+ // --- props -------------------------------------------------------------------
121
+
122
+ /** What `value`, `defaultValue` and `ev.value` are written in. */
123
+ export type EditorFormat = 'markdown' | 'html' | 'text';
124
+
125
+ /** What `onChange` hears. `value` is serialized when it is first read, so a
126
+ * listener that only wants to know *that* something changed pays nothing. */
127
+ export interface RichTextEditorChangeEvent {
128
+ type: 'change';
129
+ /** The document, in `format`. */
130
+ readonly value: string;
131
+ readonly doc: PMNode;
132
+ readonly state: EditorState;
133
+ readonly transactions: readonly Transaction[];
134
+ readonly name: string | undefined;
135
+ readonly target: RichTextEditorHandle;
136
+ }
137
+
138
+ /** What `onSubmit` and `onSelectionChange` hear. */
139
+ export interface RichTextEditorEvent {
140
+ type: 'submit' | 'selectionchange';
141
+ readonly value: string;
142
+ readonly state: EditorState;
143
+ readonly name: string | undefined;
144
+ readonly target: RichTextEditorHandle;
145
+ }
146
+
147
+ /** The editor's imperative surface — the component's `ref`. */
148
+ export interface RichTextEditorHandle {
149
+ /** The EditorView-shaped view: `dispatch`, `state`, `someProp`,
150
+ * `posAtCoords`… — what a ProseMirror plugin or command is handed. */
151
+ readonly view: RichEditorView;
152
+ readonly state: EditorState;
153
+ readonly schema: Schema;
154
+ /** The document in `format` — the editor's own, when not given. */
155
+ getValue(format?: EditorFormat): string;
156
+ /** Replace the document. Not undoable, the way a form reset is not,
157
+ * unless `addToHistory` says so. */
158
+ setValue(
159
+ value: string | PMNode,
160
+ options?: { format?: EditorFormat; addToHistory?: boolean },
161
+ ): void;
162
+ /** Insert content where the selection is — markdown, HTML, text or a node. */
163
+ insertContent(value: string | PMNode, format?: EditorFormat): void;
164
+ /** Run a ProseMirror command against the current state. */
165
+ run(command: Command): boolean;
166
+ /** Could `command` run now? — the question a toolbar greys a button on. */
167
+ can(command: Command): boolean;
168
+ /** A mark (by name) on the selection, or a node (by name, with `attrs`)
169
+ * around it. */
170
+ isActive(name: string, attrs?: Record<string, unknown>): boolean;
171
+ toggleMark(name: string, attrs?: Record<string, unknown>): boolean;
172
+ /** A textblock type — or back to a paragraph when it already is one. */
173
+ setBlock(name: string, attrs?: Record<string, unknown>): boolean;
174
+ toggleList(name: string): boolean;
175
+ toggleWrap(name: string): boolean;
176
+ undo(): boolean;
177
+ redo(): boolean;
178
+ /** Open the link editor on the selection — what Mod-K does. */
179
+ editLink(): void;
180
+ focus(): void;
181
+ blur(): void;
182
+ }
183
+
184
+ /** A document the app hands over as a value, not as ProseMirror state. */
185
+ interface ValueProps {
186
+ /** Uncontrolled initial document: a string in `format`, or a node. */
187
+ defaultValue?: string | PMNode;
188
+ /**
189
+ * The document, as a string in `format`. A value different from the one
190
+ * the editor last reported replaces the document — a reset, not undoable;
191
+ * handing back what `onChange` reported changes nothing.
192
+ */
193
+ value?: string;
194
+ state?: undefined;
195
+ dispatchTransaction?: undefined;
196
+ }
197
+
198
+ /** The top rung: the app owns the ProseMirror state. */
199
+ interface StateProps {
200
+ /** The whole EditorState — schema, plugins and all. The editor's default
201
+ * plugins are the app's to include: `defaultPlugins(schema)`. */
202
+ state: EditorState;
203
+ /** Called with every transaction; the app applies it and passes the new
204
+ * state back (ProseMirror's own contract). */
205
+ dispatchTransaction: (tr: Transaction) => void;
206
+ defaultValue?: never;
207
+ value?: never;
208
+ }
209
+
210
+ interface CommonProps {
211
+ /** What `value`/`defaultValue`/`ev.value` are written in. Default
212
+ * `'markdown'`. */
213
+ format?: EditorFormat;
214
+ onChange?: (ev: RichTextEditorChangeEvent) => void;
215
+ onSelectionChange?: (ev: RichTextEditorEvent) => void;
216
+ /** Mod-Enter — or plain Enter, with `submitOnEnter`. */
217
+ onSubmit?: (ev: RichTextEditorEvent) => void;
218
+ /** Enter submits (Shift+Enter is a new line) — a chat composer. Not in a
219
+ * list or a code block, where Enter goes on editing. */
220
+ submitOnEnter?: boolean;
221
+ /** A link was activated: Mod-click while editing, a click when read-only.
222
+ * The editor never navigates by itself. */
223
+ onLink?: (href: string, ev: X11MouseEvent<DrawnNode>) => void;
224
+ /** Lists that open at a trigger character as its word is typed — `@` for
225
+ * people, `#` for issues, `/` for a block menu. A new array on every
226
+ * render is fine: the plugin reads the latest one. */
227
+ suggestions?: readonly Suggester[];
228
+ placeholder?: string;
229
+ /** Selectable and copyable, not editable. */
230
+ readOnly?: boolean;
231
+ /** Inert, out of the tab order, dimmed. */
232
+ disabled?: boolean;
233
+ autoFocus?: boolean;
234
+ /** Echoed on every event. */
235
+ name?: string;
236
+
237
+ /** The formatting toolbar: `true` for the default set, an array to pick
238
+ * and order (built-in names, `'|'`, items of your own), or a function
239
+ * rendering your own, handed the editor. */
240
+ toolbar?:
241
+ | boolean
242
+ | readonly ToolbarEntry[]
243
+ | ((editor: RichTextEditorHandle) => ReactNode);
244
+
245
+ /** Base text. Default: the theme's `fontSize`, and `sans-serif`. */
246
+ fontSize?: number;
247
+ fontFamily?: string;
248
+ /** Code, inline and fenced. Default `'monospace'`. */
249
+ monoFamily?: string;
250
+ /** How a mark looks, by mark name — over, or instead of, the built-in
251
+ * look. A function is handed the mark and the style under it. */
252
+ markStyles?: Readonly<Record<string, MarkStyle>>;
253
+ /** How a decoration's `class` looks: the one part of a plugin's DOM
254
+ * decoration this editor cannot read off the decoration itself. */
255
+ decorationClasses?: Readonly<Record<string, RunStyle>>;
256
+ /** Syntax colouring in code blocks. Default true. */
257
+ highlight?: boolean;
258
+ /** A `Language` for a fence tag the built-in tokenizers do not cover —
259
+ * `<Markdown>`'s seam, for the same fences. */
260
+ resolveLanguage?: (tag: string) => Language | null;
261
+ /** An image alone in its paragraph, drawn. The editor fetches nothing:
262
+ * without this an image is its alt text. */
263
+ renderImage?: (image: ImageInfo) => ReactNode;
264
+
265
+ /** The document model. Default: GFM (`schema` from this module). */
266
+ schema?: Schema;
267
+ /** A markdown codec for a schema the built-in one cannot read or write. */
268
+ markdown?: MarkdownCodec;
269
+ /** ProseMirror plugins, ahead of the editor's own — so a keymap here wins
270
+ * over the defaults. Give the array a stable identity. */
271
+ plugins?: readonly Plugin[];
272
+ /** The defaults — each can be turned off: undo history, markdown input
273
+ * rules, formatting shortcuts, and (off by default) typographic quotes. */
274
+ history?: boolean;
275
+ inputRules?: boolean;
276
+ keymap?: boolean;
277
+ typography?: boolean;
278
+ /** ProseMirror's view props — `handleKeyDown`, `handlePaste`,
279
+ * `decorations`, `transformPasted`… — without writing a plugin. */
280
+ editorProps?: EditorProps<unknown>;
281
+ /** Custom rendering by node type: a React component, handed the node, its
282
+ * rendered content and a way to update its attributes. */
283
+ nodeViews?: Readonly<Record<string, ComponentType<NodeViewProps>>>;
284
+ /**
285
+ * Draw only the top-level blocks near the viewport, for a long
286
+ * document. `'auto'`, the default, does once there are more than 200
287
+ * of them — so a composer never does. It matters only in an editor
288
+ * given a height to scroll in.
289
+ */
290
+ virtual?: boolean | 'auto';
291
+
292
+ /** The frame: width, height, `flexGrow`, border, background. */
293
+ style?: Style | Style[];
294
+ /** The parts inside it. */
295
+ styles?: { toolbar?: Style; content?: Style };
296
+ ref?: Ref<RichTextEditorHandle | null>;
297
+ onKeyDown?: (ev: KeyboardEvent) => void;
298
+ onMouseDown?: (ev: X11MouseEvent) => void;
299
+ onFocus?: (ev: FocusEvent) => void;
300
+ onBlur?: (ev: FocusEvent) => void;
301
+ 'aria-label'?: string;
302
+ 'data-testname'?: string;
303
+ }
304
+
305
+ export type RichTextEditorProps = CommonProps & (ValueProps | StateProps);
306
+
307
+ // --- formats -----------------------------------------------------------------
308
+
309
+ interface Codec {
310
+ parse(source: string): PMNode;
311
+ serialize(doc: PMNode): string;
312
+ }
313
+
314
+ function codecFor(
315
+ schema: Schema,
316
+ format: EditorFormat,
317
+ markdown?: MarkdownCodec,
318
+ ): Codec {
319
+ switch (format) {
320
+ case 'html':
321
+ return {
322
+ parse: (source) => docFromHTML(schema, source),
323
+ serialize: (doc) => htmlFromContent(schema, doc),
324
+ };
325
+ case 'text':
326
+ return {
327
+ parse: (source) => docFromText(schema, source),
328
+ serialize: textFromDoc,
329
+ };
330
+ default:
331
+ return markdown ?? markdownCodec(schema);
332
+ }
333
+ }
334
+
335
+ function replaceDocument(
336
+ view: RichEditorView,
337
+ doc: PMNode,
338
+ addToHistory: boolean,
339
+ ): void {
340
+ const { state } = view;
341
+ const tr = state.tr.replaceWith(0, state.doc.content.size, doc.content);
342
+ // keep the caret about where it was, inside what is there now
343
+ tr.setSelection(
344
+ Selection.near(
345
+ tr.doc.resolve(Math.min(state.selection.head, tr.doc.content.size)),
346
+ ),
347
+ );
348
+ if (!addToHistory) tr.setMeta('addToHistory', false);
349
+ view.dispatch(tr);
350
+ }
351
+
352
+ function inListOrCode(state: EditorState): boolean {
353
+ const { $from } = state.selection;
354
+ if ($from.parent.type.spec.code) return true;
355
+ for (let d = $from.depth; d > 0; d--) {
356
+ if (/item$/i.test($from.node(d).type.name)) return true;
357
+ }
358
+ return false;
359
+ }
360
+
361
+ function linkAt(state: EditorState, pos: number): string | null {
362
+ const $pos = state.doc.resolve(pos);
363
+ const node =
364
+ $pos.parent.childAfter($pos.parentOffset).node ?? $pos.nodeBefore;
365
+ const link = node?.marks.find((m) => m.type.name === 'link');
366
+ return link && typeof link.attrs.href === 'string' ? link.attrs.href : null;
367
+ }
368
+
369
+ // --- the component -----------------------------------------------------------
370
+
371
+ interface LinkEdit {
372
+ at: { x: number; y: number; width: number; height: number };
373
+ draft: string;
374
+ }
375
+
376
+ /**
377
+ * ```tsx
378
+ * <RichTextEditor
379
+ * defaultValue={note.body}
380
+ * onChange={(ev) => save(ev.value)}
381
+ * placeholder="Write something…"
382
+ * toolbar
383
+ * style={{ flexGrow: 1 }}
384
+ * />
385
+ * ```
386
+ */
387
+ export function RichTextEditor(props: RichTextEditorProps): ReactElement {
388
+ const theme = useTheme() as unknown as Record<string, unknown>;
389
+ const clipboard = useClipboard();
390
+ const {
391
+ placeholder,
392
+ readOnly = false,
393
+ disabled = false,
394
+ autoFocus,
395
+ toolbar,
396
+ style,
397
+ styles,
398
+ } = props;
399
+ const format = props.format ?? 'markdown';
400
+ const schema = props.state?.schema ?? props.schema ?? defaultSchema;
401
+ const codec = useMemo(
402
+ () => codecFor(schema, format, props.markdown),
403
+ [schema, format, props.markdown],
404
+ );
405
+
406
+ // The latest props, for the callbacks the view holds on to.
407
+ const latest = useRef(props);
408
+ latest.current = props;
409
+ const codecRef = useRef(codec);
410
+ codecRef.current = codec;
411
+
412
+ const look = useMemo(
413
+ () =>
414
+ deriveLook(theme, {
415
+ fontSize: props.fontSize,
416
+ fontFamily: props.fontFamily,
417
+ monoFamily: props.monoFamily,
418
+ markStyles: props.markStyles,
419
+ resolveLanguage: props.resolveLanguage,
420
+ highlight: props.highlight,
421
+ dim: disabled,
422
+ }),
423
+ [
424
+ theme,
425
+ props.fontSize,
426
+ props.fontFamily,
427
+ props.monoFamily,
428
+ props.markStyles,
429
+ props.resolveLanguage,
430
+ props.highlight,
431
+ disabled,
432
+ ],
433
+ );
434
+
435
+ // The editor's own plugins, made once per schema and option set — a
436
+ // plugin instance carries its state across a reconfigure, so the undo
437
+ // history survives an app swapping its own plugins.
438
+ const defaults = useMemo(
439
+ () =>
440
+ defaultPlugins(schema, {
441
+ history: props.history,
442
+ inputRules: props.inputRules,
443
+ keymap: props.keymap,
444
+ typography: props.typography,
445
+ }),
446
+ [schema, props.history, props.inputRules, props.keymap, props.typography],
447
+ );
448
+ // The suggestion plugin, made once while there are suggesters: it reads
449
+ // the latest prop through `latest`, so an inline array is not a rebuild —
450
+ // a rebuild would drop the list that is open.
451
+ const suggesting = !!props.suggestions?.length;
452
+ const suggest = useMemo(
453
+ () =>
454
+ suggesting
455
+ ? suggestionPlugin(() => latest.current.suggestions ?? [])
456
+ : null,
457
+ [suggesting],
458
+ );
459
+ const plugins = useMemo(
460
+ () => [
461
+ ...(props.plugins ?? []),
462
+ // ahead of the defaults, so a row takes Enter before the keymap does
463
+ ...(suggest ? [suggest] : []),
464
+ ...defaults,
465
+ ],
466
+ [props.plugins, suggest, defaults],
467
+ );
468
+
469
+ const listeners = useRef(new Set<() => void>());
470
+ /** The value last reported through `onChange`, or accepted from `value`. */
471
+ const reported = useRef<string | null>(null);
472
+ const handleRef = useRef<RichTextEditorHandle | null>(null);
473
+ const rootRef = useRef<DrawnNode | null>(null);
474
+ const [linkEdit, setLinkEdit] = useState<LinkEdit | null>(null);
475
+ // A suggestion list shows only while the editor has focus.
476
+ const [focused, setFocused] = useState(false);
477
+
478
+ // The trigger being typed is drawn in the accent colour, unless the app
479
+ // says what the `suggestion` class looks like. Memoized: a new object
480
+ // redraws every block's decorations (`configure`).
481
+ const decorationClasses = useMemo(
482
+ () => ({ suggestion: { color: look.accent }, ...props.decorationClasses }),
483
+ [look.accent, props.decorationClasses],
484
+ );
485
+
486
+ const config: ViewConfig = {
487
+ onRender: () => {},
488
+ onUpdate: (prev, trs) => onUpdate(prev, trs),
489
+ onFocusChange: setFocused,
490
+ clipboard,
491
+ ...(placeholder ? { placeholder } : null),
492
+ colors: {
493
+ selection: look.selection,
494
+ selectionBlurred: look.selectionBlurred,
495
+ caret: look.caret,
496
+ placeholder: look.muted,
497
+ preedit: look.text,
498
+ },
499
+ decorationClasses,
500
+ };
501
+
502
+ const viewRef = useRef<RichEditorView | null>(null);
503
+ if (!viewRef.current) {
504
+ let state = props.state;
505
+ if (!state) {
506
+ const initial = props.value ?? props.defaultValue;
507
+ if (typeof initial === 'string') reported.current = initial;
508
+ const doc =
509
+ initial === undefined
510
+ ? undefined
511
+ : typeof initial === 'string'
512
+ ? codec.parse(initial)
513
+ : initial;
514
+ state = EditorState.create({ schema, doc, plugins });
515
+ }
516
+ const viewProps: ViewProps = {
517
+ ...props.editorProps,
518
+ editable: () => !latest.current.readOnly && !latest.current.disabled,
519
+ handleClick: (view, pos, event) => {
520
+ const own = latest.current.editorProps?.handleClick;
521
+ if (own?.call(undefined, view, pos, event)) return true;
522
+ return openLinkAt(pos, event as unknown as X11MouseEvent<DrawnNode>);
523
+ },
524
+ ...(props.dispatchTransaction
525
+ ? {
526
+ dispatchTransaction: (tr: Transaction) =>
527
+ latest.current.dispatchTransaction?.(tr),
528
+ }
529
+ : null),
530
+ };
531
+ viewRef.current = new RichEditorView(state, viewProps, config);
532
+ }
533
+ const view = viewRef.current;
534
+
535
+ // Every state change re-renders this component; the document itself only
536
+ // re-renders the blocks whose nodes changed (./render.ts).
537
+ const subscribe = useCallback((fn: () => void) => {
538
+ listeners.current.add(fn);
539
+ return () => {
540
+ listeners.current.delete(fn);
541
+ };
542
+ }, []);
543
+ const state = useSyncExternalStore(
544
+ subscribe,
545
+ () => view.state,
546
+ () => view.state,
547
+ );
548
+
549
+ function event(type: RichTextEditorEvent['type']): RichTextEditorEvent {
550
+ const at = view.state;
551
+ let cached: string | null = null;
552
+ return {
553
+ type,
554
+ get value() {
555
+ return (cached ??= codecRef.current.serialize(at.doc));
556
+ },
557
+ state: at,
558
+ name: latest.current.name,
559
+ target: handleRef.current!,
560
+ };
561
+ }
562
+
563
+ function onUpdate(prev: EditorState, trs: readonly Transaction[]): void {
564
+ for (const fn of [...listeners.current]) fn();
565
+ const now = view.state;
566
+ const p = latest.current;
567
+ if (prev.doc !== now.doc && p.onChange) {
568
+ let cached: string | null = null;
569
+ p.onChange({
570
+ type: 'change',
571
+ get value() {
572
+ if (cached === null) {
573
+ cached = codecRef.current.serialize(now.doc);
574
+ reported.current = cached;
575
+ }
576
+ return cached;
577
+ },
578
+ doc: now.doc,
579
+ state: now,
580
+ transactions: trs,
581
+ name: p.name,
582
+ target: handleRef.current!,
583
+ });
584
+ }
585
+ if (!prev.selection.eq(now.selection))
586
+ p.onSelectionChange?.(event('selectionchange'));
587
+ }
588
+
589
+ function openLinkAt(pos: number, ev: X11MouseEvent<DrawnNode>): boolean {
590
+ const p = latest.current;
591
+ if (!p.onLink) return false;
592
+ const editing = !p.readOnly && !p.disabled;
593
+ // editing, a plain click places the caret; Mod-click follows
594
+ if (editing && !(ev.ctrlKey || ev.metaKey)) return false;
595
+ const href = linkAt(view.state, pos);
596
+ if (!href) return false;
597
+ p.onLink(href, ev);
598
+ return true;
599
+ }
600
+
601
+ const editLink = useCallback((): void => {
602
+ const root = rootRef.current;
603
+ const frame = root?.getClientRects()[0];
604
+ if (!root || !frame || !view.state.schema.marks.link) return;
605
+ const { from, to } = view.state.selection;
606
+ const a = view.coordsAtPos(from);
607
+ const b = view.coordsAtPos(to);
608
+ const attrs = markAttrs(view.state, view.state.schema.marks.link);
609
+ setLinkEdit({
610
+ at: {
611
+ x: a.left - frame.x,
612
+ y: Math.min(a.top, b.top) - frame.y,
613
+ width: Math.max(1, (a.top === b.top ? b.right : a.left) - a.left),
614
+ height: Math.max(a.bottom, b.bottom) - Math.min(a.top, b.top),
615
+ },
616
+ draft: typeof attrs?.href === 'string' ? attrs.href : '',
617
+ });
618
+ }, [view]);
619
+
620
+ const handle = useMemo<RichTextEditorHandle>(() => {
621
+ const nodeType = (name: string) => view.state.schema.nodes[name];
622
+ const markType = (name: string) => view.state.schema.marks[name];
623
+ const itemType = () =>
624
+ view.state.schema.nodes.list_item ?? view.state.schema.nodes.listItem;
625
+ return {
626
+ get view() {
627
+ return view;
628
+ },
629
+ get state() {
630
+ return view.state;
631
+ },
632
+ get schema() {
633
+ return view.state.schema;
634
+ },
635
+ getValue(fmt) {
636
+ const c = fmt
637
+ ? codecFor(view.state.schema, fmt, latest.current.markdown)
638
+ : codecRef.current;
639
+ return c.serialize(view.state.doc);
640
+ },
641
+ setValue(value, options = {}) {
642
+ const c = options.format
643
+ ? codecFor(view.state.schema, options.format, latest.current.markdown)
644
+ : codecRef.current;
645
+ const doc = typeof value === 'string' ? c.parse(value) : value;
646
+ if (typeof value === 'string' && !options.format)
647
+ reported.current = value;
648
+ replaceDocument(view, doc, options.addToHistory ?? false);
649
+ },
650
+ insertContent(value, fmt) {
651
+ const doc =
652
+ typeof value === 'string'
653
+ ? (fmt
654
+ ? codecFor(view.state.schema, fmt, latest.current.markdown)
655
+ : codecRef.current
656
+ ).parse(value)
657
+ : value;
658
+ // a whole document goes in as its blocks, open at both ends so a
659
+ // single paragraph joins the one the caret is in
660
+ const content =
661
+ doc.type === view.state.schema.topNodeType
662
+ ? doc.content
663
+ : Fragment.from(doc);
664
+ view.dispatch(
665
+ view.state.tr
666
+ .replaceSelection(Slice.maxOpen(content))
667
+ .scrollIntoView(),
668
+ );
669
+ },
670
+ run: (command) => view.run(command),
671
+ can: (command) => command(view.state, undefined, view.asEditorView),
672
+ isActive(name, attrs) {
673
+ const mark = markType(name);
674
+ if (mark) return isMarkActive(view.state, mark);
675
+ const node = nodeType(name);
676
+ return node ? isBlockActive(view.state, node, attrs) : false;
677
+ },
678
+ toggleMark(name, attrs) {
679
+ const type = markType(name);
680
+ return !!type && view.run(toggleMark(type, attrs));
681
+ },
682
+ setBlock(name, attrs) {
683
+ const type = nodeType(name);
684
+ return !!type && view.run(toggleBlockType(type, attrs));
685
+ },
686
+ toggleList(name) {
687
+ const type = nodeType(name);
688
+ const item = itemType();
689
+ return !!type && !!item && view.run(toggleList(type, item));
690
+ },
691
+ toggleWrap(name) {
692
+ const type = nodeType(name);
693
+ return !!type && view.run(toggleWrap(type));
694
+ },
695
+ undo: () => view.run(undo),
696
+ redo: () => view.run(redo),
697
+ editLink,
698
+ focus: () => view.focus(),
699
+ blur: () => {
700
+ (view.dom as unknown as { blur?: () => void } | null)?.blur?.();
701
+ },
702
+ };
703
+ }, [view, editLink]);
704
+ handleRef.current = handle;
705
+ useImperativeHandle(props.ref, () => handle, [handle]);
706
+
707
+ // The component's half of the view, refreshed every render; the view
708
+ // acts only on what changed.
709
+ useLayoutEffect(() => {
710
+ view.configure(config);
711
+ });
712
+
713
+ // An app-owned state, handed back after each transaction.
714
+ useLayoutEffect(() => {
715
+ if (props.state && props.state !== view.state)
716
+ view.updateState(props.state);
717
+ }, [props.state, view]);
718
+
719
+ // A controlled value: anything but what the editor last reported replaces
720
+ // the document.
721
+ useLayoutEffect(() => {
722
+ const value = props.value;
723
+ if (value === undefined || props.state || value === reported.current)
724
+ return;
725
+ reported.current = value;
726
+ replaceDocument(view, codec.parse(value), false);
727
+ }, [props.value, props.state, codec, view]);
728
+
729
+ // New plugins (the app's, or the defaults' options): reconfigure, keeping
730
+ // every plugin instance that is still there — and its state.
731
+ const configured = useRef(plugins);
732
+ useLayoutEffect(() => {
733
+ if (props.state || plugins === configured.current) return;
734
+ configured.current = plugins;
735
+ view.updateState(view.state.reconfigure({ plugins }));
736
+ }, [plugins, props.state, view]);
737
+
738
+ const editorProps = props.editorProps;
739
+ const firstProps = useRef(editorProps);
740
+ useLayoutEffect(() => {
741
+ if (editorProps === firstProps.current) return;
742
+ firstProps.current = editorProps;
743
+ view.setProps({
744
+ ...editorProps,
745
+ editable: view.props.editable,
746
+ handleClick: view.props.handleClick,
747
+ });
748
+ }, [editorProps, view]);
749
+
750
+ // the plugins' views, now that the root element exists for them to find
751
+ useLayoutEffect(() => {
752
+ view.mountPluginViews();
753
+ }, [view]);
754
+
755
+ useEffect(() => () => view.destroy(), [view]);
756
+
757
+ const editable = !readOnly && !disabled;
758
+ // what a table measures its columns with; none on the mock backend
759
+ const fonts =
760
+ (useApp() as { fonts?: RenderContext['fonts'] } | null)?.fonts ?? null;
761
+ const ctx = useMemo<RenderContext>(
762
+ () => ({
763
+ view,
764
+ look,
765
+ editable,
766
+ fonts,
767
+ ...(props.nodeViews ? { nodeViews: props.nodeViews } : null),
768
+ ...(props.renderImage ? { renderImage: props.renderImage } : null),
769
+ }),
770
+ [view, look, editable, fonts, props.nodeViews, props.renderImage],
771
+ );
772
+ // A long document draws only the blocks near the viewport, in a window
773
+ // the view asks about when what it needs is not drawn (./virtual.ts).
774
+ const windowed = useBlockWindow({
775
+ doc: state.doc,
776
+ keys: view.keys,
777
+ virtual: props.virtual ?? 'auto',
778
+ estimate: Math.round(look.size * 1.5) + look.blockGap,
779
+ gap: look.blockGap,
780
+ });
781
+ const { virtualizing, first, last, register } = windowed;
782
+ const blocks = useMemo(
783
+ () =>
784
+ virtualizing
785
+ ? renderBlockRange(state.doc, first, last, ctx, register)
786
+ : renderBlocks(state.doc, 0, ctx),
787
+ [state.doc, ctx, virtualizing, first, last, register],
788
+ );
789
+ useLayoutEffect(() => {
790
+ view.attachWindow(virtualizing ? windowed.window : null);
791
+ }, [view, virtualizing, windowed.window]);
792
+
793
+ // --- input ---------------------------------------------------------------
794
+
795
+ const onKeyDown = (ev: KeyboardEvent): void => {
796
+ const p = latest.current;
797
+ p.onKeyDown?.(ev);
798
+ if (ev.defaultPrevented || p.disabled) return;
799
+ const mac = view.primaryModifier === 'meta';
800
+ const primary = mac ? ev.metaKey : ev.ctrlKey;
801
+ const k = ev.keysym;
802
+ if ((k === XK_RETURN || k === XK_KP_ENTER) && p.onSubmit) {
803
+ const plain = !ev.shiftKey && !primary && !ev.altKey;
804
+ // a plain Enter while a suggestion list shows rows takes a row: it is
805
+ // the list's plugin's, a step further on
806
+ const listing =
807
+ plain && editable && !!suggestionState(view.state)?.items?.length;
808
+ const submit =
809
+ !listing &&
810
+ (p.submitOnEnter
811
+ ? plain && !inListOrCode(view.state)
812
+ : primary && !ev.shiftKey);
813
+ if (submit) {
814
+ ev.preventDefault();
815
+ p.onSubmit(event('submit'));
816
+ return;
817
+ }
818
+ }
819
+ if (
820
+ primary &&
821
+ !ev.altKey &&
822
+ !ev.shiftKey &&
823
+ k === 0x6b /* k */ &&
824
+ editable
825
+ ) {
826
+ ev.preventDefault();
827
+ editLink();
828
+ return;
829
+ }
830
+ if (k === XK_ESCAPE && linkEdit) setLinkEdit(null);
831
+ };
832
+
833
+ const onMouseDown = (ev: X11MouseEvent): void => {
834
+ latest.current.onMouseDown?.(ev);
835
+ if (ev.defaultPrevented || latest.current.disabled) return;
836
+ if (view.mouseDown(ev)) ev.preventDefault();
837
+ };
838
+
839
+ const setRoot = useCallback((node: unknown) => {
840
+ rootRef.current = (node as DrawnNode | null) ?? null;
841
+ }, []);
842
+ const pane = windowed.box;
843
+ const setScroller = useCallback(
844
+ (node: unknown) => {
845
+ pane.current = (node as ScrollableNode | null) ?? null;
846
+ view.attachScroller(pane.current);
847
+ },
848
+ [view, pane],
849
+ );
850
+
851
+ // What a drag out of the editor offers, resolved when a drop asks for it:
852
+ // a copy's text and HTML, and the slice itself for a drop in this app.
853
+ const dragData = useMemo(
854
+ () => ({
855
+ 'text/plain': () => view.dragText(),
856
+ 'text/html': () => view.dragHTML(),
857
+ [SLICE_TYPE]: () => view.dragPayload(),
858
+ }),
859
+ [view],
860
+ );
861
+
862
+ // --- chrome --------------------------------------------------------------
863
+
864
+ let chrome: ReactNode = null;
865
+ if (typeof toolbar === 'function') {
866
+ chrome = toolbar(handle);
867
+ } else if (toolbar) {
868
+ const items = toolbarItems(schema, {
869
+ link: () => {
870
+ editLink();
871
+ return true;
872
+ },
873
+ });
874
+ chrome = h(Toolbar, {
875
+ entries: resolveToolbar(
876
+ toolbar === true ? DEFAULT_TOOLBAR : toolbar,
877
+ items,
878
+ ),
879
+ state,
880
+ run: (command: Command) => {
881
+ view.run(command);
882
+ view.focus();
883
+ },
884
+ look,
885
+ disabled: !editable,
886
+ ...(styles?.toolbar ? { style: styles.toolbar } : null),
887
+ });
888
+ }
889
+
890
+ const closeLink = (): void => {
891
+ setLinkEdit(null);
892
+ view.focus();
893
+ };
894
+ const applyLink = (href: string): void => {
895
+ const target = href.trim();
896
+ view.run(setLink(target === '' ? null : target));
897
+ closeLink();
898
+ };
899
+ const linkPopup =
900
+ linkEdit &&
901
+ hx(
902
+ 'popup',
903
+ {
904
+ theme: theme as Record<string, string | number>,
905
+ anchor: {
906
+ to: rootRef as unknown as React.RefObject<DrawnNode | null>,
907
+ at: linkEdit.at,
908
+ placement: 'bottom',
909
+ },
910
+ onDismiss: closeLink,
911
+ },
912
+ hx(
913
+ 'box',
914
+ {
915
+ style: {
916
+ flexDirection: 'row',
917
+ alignItems: 'center',
918
+ gap: 6,
919
+ padding: 6,
920
+ borderWidth: 1,
921
+ borderColor: look.border,
922
+ borderRadius: look.radius,
923
+ backgroundColor: look.surface,
924
+ },
925
+ },
926
+ hx('textinput', {
927
+ value: linkEdit.draft,
928
+ placeholder: 'https://',
929
+ autoFocus: true,
930
+ style: { width: 260 },
931
+ onChange: (ev) =>
932
+ setLinkEdit((cur) => (cur ? { ...cur, draft: ev.value } : cur)),
933
+ onSubmit: (ev) => applyLink(ev.value),
934
+ onKeyDown: (ev) => {
935
+ if (ev.keysym === XK_ESCAPE) {
936
+ ev.preventDefault();
937
+ closeLink();
938
+ }
939
+ },
940
+ }),
941
+ ),
942
+ );
943
+
944
+ // The suggestion list, drawn from the plugin's state and nothing else
945
+ // (./suggest.ts) — so an app that owns the EditorState gets it too. It hangs
946
+ // off the textblock the trigger is in, at the trigger, so it follows the
947
+ // text as the document scrolls and unmaps while the text is out of view.
948
+ // It never takes focus: a press on a row takes the row, and the caret
949
+ // stays in the editor.
950
+ const suggestion = suggestionState(state);
951
+ const firstRow = useRef(0);
952
+ const rowsShown = focused && editable && !!suggestion?.items?.length;
953
+ const anchor =
954
+ rowsShown && suggestion ? view.anchorAt(suggestion.from) : null;
955
+ let suggestionPopup: ReactNode = null;
956
+ if (suggestion?.items && anchor) {
957
+ const { items, selected } = suggestion;
958
+ // the window of rows moves only as far as keeps the highlight in it
959
+ let first = Math.min(
960
+ firstRow.current,
961
+ Math.max(0, items.length - SUGGESTION_PAGE),
962
+ );
963
+ if (selected < first) first = selected;
964
+ if (selected >= first + SUGGESTION_PAGE)
965
+ first = selected - SUGGESTION_PAGE + 1;
966
+ firstRow.current = first;
967
+ // a suggester may draw its rows itself; the row box — the highlight
968
+ // behind it, the press on it — stays the editor's
969
+ const custom = suggesterFor(state)?.renderItem;
970
+ const rowHeight = Math.round(look.size + 12);
971
+ const rows = items.slice(first, first + SUGGESTION_PAGE).map((item, i) => {
972
+ const index = first + i;
973
+ const on = index === selected;
974
+ if (custom) {
975
+ return hx(
976
+ 'box',
977
+ {
978
+ key: `${index} ${item.label}`,
979
+ style: {
980
+ flexDirection: 'row',
981
+ alignItems: 'center',
982
+ minHeight: rowHeight,
983
+ paddingLeft: 8,
984
+ paddingRight: 8,
985
+ borderRadius: Math.max(0, look.radius - 2),
986
+ backgroundColor: on ? look.accent : 'transparent',
987
+ ...(on
988
+ ? null
989
+ : { ':hover': { backgroundColor: tint(look.text, 0.08) } }),
990
+ },
991
+ onMouseDown: (ev: X11MouseEvent) => {
992
+ ev.preventDefault();
993
+ view.run(acceptSuggestion(index));
994
+ },
995
+ },
996
+ custom(item, { selected: on, query: suggestion.query }),
997
+ );
998
+ }
999
+ return hx(
1000
+ 'box',
1001
+ {
1002
+ key: `${index} ${item.label}`,
1003
+ style: {
1004
+ flexDirection: 'row',
1005
+ alignItems: 'center',
1006
+ gap: 12,
1007
+ height: rowHeight,
1008
+ paddingLeft: 8,
1009
+ paddingRight: 8,
1010
+ borderRadius: Math.max(0, look.radius - 2),
1011
+ backgroundColor: on ? look.accent : 'transparent',
1012
+ ...(on
1013
+ ? null
1014
+ : { ':hover': { backgroundColor: tint(look.text, 0.08) } }),
1015
+ },
1016
+ onMouseDown: (ev: X11MouseEvent) => {
1017
+ ev.preventDefault();
1018
+ view.run(acceptSuggestion(index));
1019
+ },
1020
+ },
1021
+ hx(
1022
+ 'text',
1023
+ {
1024
+ style: {
1025
+ flexGrow: 1,
1026
+ fontFamily: look.family,
1027
+ fontSize: look.size,
1028
+ color: on ? look.accentText : look.text,
1029
+ },
1030
+ },
1031
+ item.label,
1032
+ ),
1033
+ item.detail
1034
+ ? hx(
1035
+ 'text',
1036
+ {
1037
+ style: {
1038
+ fontFamily: look.family,
1039
+ fontSize: Math.max(10, look.size - 2),
1040
+ color: on ? look.accentText : look.muted,
1041
+ },
1042
+ },
1043
+ item.detail,
1044
+ )
1045
+ : null,
1046
+ );
1047
+ });
1048
+ suggestionPopup = hx(
1049
+ 'popup',
1050
+ {
1051
+ theme: theme as Record<string, string | number>,
1052
+ anchor: { to: anchor.node, at: anchor.at, placement: 'bottom' },
1053
+ minWidth: 200,
1054
+ maxWidth: 420,
1055
+ },
1056
+ hx(
1057
+ 'box',
1058
+ {
1059
+ style: {
1060
+ flexGrow: 1,
1061
+ flexDirection: 'column',
1062
+ padding: 4,
1063
+ borderWidth: 1,
1064
+ borderColor: look.border,
1065
+ borderRadius: look.radius,
1066
+ backgroundColor: look.surface,
1067
+ },
1068
+ },
1069
+ ...rows,
1070
+ ),
1071
+ );
1072
+ } else {
1073
+ firstRow.current = 0;
1074
+ }
1075
+
1076
+ const frame: Style = {
1077
+ flexDirection: 'column',
1078
+ borderWidth: 1,
1079
+ borderColor: look.border,
1080
+ borderRadius: look.radius,
1081
+ backgroundColor: look.background,
1082
+ overflow: 'hidden',
1083
+ ...(editable
1084
+ ? {
1085
+ ':focus-within': {
1086
+ borderColor: String(theme.borderFocus ?? look.accent),
1087
+ },
1088
+ }
1089
+ : null),
1090
+ };
1091
+
1092
+ return h(
1093
+ ROOT_ELEMENT,
1094
+ {
1095
+ ref: setRoot,
1096
+ host: view,
1097
+ focusable: !disabled,
1098
+ disabled,
1099
+ autoFocus,
1100
+ 'aria-label': props['aria-label'] ?? placeholder,
1101
+ 'data-testname': props['data-testname'],
1102
+ style: style
1103
+ ? [frame, ...(Array.isArray(style) ? style : [style])]
1104
+ : frame,
1105
+ onKeyDown,
1106
+ onMouseDown,
1107
+ onMouseMove: (ev: X11MouseEvent) => {
1108
+ if (view.mouseMove(ev)) ev.preventDefault();
1109
+ },
1110
+ onMouseUp: () => view.mouseUp(),
1111
+ onContextMenu: (ev: X11MouseEvent) => {
1112
+ if (ev.defaultPrevented || latest.current.disabled) return;
1113
+ view.contextMenu(ev);
1114
+ ev.preventDefault();
1115
+ },
1116
+ // drag and drop (./drag.ts): the selection drags out, and a drop is
1117
+ // read like a paste. A press on the selection is left to core, which
1118
+ // arms the drag here; `onDragStart` cancels one that is not the
1119
+ // selection's, and leaves a drag of something inside alone.
1120
+ draggable: !disabled,
1121
+ dragData,
1122
+ dragActions: editable ? MOVE_OR_COPY : COPY_ONLY,
1123
+ onDragStart: (ev: DragSourceEvent) => {
1124
+ if (ev.target !== rootRef.current) return;
1125
+ if (!view.dragStart()) ev.preventDefault();
1126
+ },
1127
+ onDrag: (ev: DragSourceEvent) => {
1128
+ if (ev.target === rootRef.current)
1129
+ view.dragMoved(ev.ctrlKey, ev.altKey);
1130
+ },
1131
+ onDragEnd: (ev: DragEndEvent) => {
1132
+ if (ev.target === rootRef.current) view.dragEnd(ev.action, ev.dropped);
1133
+ },
1134
+ ...(editable
1135
+ ? {
1136
+ dropAccept: DROP_TYPES,
1137
+ onDragOver: (ev: DragEvent) => {
1138
+ if (!view.dragOver(ev.x, ev.y)) ev.reject();
1139
+ },
1140
+ onDragLeave: () => view.dragLeave(),
1141
+ onDrop: (ev: DropEvent) => {
1142
+ const settle = (answer: DropAnswer): void => {
1143
+ if (answer) ev.accept(answer);
1144
+ else ev.reject();
1145
+ };
1146
+ const answer = view.drop(ev);
1147
+ if (answer instanceof Promise) return answer.then(settle);
1148
+ settle(answer);
1149
+ return undefined;
1150
+ },
1151
+ }
1152
+ : null),
1153
+ onFocus: props.onFocus,
1154
+ onBlur: props.onBlur,
1155
+ },
1156
+ chrome,
1157
+ hx(
1158
+ 'box',
1159
+ {
1160
+ ref: setScroller,
1161
+ // a press inside focuses the editor, not the viewport it scrolls in
1162
+ focusable: false,
1163
+ // `flexBasis: 'auto'` is load-bearing: a scroll box that grows
1164
+ // takes a zero basis, so an editor nobody gave a height collapsed
1165
+ // to its border. With it the viewport is its content's height — a
1166
+ // composer grows as it is typed in, up to the frame's `maxHeight`,
1167
+ // and past that it shrinks to fit and scrolls; given a height or a
1168
+ // `flexGrow`, it fills.
1169
+ style: {
1170
+ overflow: 'scroll',
1171
+ flexGrow: 1,
1172
+ flexShrink: 1,
1173
+ flexBasis: 'auto',
1174
+ flexDirection: 'column',
1175
+ },
1176
+ ...(virtualizing
1177
+ ? { onViewport: windowed.onViewport, onScroll: windowed.onScroll }
1178
+ : null),
1179
+ },
1180
+ hx(
1181
+ 'box',
1182
+ {
1183
+ style: {
1184
+ flexDirection: 'column',
1185
+ gap: look.blockGap,
1186
+ padding: Math.round(look.size * 0.85),
1187
+ flexGrow: 1,
1188
+ ...styles?.content,
1189
+ },
1190
+ },
1191
+ // the blocks outside a long document's window, as space
1192
+ windowed.above > 0
1193
+ ? hx('box', {
1194
+ key: 'spacer:before',
1195
+ style: { flexShrink: 0, height: windowed.above },
1196
+ })
1197
+ : null,
1198
+ ...blocks,
1199
+ windowed.below > 0
1200
+ ? hx('box', {
1201
+ key: 'spacer:after',
1202
+ style: { flexShrink: 0, height: windowed.below },
1203
+ })
1204
+ : null,
1205
+ ),
1206
+ ),
1207
+ linkPopup,
1208
+ suggestionPopup,
1209
+ );
1210
+ }
1211
+
1212
+ // --- the elements, for JSX ---------------------------------------------------
1213
+
1214
+ /** The raw `<richeditor>`'s props. Structural on purpose — see the note on
1215
+ * `CodeEditorElementProps` in `../code-editor/index.ts`. */
1216
+ export interface RichEditorElementProps {
1217
+ host?: unknown;
1218
+ focusable?: boolean;
1219
+ autoFocus?: boolean;
1220
+ disabled?: boolean;
1221
+ style?: Style | Style[];
1222
+ ref?: Ref<unknown>;
1223
+ children?: ReactNode;
1224
+ onKeyDown?: (ev: KeyboardEvent) => void;
1225
+ onMouseDown?: (ev: X11MouseEvent) => void;
1226
+ onMouseMove?: (ev: X11MouseEvent) => void;
1227
+ onMouseUp?: (ev: X11MouseEvent) => void;
1228
+ onContextMenu?: (ev: X11MouseEvent) => void;
1229
+ onFocus?: (ev: FocusEvent) => void;
1230
+ onBlur?: (ev: FocusEvent) => void;
1231
+ }
1232
+
1233
+ /** The raw `<richeditortext>`'s props. */
1234
+ export interface RichEditorTextElementProps {
1235
+ runs: TextRun[];
1236
+ map?: unknown;
1237
+ node?: unknown;
1238
+ blockKey?: string;
1239
+ host?: unknown;
1240
+ wrap?: boolean;
1241
+ style?: Style | Style[];
1242
+ ref?: Ref<unknown>;
1243
+ }
1244
+
1245
+ declare module 'react-x11/jsx-runtime' {
1246
+ namespace JSX {
1247
+ interface IntrinsicElements {
1248
+ richeditor: RichEditorElementProps;
1249
+ richeditortext: RichEditorTextElementProps;
1250
+ }
1251
+ }
1252
+ }
1253
+
1254
+ // --- the rest of the surface -------------------------------------------------
1255
+
1256
+ export {
1257
+ ROOT_ELEMENT as RICH_EDITOR_ELEMENT,
1258
+ TEXT_ELEMENT as RICH_EDITOR_TEXT_ELEMENT,
1259
+ };
1260
+ export { RichEditorView };
1261
+ export type { EditorView };
1262
+ export type { ViewProps, BlockDecorations, MoveUnit } from './view.js';
1263
+ export { schema, nodes, marks } from './schema.js';
1264
+ export { defaultPlugins, editingKeymap, markdownInputRules } from './keymap.js';
1265
+ // What a plugin's `handleKeyDown` is really handed: ProseMirror's
1266
+ // declarations say DOM `KeyboardEvent`, which this platform has no values of.
1267
+ export type { DefaultPluginOptions, DomKeyEvent } from './keymap.js';
1268
+ export {
1269
+ dedentCode,
1270
+ goToCell,
1271
+ indentCode,
1272
+ insertHorizontalRule,
1273
+ isBlockActive,
1274
+ isMarkActive,
1275
+ markAttrs,
1276
+ setLink,
1277
+ setTaskChecked,
1278
+ splitItem,
1279
+ toggleBlockType,
1280
+ toggleList,
1281
+ toggleTaskList,
1282
+ toggleWrap,
1283
+ } from './commands.js';
1284
+ export {
1285
+ docFromMarkdown,
1286
+ markdownCodec,
1287
+ markdownFromDoc,
1288
+ docFromText,
1289
+ textFromDoc,
1290
+ } from './markdown.js';
1291
+ export type { MarkdownCodec } from './markdown.js';
1292
+ export { docFromHTML, htmlFromContent } from './html.js';
1293
+ export { DEFAULT_TOOLBAR, toolbarItems } from './toolbar.js';
1294
+ export type { ToolbarEntry, ToolbarItem, ToolbarItemName } from './toolbar.js';
1295
+ export {
1296
+ acceptSuggestion,
1297
+ dismissSuggestion,
1298
+ filterSuggestions,
1299
+ selectSuggestion,
1300
+ suggesterFor,
1301
+ suggestionState,
1302
+ suggestions,
1303
+ } from './suggest.js';
1304
+ export type {
1305
+ Suggester,
1306
+ SuggestionItem,
1307
+ SuggestionQuery,
1308
+ SuggestionRow,
1309
+ SuggestionState,
1310
+ } from './suggest.js';
1311
+ export {
1312
+ addColumnAfter,
1313
+ addColumnBefore,
1314
+ addRowAfter,
1315
+ addRowBefore,
1316
+ columnAlign,
1317
+ deleteColumn,
1318
+ deleteRow,
1319
+ deleteTable,
1320
+ insertTable,
1321
+ isInTable,
1322
+ setColumnAlign,
1323
+ tableRepair,
1324
+ } from './tables.js';
1325
+ export type { ColumnAlign } from './tables.js';
1326
+ export { remoteCaret } from './collab.js';
1327
+ export type { RemoteCaret } from './collab.js';
1328
+ export type { DomFocusEvent } from './nodes.js';
1329
+ export type { ImageInfo, NodeViewProps } from './render.js';
1330
+ export type { MarkStyle } from './look.js';
1331
+ export type { RunStyle } from './inline.js';