@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,347 @@
1
+ // Long documents: only the top-level blocks near the viewport are drawn.
2
+ //
3
+ // The machinery is `<Tree>`'s and `<Table>`'s — the height index
4
+ // (../internal/heights.ts), the window (../internal/window.ts) and the owed
5
+ // reveal (../internal/scroll.ts) — laid over the document's top-level blocks
6
+ // and keyed by their block keys (./keys.ts), so a height measured once
7
+ // survives every edit that keeps its block. Each drawn block is in a box of
8
+ // its own, measured after layout (./render.ts, `renderBlockRange`), and the
9
+ // blocks outside the window are two spacers, so the scrollbar measures the
10
+ // whole document.
11
+ //
12
+ // The view's geometry is laid-out text — a caret, a hit, the line an arrow
13
+ // key moves to — so a block that is not drawn has none. The view asks this
14
+ // window (`BlockWindow`) whether the block a position is in is drawn, and
15
+ // when it is not, because the reader scrolled away from the caret, it has
16
+ // the block revealed first and does what it meant to once the block is laid
17
+ // out (./view.ts: `scrollToSelection` and `move`).
18
+ //
19
+ // A block is measured with the gap after it, so that the index's offsets are
20
+ // where blocks start: the content column keeps its `gap`, and each spacer
21
+ // gives back the one gap it stands beside.
22
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
23
+ import type { Node as PMNode } from 'prosemirror-model';
24
+ import type { DrawnNode, ScrollableNode } from 'react-x11';
25
+
26
+ import { RowHeights } from '../internal/heights.js';
27
+ import type { RowKey } from '../internal/heights.js';
28
+ import { useReveal } from '../internal/scroll.js';
29
+ import {
30
+ afterLayout,
31
+ cancelAfterLayout,
32
+ cancelLater,
33
+ later,
34
+ } from '../internal/timers.js';
35
+ import type { DelayTick, LayoutTick } from '../internal/timers.js';
36
+ import { scaleOf } from '../internal/units.js';
37
+ import {
38
+ BURST_BUDGET,
39
+ DEFAULT_OVERSCAN,
40
+ DEFAULT_PREFETCH,
41
+ SETTLE_BUDGET,
42
+ useVirtualWindow,
43
+ } from '../internal/window.js';
44
+ import type { BlockKeys } from './keys.js';
45
+
46
+ /** Top-level blocks past which `virtual: 'auto'` draws only a window of
47
+ * them — `<Tree>`'s threshold, and far more than a composer ever holds. */
48
+ export const VIRTUAL_THRESHOLD = 200;
49
+
50
+ /** What the view asks of a long document's window. */
51
+ export interface BlockWindow {
52
+ /** Whether top-level block `index` is drawn and laid out. */
53
+ drawn(index: number): boolean;
54
+ /** Scroll top-level block `index` into view, and so draw it; `then` runs
55
+ * once it is laid out — unless the reader scrolls somewhere else first,
56
+ * which drops it. */
57
+ reveal(index: number, then: () => void): void;
58
+ }
59
+
60
+ interface Row {
61
+ id: string;
62
+ }
63
+
64
+ interface Drawn {
65
+ node: DrawnNode;
66
+ at: number;
67
+ }
68
+
69
+ /** The document's top-level blocks, by key, in order. */
70
+ function topBlocks(doc: PMNode, keys: BlockKeys): Row[] {
71
+ const rows: Row[] = [];
72
+ doc.forEach((_node, offset) => {
73
+ rows.push({ id: keys.keyAt(offset) ?? `p${offset}` });
74
+ });
75
+ return rows;
76
+ }
77
+
78
+ export interface BlockWindowInputs {
79
+ doc: PMNode;
80
+ keys: BlockKeys;
81
+ virtual: boolean | 'auto';
82
+ /** What a block nobody has measured is guessed at, gap included —
83
+ * logical pixels, like every length here. */
84
+ estimate: number;
85
+ /** The content column's gap between blocks. */
86
+ gap: number;
87
+ }
88
+
89
+ export interface BlockWindowState {
90
+ virtualizing: boolean;
91
+ /** The top-level blocks to draw, `last` exclusive. */
92
+ first: number;
93
+ last: number;
94
+ /** The spacers before and after them — 0 for none. */
95
+ above: number;
96
+ below: number;
97
+ /** The scroll pane. */
98
+ box: { current: ScrollableNode | null };
99
+ /** The pane's handlers, while the window is on. */
100
+ onViewport(ev: { width: number; height: number }): void;
101
+ onScroll(ev: { scrollY: number }): void;
102
+ /** A drawn block's box as it mounts, and null as it goes. */
103
+ register(key: string, at: number, node: unknown): void;
104
+ /** What the view asks — see `BlockWindow`. */
105
+ window: BlockWindow;
106
+ }
107
+
108
+ /** The window over a document's top-level blocks. Call every render, with
109
+ * the document about to be drawn. */
110
+ export function useBlockWindow(inputs: BlockWindowInputs): BlockWindowState {
111
+ const { doc, keys, virtual, estimate, gap } = inputs;
112
+ const box = useRef<ScrollableNode | null>(null);
113
+ const [, setMeasured] = useState(0);
114
+ const rows = useMemo(() => topBlocks(doc, keys), [doc, keys]);
115
+ const rowsRef = useRef(rows);
116
+ rowsRef.current = rows;
117
+ const gapRef = useRef(gap);
118
+ gapRef.current = gap;
119
+ /** The drawn blocks' boxes, by key, with the index each was drawn at. */
120
+ const nodes = useRef(new Map<RowKey, Drawn>());
121
+ const virtualizing =
122
+ virtual === true || (virtual === 'auto' && rows.length > VIRTUAL_THRESHOLD);
123
+
124
+ // One index per editor, for as long as it is mounted. A new look is a new
125
+ // height for every block, so what was measured under the old one goes.
126
+ const [heights] = useState(() => new RowHeights(estimate));
127
+ const estimated = useRef(estimate);
128
+ if (estimated.current !== estimate) {
129
+ estimated.current = estimate;
130
+ heights.reset();
131
+ }
132
+ heights.sync(rows, estimate);
133
+
134
+ const win = useVirtualWindow({
135
+ box,
136
+ heights,
137
+ rows,
138
+ exact: false,
139
+ virtualizing,
140
+ overscan: DEFAULT_OVERSCAN,
141
+ prefetch: DEFAULT_PREFETCH,
142
+ // a block is built whole or not at all: no skeletons
143
+ threshold: Number.POSITIVE_INFINITY,
144
+ burstBudget: BURST_BUDGET,
145
+ settleBudget: SETTLE_BUDGET,
146
+ });
147
+ const reveal = useReveal({ box, rows: rowsRef, nodes, heights });
148
+
149
+ /** A reveal the view is waiting on, and what it does once the block is
150
+ * laid out. */
151
+ const owed = useRef<{ id: string; then: () => void } | null>(null);
152
+
153
+ const drawnAt = useCallback((index: number): boolean => {
154
+ const row = rowsRef.current[index];
155
+ const drawn = row ? nodes.current.get(row.id) : undefined;
156
+ return !!drawn && drawn.at === index && drawn.node.abs.height > 0;
157
+ }, []);
158
+
159
+ /** What the drawn blocks laid out at, into the index — `<Tree>`'s pass:
160
+ * what grew above the viewport comes back out of the offset, so the text
161
+ * being read does not move. */
162
+ const measure = (): boolean => {
163
+ const pane = box.current;
164
+ const list = rowsRef.current;
165
+ const anchor = pane ? heights.indexAt(pane.scrollY / scaleOf(pane)) : 0;
166
+ let shift = 0;
167
+ let changed = false;
168
+ for (const [id, { node, at }] of nodes.current) {
169
+ if (list[at]?.id !== id || !(node.abs.height > 0)) continue;
170
+ // `abs` is device, the index logical (../internal/units.ts)
171
+ const height = node.abs.height / scaleOf(node) + gapRef.current;
172
+ const was = heights.heightAt(at);
173
+ if (!heights.measure(id, at, height)) continue;
174
+ changed = true;
175
+ if (at < anchor) shift += height - was;
176
+ }
177
+ if (!changed) return false;
178
+ reveal.nudge(shift);
179
+ setMeasured((n) => n + 1);
180
+ return true;
181
+ };
182
+
183
+ /** The estimate learns the measured mean — idle only, and the screen
184
+ * kept still by the same anchor arithmetic. */
185
+ const adapt = (): boolean => {
186
+ const pane = box.current;
187
+ if (!pane) return false;
188
+ const anchor = heights.indexAt(pane.scrollY / scaleOf(pane));
189
+ const before = heights.offsetAt(anchor);
190
+ if (!heights.adapt()) return false;
191
+ reveal.nudge(heights.offsetAt(anchor) - before);
192
+ setMeasured((n) => n + 1);
193
+ return true;
194
+ };
195
+
196
+ /** Whether a drawn block has no size yet: a commit can land between frame
197
+ * flushes, and a pass over it reads zeros. */
198
+ const unsized = (): boolean => {
199
+ const list = rowsRef.current;
200
+ for (const [id, { node, at }] of nodes.current) {
201
+ if (list[at]?.id === id && !(node.abs.height > 0)) return true;
202
+ }
203
+ return false;
204
+ };
205
+
206
+ /** Hand the view the block it was waiting on, once it is laid out — or
207
+ * drop the wait, when the block has left the document. */
208
+ const settle = (): void => {
209
+ const o = owed.current;
210
+ if (!o) return;
211
+ const drawn = nodes.current.get(o.id);
212
+ if (
213
+ drawn &&
214
+ rowsRef.current[drawn.at]?.id === o.id &&
215
+ drawn.node.abs.height > 0
216
+ ) {
217
+ owed.current = null;
218
+ o.then();
219
+ } else if (!rowsRef.current.some((row) => row.id === o.id)) {
220
+ owed.current = null;
221
+ }
222
+ };
223
+
224
+ // The tick after layout, and everything only known there: what the blocks
225
+ // measured, whether an owed scroll can go further, where the pane really
226
+ // is, and whether the block the view waits on is laid out — in that order,
227
+ // each able to move what the next one reads.
228
+ const ticks = useRef<{ layout: LayoutTick; look: DelayTick; tries: number }>({
229
+ layout: null,
230
+ look: null,
231
+ tries: 0,
232
+ });
233
+ const pass = useRef<() => void>(() => {});
234
+ pass.current = (): void => {
235
+ if (!virtualizing) return;
236
+ const pane = box.current;
237
+ // `onViewport` reports a change, so a window turned on after the pane's
238
+ // first layout has never heard its size: read it here
239
+ if (pane && win.viewRef.current.height === 0 && pane.abs.height > 0) {
240
+ const s = scaleOf(pane);
241
+ win.sized(pane.abs.width / s, pane.abs.height / s);
242
+ }
243
+ const moved = win.fast() ? false : measure();
244
+ const adapted = !win.scrolling() && adapt();
245
+ reveal.retry(moved || adapted);
246
+ win.sync();
247
+ settle();
248
+ // look again, briefly, while a block is unsized or the view still waits
249
+ // — nothing else would come back for them
250
+ const t = ticks.current;
251
+ if ((unsized() || owed.current !== null) && t.tries++ < 8) {
252
+ t.look = later(() => {
253
+ t.look = null;
254
+ pass.current();
255
+ }, 16);
256
+ }
257
+ };
258
+ const kick = useCallback((): void => {
259
+ const t = ticks.current;
260
+ cancelAfterLayout(t.layout);
261
+ cancelLater(t.look);
262
+ t.look = null;
263
+ t.tries = 0;
264
+ t.layout = afterLayout(() => {
265
+ t.layout = null;
266
+ pass.current();
267
+ });
268
+ }, []);
269
+ useEffect(() => {
270
+ if (virtualizing) kick();
271
+ });
272
+ useEffect(
273
+ () => () => {
274
+ const t = ticks.current;
275
+ cancelAfterLayout(t.layout);
276
+ cancelLater(t.look);
277
+ },
278
+ [],
279
+ );
280
+
281
+ const { sized, scrolled, sync } = win;
282
+ const onViewport = useCallback(
283
+ (ev: { width: number; height: number }): void => {
284
+ sized(ev.width, ev.height);
285
+ // the content changed size: an owed scroll may reach further, and the
286
+ // pane may have re-clamped its offset without a word
287
+ reveal.retry(true);
288
+ sync();
289
+ },
290
+ [sized, reveal, sync],
291
+ );
292
+ const onScroll = useCallback(
293
+ (ev: { scrollY: number }): void => {
294
+ // a scroll this editor did not ask for is the reader's, and what the
295
+ // view was waiting to do gives way to it
296
+ if (!reveal.heard(ev.scrollY)) owed.current = null;
297
+ scrolled(ev.scrollY);
298
+ },
299
+ [reveal, scrolled],
300
+ );
301
+
302
+ const register = useCallback(
303
+ (key: string, at: number, node: unknown): void => {
304
+ const map = nodes.current;
305
+ if (node) map.set(key, { node: node as DrawnNode, at });
306
+ else if (map.get(key)?.at === at) map.delete(key);
307
+ },
308
+ [],
309
+ );
310
+
311
+ const window = useMemo<BlockWindow>(
312
+ () => ({
313
+ drawn: drawnAt,
314
+ reveal: (index, then) => {
315
+ const row = rowsRef.current[index];
316
+ if (!row) return;
317
+ if (drawnAt(index)) {
318
+ then();
319
+ return;
320
+ }
321
+ owed.current = { id: row.id, then };
322
+ reveal.to(row.id);
323
+ // the reveal may need no render of its own — a block already in the
324
+ // window, not yet laid out — so a pass is asked for here
325
+ kick();
326
+ },
327
+ }),
328
+ [drawnAt, reveal, kick],
329
+ );
330
+
331
+ const { first, last } = win.slice;
332
+ return {
333
+ virtualizing,
334
+ first,
335
+ last,
336
+ above: virtualizing && first > 0 ? Math.max(0, win.slice.above - gap) : 0,
337
+ below:
338
+ virtualizing && last < rows.length
339
+ ? Math.max(0, win.slice.below - gap)
340
+ : 0,
341
+ box,
342
+ onViewport,
343
+ onScroll,
344
+ register,
345
+ window,
346
+ };
347
+ }
@@ -37,6 +37,7 @@ import {
37
37
  paintRunBackgrounds,
38
38
  paintRunRules,
39
39
  } from './runs.js';
