@react-x11/components 0.8.0 → 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 (323) hide show
  1. package/README.md +95 -34
  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/embed/client.d.ts +48 -3
  7. package/dist/embed/client.d.ts.map +1 -1
  8. package/dist/embed/client.js +79 -5
  9. package/dist/embed/client.js.map +1 -1
  10. package/dist/embed/index.d.ts +1 -1
  11. package/dist/embed/index.d.ts.map +1 -1
  12. package/dist/embed/index.js +1 -1
  13. package/dist/embed/index.js.map +1 -1
  14. package/dist/html/node.d.ts +3 -1
  15. package/dist/html/node.d.ts.map +1 -1
  16. package/dist/html/node.js +7 -5
  17. package/dist/html/node.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
  23. package/dist/internal/markdown/ast.d.ts.map +1 -0
  24. package/dist/internal/markdown/ast.js +22 -0
  25. package/dist/internal/markdown/ast.js.map +1 -0
  26. package/dist/internal/markdown/parse.d.ts.map +1 -0
  27. package/dist/{markdown → internal/markdown}/parse.js +112 -22
  28. package/dist/internal/markdown/parse.js.map +1 -0
  29. package/dist/internal/markdown/stringify.d.ts +4 -0
  30. package/dist/internal/markdown/stringify.d.ts.map +1 -0
  31. package/dist/internal/markdown/stringify.js +684 -0
  32. package/dist/internal/markdown/stringify.js.map +1 -0
  33. package/dist/internal/markdown/tags.d.ts.map +1 -0
  34. package/dist/internal/markdown/tags.js.map +1 -0
  35. package/dist/internal/scroll.d.ts +3 -2
  36. package/dist/internal/scroll.d.ts.map +1 -1
  37. package/dist/internal/scroll.js +1 -0
  38. package/dist/internal/scroll.js.map +1 -1
  39. package/dist/maps/anchors.d.ts +98 -0
  40. package/dist/maps/anchors.d.ts.map +1 -0
  41. package/dist/maps/anchors.js +531 -0
  42. package/dist/maps/anchors.js.map +1 -0
  43. package/dist/maps/controller.d.ts +283 -0
  44. package/dist/maps/controller.d.ts.map +1 -0
  45. package/dist/maps/controller.js +743 -0
  46. package/dist/maps/controller.js.map +1 -0
  47. package/dist/maps/gl/buckets.d.ts +95 -0
  48. package/dist/maps/gl/buckets.d.ts.map +1 -0
  49. package/dist/maps/gl/buckets.js +362 -0
  50. package/dist/maps/gl/buckets.js.map +1 -0
  51. package/dist/maps/gl/build-worker.d.ts +13 -0
  52. package/dist/maps/gl/build-worker.d.ts.map +1 -0
  53. package/dist/maps/gl/build-worker.js +44 -0
  54. package/dist/maps/gl/build-worker.js.map +1 -0
  55. package/dist/maps/gl/color.d.ts +8 -0
  56. package/dist/maps/gl/color.d.ts.map +1 -0
  57. package/dist/maps/gl/color.js +57 -0
  58. package/dist/maps/gl/color.js.map +1 -0
  59. package/dist/maps/gl/cover.d.ts +51 -0
  60. package/dist/maps/gl/cover.d.ts.map +1 -0
  61. package/dist/maps/gl/cover.js +124 -0
  62. package/dist/maps/gl/cover.js.map +1 -0
  63. package/dist/maps/gl/index.d.ts +3 -0
  64. package/dist/maps/gl/index.d.ts.map +1 -0
  65. package/dist/maps/gl/index.js +20 -0
  66. package/dist/maps/gl/index.js.map +1 -0
  67. package/dist/maps/gl/markers.d.ts +28 -0
  68. package/dist/maps/gl/markers.d.ts.map +1 -0
  69. package/dist/maps/gl/markers.js +60 -0
  70. package/dist/maps/gl/markers.js.map +1 -0
  71. package/dist/maps/gl/overlays.d.ts +68 -0
  72. package/dist/maps/gl/overlays.d.ts.map +1 -0
  73. package/dist/maps/gl/overlays.js +300 -0
  74. package/dist/maps/gl/overlays.js.map +1 -0
  75. package/dist/maps/gl/pane.d.ts +12 -0
  76. package/dist/maps/gl/pane.d.ts.map +1 -0
  77. package/dist/maps/gl/pane.js +37 -0
  78. package/dist/maps/gl/pane.js.map +1 -0
  79. package/dist/maps/gl/placement.d.ts +111 -0
  80. package/dist/maps/gl/placement.d.ts.map +1 -0
  81. package/dist/maps/gl/placement.js +567 -0
  82. package/dist/maps/gl/placement.js.map +1 -0
  83. package/dist/maps/gl/renderer.d.ts +352 -0
  84. package/dist/maps/gl/renderer.d.ts.map +1 -0
  85. package/dist/maps/gl/renderer.js +1127 -0
  86. package/dist/maps/gl/renderer.js.map +1 -0
  87. package/dist/maps/gl/shaders.d.ts +120 -0
  88. package/dist/maps/gl/shaders.d.ts.map +1 -0
  89. package/dist/maps/gl/shaders.js +451 -0
  90. package/dist/maps/gl/shaders.js.map +1 -0
  91. package/dist/maps/gl/store.d.ts +115 -0
  92. package/dist/maps/gl/store.d.ts.map +1 -0
  93. package/dist/maps/gl/store.js +469 -0
  94. package/dist/maps/gl/store.js.map +1 -0
  95. package/dist/maps/gl/text.d.ts +154 -0
  96. package/dist/maps/gl/text.d.ts.map +1 -0
  97. package/dist/maps/gl/text.js +382 -0
  98. package/dist/maps/gl/text.js.map +1 -0
  99. package/dist/maps/gl/view.d.ts +54 -0
  100. package/dist/maps/gl/view.d.ts.map +1 -0
  101. package/dist/maps/gl/view.js +974 -0
  102. package/dist/maps/gl/view.js.map +1 -0
  103. package/dist/maps/index.d.ts +1 -1
  104. package/dist/maps/index.d.ts.map +1 -1
  105. package/dist/maps/index.js +216 -52
  106. package/dist/maps/index.js.map +1 -1
  107. package/dist/maps/labels.d.ts +37 -16
  108. package/dist/maps/labels.d.ts.map +1 -1
  109. package/dist/maps/labels.js +188 -119
  110. package/dist/maps/labels.js.map +1 -1
  111. package/dist/maps/node.d.ts +162 -56
  112. package/dist/maps/node.d.ts.map +1 -1
  113. package/dist/maps/node.js +607 -515
  114. package/dist/maps/node.js.map +1 -1
  115. package/dist/maps/overlay.d.ts +72 -9
  116. package/dist/maps/overlay.d.ts.map +1 -1
  117. package/dist/maps/overlay.js +108 -28
  118. package/dist/maps/overlay.js.map +1 -1
  119. package/dist/maps/paint.d.ts +3 -0
  120. package/dist/maps/paint.d.ts.map +1 -1
  121. package/dist/maps/paint.js.map +1 -1
  122. package/dist/maps/proj.d.ts +14 -0
  123. package/dist/maps/proj.d.ts.map +1 -1
  124. package/dist/maps/proj.js +28 -0
  125. package/dist/maps/proj.js.map +1 -1
  126. package/dist/maps/renderer.d.ts +69 -0
  127. package/dist/maps/renderer.d.ts.map +1 -0
  128. package/dist/maps/renderer.js +66 -0
  129. package/dist/maps/renderer.js.map +1 -0
  130. package/dist/maps/sources.d.ts +25 -3
  131. package/dist/maps/sources.d.ts.map +1 -1
  132. package/dist/maps/sources.js +17 -0
  133. package/dist/maps/sources.js.map +1 -1
  134. package/dist/maps/theme.d.ts +19 -0
  135. package/dist/maps/theme.d.ts.map +1 -0
  136. package/dist/maps/theme.js +44 -0
  137. package/dist/maps/theme.js.map +1 -0
  138. package/dist/maps/tiles.d.ts +117 -17
  139. package/dist/maps/tiles.d.ts.map +1 -1
  140. package/dist/maps/tiles.js +163 -30
  141. package/dist/maps/tiles.js.map +1 -1
  142. package/dist/maps/types.d.ts +188 -20
  143. package/dist/maps/types.d.ts.map +1 -1
  144. package/dist/markdown/expressions.d.ts +1 -1
  145. package/dist/markdown/expressions.d.ts.map +1 -1
  146. package/dist/markdown/expressions.js.map +1 -1
  147. package/dist/markdown/index.d.ts +7 -6
  148. package/dist/markdown/index.d.ts.map +1 -1
  149. package/dist/markdown/index.js +9 -7
  150. package/dist/markdown/index.js.map +1 -1
  151. package/dist/markdown/spans.d.ts +1 -1
  152. package/dist/markdown/spans.d.ts.map +1 -1
  153. package/dist/media-player/index.d.ts +11 -3
  154. package/dist/media-player/index.d.ts.map +1 -1
  155. package/dist/media-player/index.js +13 -2
  156. package/dist/media-player/index.js.map +1 -1
  157. package/dist/rich-text-editor/clipboard.d.ts +23 -0
  158. package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
  159. package/dist/rich-text-editor/clipboard.js +307 -0
  160. package/dist/rich-text-editor/clipboard.js.map +1 -0
  161. package/dist/rich-text-editor/collab.d.ts +32 -0
  162. package/dist/rich-text-editor/collab.d.ts.map +1 -0
  163. package/dist/rich-text-editor/collab.js +55 -0
  164. package/dist/rich-text-editor/collab.js.map +1 -0
  165. package/dist/rich-text-editor/commands.d.ts +50 -0
  166. package/dist/rich-text-editor/commands.d.ts.map +1 -0
  167. package/dist/rich-text-editor/commands.js +376 -0
  168. package/dist/rich-text-editor/commands.js.map +1 -0
  169. package/dist/rich-text-editor/drag.d.ts +68 -0
  170. package/dist/rich-text-editor/drag.d.ts.map +1 -0
  171. package/dist/rich-text-editor/drag.js +148 -0
  172. package/dist/rich-text-editor/drag.js.map +1 -0
  173. package/dist/rich-text-editor/html.d.ts +145 -0
  174. package/dist/rich-text-editor/html.d.ts.map +1 -0
  175. package/dist/rich-text-editor/html.js +516 -0
  176. package/dist/rich-text-editor/html.js.map +1 -0
  177. package/dist/rich-text-editor/index.d.ts +263 -0
  178. package/dist/rich-text-editor/index.d.ts.map +1 -0
  179. package/dist/rich-text-editor/index.js +822 -0
  180. package/dist/rich-text-editor/index.js.map +1 -0
  181. package/dist/rich-text-editor/inline.d.ts +94 -0
  182. package/dist/rich-text-editor/inline.d.ts.map +1 -0
  183. package/dist/rich-text-editor/inline.js +252 -0
  184. package/dist/rich-text-editor/inline.js.map +1 -0
  185. package/dist/rich-text-editor/keymap.d.ts +71 -0
  186. package/dist/rich-text-editor/keymap.d.ts.map +1 -0
  187. package/dist/rich-text-editor/keymap.js +393 -0
  188. package/dist/rich-text-editor/keymap.js.map +1 -0
  189. package/dist/rich-text-editor/keys.d.ts +29 -0
  190. package/dist/rich-text-editor/keys.d.ts.map +1 -0
  191. package/dist/rich-text-editor/keys.js +93 -0
  192. package/dist/rich-text-editor/keys.js.map +1 -0
  193. package/dist/rich-text-editor/look.d.ts +71 -0
  194. package/dist/rich-text-editor/look.d.ts.map +1 -0
  195. package/dist/rich-text-editor/look.js +342 -0
  196. package/dist/rich-text-editor/look.js.map +1 -0
  197. package/dist/rich-text-editor/markdown.d.ts +18 -0
  198. package/dist/rich-text-editor/markdown.d.ts.map +1 -0
  199. package/dist/rich-text-editor/markdown.js +496 -0
  200. package/dist/rich-text-editor/markdown.js.map +1 -0
  201. package/dist/rich-text-editor/nodes.d.ts +135 -0
  202. package/dist/rich-text-editor/nodes.d.ts.map +1 -0
  203. package/dist/rich-text-editor/nodes.js +397 -0
  204. package/dist/rich-text-editor/nodes.js.map +1 -0
  205. package/dist/rich-text-editor/render.d.ts +68 -0
  206. package/dist/rich-text-editor/render.d.ts.map +1 -0
  207. package/dist/rich-text-editor/render.js +500 -0
  208. package/dist/rich-text-editor/render.js.map +1 -0
  209. package/dist/rich-text-editor/schema.d.ts +16 -0
  210. package/dist/rich-text-editor/schema.d.ts.map +1 -0
  211. package/dist/rich-text-editor/schema.js +386 -0
  212. package/dist/rich-text-editor/schema.js.map +1 -0
  213. package/dist/rich-text-editor/store.d.ts +11 -0
  214. package/dist/rich-text-editor/store.d.ts.map +1 -0
  215. package/dist/rich-text-editor/store.js +43 -0
  216. package/dist/rich-text-editor/store.js.map +1 -0
  217. package/dist/rich-text-editor/suggest.d.ts +127 -0
  218. package/dist/rich-text-editor/suggest.d.ts.map +1 -0
  219. package/dist/rich-text-editor/suggest.js +0 -0
  220. package/dist/rich-text-editor/suggest.js.map +1 -0
  221. package/dist/rich-text-editor/tables.d.ts +41 -0
  222. package/dist/rich-text-editor/tables.d.ts.map +1 -0
  223. package/dist/rich-text-editor/tables.js +204 -0
  224. package/dist/rich-text-editor/tables.js.map +1 -0
  225. package/dist/rich-text-editor/toolbar.d.ts +58 -0
  226. package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
  227. package/dist/rich-text-editor/toolbar.js +548 -0
  228. package/dist/rich-text-editor/toolbar.js.map +1 -0
  229. package/dist/rich-text-editor/view.d.ts +368 -0
  230. package/dist/rich-text-editor/view.d.ts.map +1 -0
  231. package/dist/rich-text-editor/view.js +1968 -0
  232. package/dist/rich-text-editor/view.js.map +1 -0
  233. package/dist/rich-text-editor/virtual.d.ts +54 -0
  234. package/dist/rich-text-editor/virtual.d.ts.map +1 -0
  235. package/dist/rich-text-editor/virtual.js +262 -0
  236. package/dist/rich-text-editor/virtual.js.map +1 -0
  237. package/dist/richtext/node.d.ts +25 -2
  238. package/dist/richtext/node.d.ts.map +1 -1
  239. package/dist/richtext/node.js +46 -24
  240. package/dist/richtext/node.js.map +1 -1
  241. package/dist/terminal/backends.d.ts +4 -1
  242. package/dist/terminal/backends.d.ts.map +1 -1
  243. package/dist/terminal/backends.js.map +1 -1
  244. package/dist/terminal/index.d.ts +18 -7
  245. package/dist/terminal/index.d.ts.map +1 -1
  246. package/dist/terminal/index.js +28 -11
  247. package/dist/terminal/index.js.map +1 -1
  248. package/package.json +23 -4
  249. package/src/charts/node.ts +6 -2
  250. package/src/embed/client.ts +102 -8
  251. package/src/embed/index.ts +2 -0
  252. package/src/html/node.ts +7 -5
  253. package/src/index.ts +2 -0
  254. package/src/{markdown → internal/markdown}/ast.ts +24 -7
  255. package/src/{markdown → internal/markdown}/parse.ts +134 -24
  256. package/src/internal/markdown/stringify.ts +772 -0
  257. package/src/internal/scroll.ts +5 -3
  258. package/src/maps/anchors.ts +607 -0
  259. package/src/maps/controller.ts +999 -0
  260. package/src/maps/gl/buckets.ts +457 -0
  261. package/src/maps/gl/build-worker.ts +55 -0
  262. package/src/maps/gl/color.ts +62 -0
  263. package/src/maps/gl/cover.ts +170 -0
  264. package/src/maps/gl/index.ts +23 -0
  265. package/src/maps/gl/markers.ts +83 -0
  266. package/src/maps/gl/overlays.ts +379 -0
  267. package/src/maps/gl/pane.ts +42 -0
  268. package/src/maps/gl/placement.ts +816 -0
  269. package/src/maps/gl/renderer.ts +1576 -0
  270. package/src/maps/gl/shaders.ts +486 -0
  271. package/src/maps/gl/store.ts +566 -0
  272. package/src/maps/gl/text.ts +495 -0
  273. package/src/maps/gl/view.ts +1224 -0
  274. package/src/maps/index.ts +270 -81
  275. package/src/maps/labels.ts +214 -121
  276. package/src/maps/node.ts +763 -629
  277. package/src/maps/overlay.ts +148 -43
  278. package/src/maps/paint.ts +3 -0
  279. package/src/maps/proj.ts +31 -0
  280. package/src/maps/renderer.ts +155 -0
  281. package/src/maps/sources.ts +38 -3
  282. package/src/maps/theme.ts +51 -0
  283. package/src/maps/tiles.ts +191 -33
  284. package/src/maps/types.ts +188 -20
  285. package/src/markdown/expressions.ts +5 -1
  286. package/src/markdown/index.ts +14 -11
  287. package/src/markdown/spans.ts +1 -1
  288. package/src/media-player/index.ts +18 -4
  289. package/src/rich-text-editor/clipboard.ts +439 -0
  290. package/src/rich-text-editor/collab.ts +91 -0
  291. package/src/rich-text-editor/commands.ts +448 -0
  292. package/src/rich-text-editor/dom-globals.d.ts +23 -0
  293. package/src/rich-text-editor/drag.ts +211 -0
  294. package/src/rich-text-editor/html.ts +619 -0
  295. package/src/rich-text-editor/index.ts +1331 -0
  296. package/src/rich-text-editor/inline.ts +345 -0
  297. package/src/rich-text-editor/keymap.ts +510 -0
  298. package/src/rich-text-editor/keys.ts +119 -0
  299. package/src/rich-text-editor/look.ts +427 -0
  300. package/src/rich-text-editor/markdown.ts +653 -0
  301. package/src/rich-text-editor/nodes.ts +497 -0
  302. package/src/rich-text-editor/render.ts +800 -0
  303. package/src/rich-text-editor/schema.ts +431 -0
  304. package/src/rich-text-editor/store.ts +39 -0
  305. package/src/rich-text-editor/suggest.ts +597 -0
  306. package/src/rich-text-editor/tables.ts +272 -0
  307. package/src/rich-text-editor/toolbar.ts +724 -0
  308. package/src/rich-text-editor/view.ts +2287 -0
  309. package/src/rich-text-editor/virtual.ts +347 -0
  310. package/src/richtext/node.ts +59 -30
  311. package/src/terminal/backends.ts +4 -1
  312. package/src/terminal/index.ts +43 -15
  313. package/dist/markdown/ast.d.ts.map +0 -1
  314. package/dist/markdown/ast.js +0 -17
  315. package/dist/markdown/ast.js.map +0 -1
  316. package/dist/markdown/parse.d.ts.map +0 -1
  317. package/dist/markdown/parse.js.map +0 -1
  318. package/dist/markdown/tags.d.ts.map +0 -1
  319. package/dist/markdown/tags.js.map +0 -1
  320. /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
  321. /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
  322. /package/dist/{markdown → internal/markdown}/tags.js +0 -0
  323. /package/src/{markdown → internal/markdown}/tags.ts +0 -0
