@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,822 @@
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, { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
28
+ import { useApp, useClipboard, useTheme } from 'react-x11';
29
+ import { XK_ESCAPE, XK_KP_ENTER, XK_RETURN } from 'react-x11/keysyms';
30
+ import { tint } from 'react-x11/style';
31
+ import { toggleMark } from 'prosemirror-commands';
32
+ import { redo, undo } from 'prosemirror-history';
33
+ import { Fragment, Slice } from 'prosemirror-model';
34
+ import { EditorState, Selection } from 'prosemirror-state';
35
+ import { hx } from '../internal/hx.js';
36
+ import { registerRichText } from '../richtext/index.js';
37
+ import { isBlockActive, isMarkActive, markAttrs, setLink, toggleBlockType, toggleList, toggleWrap, } from './commands.js';
38
+ import { DROP_TYPES, SLICE_TYPE } from './drag.js';
39
+ import { docFromHTML, htmlFromContent } from './html.js';
40
+ import { defaultPlugins } from './keymap.js';
41
+ import { deriveLook } from './look.js';
42
+ import { docFromText, markdownCodec, textFromDoc } from './markdown.js';
43
+ import { registerEditorElements, ROOT_ELEMENT, TEXT_ELEMENT } from './nodes.js';
44
+ import { renderBlockRange, renderBlocks } from './render.js';
45
+ import { schema as defaultSchema } from './schema.js';
46
+ import { acceptSuggestion, SUGGESTION_PAGE, suggesterFor, suggestionState, suggestions as suggestionPlugin, } from './suggest.js';
47
+ import { DEFAULT_TOOLBAR, resolveToolbar, Toolbar, toolbarItems, } from './toolbar.js';
48
+ import { RichEditorView } from './view.js';
49
+ import { useBlockWindow } from './virtual.js';
50
+ registerRichText();
51
+ registerEditorElements();
52
+ const h = React.createElement;
53
+ /** What a drag out of the editor offers to do: an editable one moves or
54
+ * copies, a read-only one only copies. */
55
+ const MOVE_OR_COPY = ['move', 'copy'];
56
+ const COPY_ONLY = ['copy'];
57
+ function codecFor(schema, format, markdown) {
58
+ switch (format) {
59
+ case 'html':
60
+ return {
61
+ parse: (source) => docFromHTML(schema, source),
62
+ serialize: (doc) => htmlFromContent(schema, doc),
63
+ };
64
+ case 'text':
65
+ return {
66
+ parse: (source) => docFromText(schema, source),
67
+ serialize: textFromDoc,
68
+ };
69
+ default:
70
+ return markdown ?? markdownCodec(schema);
71
+ }
72
+ }
73
+ function replaceDocument(view, doc, addToHistory) {
74
+ const { state } = view;
75
+ const tr = state.tr.replaceWith(0, state.doc.content.size, doc.content);
76
+ // keep the caret about where it was, inside what is there now
77
+ tr.setSelection(Selection.near(tr.doc.resolve(Math.min(state.selection.head, tr.doc.content.size))));
78
+ if (!addToHistory)
79
+ tr.setMeta('addToHistory', false);
80
+ view.dispatch(tr);
81
+ }
82
+ function inListOrCode(state) {
83
+ const { $from } = state.selection;
84
+ if ($from.parent.type.spec.code)
85
+ return true;
86
+ for (let d = $from.depth; d > 0; d--) {
87
+ if (/item$/i.test($from.node(d).type.name))
88
+ return true;
89
+ }
90
+ return false;
91
+ }
92
+ function linkAt(state, pos) {
93
+ const $pos = state.doc.resolve(pos);
94
+ const node = $pos.parent.childAfter($pos.parentOffset).node ?? $pos.nodeBefore;
95
+ const link = node?.marks.find((m) => m.type.name === 'link');
96
+ return link && typeof link.attrs.href === 'string' ? link.attrs.href : null;
97
+ }
98
+ /**
99
+ * ```tsx
100
+ * <RichTextEditor
101
+ * defaultValue={note.body}
102
+ * onChange={(ev) => save(ev.value)}
103
+ * placeholder="Write something…"
104
+ * toolbar
105
+ * style={{ flexGrow: 1 }}
106
+ * />
107
+ * ```
108
+ */
109
+ export function RichTextEditor(props) {
110
+ const theme = useTheme();
111
+ const clipboard = useClipboard();
112
+ const { placeholder, readOnly = false, disabled = false, autoFocus, toolbar, style, styles, } = props;
113
+ const format = props.format ?? 'markdown';
114
+ const schema = props.state?.schema ?? props.schema ?? defaultSchema;
115
+ const codec = useMemo(() => codecFor(schema, format, props.markdown), [schema, format, props.markdown]);
116
+ // The latest props, for the callbacks the view holds on to.
117
+ const latest = useRef(props);
118
+ latest.current = props;
119
+ const codecRef = useRef(codec);
120
+ codecRef.current = codec;
121
+ const look = useMemo(() => deriveLook(theme, {
122
+ fontSize: props.fontSize,
123
+ fontFamily: props.fontFamily,
124
+ monoFamily: props.monoFamily,
125
+ markStyles: props.markStyles,
126
+ resolveLanguage: props.resolveLanguage,
127
+ highlight: props.highlight,
128
+ dim: disabled,
129
+ }), [
130
+ theme,
131
+ props.fontSize,
132
+ props.fontFamily,
133
+ props.monoFamily,
134
+ props.markStyles,
135
+ props.resolveLanguage,
136
+ props.highlight,
137
+ disabled,
138
+ ]);
139
+ // The editor's own plugins, made once per schema and option set — a
140
+ // plugin instance carries its state across a reconfigure, so the undo
141
+ // history survives an app swapping its own plugins.
142
+ const defaults = useMemo(() => defaultPlugins(schema, {
143
+ history: props.history,
144
+ inputRules: props.inputRules,
145
+ keymap: props.keymap,
146
+ typography: props.typography,
147
+ }), [schema, props.history, props.inputRules, props.keymap, props.typography]);
148
+ // The suggestion plugin, made once while there are suggesters: it reads
149
+ // the latest prop through `latest`, so an inline array is not a rebuild —
150
+ // a rebuild would drop the list that is open.
151
+ const suggesting = !!props.suggestions?.length;
152
+ const suggest = useMemo(() => suggesting
153
+ ? suggestionPlugin(() => latest.current.suggestions ?? [])
154
+ : null, [suggesting]);
155
+ const plugins = useMemo(() => [
156
+ ...(props.plugins ?? []),
157
+ // ahead of the defaults, so a row takes Enter before the keymap does
158
+ ...(suggest ? [suggest] : []),
159
+ ...defaults,
160
+ ], [props.plugins, suggest, defaults]);
161
+ const listeners = useRef(new Set());
162
+ /** The value last reported through `onChange`, or accepted from `value`. */
163
+ const reported = useRef(null);
164
+ const handleRef = useRef(null);
165
+ const rootRef = useRef(null);
166
+ const [linkEdit, setLinkEdit] = useState(null);
167
+ // A suggestion list shows only while the editor has focus.
168
+ const [focused, setFocused] = useState(false);
169
+ // The trigger being typed is drawn in the accent colour, unless the app
170
+ // says what the `suggestion` class looks like. Memoized: a new object
171
+ // redraws every block's decorations (`configure`).
172
+ const decorationClasses = useMemo(() => ({ suggestion: { color: look.accent }, ...props.decorationClasses }), [look.accent, props.decorationClasses]);
173
+ const config = {
174
+ onRender: () => { },
175
+ onUpdate: (prev, trs) => onUpdate(prev, trs),
176
+ onFocusChange: setFocused,
177
+ clipboard,
178
+ ...(placeholder ? { placeholder } : null),
179
+ colors: {
180
+ selection: look.selection,
181
+ selectionBlurred: look.selectionBlurred,
182
+ caret: look.caret,
183
+ placeholder: look.muted,
184
+ preedit: look.text,
185
+ },
186
+ decorationClasses,
187
+ };
188
+ const viewRef = useRef(null);
189
+ if (!viewRef.current) {
190
+ let state = props.state;
191
+ if (!state) {
192
+ const initial = props.value ?? props.defaultValue;
193
+ if (typeof initial === 'string')
194
+ reported.current = initial;
195
+ const doc = initial === undefined
196
+ ? undefined
197
+ : typeof initial === 'string'
198
+ ? codec.parse(initial)
199
+ : initial;
200
+ state = EditorState.create({ schema, doc, plugins });
201
+ }
202
+ const viewProps = {
203
+ ...props.editorProps,
204
+ editable: () => !latest.current.readOnly && !latest.current.disabled,
205
+ handleClick: (view, pos, event) => {
206
+ const own = latest.current.editorProps?.handleClick;
207
+ if (own?.call(undefined, view, pos, event))
208
+ return true;
209
+ return openLinkAt(pos, event);
210
+ },
211
+ ...(props.dispatchTransaction
212
+ ? {
213
+ dispatchTransaction: (tr) => latest.current.dispatchTransaction?.(tr),
214
+ }
215
+ : null),
216
+ };
217
+ viewRef.current = new RichEditorView(state, viewProps, config);
218
+ }
219
+ const view = viewRef.current;
220
+ // Every state change re-renders this component; the document itself only
221
+ // re-renders the blocks whose nodes changed (./render.ts).
222
+ const subscribe = useCallback((fn) => {
223
+ listeners.current.add(fn);
224
+ return () => {
225
+ listeners.current.delete(fn);
226
+ };
227
+ }, []);
228
+ const state = useSyncExternalStore(subscribe, () => view.state, () => view.state);
229
+ function event(type) {
230
+ const at = view.state;
231
+ let cached = null;
232
+ return {
233
+ type,
234
+ get value() {
235
+ return (cached ??= codecRef.current.serialize(at.doc));
236
+ },
237
+ state: at,
238
+ name: latest.current.name,
239
+ target: handleRef.current,
240
+ };
241
+ }
242
+ function onUpdate(prev, trs) {
243
+ for (const fn of [...listeners.current])
244
+ fn();
245
+ const now = view.state;
246
+ const p = latest.current;
247
+ if (prev.doc !== now.doc && p.onChange) {
248
+ let cached = null;
249
+ p.onChange({
250
+ type: 'change',
251
+ get value() {
252
+ if (cached === null) {
253
+ cached = codecRef.current.serialize(now.doc);
254
+ reported.current = cached;
255
+ }
256
+ return cached;
257
+ },
258
+ doc: now.doc,
259
+ state: now,
260
+ transactions: trs,
261
+ name: p.name,
262
+ target: handleRef.current,
263
+ });
264
+ }
265
+ if (!prev.selection.eq(now.selection))
266
+ p.onSelectionChange?.(event('selectionchange'));
267
+ }
268
+ function openLinkAt(pos, ev) {
269
+ const p = latest.current;
270
+ if (!p.onLink)
271
+ return false;
272
+ const editing = !p.readOnly && !p.disabled;
273
+ // editing, a plain click places the caret; Mod-click follows
274
+ if (editing && !(ev.ctrlKey || ev.metaKey))
275
+ return false;
276
+ const href = linkAt(view.state, pos);
277
+ if (!href)
278
+ return false;
279
+ p.onLink(href, ev);
280
+ return true;
281
+ }
282
+ const editLink = useCallback(() => {
283
+ const root = rootRef.current;
284
+ const frame = root?.getClientRects()[0];
285
+ if (!root || !frame || !view.state.schema.marks.link)
286
+ return;
287
+ const { from, to } = view.state.selection;
288
+ const a = view.coordsAtPos(from);
289
+ const b = view.coordsAtPos(to);
290
+ const attrs = markAttrs(view.state, view.state.schema.marks.link);
291
+ setLinkEdit({
292
+ at: {
293
+ x: a.left - frame.x,
294
+ y: Math.min(a.top, b.top) - frame.y,
295
+ width: Math.max(1, (a.top === b.top ? b.right : a.left) - a.left),
296
+ height: Math.max(a.bottom, b.bottom) - Math.min(a.top, b.top),
297
+ },
298
+ draft: typeof attrs?.href === 'string' ? attrs.href : '',
299
+ });
300
+ }, [view]);
301
+ const handle = useMemo(() => {
302
+ const nodeType = (name) => view.state.schema.nodes[name];
303
+ const markType = (name) => view.state.schema.marks[name];
304
+ const itemType = () => view.state.schema.nodes.list_item ?? view.state.schema.nodes.listItem;
305
+ return {
306
+ get view() {
307
+ return view;
308
+ },
309
+ get state() {
310
+ return view.state;
311
+ },
312
+ get schema() {
313
+ return view.state.schema;
314
+ },
315
+ getValue(fmt) {
316
+ const c = fmt
317
+ ? codecFor(view.state.schema, fmt, latest.current.markdown)
318
+ : codecRef.current;
319
+ return c.serialize(view.state.doc);
320
+ },
321
+ setValue(value, options = {}) {
322
+ const c = options.format
323
+ ? codecFor(view.state.schema, options.format, latest.current.markdown)
324
+ : codecRef.current;
325
+ const doc = typeof value === 'string' ? c.parse(value) : value;
326
+ if (typeof value === 'string' && !options.format)
327
+ reported.current = value;
328
+ replaceDocument(view, doc, options.addToHistory ?? false);
329
+ },
330
+ insertContent(value, fmt) {
331
+ const doc = typeof value === 'string'
332
+ ? (fmt
333
+ ? codecFor(view.state.schema, fmt, latest.current.markdown)
334
+ : codecRef.current).parse(value)
335
+ : value;
336
+ // a whole document goes in as its blocks, open at both ends so a
337
+ // single paragraph joins the one the caret is in
338
+ const content = doc.type === view.state.schema.topNodeType
339
+ ? doc.content
340
+ : Fragment.from(doc);
341
+ view.dispatch(view.state.tr
342
+ .replaceSelection(Slice.maxOpen(content))
343
+ .scrollIntoView());
344
+ },
345
+ run: (command) => view.run(command),
346
+ can: (command) => command(view.state, undefined, view.asEditorView),
347
+ isActive(name, attrs) {
348
+ const mark = markType(name);
349
+ if (mark)
350
+ return isMarkActive(view.state, mark);
351
+ const node = nodeType(name);
352
+ return node ? isBlockActive(view.state, node, attrs) : false;
353
+ },
354
+ toggleMark(name, attrs) {
355
+ const type = markType(name);
356
+ return !!type && view.run(toggleMark(type, attrs));
357
+ },
358
+ setBlock(name, attrs) {
359
+ const type = nodeType(name);
360
+ return !!type && view.run(toggleBlockType(type, attrs));
361
+ },
362
+ toggleList(name) {
363
+ const type = nodeType(name);
364
+ const item = itemType();
365
+ return !!type && !!item && view.run(toggleList(type, item));
366
+ },
367
+ toggleWrap(name) {
368
+ const type = nodeType(name);
369
+ return !!type && view.run(toggleWrap(type));
370
+ },
371
+ undo: () => view.run(undo),
372
+ redo: () => view.run(redo),
373
+ editLink,
374
+ focus: () => view.focus(),
375
+ blur: () => {
376
+ view.dom?.blur?.();
377
+ },
378
+ };
379
+ }, [view, editLink]);
380
+ handleRef.current = handle;
381
+ useImperativeHandle(props.ref, () => handle, [handle]);
382
+ // The component's half of the view, refreshed every render; the view
383
+ // acts only on what changed.
384
+ useLayoutEffect(() => {
385
+ view.configure(config);
386
+ });
387
+ // An app-owned state, handed back after each transaction.
388
+ useLayoutEffect(() => {
389
+ if (props.state && props.state !== view.state)
390
+ view.updateState(props.state);
391
+ }, [props.state, view]);
392
+ // A controlled value: anything but what the editor last reported replaces
393
+ // the document.
394
+ useLayoutEffect(() => {
395
+ const value = props.value;
396
+ if (value === undefined || props.state || value === reported.current)
397
+ return;
398
+ reported.current = value;
399
+ replaceDocument(view, codec.parse(value), false);
400
+ }, [props.value, props.state, codec, view]);
401
+ // New plugins (the app's, or the defaults' options): reconfigure, keeping
402
+ // every plugin instance that is still there — and its state.
403
+ const configured = useRef(plugins);
404
+ useLayoutEffect(() => {
405
+ if (props.state || plugins === configured.current)
406
+ return;
407
+ configured.current = plugins;
408
+ view.updateState(view.state.reconfigure({ plugins }));
409
+ }, [plugins, props.state, view]);
410
+ const editorProps = props.editorProps;
411
+ const firstProps = useRef(editorProps);
412
+ useLayoutEffect(() => {
413
+ if (editorProps === firstProps.current)
414
+ return;
415
+ firstProps.current = editorProps;
416
+ view.setProps({
417
+ ...editorProps,
418
+ editable: view.props.editable,
419
+ handleClick: view.props.handleClick,
420
+ });
421
+ }, [editorProps, view]);
422
+ // the plugins' views, now that the root element exists for them to find
423
+ useLayoutEffect(() => {
424
+ view.mountPluginViews();
425
+ }, [view]);
426
+ useEffect(() => () => view.destroy(), [view]);
427
+ const editable = !readOnly && !disabled;
428
+ // what a table measures its columns with; none on the mock backend
429
+ const fonts = useApp()?.fonts ?? null;
430
+ const ctx = useMemo(() => ({
431
+ view,
432
+ look,
433
+ editable,
434
+ fonts,
435
+ ...(props.nodeViews ? { nodeViews: props.nodeViews } : null),
436
+ ...(props.renderImage ? { renderImage: props.renderImage } : null),
437
+ }), [view, look, editable, fonts, props.nodeViews, props.renderImage]);
438
+ // A long document draws only the blocks near the viewport, in a window
439
+ // the view asks about when what it needs is not drawn (./virtual.ts).
440
+ const windowed = useBlockWindow({
441
+ doc: state.doc,
442
+ keys: view.keys,
443
+ virtual: props.virtual ?? 'auto',
444
+ estimate: Math.round(look.size * 1.5) + look.blockGap,
445
+ gap: look.blockGap,
446
+ });
447
+ const { virtualizing, first, last, register } = windowed;
448
+ const blocks = useMemo(() => virtualizing
449
+ ? renderBlockRange(state.doc, first, last, ctx, register)
450
+ : renderBlocks(state.doc, 0, ctx), [state.doc, ctx, virtualizing, first, last, register]);
451
+ useLayoutEffect(() => {
452
+ view.attachWindow(virtualizing ? windowed.window : null);
453
+ }, [view, virtualizing, windowed.window]);
454
+ // --- input ---------------------------------------------------------------
455
+ const onKeyDown = (ev) => {
456
+ const p = latest.current;
457
+ p.onKeyDown?.(ev);
458
+ if (ev.defaultPrevented || p.disabled)
459
+ return;
460
+ const mac = view.primaryModifier === 'meta';
461
+ const primary = mac ? ev.metaKey : ev.ctrlKey;
462
+ const k = ev.keysym;
463
+ if ((k === XK_RETURN || k === XK_KP_ENTER) && p.onSubmit) {
464
+ const plain = !ev.shiftKey && !primary && !ev.altKey;
465
+ // a plain Enter while a suggestion list shows rows takes a row: it is
466
+ // the list's plugin's, a step further on
467
+ const listing = plain && editable && !!suggestionState(view.state)?.items?.length;
468
+ const submit = !listing &&
469
+ (p.submitOnEnter
470
+ ? plain && !inListOrCode(view.state)
471
+ : primary && !ev.shiftKey);
472
+ if (submit) {
473
+ ev.preventDefault();
474
+ p.onSubmit(event('submit'));
475
+ return;
476
+ }
477
+ }
478
+ if (primary &&
479
+ !ev.altKey &&
480
+ !ev.shiftKey &&
481
+ k === 0x6b /* k */ &&
482
+ editable) {
483
+ ev.preventDefault();
484
+ editLink();
485
+ return;
486
+ }
487
+ if (k === XK_ESCAPE && linkEdit)
488
+ setLinkEdit(null);
489
+ };
490
+ const onMouseDown = (ev) => {
491
+ latest.current.onMouseDown?.(ev);
492
+ if (ev.defaultPrevented || latest.current.disabled)
493
+ return;
494
+ if (view.mouseDown(ev))
495
+ ev.preventDefault();
496
+ };
497
+ const setRoot = useCallback((node) => {
498
+ rootRef.current = node ?? null;
499
+ }, []);
500
+ const pane = windowed.box;
501
+ const setScroller = useCallback((node) => {
502
+ pane.current = node ?? null;
503
+ view.attachScroller(pane.current);
504
+ }, [view, pane]);
505
+ // What a drag out of the editor offers, resolved when a drop asks for it:
506
+ // a copy's text and HTML, and the slice itself for a drop in this app.
507
+ const dragData = useMemo(() => ({
508
+ 'text/plain': () => view.dragText(),
509
+ 'text/html': () => view.dragHTML(),
510
+ [SLICE_TYPE]: () => view.dragPayload(),
511
+ }), [view]);
512
+ // --- chrome --------------------------------------------------------------
513
+ let chrome = null;
514
+ if (typeof toolbar === 'function') {
515
+ chrome = toolbar(handle);
516
+ }
517
+ else if (toolbar) {
518
+ const items = toolbarItems(schema, {
519
+ link: () => {
520
+ editLink();
521
+ return true;
522
+ },
523
+ });
524
+ chrome = h(Toolbar, {
525
+ entries: resolveToolbar(toolbar === true ? DEFAULT_TOOLBAR : toolbar, items),
526
+ state,
527
+ run: (command) => {
528
+ view.run(command);
529
+ view.focus();
530
+ },
531
+ look,
532
+ disabled: !editable,
533
+ ...(styles?.toolbar ? { style: styles.toolbar } : null),
534
+ });
535
+ }
536
+ const closeLink = () => {
537
+ setLinkEdit(null);
538
+ view.focus();
539
+ };
540
+ const applyLink = (href) => {
541
+ const target = href.trim();
542
+ view.run(setLink(target === '' ? null : target));
543
+ closeLink();
544
+ };
545
+ const linkPopup = linkEdit &&
546
+ hx('popup', {
547
+ theme: theme,
548
+ anchor: {
549
+ to: rootRef,
550
+ at: linkEdit.at,
551
+ placement: 'bottom',
552
+ },
553
+ onDismiss: closeLink,
554
+ }, hx('box', {
555
+ style: {
556
+ flexDirection: 'row',
557
+ alignItems: 'center',
558
+ gap: 6,
559
+ padding: 6,
560
+ borderWidth: 1,
561
+ borderColor: look.border,
562
+ borderRadius: look.radius,
563
+ backgroundColor: look.surface,
564
+ },
565
+ }, hx('textinput', {
566
+ value: linkEdit.draft,
567
+ placeholder: 'https://',
568
+ autoFocus: true,
569
+ style: { width: 260 },
570
+ onChange: (ev) => setLinkEdit((cur) => (cur ? { ...cur, draft: ev.value } : cur)),
571
+ onSubmit: (ev) => applyLink(ev.value),
572
+ onKeyDown: (ev) => {
573
+ if (ev.keysym === XK_ESCAPE) {
574
+ ev.preventDefault();
575
+ closeLink();
576
+ }
577
+ },
578
+ })));
579
+ // The suggestion list, drawn from the plugin's state and nothing else
580
+ // (./suggest.ts) — so an app that owns the EditorState gets it too. It hangs
581
+ // off the textblock the trigger is in, at the trigger, so it follows the
582
+ // text as the document scrolls and unmaps while the text is out of view.
583
+ // It never takes focus: a press on a row takes the row, and the caret
584
+ // stays in the editor.
585
+ const suggestion = suggestionState(state);
586
+ const firstRow = useRef(0);
587
+ const rowsShown = focused && editable && !!suggestion?.items?.length;
588
+ const anchor = rowsShown && suggestion ? view.anchorAt(suggestion.from) : null;
589
+ let suggestionPopup = null;
590
+ if (suggestion?.items && anchor) {
591
+ const { items, selected } = suggestion;
592
+ // the window of rows moves only as far as keeps the highlight in it
593
+ let first = Math.min(firstRow.current, Math.max(0, items.length - SUGGESTION_PAGE));
594
+ if (selected < first)
595
+ first = selected;
596
+ if (selected >= first + SUGGESTION_PAGE)
597
+ first = selected - SUGGESTION_PAGE + 1;
598
+ firstRow.current = first;
599
+ // a suggester may draw its rows itself; the row box — the highlight
600
+ // behind it, the press on it — stays the editor's
601
+ const custom = suggesterFor(state)?.renderItem;
602
+ const rowHeight = Math.round(look.size + 12);
603
+ const rows = items.slice(first, first + SUGGESTION_PAGE).map((item, i) => {
604
+ const index = first + i;
605
+ const on = index === selected;
606
+ if (custom) {
607
+ return hx('box', {
608
+ key: `${index} ${item.label}`,
609
+ style: {
610
+ flexDirection: 'row',
611
+ alignItems: 'center',
612
+ minHeight: rowHeight,
613
+ paddingLeft: 8,
614
+ paddingRight: 8,
615
+ borderRadius: Math.max(0, look.radius - 2),
616
+ backgroundColor: on ? look.accent : 'transparent',
617
+ ...(on
618
+ ? null
619
+ : { ':hover': { backgroundColor: tint(look.text, 0.08) } }),
620
+ },
621
+ onMouseDown: (ev) => {
622
+ ev.preventDefault();
623
+ view.run(acceptSuggestion(index));
624
+ },
625
+ }, custom(item, { selected: on, query: suggestion.query }));
626
+ }
627
+ return hx('box', {
628
+ key: `${index} ${item.label}`,
629
+ style: {
630
+ flexDirection: 'row',
631
+ alignItems: 'center',
632
+ gap: 12,
633
+ height: rowHeight,
634
+ paddingLeft: 8,
635
+ paddingRight: 8,
636
+ borderRadius: Math.max(0, look.radius - 2),
637
+ backgroundColor: on ? look.accent : 'transparent',
638
+ ...(on
639
+ ? null
640
+ : { ':hover': { backgroundColor: tint(look.text, 0.08) } }),
641
+ },
642
+ onMouseDown: (ev) => {
643
+ ev.preventDefault();
644
+ view.run(acceptSuggestion(index));
645
+ },
646
+ }, hx('text', {
647
+ style: {
648
+ flexGrow: 1,
649
+ fontFamily: look.family,
650
+ fontSize: look.size,
651
+ color: on ? look.accentText : look.text,
652
+ },
653
+ }, item.label), item.detail
654
+ ? hx('text', {
655
+ style: {
656
+ fontFamily: look.family,
657
+ fontSize: Math.max(10, look.size - 2),
658
+ color: on ? look.accentText : look.muted,
659
+ },
660
+ }, item.detail)
661
+ : null);
662
+ });
663
+ suggestionPopup = hx('popup', {
664
+ theme: theme,
665
+ anchor: { to: anchor.node, at: anchor.at, placement: 'bottom' },
666
+ minWidth: 200,
667
+ maxWidth: 420,
668
+ }, hx('box', {
669
+ style: {
670
+ flexGrow: 1,
671
+ flexDirection: 'column',
672
+ padding: 4,
673
+ borderWidth: 1,
674
+ borderColor: look.border,
675
+ borderRadius: look.radius,
676
+ backgroundColor: look.surface,
677
+ },
678
+ }, ...rows));
679
+ }
680
+ else {
681
+ firstRow.current = 0;
682
+ }
683
+ const frame = {
684
+ flexDirection: 'column',
685
+ borderWidth: 1,
686
+ borderColor: look.border,
687
+ borderRadius: look.radius,
688
+ backgroundColor: look.background,
689
+ overflow: 'hidden',
690
+ ...(editable
691
+ ? {
692
+ ':focus-within': {
693
+ borderColor: String(theme.borderFocus ?? look.accent),
694
+ },
695
+ }
696
+ : null),
697
+ };
698
+ return h(ROOT_ELEMENT, {
699
+ ref: setRoot,
700
+ host: view,
701
+ focusable: !disabled,
702
+ disabled,
703
+ autoFocus,
704
+ 'aria-label': props['aria-label'] ?? placeholder,
705
+ 'data-testname': props['data-testname'],
706
+ style: style
707
+ ? [frame, ...(Array.isArray(style) ? style : [style])]
708
+ : frame,
709
+ onKeyDown,
710
+ onMouseDown,
711
+ onMouseMove: (ev) => {
712
+ if (view.mouseMove(ev))
713
+ ev.preventDefault();
714
+ },
715
+ onMouseUp: () => view.mouseUp(),
716
+ onContextMenu: (ev) => {
717
+ if (ev.defaultPrevented || latest.current.disabled)
718
+ return;
719
+ view.contextMenu(ev);
720
+ ev.preventDefault();
721
+ },
722
+ // drag and drop (./drag.ts): the selection drags out, and a drop is
723
+ // read like a paste. A press on the selection is left to core, which
724
+ // arms the drag here; `onDragStart` cancels one that is not the
725
+ // selection's, and leaves a drag of something inside alone.
726
+ draggable: !disabled,
727
+ dragData,
728
+ dragActions: editable ? MOVE_OR_COPY : COPY_ONLY,
729
+ onDragStart: (ev) => {
730
+ if (ev.target !== rootRef.current)
731
+ return;
732
+ if (!view.dragStart())
733
+ ev.preventDefault();
734
+ },
735
+ onDrag: (ev) => {
736
+ if (ev.target === rootRef.current)
737
+ view.dragMoved(ev.ctrlKey, ev.altKey);
738
+ },
739
+ onDragEnd: (ev) => {
740
+ if (ev.target === rootRef.current)
741
+ view.dragEnd(ev.action, ev.dropped);
742
+ },
743
+ ...(editable
744
+ ? {
745
+ dropAccept: DROP_TYPES,
746
+ onDragOver: (ev) => {
747
+ if (!view.dragOver(ev.x, ev.y))
748
+ ev.reject();
749
+ },
750
+ onDragLeave: () => view.dragLeave(),
751
+ onDrop: (ev) => {
752
+ const settle = (answer) => {
753
+ if (answer)
754
+ ev.accept(answer);
755
+ else
756
+ ev.reject();
757
+ };
758
+ const answer = view.drop(ev);
759
+ if (answer instanceof Promise)
760
+ return answer.then(settle);
761
+ settle(answer);
762
+ return undefined;
763
+ },
764
+ }
765
+ : null),
766
+ onFocus: props.onFocus,
767
+ onBlur: props.onBlur,
768
+ }, chrome, hx('box', {
769
+ ref: setScroller,
770
+ // a press inside focuses the editor, not the viewport it scrolls in
771
+ focusable: false,
772
+ // `flexBasis: 'auto'` is load-bearing: a scroll box that grows
773
+ // takes a zero basis, so an editor nobody gave a height collapsed
774
+ // to its border. With it the viewport is its content's height — a
775
+ // composer grows as it is typed in, up to the frame's `maxHeight`,
776
+ // and past that it shrinks to fit and scrolls; given a height or a
777
+ // `flexGrow`, it fills.
778
+ style: {
779
+ overflow: 'scroll',
780
+ flexGrow: 1,
781
+ flexShrink: 1,
782
+ flexBasis: 'auto',
783
+ flexDirection: 'column',
784
+ },
785
+ ...(virtualizing
786
+ ? { onViewport: windowed.onViewport, onScroll: windowed.onScroll }
787
+ : null),
788
+ }, hx('box', {
789
+ style: {
790
+ flexDirection: 'column',
791
+ gap: look.blockGap,
792
+ padding: Math.round(look.size * 0.85),
793
+ flexGrow: 1,
794
+ ...styles?.content,
795
+ },
796
+ },
797
+ // the blocks outside a long document's window, as space
798
+ windowed.above > 0
799
+ ? hx('box', {
800
+ key: 'spacer:before',
801
+ style: { flexShrink: 0, height: windowed.above },
802
+ })
803
+ : null, ...blocks, windowed.below > 0
804
+ ? hx('box', {
805
+ key: 'spacer:after',
806
+ style: { flexShrink: 0, height: windowed.below },
807
+ })
808
+ : null)), linkPopup, suggestionPopup);
809
+ }
810
+ // --- the rest of the surface -------------------------------------------------
811
+ export { ROOT_ELEMENT as RICH_EDITOR_ELEMENT, TEXT_ELEMENT as RICH_EDITOR_TEXT_ELEMENT, };
812
+ export { RichEditorView };
813
+ export { schema, nodes, marks } from './schema.js';
814
+ export { defaultPlugins, editingKeymap, markdownInputRules } from './keymap.js';
815
+ export { dedentCode, goToCell, indentCode, insertHorizontalRule, isBlockActive, isMarkActive, markAttrs, setLink, setTaskChecked, splitItem, toggleBlockType, toggleList, toggleTaskList, toggleWrap, } from './commands.js';
816
+ export { docFromMarkdown, markdownCodec, markdownFromDoc, docFromText, textFromDoc, } from './markdown.js';
817
+ export { docFromHTML, htmlFromContent } from './html.js';
818
+ export { DEFAULT_TOOLBAR, toolbarItems } from './toolbar.js';
819
+ export { acceptSuggestion, dismissSuggestion, filterSuggestions, selectSuggestion, suggesterFor, suggestionState, suggestions, } from './suggest.js';
820
+ export { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, columnAlign, deleteColumn, deleteRow, deleteTable, insertTable, isInTable, setColumnAlign, tableRepair, } from './tables.js';
821
+ export { remoteCaret } from './collab.js';
822
+ //# sourceMappingURL=index.js.map