40
+ import type { FillContext } from './runs.js';
40
41
  import { codeUnitOffsets } from '../internal/text.js';
41
42
 
42
43
  /** The element name — registration key, `node.kind` and JSX tag alike. */
@@ -215,8 +216,18 @@ export class RichTextNode extends Node {
215
216
  private _layouts = new Map<string, TextLayoutLike | null>();
216
217
  private _text: string | null = null;
217
218
 
218
- constructor(props: Record<string, unknown>, app: NtkApp) {
219
- super(ELEMENT, props, app);
219
+ /**
220
+ * `kind` is for a subclass registered under a name of its own — the rich
221
+ * text editor's text blocks are `<richtext>` plus a caret and a selection
222
+ * the editor owns (`src/rich-text-editor/nodes.ts`). A node's `kind` must be
223
+ * the name it was registered under, so the subclass passes its own.
224
+ */
225
+ constructor(
226
+ props: Record<string, unknown>,
227
+ app: NtkApp,
228
+ kind: string = ELEMENT,
229
+ ) {
230
+ super(kind, props, app);
220
231
  }
221
232
 
222
233
  /**
@@ -302,8 +313,10 @@ export class RichTextNode extends Node {
302
313
  * The layout this node is currently painted with — and, because it is the
303
314
  * one thing every accessor below answers from, the reason a caret rect and
304
315
  * a glyph cannot disagree (docs/extending.md, "answer from what you draw").
316
+ * Protected rather than private for the editor's subclass, whose line-wise
317
+ * caret motion reads the lines off the same layout the glyphs came from.
305
318
  */
306
- private _paintLayout(): TextLayoutLike | null {
319
+ protected paintLayout(): TextLayoutLike | null {
307
320
  const wrap = (this.props as unknown as RichTextProps).wrap !== false;
308
321
  return this._layoutFor(wrap ? this.abs.width : Infinity);
309
322
  }
@@ -347,14 +360,14 @@ export class RichTextNode extends Node {
347
360
  }
348
361
 
349
362
  override textIndexAt(x: number, y: number): number {
350
- const layout = this._paintLayout();
363
+ const layout = this.paintLayout();
351
364
  if (!layout) return 0;
352
365
  const i = layout.indexAt(x - this.abs.x, y - this.abs.y);
353
366
  return Math.max(0, Math.min([...this.textContent()].length, i));
354
367
  }
355
368
 
356
369
  override textCaretRect(index: number): Rect | null {
357
- const layout = this._paintLayout();
370
+ const layout = this.paintLayout();
358
371
  if (!layout) return null;
359
372
  const caret = layout.caretPosition(index);
360
373
  return {
@@ -366,7 +379,7 @@ export class RichTextNode extends Node {
366
379
  }
367
380
 
368
381
  override textRangeRects(start: number, end: number): Rect[] {
369
- const layout = this._paintLayout();
382
+ const layout = this.paintLayout();
370
383
  if (!layout) return [];
371
384
  return rangeBands(layout, this.textContent(), start, end).map((band) => ({
372
385
  x: this.abs.x + band.x,
@@ -381,7 +394,7 @@ export class RichTextNode extends Node {
381
394
  * selection seam: core deliberately left hover and `cursorAt` out of
382
395
  * #291, so following a link stays this package's. */
383
396
  hrefAtPoint(x: number, y: number): string | null {
384
- const layout = this._paintLayout();
397
+ const layout = this.paintLayout();
385
398
  if (!layout) return null;
386
399
  const s = this._scale;
387
400
  const lx = x * s - this.abs.x;
@@ -401,7 +414,7 @@ export class RichTextNode extends Node {
401
414
 
402
415
  override paint(ctx: Context2D): void {
403
416
  super.paint(ctx); // background, border, clip to `abs`
404
- const layout = this._paintLayout();
417
+ const layout = this.paintLayout();
405
418
  if (!layout || !canFill(ctx)) return;
406
419
  const { x, y } = this.abs;
407
420
  const s = this._scale;
@@ -412,28 +425,8 @@ export class RichTextNode extends Node {
412
425
  // terminal's cell backgrounds
413
426
  for (const line of layout.lines) paintRunBackgrounds(ctx, line, x, y, s);
414
427
 
415
- // 2. the band the document selection has claimed of this element's text,
416
- // translucent so the ink keeps its contrast on either palette (the
417
- // same reasoning as `<textarea>`'s). The range and the colour both
418
- // arrive from the surface above; `textRangeRects` is what a custom
419
- // element and core's own `<text>` both fill, so they cannot drift.
420
- const range = this.selectionRange;
421
- // The colour is the condition, not just the range: core types it
422
- // `string | null` and defines it as what to fill the rectangles with
423
- // *while a selection is set*, so a null one is "nothing to draw"
424
- // rather than a band in the default ink.
425
- const selectionColor = this.selectionColor;
426
- if (selectionColor && range && range.end > range.start) {
427
- ctx.fillStyle = selectionColor;
428
- for (const r of this.textRangeRects(range.start, range.end)) {
429
- ctx.fillRect(
430
- Math.round(r.x),
431
- Math.round(r.y),
432
- Math.ceil(r.width),
433
- Math.ceil(r.height),
434
- );
435
- }
436
- }
428
+ // 2. the band a selection has claimed of this element's text
429
+ this.paintSelection(ctx);
437
430
 
438
431
  // 3. the ink
439
432
  layout.draw(ctx, x, y);
@@ -441,6 +434,42 @@ export class RichTextNode extends Node {
441
434
  // 4. rules over the ink: link underlines, strikethrough
442
435
  for (const line of layout.lines) paintRunRules(ctx, line, x, y, s);
443
436
 
437
+ // 5. anything that belongs over everything — a subclass's caret
438
+ this.paintOverlay(ctx);
439
+
444
440
  ctx.restore();
445
441
  }
442
+
443
+ /**
444
+ * The selection band, between the run backgrounds and the ink. The default
445
+ * is core's document selection: the band the `selectable` surface above
446
+ * has claimed of this element's text, translucent so the ink keeps its
447
+ * contrast on either palette (the same reasoning as `<textarea>`'s). The
448
+ * range and the colour both arrive from that surface; `textRangeRects` is
449
+ * what a custom element and core's own `<text>` both fill, so they cannot
450
+ * drift. An element with a selection of its own — the editor's text
451
+ * blocks — overrides this and fills that one instead.
452
+ */
453
+ protected paintSelection(ctx: FillContext): void {
454
+ const range = this.selectionRange;
455
+ // The colour is the condition, not just the range: core types it
456
+ // `string | null` and defines it as what to fill the rectangles with
457
+ // *while a selection is set*, so a null one is "nothing to draw"
458
+ // rather than a band in the default ink.
459
+ const selectionColor = this.selectionColor;
460
+ if (!selectionColor || !range || range.end <= range.start) return;
461
+ ctx.fillStyle = selectionColor;
462
+ for (const r of this.textRangeRects(range.start, range.end)) {
463
+ ctx.fillRect(
464
+ Math.round(r.x),
465
+ Math.round(r.y),
466
+ Math.ceil(r.width),
467
+ Math.ceil(r.height),
468
+ );
469
+ }
470
+ }
471
+
472
+ /** Drawn last, over the ink and the rules: nothing here, a caret in the
473
+ * editor's subclass. Inside the same save/restore as the rest. */
474
+ protected paintOverlay(_ctx: FillContext): void {}
446
475
  }
@@ -70,6 +70,7 @@ import {
70
70
  cancelLater,
71
71
  later,
72
72
  } from '../internal/timers.js';
73
+ import { scaleOf } from '../internal/units.js';
73
74
  import type { DelayTick } from '../internal/timers.js';
74
75
  import { useReveal } from '../internal/scroll.js';
75
76
  import {
@@ -953,7 +954,10 @@ export function Table<Row = any>(props: TableProps<Row>): ReactElement {
953
954
  const syncScroll = useCallback((): void => {
954
955
  const box = body.current;
955
956
  if (!box) return;
956
- const x = box.scrollX;
957
+ // Device on the raw property; the header margin it feeds is a style
958
+ // length, logical — the same division every raw read here makes
959
+ // (../internal/units.ts).
960
+ const x = box.scrollX / scaleOf(box);
957
961
  setScrollX((prev) => (prev === x ? prev : x));
958
962
  winSync();
959
963
  }, [winSync]);
@@ -992,13 +996,18 @@ export function Table<Row = any>(props: TableProps<Row>): ReactElement {
992
996
  ).total;
993
997
  const box = body.current;
994
998
  const rows = orderedRef.current;
995
- const anchor = box ? heights.indexAt(box.scrollY) : 0;
999
+ const anchor = box ? heights.indexAt(box.scrollY / scaleOf(box)) : 0;
996
1000
  let shift = 0;
997
1001
  let changed = false;
998
1002
  for (const [id, { node, at }] of rowNodes.current) {
999
1003
  if (rows[at]?.id !== id) continue; // drawn against a list that moved
1000
- if (node.abs.width !== expected) continue;
1001
- const height = node.abs.height;
1004
+ // `abs` is device; the resolved widths and the height index are
1005
+ // logical. Within a pixel rather than exact: a fractional scale
1006
+ // rounds the device width to the pixel grid, and the stale-width
1007
+ // layouts this gate exists for are whole columns off, not fractions.
1008
+ const s = scaleOf(node);
1009
+ if (Math.abs(node.abs.width / s - expected) >= 1) continue;
1010
+ const height = node.abs.height / s;
1002
1011
  const was = heights.heightAt(at);
1003
1012
  if (!heights.measure(id, at, height)) continue;
1004
1013
  changed = true;
@@ -1026,7 +1035,7 @@ export function Table<Row = any>(props: TableProps<Row>): ReactElement {
1026
1035
  if (uniform || !virtualizing) return false;
1027
1036
  const box = body.current;
1028
1037
  if (!box) return false;
1029
- const anchor = heights.indexAt(box.scrollY);
1038
+ const anchor = heights.indexAt(box.scrollY / scaleOf(box));
1030
1039
  const before = heights.offsetAt(anchor);
1031
1040
  if (!heights.adapt()) return false;
1032
1041
  reveal.nudge(heights.offsetAt(anchor) - before);
@@ -21,7 +21,10 @@
21
21
  * program installed and is the only backend `write()` can work on.
22
22
  *
23
23
  * `'auto'` takes the first embeddable emulator that is installed and falls
24
- * back to `'vt'`. That order — rather than vt first — is deliberate: an
24
+ * back to `'vt'` — and on a react-x11 backend that cannot host an embedded
25
+ * window at all (the native macOS one) it takes `'vt'` without looking for
26
+ * the other three, because an emulator found on `PATH` there is one this app
27
+ * cannot show. That order — rather than vt first — is deliberate: an
25
28
  * external emulator is battle-tested, and a user who installed one expressed
26
29
  * a preference. Moving vt to the front is a later, deliberate decision.
27
30
  */