@@ -0,0 +1,510 @@
1
+ // Keys, across the one seam ProseMirror's ecosystem is written against: a
2
+ // DOM `KeyboardEvent`.
3
+ //
4
+ // A keymap plugin — prosemirror-keymap's, TipTap's, anyone's — reads
5
+ // `event.key`, `event.keyCode` and four modifier flags and nothing else, so a
6
+ // react-x11 key event is translated into exactly that and handed to
7
+ // `handleKeyDown` as if a browser had sent it. Two details decide whether a
8
+ // binding like `Mod-b` fires:
9
+ //
10
+ // - **The Latin key, under any layout.** With a modifier held, `key` is the
11
+ // character of `ev.keysym` — which react-x11 already resolves to the Latin
12
+ // key of the physical key — and `keyCode` is that key's DOM code, which is
13
+ // also what prosemirror-keymap falls back to. So Ctrl+B bolds while the
14
+ // user is typing Russian, as it does in every toolkit.
15
+ // - **Which modifier is Mod.** prosemirror-keymap decides at load time, from
16
+ // `navigator.platform`, that Mod is Cmd on a Mac and Ctrl anywhere else —
17
+ // a fact about the *host*. The answer here is the *backend's*: an X11
18
+ // application uses Ctrl even under XQuartz on a Mac (and the suite's
19
+ // in-process server is X11 on every host), while react-x11's native macOS
20
+ // backend uses Cmd, even on a Node old enough to have no `navigator`, where
21
+ // prosemirror-keymap guesses Ctrl. When the two disagree, Ctrl and Meta
22
+ // trade places in the event a plugin is shown, so the backend's primary
23
+ // modifier always arrives as prosemirror-keymap's Mod.
24
+ //
25
+ // Below that: the editing behaviour the default schema ships with — the
26
+ // plugins `defaultPlugins` assembles, in the order they have to be in.
27
+ import {
28
+ baseKeymap,
29
+ chainCommands,
30
+ exitCode,
31
+ setBlockType,
32
+ toggleMark,
33
+ wrapIn,
34
+ } from 'prosemirror-commands';
35
+ import { history, redo, undo } from 'prosemirror-history';
36
+ import {
37
+ InputRule,
38
+ ellipsis,
39
+ emDash,
40
+ inputRules,
41
+ smartQuotes,
42
+ textblockTypeInputRule,
43
+ undoInputRule,
44
+ wrappingInputRule,
45
+ } from 'prosemirror-inputrules';
46
+ import { keymap } from 'prosemirror-keymap';
47
+ import { Fragment } from 'prosemirror-model';
48
+ import type { MarkType, NodeType, Schema } from 'prosemirror-model';
49
+ import {
50
+ liftListItem,
51
+ sinkListItem,
52
+ wrapInList,
53
+ } from 'prosemirror-schema-list';
54
+ import { TextSelection } from 'prosemirror-state';
55
+ import type { Command, Plugin } from 'prosemirror-state';
56
+ import { findWrapping } from 'prosemirror-transform';
57
+
58
+ import {
59
+ dedentCode,
60
+ goToCell,
61
+ indentCode,
62
+ insertHorizontalRule,
63
+ splitItem,
64
+ toggleTaskList,
65
+ } from './commands.js';
66
+ import { hasTables, tableRepair } from './tables.js';
67
+
68
+ // --- the event ---------------------------------------------------------------
69
+
70
+ /** The backend's primary shortcut modifier: Ctrl on X11, Cmd on macOS. */
71
+ export type PrimaryModifier = 'ctrl' | 'meta';
72
+
73
+ /**
74
+ * Which modifier this app's backend uses for shortcuts. react-x11's native
75
+ * macOS application is the one carrying a bezel store (`nativeBezels`, its
76
+ * Cocoa-only view of the window chrome); nothing on the X11 backend or the
77
+ * headless mock has one.
78
+ */
79
+ export function primaryModifierOf(app: unknown): PrimaryModifier {
80
+ return app !== null && typeof app === 'object' && 'nativeBezels' in app
81
+ ? 'meta'
82
+ : 'ctrl';
83
+ }
84
+
85
+ const nav = (globalThis as { navigator?: { platform?: string } }).navigator;
86
+ /** prosemirror-keymap's own answer, reproduced: is its Mod the Meta key? */
87
+ const MOD_IS_META = !!nav && /Mac|iP(hone|[oa]d)/.test(nav.platform ?? '');
88
+
89
+ /** The slice of a react-x11 key event read here. */
90
+ export interface KeyLike {
91
+ keysym?: number;
92
+ key?: string;
93
+ codepoint?: number;
94
+ composing?: boolean;
95
+ shiftKey: boolean;
96
+ ctrlKey: boolean;
97
+ altKey: boolean;
98
+ metaKey: boolean;
99
+ }
100
+
101
+ /** A DOM `KeyboardEvent`, as far as ProseMirror and w3c-keyname read one. */
102
+ export interface DomKeyEvent {
103
+ readonly type: 'keydown';
104
+ readonly key: string;
105
+ readonly code: string;
106
+ readonly keyCode: number;
107
+ readonly which: number;
108
+ readonly charCode: number;
109
+ readonly shiftKey: boolean;
110
+ readonly ctrlKey: boolean;
111
+ readonly altKey: boolean;
112
+ readonly metaKey: boolean;
113
+ readonly repeat: boolean;
114
+ readonly isComposing: boolean;
115
+ readonly defaultPrevented: boolean;
116
+ preventDefault(): void;
117
+ stopPropagation(): void;
118
+ getModifierState(name: string): boolean;
119
+ }
120
+
121
+ /** X keysym → DOM key name and legacy key code, for the keys with names. */
122
+ const NAMED = new Map<number, [string, number]>([
123
+ [0xff0d, ['Enter', 13]],
124
+ [0xff8d, ['Enter', 13]], // KP_Enter
125
+ [0xff08, ['Backspace', 8]],
126
+ [0xffff, ['Delete', 46]],
127
+ [0xff9f, ['Delete', 46]], // KP_Delete
128
+ [0xff09, ['Tab', 9]],
129
+ [0xfe20, ['Tab', 9]], // ISO_Left_Tab — what Shift+Tab sends
130
+ [0xff1b, ['Escape', 27]],
131
+ [0xff51, ['ArrowLeft', 37]],
132
+ [0xff52, ['ArrowUp', 38]],
133
+ [0xff53, ['ArrowRight', 39]],
134
+ [0xff54, ['ArrowDown', 40]],
135
+ [0xff96, ['ArrowLeft', 37]],
136
+ [0xff97, ['ArrowUp', 38]],
137
+ [0xff98, ['ArrowRight', 39]],
138
+ [0xff99, ['ArrowDown', 40]],
139
+ [0xff50, ['Home', 36]],
140
+ [0xff95, ['Home', 36]],
141
+ [0xff57, ['End', 35]],
142
+ [0xff9c, ['End', 35]],
143
+ [0xff55, ['PageUp', 33]],
144
+ [0xff9a, ['PageUp', 33]],
145
+ [0xff56, ['PageDown', 34]],
146
+ [0xff9b, ['PageDown', 34]],
147
+ [0xff63, ['Insert', 45]],
148
+ [0xff9e, ['Insert', 45]],
149
+ [0x20, [' ', 32]],
150
+ ]);
151
+ for (let i = 0; i < 12; i++) NAMED.set(0xffbe + i, [`F${i + 1}`, 112 + i]);
152
+
153
+ /** The US-layout key a character is on, as a DOM key code. */
154
+ const PUNCT_CODES: Record<string, number> = {
155
+ ';': 186,
156
+ ':': 186,
157
+ '=': 187,
158
+ '+': 187,
159
+ ',': 188,
160
+ '<': 188,
161
+ '-': 189,
162
+ _: 189,
163
+ '.': 190,
164
+ '>': 190,
165
+ '/': 191,
166
+ '?': 191,
167
+ '`': 192,
168
+ '~': 192,
169
+ '[': 219,
170
+ '{': 219,
171
+ '\\': 220,
172
+ '|': 220,
173
+ ']': 221,
174
+ '}': 221,
175
+ "'": 222,
176
+ '"': 222,
177
+ ')': 48,
178
+ '!': 49,
179
+ '@': 50,
180
+ '#': 51,
181
+ $: 52,
182
+ '%': 53,
183
+ '^': 54,
184
+ '&': 55,
185
+ '*': 56,
186
+ '(': 57,
187
+ };
188
+
189
+ function codeOf(ch: string): number {
190
+ if (/^[a-z]$/i.test(ch)) return ch.toUpperCase().charCodeAt(0);
191
+ if (/^[0-9]$/.test(ch)) return ch.charCodeAt(0);
192
+ return PUNCT_CODES[ch] ?? 0;
193
+ }
194
+
195
+ /** A react-x11 key event as the DOM keydown a keymap expects. */
196
+ export function toDomKeyEvent(
197
+ ev: KeyLike,
198
+ primary: PrimaryModifier,
199
+ ): DomKeyEvent {
200
+ let ctrlKey = !!ev.ctrlKey;
201
+ let metaKey = !!ev.metaKey;
202
+ if ((primary === 'meta') !== MOD_IS_META)
203
+ [ctrlKey, metaKey] = [metaKey, ctrlKey];
204
+ const named = ev.keysym !== undefined ? NAMED.get(ev.keysym) : undefined;
205
+ let key: string;
206
+ let keyCode: number;
207
+ if (named) {
208
+ [key, keyCode] = named;
209
+ } else {
210
+ const latin =
211
+ ev.keysym !== undefined && ev.keysym > 0x20 && ev.keysym < 0x7f
212
+ ? String.fromCharCode(ev.keysym)
213
+ : '';
214
+ const chord = ctrlKey || metaKey || !!ev.altKey;
215
+ key = (chord && latin) || ev.key || latin || 'Unidentified';
216
+ keyCode = codeOf(latin || key);
217
+ }
218
+ let prevented = false;
219
+ const shiftKey = !!ev.shiftKey;
220
+ const altKey = !!ev.altKey;
221
+ return {
222
+ type: 'keydown',
223
+ key,
224
+ code: '',
225
+ keyCode,
226
+ which: keyCode,
227
+ charCode: 0,
228
+ shiftKey,
229
+ ctrlKey,
230
+ altKey,
231
+ metaKey,
232
+ repeat: false,
233
+ isComposing: !!ev.composing,
234
+ get defaultPrevented() {
235
+ return prevented;
236
+ },
237
+ preventDefault() {
238
+ prevented = true;
239
+ },
240
+ stopPropagation() {},
241
+ getModifierState(name: string) {
242
+ switch (name) {
243
+ case 'Shift':
244
+ return shiftKey;
245
+ case 'Control':
246
+ return ctrlKey;
247
+ case 'Alt':
248
+ return altKey;
249
+ case 'Meta':
250
+ return metaKey;
251
+ default:
252
+ return false;
253
+ }
254
+ },
255
+ };
256
+ }
257
+
258
+ // --- the default document's behaviour ---------------------------------------
259
+
260
+ export interface DefaultPluginOptions {
261
+ /** Undo and redo (prosemirror-history), with Mod-z / Mod-y. Default on. */
262
+ history?: boolean;
263
+ /** Markdown as you type: `# `, `> `, `- `, `1. `, `[ ] `, ``` ``` ```,
264
+ * `---`, and `**bold**`-style marks. Default on. */
265
+ inputRules?: boolean;
266
+ /** Formatting shortcuts — Mod-b, Mod-i, Mod-Alt-1…6, lists, quotes. Default on. */
267
+ keymap?: boolean;
268
+ /** Smart quotes, `...` → `…` and `--` → `—`. Default off: in a markdown
269
+ * document a straight quote is usually meant. */
270
+ typography?: boolean;
271
+ }
272
+
273
+ /**
274
+ * The plugins the default editor runs, in the order they have to be in:
275
+ * input rules, the formatting keymap (its Enter splits list items before the
276
+ * base keymap's Enter splits blocks), history, and prosemirror-commands'
277
+ * base keymap last. `<RichTextEditor>` puts these in its state; an app that
278
+ * owns the state (`state` + `dispatchTransaction`) builds them in itself.
279
+ */
280
+ export function defaultPlugins(
281
+ schema: Schema,
282
+ options: DefaultPluginOptions = {},
283
+ ): Plugin[] {
284
+ const out: Plugin[] = [];
285
+ const markdown = options.inputRules !== false;
286
+ if (markdown || options.typography) {
287
+ out.push(
288
+ markdownInputRules(schema, {
289
+ markdown,
290
+ typography: !!options.typography,
291
+ }),
292
+ );
293
+ }
294
+ if (options.keymap !== false) out.push(keymap(editingKeymap(schema)));
295
+ if (options.history !== false) {
296
+ out.push(
297
+ history(),
298
+ keymap({ 'Mod-z': undo, 'Shift-Mod-z': redo, 'Mod-y': redo }),
299
+ );
300
+ }
301
+ // every table kept rectangular, so the table commands can reason about it
302
+ if (hasTables(schema)) out.push(tableRepair());
303
+ out.push(keymap(baseKeymap));
304
+ return out;
305
+ }
306
+
307
+ /** The formatting shortcuts, for whatever of them the schema can do. */
308
+ export function editingKeymap(schema: Schema): Record<string, Command> {
309
+ const keys: Record<string, Command> = {};
310
+ const bind = (key: string, cmd: Command): void => {
311
+ keys[key] = keys[key] ? chainCommands(keys[key], cmd) : cmd;
312
+ };
313
+ const { marks, nodes } = schema;
314
+
315
+ bind('Backspace', undoInputRule);
316
+ if (marks.strong) bind('Mod-b', toggleMark(marks.strong));
317
+ if (marks.em) bind('Mod-i', toggleMark(marks.em));
318
+ if (marks.code) bind('Mod-`', toggleMark(marks.code));
319
+ if (marks.strike) bind('Shift-Mod-x', toggleMark(marks.strike));
320
+
321
+ if (nodes.code_block) {
322
+ // inside a fence, Tab is indentation — and Escape-then-Tab still leaves
323
+ bind('Tab', indentCode);
324
+ bind('Shift-Tab', dedentCode);
325
+ }
326
+ if (nodes.table) {
327
+ bind('Tab', goToCell(1));
328
+ bind('Shift-Tab', goToCell(-1));
329
+ }
330
+ const item = nodes.list_item;
331
+ if (item) {
332
+ bind('Enter', splitItem(item));
333
+ bind('Mod-[', liftListItem(item));
334
+ bind('Mod-]', sinkListItem(item));
335
+ bind('Tab', sinkListItem(item));
336
+ bind('Shift-Tab', liftListItem(item));
337
+ }
338
+ if (nodes.bullet_list) bind('Shift-Mod-8', wrapInList(nodes.bullet_list));
339
+ if (nodes.ordered_list) bind('Shift-Mod-7', wrapInList(nodes.ordered_list));
340
+ if (item && nodes.bullet_list && 'checked' in (item.spec.attrs ?? {})) {
341
+ bind('Shift-Mod-9', toggleTaskList(schema));
342
+ }
343
+ if (nodes.blockquote) bind('Ctrl->', wrapIn(nodes.blockquote));
344
+
345
+ const br = nodes.hard_break;
346
+ if (br) {
347
+ const hardBreak = chainCommands(exitCode, (state, dispatch) => {
348
+ dispatch?.(state.tr.replaceSelectionWith(br.create()).scrollIntoView());
349
+ return true;
350
+ });
351
+ bind('Mod-Enter', hardBreak);
352
+ bind('Shift-Enter', hardBreak);
353
+ }
354
+ if (nodes.paragraph) bind('Mod-Alt-0', setBlockType(nodes.paragraph));
355
+ if (nodes.code_block) bind('Mod-Alt-c', setBlockType(nodes.code_block));
356
+ if (nodes.heading) {
357
+ for (let level = 1; level <= 6; level++) {
358
+ bind(`Mod-Alt-${level}`, setBlockType(nodes.heading, { level }));
359
+ }
360
+ }
361
+ if (nodes.horizontal_rule)
362
+ bind('Mod-_', insertHorizontalRule(nodes.horizontal_rule));
363
+ return keys;
364
+ }
365
+
366
+ /**
367
+ * An input rule for a delimited mark: when the closing delimiter is typed,
368
+ * the delimiters go and what they held takes the mark. `match[1]` is the
369
+ * delimited run, `match[2]` what is inside it. The typed character is the
370
+ * last of the closing delimiter and is simply never inserted; the rest of
371
+ * that delimiter is in the document and is deleted. Undoable, like every
372
+ * input rule: Backspace straight after gives the characters back.
373
+ */
374
+ function markRule(pattern: RegExp, type: MarkType): InputRule {
375
+ return new InputRule(pattern, (state, match, start, end) => {
376
+ const whole = match[1];
377
+ const inner = match[2];
378
+ const from = start + match[0].length - whole.length;
379
+ const textStart = from + whole.indexOf(inner);
380
+ const textEnd = textStart + inner.length;
381
+ const tr = state.tr;
382
+ if (end > textEnd) tr.delete(textEnd, end);
383
+ tr.delete(from, textStart);
384
+ tr.addMark(from, from + inner.length, type.create());
385
+ tr.removeStoredMark(type);
386
+ return tr;
387
+ });
388
+ }
389
+
390
+ /** `---` (or `***`/`___` and a space) alone in a paragraph: a rule, and a
391
+ * fresh paragraph after it to go on typing in. */
392
+ function ruleRule(rule: NodeType): InputRule {
393
+ return new InputRule(/^(?:---|___\s|\*\*\*\s)$/, (state, _match, start) => {
394
+ const $start = state.doc.resolve(start);
395
+ const para = $start.parent;
396
+ if (!para.isTextblock || $start.parentOffset !== 0 || $start.depth < 1)
397
+ return null;
398
+ const parent = $start.node(-1);
399
+ const index = $start.index(-1);
400
+ const content = Fragment.from([rule.create(), para.type.create()]);
401
+ if (!parent.canReplace(index, index + 1, content)) return null;
402
+ const pos = $start.before();
403
+ const tr = state.tr.replaceWith(pos, pos + para.nodeSize, content);
404
+ return tr.setSelection(TextSelection.near(tr.doc.resolve(pos + 2)));
405
+ });
406
+ }
407
+
408
+ /** `[ ] ` or `[x] ` at the start of a paragraph — or of a list item's first
409
+ * paragraph — makes it a task. */
410
+ function taskRule(schema: Schema): InputRule | null {
411
+ const { list_item: item, bullet_list: list, paragraph } = schema.nodes;
412
+ if (!item || !list || !paragraph || !('checked' in (item.spec.attrs ?? {})))
413
+ return null;
414
+ return new InputRule(/^\[( |x|X)?\]\s$/, (state, match, start, end) => {
415
+ const checked = (match[1] ?? ' ').toLowerCase() === 'x';
416
+ const $start = state.doc.resolve(start);
417
+ if ($start.parentOffset !== 0 || $start.parent.type !== paragraph)
418
+ return null;
419
+ const tr = state.tr.delete(start, end);
420
+ const d = $start.depth - 1;
421
+ if (d > 0 && $start.node(d).type === item && $start.index(d) === 0) {
422
+ return tr.setNodeMarkup($start.before(d), undefined, {
423
+ ...$start.node(d).attrs,
424
+ checked,
425
+ });
426
+ }
427
+ const range = tr.doc.resolve(start).blockRange();
428
+ const wrapping = range && findWrapping(range, list);
429
+ if (!range || !wrapping) return null;
430
+ tr.wrap(range, wrapping);
431
+ // the item the wrap just made sits two positions before the paragraph
432
+ const itemPos = range.start + 1;
433
+ const made = tr.doc.nodeAt(itemPos);
434
+ if (made?.type === item)
435
+ tr.setNodeMarkup(itemPos, undefined, { ...made.attrs, checked });
436
+ return tr;
437
+ });
438
+ }
439
+
440
+ /** Markdown shortcuts, for whatever of them the schema can hold. */
441
+ export function markdownInputRules(
442
+ schema: Schema,
443
+ options: { markdown?: boolean; typography?: boolean } = {},
444
+ ): Plugin {
445
+ const rules: InputRule[] = [];
446
+ const { nodes, marks } = schema;
447
+ if (options.typography) rules.push(...smartQuotes, ellipsis, emDash);
448
+ if (options.markdown !== false) {
449
+ if (nodes.blockquote)
450
+ rules.push(wrappingInputRule(/^\s*>\s$/, nodes.blockquote));
451
+ if (nodes.ordered_list) {
452
+ rules.push(
453
+ wrappingInputRule(
454
+ /^(\d+)\.\s$/,
455
+ nodes.ordered_list,
456
+ (match) => ({ order: Number(match[1]) }),
457
+ (match, node) =>
458
+ node.childCount + Number(node.attrs.order) === Number(match[1]),
459
+ ),
460
+ );
461
+ }
462
+ const task = taskRule(schema);
463
+ if (task) rules.push(task);
464
+ if (nodes.bullet_list)
465
+ rules.push(wrappingInputRule(/^\s*([-+*])\s$/, nodes.bullet_list));
466
+ if (nodes.code_block) {
467
+ rules.push(
468
+ textblockTypeInputRule(
469
+ /^```([^\s`]*)\s$/,
470
+ nodes.code_block,
471
+ (match) => ({
472
+ params: match[1] ?? '',
473
+ }),
474
+ ),
475
+ );
476
+ }
477
+ if (nodes.heading) {
478
+ rules.push(
479
+ textblockTypeInputRule(/^(#{1,6})\s$/, nodes.heading, (match) => ({
480
+ level: match[1].length,
481
+ })),
482
+ );
483
+ }
484
+ if (nodes.horizontal_rule) rules.push(ruleRule(nodes.horizontal_rule));
485
+ // strong before em: `**x*` must not become italic on its way to bold
486
+ if (marks.strong) {
487
+ rules.push(
488
+ markRule(/(?:^|[^*\\])(\*\*(?!\s)([^*]+?)(?<!\s)\*\*)$/, marks.strong),
489
+ markRule(
490
+ /(?:^|[^_\\\p{L}\p{N}])(__(?!\s)([^_]+?)(?<!\s)__)$/u,
491
+ marks.strong,
492
+ ),
493
+ );
494
+ }
495
+ if (marks.em) {
496
+ rules.push(
497
+ markRule(/(?:^|[^*\\])(\*(?!\s)([^*]+?)(?<!\s)\*)$/, marks.em),
498
+ markRule(/(?:^|[^_\\\p{L}\p{N}])(_(?!\s)([^_]+?)(?<!\s)_)$/u, marks.em),
499
+ );
500
+ }
501
+ if (marks.code)
502
+ rules.push(markRule(/(?:^|[^`\\])(`([^`]+)`)$/, marks.code));
503
+ if (marks.strike) {
504
+ rules.push(
505
+ markRule(/(?:^|[^~\\])(~~(?!\s)([^~]+?)(?<!\s)~~)$/, marks.strike),
506
+ );
507
+ }
508
+ }
509
+ return inputRules({ rules });
510
+ }
@@ -0,0 +1,119 @@
1
+ // Stable identities for the blocks of a document, carried across edits.
2
+ //
3
+ // React needs a key per rendered block, and the three obvious ones are all
4
+ // wrong for an editor:
5
+ //
6
+ // - **an index** shifts every block after an inserted paragraph, so Enter
7
+ // at the top of a long document re-renders — and re-lays-out — every
8
+ // paragraph below it;
9
+ // - **a position** shifts on every keystroke, for the same reason;
10
+ // - **a node's identity** changes whenever its content does, because
11
+ // ProseMirror's nodes are immutable, so typing would remount the very
12
+ // paragraph being typed in and throw its layout cache away.
13
+ //
14
+ // What survives an edit is what ProseMirror's own view keys its DOM on: a
15
+ // node's *place*, mapped through the transaction. A block whose start maps
16
+ // cleanly keeps its key; one whose opening token the transaction deleted —
17
+ // the second half of a join — loses it, and a block nobody had before (the
18
+ // second half of a split) gets a fresh one. When there is no mapping — a
19
+ // whole state handed in from outside, the controlled-state rung — the node
20
+ // objects that survived are matched by identity instead, which is the other
21
+ // thing ProseMirror's structural sharing guarantees.
22
+ //
23
+ // Pure: no React, no react-x11. The component asks it three questions — the
24
+ // key at a position, the position of a key, the node under a key — and
25
+ // `test/rich-text-editor-model.test.ts` asks it the rest.
26
+ import type { Node as PMNode } from 'prosemirror-model';
27
+ import type { Mappable } from 'prosemirror-transform';
28
+
29
+ export class BlockKeys {
30
+ private counter = 0;
31
+ /** Block start position → key, for the current document. */
32
+ private byPos = new Map<number, string>();
33
+ private posByKey = new Map<string, number>();
34
+ private nodeByKey = new Map<string, PMNode>();
35
+
36
+ constructor(doc: PMNode) {
37
+ this.update(doc, null);
38
+ }
39
+
40
+ /** How many blocks the document has — every non-inline node but the
41
+ * document itself. */
42
+ get size(): number {
43
+ return this.byPos.size;
44
+ }
45
+
46
+ /** The key of the block that starts at `pos`, if one does. */
47
+ keyAt(pos: number): string | undefined {
48
+ return this.byPos.get(pos);
49
+ }
50
+
51
+ /** Where the block under `key` starts now. */
52
+ posOf(key: string): number | undefined {
53
+ return this.posByKey.get(key);
54
+ }
55
+
56
+ /** The node the block under `key` is now. */
57
+ nodeOf(key: string): PMNode | undefined {
58
+ return this.nodeByKey.get(key);
59
+ }
60
+
61
+ /**
62
+ * Re-key for a new document. `mapping` is the transaction (or the run of
63
+ * transactions) that produced it from the one the keys were last built
64
+ * for; null when nothing knows how the two relate.
65
+ *
66
+ * O(blocks), which is the right order: 10,000 paragraphs is a millisecond,
67
+ * and the render that follows touches only the blocks whose node changed.
68
+ */
69
+ update(doc: PMNode, mapping: Mappable | null): void {
70
+ // 1. Where each old block's start went.
71
+ const carried = new Map<number, string>();
72
+ if (mapping) {
73
+ for (const [pos, key] of this.byPos) {
74
+ const result = mapping.mapResult(pos, 1);
75
+ // the token after the start is the block's own opening — gone means
76
+ // the block is gone, merged into whatever preceded it
77
+ if (result.deletedAfter) continue;
78
+ if (!carried.has(result.pos)) carried.set(result.pos, key);
79
+ }
80
+ }
81
+ // 2. Which old node objects are still here, for when nothing mapped.
82
+ const byNode = new Map<PMNode, string[]>();
83
+ const carriedKeys = new Set(carried.values());
84
+ for (const [key, node] of this.nodeByKey) {
85
+ if (carriedKeys.has(key)) continue;
86
+ const list = byNode.get(node);
87
+ if (list) list.push(key);
88
+ else byNode.set(node, [key]);
89
+ }
90
+
91
+ const byPos = new Map<number, string>();
92
+ const posByKey = new Map<string, number>();
93
+ const nodeByKey = new Map<string, PMNode>();
94
+ doc.descendants((node, pos) => {
95
+ if (node.isInline) return false;
96
+ let key = carried.get(pos);
97
+ if (key !== undefined && posByKey.has(key)) key = undefined;
98
+ if (key === undefined) {
99
+ const list = byNode.get(node);
100
+ while (list && list.length > 0) {
101
+ const candidate = list.shift()!;
102
+ if (!posByKey.has(candidate)) {
103
+ key = candidate;
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ if (key === undefined) key = `b${(this.counter++).toString(36)}`;
109
+ byPos.set(pos, key);
110
+ posByKey.set(key, pos);
111
+ nodeByKey.set(key, node);
112
+ // a textblock's children are inline: nothing below it has a key
113
+ return !node.isTextblock;
114
+ });
115
+ this.byPos = byPos;
116
+ this.posByKey = posByKey;
117
+ this.nodeByKey = nodeByKey;
118
+ }
119
+ }