@react-x11/components 0.8.0 → 0.10.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 (721) hide show
  1. package/README.md +105 -38
  2. package/dist/charts/node.d.ts +12 -2
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +50 -16
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/charts/render.d.ts +12 -3
  7. package/dist/charts/render.d.ts.map +1 -1
  8. package/dist/charts/render.js +94 -25
  9. package/dist/charts/render.js.map +1 -1
  10. package/dist/code/index.d.ts.map +1 -1
  11. package/dist/code/index.js +3 -1
  12. package/dist/code/index.js.map +1 -1
  13. package/dist/code-editor/completion.d.ts.map +1 -1
  14. package/dist/code-editor/completion.js +3 -2
  15. package/dist/code-editor/completion.js.map +1 -1
  16. package/dist/code-editor/index.d.ts.map +1 -1
  17. package/dist/code-editor/index.js +19 -0
  18. package/dist/code-editor/index.js.map +1 -1
  19. package/dist/code-editor/node.d.ts +177 -7
  20. package/dist/code-editor/node.d.ts.map +1 -1
  21. package/dist/code-editor/node.js +1329 -164
  22. package/dist/code-editor/node.js.map +1 -1
  23. package/dist/code-language/languages/glsl.d.ts.map +1 -1
  24. package/dist/code-language/languages/glsl.js +10 -2
  25. package/dist/code-language/languages/glsl.js.map +1 -1
  26. package/dist/code-language/languages/javascript.d.ts.map +1 -1
  27. package/dist/code-language/languages/javascript.js +18 -3
  28. package/dist/code-language/languages/javascript.js.map +1 -1
  29. package/dist/code-language/languages/json.d.ts.map +1 -1
  30. package/dist/code-language/languages/json.js +10 -2
  31. package/dist/code-language/languages/json.js.map +1 -1
  32. package/dist/code-language/languages/shell.d.ts.map +1 -1
  33. package/dist/code-language/languages/shell.js +10 -2
  34. package/dist/code-language/languages/shell.js.map +1 -1
  35. package/dist/code-language/languages/sql.d.ts.map +1 -1
  36. package/dist/code-language/languages/sql.js +20 -2
  37. package/dist/code-language/languages/sql.js.map +1 -1
  38. package/dist/code-language/stream.d.ts +22 -0
  39. package/dist/code-language/stream.d.ts.map +1 -1
  40. package/dist/code-language/stream.js +259 -9
  41. package/dist/code-language/stream.js.map +1 -1
  42. package/dist/code-language/types.d.ts +5 -4
  43. package/dist/code-language/types.d.ts.map +1 -1
  44. package/dist/codeblock/index.d.ts +13 -2
  45. package/dist/codeblock/index.d.ts.map +1 -1
  46. package/dist/codeblock/index.js +20 -3
  47. package/dist/codeblock/index.js.map +1 -1
  48. package/dist/embed/client.d.ts +48 -3
  49. package/dist/embed/client.d.ts.map +1 -1
  50. package/dist/embed/client.js +79 -5
  51. package/dist/embed/client.js.map +1 -1
  52. package/dist/embed/index.d.ts +1 -1
  53. package/dist/embed/index.d.ts.map +1 -1
  54. package/dist/embed/index.js +1 -1
  55. package/dist/embed/index.js.map +1 -1
  56. package/dist/flow/draw.d.ts +50 -0
  57. package/dist/flow/draw.d.ts.map +1 -1
  58. package/dist/flow/draw.js +132 -33
  59. package/dist/flow/draw.js.map +1 -1
  60. package/dist/flow/gl/color.d.ts +13 -0
  61. package/dist/flow/gl/color.d.ts.map +1 -0
  62. package/dist/flow/gl/color.js +82 -0
  63. package/dist/flow/gl/color.js.map +1 -0
  64. package/dist/flow/gl/index.d.ts +60 -0
  65. package/dist/flow/gl/index.d.ts.map +1 -0
  66. package/dist/flow/gl/index.js +283 -0
  67. package/dist/flow/gl/index.js.map +1 -0
  68. package/dist/flow/gl/pack.d.ts +133 -0
  69. package/dist/flow/gl/pack.d.ts.map +1 -0
  70. package/dist/flow/gl/pack.js +408 -0
  71. package/dist/flow/gl/pack.js.map +1 -0
  72. package/dist/flow/gl/renderer.d.ts +95 -0
  73. package/dist/flow/gl/renderer.d.ts.map +1 -0
  74. package/dist/flow/gl/renderer.js +459 -0
  75. package/dist/flow/gl/renderer.js.map +1 -0
  76. package/dist/flow/gl/shaders.d.ts +79 -0
  77. package/dist/flow/gl/shaders.d.ts.map +1 -0
  78. package/dist/flow/gl/shaders.js +346 -0
  79. package/dist/flow/gl/shaders.js.map +1 -0
  80. package/dist/flow/gl/text.d.ts +318 -0
  81. package/dist/flow/gl/text.d.ts.map +1 -0
  82. package/dist/flow/gl/text.js +1103 -0
  83. package/dist/flow/gl/text.js.map +1 -0
  84. package/dist/flow/index.d.ts +1 -1
  85. package/dist/flow/index.d.ts.map +1 -1
  86. package/dist/flow/index.js +619 -64
  87. package/dist/flow/index.js.map +1 -1
  88. package/dist/flow/node.d.ts +621 -72
  89. package/dist/flow/node.d.ts.map +1 -1
  90. package/dist/flow/node.js +2253 -1018
  91. package/dist/flow/node.js.map +1 -1
  92. package/dist/flow/paint.d.ts +35 -0
  93. package/dist/flow/paint.d.ts.map +1 -0
  94. package/dist/flow/paint.js +393 -0
  95. package/dist/flow/paint.js.map +1 -0
  96. package/dist/flow/paths.d.ts +14 -0
  97. package/dist/flow/paths.d.ts.map +1 -1
  98. package/dist/flow/paths.js +45 -19
  99. package/dist/flow/paths.js.map +1 -1
  100. package/dist/flow/scene.d.ts +584 -0
  101. package/dist/flow/scene.d.ts.map +1 -0
  102. package/dist/flow/scene.js +1453 -0
  103. package/dist/flow/scene.js.map +1 -0
  104. package/dist/flow/types.d.ts +111 -1
  105. package/dist/flow/types.d.ts.map +1 -1
  106. package/dist/html/controls.d.ts +28 -0
  107. package/dist/html/controls.d.ts.map +1 -1
  108. package/dist/html/controls.js +52 -2
  109. package/dist/html/controls.js.map +1 -1
  110. package/dist/html/css/calc.d.ts +14 -0
  111. package/dist/html/css/calc.d.ts.map +1 -0
  112. package/dist/html/css/calc.js +344 -0
  113. package/dist/html/css/calc.js.map +1 -0
  114. package/dist/html/css/cascade.d.ts +240 -6
  115. package/dist/html/css/cascade.d.ts.map +1 -1
  116. package/dist/html/css/cascade.js +1327 -66
  117. package/dist/html/css/cascade.js.map +1 -1
  118. package/dist/html/css/color.d.ts +23 -0
  119. package/dist/html/css/color.d.ts.map +1 -0
  120. package/dist/html/css/color.js +740 -0
  121. package/dist/html/css/color.js.map +1 -0
  122. package/dist/html/css/content.d.ts +92 -0
  123. package/dist/html/css/content.d.ts.map +1 -0
  124. package/dist/html/css/content.js +388 -0
  125. package/dist/html/css/content.js.map +1 -0
  126. package/dist/html/css/counter-styles.d.ts +76 -0
  127. package/dist/html/css/counter-styles.d.ts.map +1 -0
  128. package/dist/html/css/counter-styles.js +893 -0
  129. package/dist/html/css/counter-styles.js.map +1 -0
  130. package/dist/html/css/decode.d.ts +12 -0
  131. package/dist/html/css/decode.d.ts.map +1 -0
  132. package/dist/html/css/decode.js +116 -0
  133. package/dist/html/css/decode.js.map +1 -0
  134. package/dist/html/css/parse.d.ts +110 -10
  135. package/dist/html/css/parse.d.ts.map +1 -1
  136. package/dist/html/css/parse.js +1473 -180
  137. package/dist/html/css/parse.js.map +1 -1
  138. package/dist/html/css/style.d.ts +498 -15
  139. package/dist/html/css/style.d.ts.map +1 -1
  140. package/dist/html/css/style.js +3698 -206
  141. package/dist/html/css/style.js.map +1 -1
  142. package/dist/html/css/ua.d.ts.map +1 -1
  143. package/dist/html/css/ua.js +57 -11
  144. package/dist/html/css/ua.js.map +1 -1
  145. package/dist/html/css/values.d.ts +37 -3
  146. package/dist/html/css/values.d.ts.map +1 -1
  147. package/dist/html/css/values.js +122 -48
  148. package/dist/html/css/values.js.map +1 -1
  149. package/dist/html/css/vars.d.ts +52 -0
  150. package/dist/html/css/vars.d.ts.map +1 -0
  151. package/dist/html/css/vars.js +358 -0
  152. package/dist/html/css/vars.js.map +1 -0
  153. package/dist/html/dom.d.ts +30 -4
  154. package/dist/html/dom.d.ts.map +1 -1
  155. package/dist/html/dom.js +278 -17
  156. package/dist/html/dom.js.map +1 -1
  157. package/dist/html/fonts.d.ts +106 -0
  158. package/dist/html/fonts.d.ts.map +1 -0
  159. package/dist/html/fonts.js +565 -0
  160. package/dist/html/fonts.js.map +1 -0
  161. package/dist/html/gif.d.ts +15 -0
  162. package/dist/html/gif.d.ts.map +1 -0
  163. package/dist/html/gif.js +229 -0
  164. package/dist/html/gif.js.map +1 -0
  165. package/dist/html/index.d.ts +50 -11
  166. package/dist/html/index.d.ts.map +1 -1
  167. package/dist/html/index.js +115 -9
  168. package/dist/html/index.js.map +1 -1
  169. package/dist/html/layout/block.d.ts +226 -3
  170. package/dist/html/layout/block.d.ts.map +1 -1
  171. package/dist/html/layout/block.js +2937 -197
  172. package/dist/html/layout/block.js.map +1 -1
  173. package/dist/html/layout/boxes.d.ts +340 -17
  174. package/dist/html/layout/boxes.d.ts.map +1 -1
  175. package/dist/html/layout/boxes.js +1714 -182
  176. package/dist/html/layout/boxes.js.map +1 -1
  177. package/dist/html/layout/cache.d.ts +58 -0
  178. package/dist/html/layout/cache.d.ts.map +1 -0
  179. package/dist/html/layout/cache.js +224 -0
  180. package/dist/html/layout/cache.js.map +1 -0
  181. package/dist/html/layout/collapse.d.ts +45 -0
  182. package/dist/html/layout/collapse.d.ts.map +1 -0
  183. package/dist/html/layout/collapse.js +246 -0
  184. package/dist/html/layout/collapse.js.map +1 -0
  185. package/dist/html/layout/css-grid.d.ts +7 -0
  186. package/dist/html/layout/css-grid.d.ts.map +1 -0
  187. package/dist/html/layout/css-grid.js +900 -0
  188. package/dist/html/layout/css-grid.js.map +1 -0
  189. package/dist/html/layout/flex.d.ts.map +1 -1
  190. package/dist/html/layout/flex.js +1025 -89
  191. package/dist/html/layout/flex.js.map +1 -1
  192. package/dist/html/layout/floats.d.ts +45 -6
  193. package/dist/html/layout/floats.d.ts.map +1 -1
  194. package/dist/html/layout/floats.js +113 -14
  195. package/dist/html/layout/floats.js.map +1 -1
  196. package/dist/html/layout/grid-lines.d.ts +39 -0
  197. package/dist/html/layout/grid-lines.d.ts.map +1 -0
  198. package/dist/html/layout/grid-lines.js +119 -0
  199. package/dist/html/layout/grid-lines.js.map +1 -0
  200. package/dist/html/layout/grid.d.ts +53 -0
  201. package/dist/html/layout/grid.d.ts.map +1 -0
  202. package/dist/html/layout/grid.js +153 -0
  203. package/dist/html/layout/grid.js.map +1 -0
  204. package/dist/html/layout/inline.d.ts +151 -1
  205. package/dist/html/layout/inline.d.ts.map +1 -1
  206. package/dist/html/layout/inline.js +3375 -174
  207. package/dist/html/layout/inline.js.map +1 -1
  208. package/dist/html/layout/shaping.d.ts +6 -0
  209. package/dist/html/layout/shaping.d.ts.map +1 -0
  210. package/dist/html/layout/shaping.js +100 -0
  211. package/dist/html/layout/shaping.js.map +1 -0
  212. package/dist/html/layout/table.d.ts +3 -0
  213. package/dist/html/layout/table.d.ts.map +1 -1
  214. package/dist/html/layout/table.js +681 -175
  215. package/dist/html/layout/table.js.map +1 -1
  216. package/dist/html/layout/tracks.d.ts +44 -0
  217. package/dist/html/layout/tracks.d.ts.map +1 -0
  218. package/dist/html/layout/tracks.js +349 -0
  219. package/dist/html/layout/tracks.js.map +1 -0
  220. package/dist/html/node.d.ts +172 -5
  221. package/dist/html/node.d.ts.map +1 -1
  222. package/dist/html/node.js +1336 -103
  223. package/dist/html/node.js.map +1 -1
  224. package/dist/html/paint.d.ts +92 -3
  225. package/dist/html/paint.d.ts.map +1 -1
  226. package/dist/html/paint.js +3503 -141
  227. package/dist/html/paint.js.map +1 -1
  228. package/dist/html/resources.d.ts +60 -14
  229. package/dist/html/resources.d.ts.map +1 -1
  230. package/dist/html/resources.js +122 -36
  231. package/dist/html/resources.js.map +1 -1
  232. package/dist/html/surfaces.d.ts +23 -0
  233. package/dist/html/surfaces.d.ts.map +1 -0
  234. package/dist/html/surfaces.js +68 -0
  235. package/dist/html/surfaces.js.map +1 -0
  236. package/dist/html/svg.d.ts +80 -0
  237. package/dist/html/svg.d.ts.map +1 -0
  238. package/dist/html/svg.js +435 -0
  239. package/dist/html/svg.js.map +1 -0
  240. package/dist/html/url.d.ts +23 -0
  241. package/dist/html/url.d.ts.map +1 -0
  242. package/dist/html/url.js +80 -0
  243. package/dist/html/url.js.map +1 -0
  244. package/dist/index.d.ts +2 -2
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js +1 -1
  247. package/dist/index.js.map +1 -1
  248. package/dist/internal/collate.d.ts +3 -0
  249. package/dist/internal/collate.d.ts.map +1 -0
  250. package/dist/internal/collate.js +11 -0
  251. package/dist/internal/collate.js.map +1 -0
  252. package/dist/{markdown → internal/markdown}/ast.d.ts +23 -0
  253. package/dist/internal/markdown/ast.d.ts.map +1 -0
  254. package/dist/internal/markdown/ast.js +22 -0
  255. package/dist/internal/markdown/ast.js.map +1 -0
  256. package/dist/internal/markdown/parse.d.ts.map +1 -0
  257. package/dist/{markdown → internal/markdown}/parse.js +221 -30
  258. package/dist/internal/markdown/parse.js.map +1 -0
  259. package/dist/internal/markdown/stringify.d.ts +4 -0
  260. package/dist/internal/markdown/stringify.d.ts.map +1 -0
  261. package/dist/internal/markdown/stringify.js +684 -0
  262. package/dist/internal/markdown/stringify.js.map +1 -0
  263. package/dist/internal/markdown/tags.d.ts.map +1 -0
  264. package/dist/internal/markdown/tags.js.map +1 -0
  265. package/dist/internal/prewarm.d.ts +18 -0
  266. package/dist/internal/prewarm.d.ts.map +1 -0
  267. package/dist/internal/prewarm.js +42 -0
  268. package/dist/internal/prewarm.js.map +1 -0
  269. package/dist/internal/scroll.d.ts +3 -2
  270. package/dist/internal/scroll.d.ts.map +1 -1
  271. package/dist/internal/scroll.js +1 -0
  272. package/dist/internal/scroll.js.map +1 -1
  273. package/dist/internal/sdf.d.ts +20 -0
  274. package/dist/internal/sdf.d.ts.map +1 -0
  275. package/dist/internal/sdf.js +216 -0
  276. package/dist/internal/sdf.js.map +1 -0
  277. package/dist/internal/splice.d.ts +12 -0
  278. package/dist/internal/splice.d.ts.map +1 -0
  279. package/dist/internal/splice.js +23 -0
  280. package/dist/internal/splice.js.map +1 -0
  281. package/dist/internal/window.d.ts +12 -0
  282. package/dist/internal/window.d.ts.map +1 -1
  283. package/dist/internal/window.js +23 -6
  284. package/dist/internal/window.js.map +1 -1
  285. package/dist/maps/anchors.d.ts +103 -0
  286. package/dist/maps/anchors.d.ts.map +1 -0
  287. package/dist/maps/anchors.js +790 -0
  288. package/dist/maps/anchors.js.map +1 -0
  289. package/dist/maps/controller.d.ts +340 -0
  290. package/dist/maps/controller.d.ts.map +1 -0
  291. package/dist/maps/controller.js +822 -0
  292. package/dist/maps/controller.js.map +1 -0
  293. package/dist/maps/gl/buckets.d.ts +99 -0
  294. package/dist/maps/gl/buckets.d.ts.map +1 -0
  295. package/dist/maps/gl/buckets.js +365 -0
  296. package/dist/maps/gl/buckets.js.map +1 -0
  297. package/dist/maps/gl/build-worker.d.ts +15 -0
  298. package/dist/maps/gl/build-worker.d.ts.map +1 -0
  299. package/dist/maps/gl/build-worker.js +44 -0
  300. package/dist/maps/gl/build-worker.js.map +1 -0
  301. package/dist/maps/gl/color.d.ts +8 -0
  302. package/dist/maps/gl/color.d.ts.map +1 -0
  303. package/dist/maps/gl/color.js +57 -0
  304. package/dist/maps/gl/color.js.map +1 -0
  305. package/dist/maps/gl/cover.d.ts +70 -0
  306. package/dist/maps/gl/cover.d.ts.map +1 -0
  307. package/dist/maps/gl/cover.js +137 -0
  308. package/dist/maps/gl/cover.js.map +1 -0
  309. package/dist/maps/gl/index.d.ts +3 -0
  310. package/dist/maps/gl/index.d.ts.map +1 -0
  311. package/dist/maps/gl/index.js +20 -0
  312. package/dist/maps/gl/index.js.map +1 -0
  313. package/dist/maps/gl/markers.d.ts +28 -0
  314. package/dist/maps/gl/markers.d.ts.map +1 -0
  315. package/dist/maps/gl/markers.js +60 -0
  316. package/dist/maps/gl/markers.js.map +1 -0
  317. package/dist/maps/gl/overlays.d.ts +68 -0
  318. package/dist/maps/gl/overlays.d.ts.map +1 -0
  319. package/dist/maps/gl/overlays.js +300 -0
  320. package/dist/maps/gl/overlays.js.map +1 -0
  321. package/dist/maps/gl/pane.d.ts +12 -0
  322. package/dist/maps/gl/pane.d.ts.map +1 -0
  323. package/dist/maps/gl/pane.js +37 -0
  324. package/dist/maps/gl/pane.js.map +1 -0
  325. package/dist/maps/gl/placement.d.ts +124 -0
  326. package/dist/maps/gl/placement.d.ts.map +1 -0
  327. package/dist/maps/gl/placement.js +642 -0
  328. package/dist/maps/gl/placement.js.map +1 -0
  329. package/dist/maps/gl/renderer.d.ts +384 -0
  330. package/dist/maps/gl/renderer.d.ts.map +1 -0
  331. package/dist/maps/gl/renderer.js +1184 -0
  332. package/dist/maps/gl/renderer.js.map +1 -0
  333. package/dist/maps/gl/shaders.d.ts +121 -0
  334. package/dist/maps/gl/shaders.d.ts.map +1 -0
  335. package/dist/maps/gl/shaders.js +453 -0
  336. package/dist/maps/gl/shaders.js.map +1 -0
  337. package/dist/maps/gl/store.d.ts +125 -0
  338. package/dist/maps/gl/store.d.ts.map +1 -0
  339. package/dist/maps/gl/store.js +512 -0
  340. package/dist/maps/gl/store.js.map +1 -0
  341. package/dist/maps/gl/text.d.ts +283 -0
  342. package/dist/maps/gl/text.d.ts.map +1 -0
  343. package/dist/maps/gl/text.js +698 -0
  344. package/dist/maps/gl/text.js.map +1 -0
  345. package/dist/maps/gl/view.d.ts +345 -0
  346. package/dist/maps/gl/view.d.ts.map +1 -0
  347. package/dist/maps/gl/view.js +1209 -0
  348. package/dist/maps/gl/view.js.map +1 -0
  349. package/dist/maps/icons.d.ts +32 -0
  350. package/dist/maps/icons.d.ts.map +1 -0
  351. package/dist/maps/icons.js +151 -0
  352. package/dist/maps/icons.js.map +1 -0
  353. package/dist/maps/index.d.ts +3 -1
  354. package/dist/maps/index.d.ts.map +1 -1
  355. package/dist/maps/index.js +218 -52
  356. package/dist/maps/index.js.map +1 -1
  357. package/dist/maps/labels.d.ts +91 -18
  358. package/dist/maps/labels.d.ts.map +1 -1
  359. package/dist/maps/labels.js +376 -140
  360. package/dist/maps/labels.js.map +1 -1
  361. package/dist/maps/node.d.ts +179 -56
  362. package/dist/maps/node.d.ts.map +1 -1
  363. package/dist/maps/node.js +663 -537
  364. package/dist/maps/node.js.map +1 -1
  365. package/dist/maps/overlay.d.ts +72 -9
  366. package/dist/maps/overlay.d.ts.map +1 -1
  367. package/dist/maps/overlay.js +108 -28
  368. package/dist/maps/overlay.js.map +1 -1
  369. package/dist/maps/paint.d.ts +3 -0
  370. package/dist/maps/paint.d.ts.map +1 -1
  371. package/dist/maps/paint.js.map +1 -1
  372. package/dist/maps/proj.d.ts +62 -0
  373. package/dist/maps/proj.d.ts.map +1 -1
  374. package/dist/maps/proj.js +101 -0
  375. package/dist/maps/proj.js.map +1 -1
  376. package/dist/maps/renderer.d.ts +69 -0
  377. package/dist/maps/renderer.d.ts.map +1 -0
  378. package/dist/maps/renderer.js +66 -0
  379. package/dist/maps/renderer.js.map +1 -0
  380. package/dist/maps/sources.d.ts +30 -6
  381. package/dist/maps/sources.d.ts.map +1 -1
  382. package/dist/maps/sources.js +22 -3
  383. package/dist/maps/sources.js.map +1 -1
  384. package/dist/maps/style.d.ts +35 -0
  385. package/dist/maps/style.d.ts.map +1 -1
  386. package/dist/maps/style.js.map +1 -1
  387. package/dist/maps/styles.d.ts +12 -0
  388. package/dist/maps/styles.d.ts.map +1 -1
  389. package/dist/maps/styles.js +120 -6
  390. package/dist/maps/styles.js.map +1 -1
  391. package/dist/maps/theme.d.ts +19 -0
  392. package/dist/maps/theme.d.ts.map +1 -0
  393. package/dist/maps/theme.js +44 -0
  394. package/dist/maps/theme.js.map +1 -0
  395. package/dist/maps/tiles.d.ts +147 -27
  396. package/dist/maps/tiles.d.ts.map +1 -1
  397. package/dist/maps/tiles.js +221 -54
  398. package/dist/maps/tiles.js.map +1 -1
  399. package/dist/maps/types.d.ts +213 -21
  400. package/dist/maps/types.d.ts.map +1 -1
  401. package/dist/markdown/expressions.d.ts +1 -1
  402. package/dist/markdown/expressions.d.ts.map +1 -1
  403. package/dist/markdown/expressions.js.map +1 -1
  404. package/dist/markdown/index.d.ts +7 -6
  405. package/dist/markdown/index.d.ts.map +1 -1
  406. package/dist/markdown/index.js +121 -50
  407. package/dist/markdown/index.js.map +1 -1
  408. package/dist/markdown/spans.d.ts +1 -1
  409. package/dist/markdown/spans.d.ts.map +1 -1
  410. package/dist/markdown/spans.js +6 -0
  411. package/dist/markdown/spans.js.map +1 -1
  412. package/dist/media-player/index.d.ts +11 -3
  413. package/dist/media-player/index.d.ts.map +1 -1
  414. package/dist/media-player/index.js +13 -2
  415. package/dist/media-player/index.js.map +1 -1
  416. package/dist/qml/containers.d.ts.map +1 -1
  417. package/dist/qml/containers.js +6 -1
  418. package/dist/qml/containers.js.map +1 -1
  419. package/dist/qml/interaction.d.ts.map +1 -1
  420. package/dist/qml/interaction.js +38 -5
  421. package/dist/qml/interaction.js.map +1 -1
  422. package/dist/qml/layouts.d.ts.map +1 -1
  423. package/dist/qml/layouts.js +21 -6
  424. package/dist/qml/layouts.js.map +1 -1
  425. package/dist/qml/qtquick.d.ts.map +1 -1
  426. package/dist/qml/qtquick.js +9 -2
  427. package/dist/qml/qtquick.js.map +1 -1
  428. package/dist/qml/react.d.ts.map +1 -1
  429. package/dist/qml/react.js +9 -7
  430. package/dist/qml/react.js.map +1 -1
  431. package/dist/reorder/clock.d.ts +7 -0
  432. package/dist/reorder/clock.d.ts.map +1 -0
  433. package/dist/reorder/clock.js +26 -0
  434. package/dist/reorder/clock.js.map +1 -0
  435. package/dist/reorder/index.d.ts +25 -3
  436. package/dist/reorder/index.d.ts.map +1 -1
  437. package/dist/reorder/index.js +64 -19
  438. package/dist/reorder/index.js.map +1 -1
  439. package/dist/rich-text-editor/clipboard.d.ts +23 -0
  440. package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
  441. package/dist/rich-text-editor/clipboard.js +307 -0
  442. package/dist/rich-text-editor/clipboard.js.map +1 -0
  443. package/dist/rich-text-editor/collab.d.ts +32 -0
  444. package/dist/rich-text-editor/collab.d.ts.map +1 -0
  445. package/dist/rich-text-editor/collab.js +55 -0
  446. package/dist/rich-text-editor/collab.js.map +1 -0
  447. package/dist/rich-text-editor/commands.d.ts +50 -0
  448. package/dist/rich-text-editor/commands.d.ts.map +1 -0
  449. package/dist/rich-text-editor/commands.js +376 -0
  450. package/dist/rich-text-editor/commands.js.map +1 -0
  451. package/dist/rich-text-editor/drag.d.ts +68 -0
  452. package/dist/rich-text-editor/drag.d.ts.map +1 -0
  453. package/dist/rich-text-editor/drag.js +148 -0
  454. package/dist/rich-text-editor/drag.js.map +1 -0
  455. package/dist/rich-text-editor/html.d.ts +145 -0
  456. package/dist/rich-text-editor/html.d.ts.map +1 -0
  457. package/dist/rich-text-editor/html.js +516 -0
  458. package/dist/rich-text-editor/html.js.map +1 -0
  459. package/dist/rich-text-editor/index.d.ts +252 -0
  460. package/dist/rich-text-editor/index.d.ts.map +1 -0
  461. package/dist/rich-text-editor/index.js +856 -0
  462. package/dist/rich-text-editor/index.js.map +1 -0
  463. package/dist/rich-text-editor/inline.d.ts +94 -0
  464. package/dist/rich-text-editor/inline.d.ts.map +1 -0
  465. package/dist/rich-text-editor/inline.js +252 -0
  466. package/dist/rich-text-editor/inline.js.map +1 -0
  467. package/dist/rich-text-editor/keymap.d.ts +71 -0
  468. package/dist/rich-text-editor/keymap.d.ts.map +1 -0
  469. package/dist/rich-text-editor/keymap.js +394 -0
  470. package/dist/rich-text-editor/keymap.js.map +1 -0
  471. package/dist/rich-text-editor/keys.d.ts +40 -0
  472. package/dist/rich-text-editor/keys.d.ts.map +1 -0
  473. package/dist/rich-text-editor/keys.js +202 -0
  474. package/dist/rich-text-editor/keys.js.map +1 -0
  475. package/dist/rich-text-editor/look.d.ts +71 -0
  476. package/dist/rich-text-editor/look.d.ts.map +1 -0
  477. package/dist/rich-text-editor/look.js +342 -0
  478. package/dist/rich-text-editor/look.js.map +1 -0
  479. package/dist/rich-text-editor/markdown.d.ts +18 -0
  480. package/dist/rich-text-editor/markdown.d.ts.map +1 -0
  481. package/dist/rich-text-editor/markdown.js +496 -0
  482. package/dist/rich-text-editor/markdown.js.map +1 -0
  483. package/dist/rich-text-editor/marks.d.ts +9 -0
  484. package/dist/rich-text-editor/marks.d.ts.map +1 -0
  485. package/dist/rich-text-editor/marks.js +83 -0
  486. package/dist/rich-text-editor/marks.js.map +1 -0
  487. package/dist/rich-text-editor/nodes.d.ts +135 -0
  488. package/dist/rich-text-editor/nodes.d.ts.map +1 -0
  489. package/dist/rich-text-editor/nodes.js +397 -0
  490. package/dist/rich-text-editor/nodes.js.map +1 -0
  491. package/dist/rich-text-editor/render.d.ts +68 -0
  492. package/dist/rich-text-editor/render.d.ts.map +1 -0
  493. package/dist/rich-text-editor/render.js +518 -0
  494. package/dist/rich-text-editor/render.js.map +1 -0
  495. package/dist/rich-text-editor/schema.d.ts +16 -0
  496. package/dist/rich-text-editor/schema.d.ts.map +1 -0
  497. package/dist/rich-text-editor/schema.js +386 -0
  498. package/dist/rich-text-editor/schema.js.map +1 -0
  499. package/dist/rich-text-editor/store.d.ts +11 -0
  500. package/dist/rich-text-editor/store.d.ts.map +1 -0
  501. package/dist/rich-text-editor/store.js +43 -0
  502. package/dist/rich-text-editor/store.js.map +1 -0
  503. package/dist/rich-text-editor/suggest.d.ts +127 -0
  504. package/dist/rich-text-editor/suggest.d.ts.map +1 -0
  505. package/dist/rich-text-editor/suggest.js +0 -0
  506. package/dist/rich-text-editor/suggest.js.map +1 -0
  507. package/dist/rich-text-editor/tables.d.ts +41 -0
  508. package/dist/rich-text-editor/tables.d.ts.map +1 -0
  509. package/dist/rich-text-editor/tables.js +204 -0
  510. package/dist/rich-text-editor/tables.js.map +1 -0
  511. package/dist/rich-text-editor/toolbar.d.ts +58 -0
  512. package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
  513. package/dist/rich-text-editor/toolbar.js +548 -0
  514. package/dist/rich-text-editor/toolbar.js.map +1 -0
  515. package/dist/rich-text-editor/view.d.ts +368 -0
  516. package/dist/rich-text-editor/view.d.ts.map +1 -0
  517. package/dist/rich-text-editor/view.js +1968 -0
  518. package/dist/rich-text-editor/view.js.map +1 -0
  519. package/dist/rich-text-editor/virtual.d.ts +54 -0
  520. package/dist/rich-text-editor/virtual.d.ts.map +1 -0
  521. package/dist/rich-text-editor/virtual.js +279 -0
  522. package/dist/rich-text-editor/virtual.js.map +1 -0
  523. package/dist/richtext/node.d.ts +71 -4
  524. package/dist/richtext/node.d.ts.map +1 -1
  525. package/dist/richtext/node.js +233 -28
  526. package/dist/richtext/node.js.map +1 -1
  527. package/dist/richtext/runs.d.ts +13 -8
  528. package/dist/richtext/runs.d.ts.map +1 -1
  529. package/dist/richtext/runs.js +18 -8
  530. package/dist/richtext/runs.js.map +1 -1
  531. package/dist/table/index.d.ts.map +1 -1
  532. package/dist/table/index.js +23 -0
  533. package/dist/table/index.js.map +1 -1
  534. package/dist/table/rows.d.ts +3 -2
  535. package/dist/table/rows.d.ts.map +1 -1
  536. package/dist/table/rows.js +20 -6
  537. package/dist/table/rows.js.map +1 -1
  538. package/dist/terminal/backends.d.ts +4 -1
  539. package/dist/terminal/backends.d.ts.map +1 -1
  540. package/dist/terminal/backends.js.map +1 -1
  541. package/dist/terminal/index.d.ts +18 -7
  542. package/dist/terminal/index.d.ts.map +1 -1
  543. package/dist/terminal/index.js +32 -11
  544. package/dist/terminal/index.js.map +1 -1
  545. package/dist/terminal/vt/pty.d.ts +13 -2
  546. package/dist/terminal/vt/pty.d.ts.map +1 -1
  547. package/dist/terminal/vt/pty.js +18 -3
  548. package/dist/terminal/vt/pty.js.map +1 -1
  549. package/dist/terminal-output/index.d.ts.map +1 -1
  550. package/dist/terminal-output/index.js +6 -1
  551. package/dist/terminal-output/index.js.map +1 -1
  552. package/dist/three/canvas.d.ts.map +1 -1
  553. package/dist/three/canvas.js +85 -7
  554. package/dist/three/canvas.js.map +1 -1
  555. package/dist/three/events.d.ts +22 -4
  556. package/dist/three/events.d.ts.map +1 -1
  557. package/dist/three/events.js +34 -4
  558. package/dist/three/events.js.map +1 -1
  559. package/dist/tree/index.d.ts.map +1 -1
  560. package/dist/tree/index.js +17 -0
  561. package/dist/tree/index.js.map +1 -1
  562. package/package.json +59 -31
  563. package/src/charts/node.ts +68 -28
  564. package/src/charts/render.ts +120 -30
  565. package/src/code/index.ts +3 -1
  566. package/src/code-editor/completion.ts +3 -2
  567. package/src/code-editor/index.ts +19 -0
  568. package/src/code-editor/node.ts +1574 -184
  569. package/src/code-language/languages/glsl.ts +12 -2
  570. package/src/code-language/languages/javascript.ts +21 -4
  571. package/src/code-language/languages/json.ts +12 -2
  572. package/src/code-language/languages/shell.ts +12 -2
  573. package/src/code-language/languages/sql.ts +22 -2
  574. package/src/code-language/stream.ts +257 -17
  575. package/src/code-language/types.ts +5 -4
  576. package/src/codeblock/index.ts +20 -3
  577. package/src/embed/client.ts +102 -8
  578. package/src/embed/index.ts +2 -0
  579. package/src/flow/draw.ts +167 -41
  580. package/src/flow/gl/color.ts +85 -0
  581. package/src/flow/gl/index.ts +342 -0
  582. package/src/flow/gl/pack.ts +585 -0
  583. package/src/flow/gl/renderer.ts +679 -0
  584. package/src/flow/gl/shaders.ts +371 -0
  585. package/src/flow/gl/text.ts +1295 -0
  586. package/src/flow/index.ts +794 -67
  587. package/src/flow/node.ts +2496 -1256
  588. package/src/flow/paint.ts +511 -0
  589. package/src/flow/paths.ts +80 -15
  590. package/src/flow/scene.ts +2199 -0
  591. package/src/flow/types.ts +113 -1
  592. package/src/html/controls.ts +73 -2
  593. package/src/html/css/calc.ts +385 -0
  594. package/src/html/css/cascade.ts +1403 -62
  595. package/src/html/css/color.ts +755 -0
  596. package/src/html/css/content.ts +417 -0
  597. package/src/html/css/counter-styles.ts +973 -0
  598. package/src/html/css/decode.ts +130 -0
  599. package/src/html/css/parse.ts +1514 -160
  600. package/src/html/css/style.ts +4102 -222
  601. package/src/html/css/ua.ts +58 -12
  602. package/src/html/css/values.ts +150 -50
  603. package/src/html/css/vars.ts +348 -0
  604. package/src/html/dom.ts +284 -17
  605. package/src/html/fonts.ts +641 -0
  606. package/src/html/gif.ts +233 -0
  607. package/src/html/index.ts +180 -20
  608. package/src/html/layout/block.ts +3456 -230
  609. package/src/html/layout/boxes.ts +2100 -190
  610. package/src/html/layout/cache.ts +289 -0
  611. package/src/html/layout/collapse.ts +317 -0
  612. package/src/html/layout/css-grid.ts +1063 -0
  613. package/src/html/layout/flex.ts +1115 -106
  614. package/src/html/layout/floats.ts +128 -14
  615. package/src/html/layout/grid-lines.ts +141 -0
  616. package/src/html/layout/grid.ts +181 -0
  617. package/src/html/layout/inline.ts +3943 -192
  618. package/src/html/layout/shaping.ts +140 -0
  619. package/src/html/layout/table.ts +696 -186
  620. package/src/html/layout/tracks.ts +437 -0
  621. package/src/html/node.ts +1486 -121
  622. package/src/html/paint.ts +4293 -169
  623. package/src/html/resources.ts +161 -50
  624. package/src/html/surfaces.ts +81 -0
  625. package/src/html/svg.ts +493 -0
  626. package/src/html/url.ts +86 -0
  627. package/src/index.ts +4 -0
  628. package/src/internal/collate.ts +12 -0
  629. package/src/{markdown → internal/markdown}/ast.ts +35 -7
  630. package/src/{markdown → internal/markdown}/parse.ts +288 -35
  631. package/src/internal/markdown/stringify.ts +772 -0
  632. package/src/internal/prewarm.ts +45 -0
  633. package/src/internal/scroll.ts +5 -3
  634. package/src/internal/sdf.ts +227 -0
  635. package/src/internal/splice.ts +25 -0
  636. package/src/internal/window.ts +36 -5
  637. package/src/maps/anchors.ts +913 -0
  638. package/src/maps/controller.ts +1084 -0
  639. package/src/maps/gl/buckets.ts +461 -0
  640. package/src/maps/gl/build-worker.ts +66 -0
  641. package/src/maps/gl/color.ts +62 -0
  642. package/src/maps/gl/cover.ts +205 -0
  643. package/src/maps/gl/index.ts +23 -0
  644. package/src/maps/gl/markers.ts +83 -0
  645. package/src/maps/gl/overlays.ts +379 -0
  646. package/src/maps/gl/pane.ts +42 -0
  647. package/src/maps/gl/placement.ts +968 -0
  648. package/src/maps/gl/renderer.ts +1660 -0
  649. package/src/maps/gl/shaders.ts +489 -0
  650. package/src/maps/gl/store.ts +615 -0
  651. package/src/maps/gl/text.ts +868 -0
  652. package/src/maps/gl/view.ts +1489 -0
  653. package/src/maps/icons.ts +183 -0
  654. package/src/maps/index.ts +274 -81
  655. package/src/maps/labels.ts +457 -144
  656. package/src/maps/node.ts +832 -665
  657. package/src/maps/overlay.ts +148 -43
  658. package/src/maps/paint.ts +3 -0
  659. package/src/maps/proj.ts +118 -0
  660. package/src/maps/renderer.ts +155 -0
  661. package/src/maps/sources.ts +43 -6
  662. package/src/maps/style.ts +32 -0
  663. package/src/maps/styles.ts +215 -4
  664. package/src/maps/theme.ts +51 -0
  665. package/src/maps/tiles.ts +253 -62
  666. package/src/maps/types.ts +213 -21
  667. package/src/markdown/expressions.ts +5 -1
  668. package/src/markdown/index.ts +163 -78
  669. package/src/markdown/spans.ts +6 -1
  670. package/src/media-player/index.ts +18 -4
  671. package/src/qml/containers.tsx +6 -1
  672. package/src/qml/interaction.tsx +42 -5
  673. package/src/qml/layouts.tsx +21 -6
  674. package/src/qml/qtquick.tsx +9 -2
  675. package/src/qml/react.tsx +7 -5
  676. package/src/reorder/clock.ts +27 -0
  677. package/src/reorder/index.ts +100 -27
  678. package/src/rich-text-editor/clipboard.ts +439 -0
  679. package/src/rich-text-editor/collab.ts +91 -0
  680. package/src/rich-text-editor/commands.ts +448 -0
  681. package/src/rich-text-editor/dom-globals.d.ts +23 -0
  682. package/src/rich-text-editor/drag.ts +211 -0
  683. package/src/rich-text-editor/html.ts +619 -0
  684. package/src/rich-text-editor/index.ts +1364 -0
  685. package/src/rich-text-editor/inline.ts +345 -0
  686. package/src/rich-text-editor/keymap.ts +510 -0
  687. package/src/rich-text-editor/keys.ts +228 -0
  688. package/src/rich-text-editor/look.ts +427 -0
  689. package/src/rich-text-editor/markdown.ts +653 -0
  690. package/src/rich-text-editor/marks.ts +101 -0
  691. package/src/rich-text-editor/nodes.ts +497 -0
  692. package/src/rich-text-editor/render.ts +837 -0
  693. package/src/rich-text-editor/schema.ts +431 -0
  694. package/src/rich-text-editor/store.ts +39 -0
  695. package/src/rich-text-editor/suggest.ts +597 -0
  696. package/src/rich-text-editor/tables.ts +272 -0
  697. package/src/rich-text-editor/toolbar.ts +724 -0
  698. package/src/rich-text-editor/view.ts +2287 -0
  699. package/src/rich-text-editor/virtual.ts +372 -0
  700. package/src/richtext/node.ts +294 -44
  701. package/src/richtext/runs.ts +23 -12
  702. package/src/table/index.ts +29 -0
  703. package/src/table/rows.ts +18 -8
  704. package/src/terminal/backends.ts +4 -1
  705. package/src/terminal/index.ts +47 -15
  706. package/src/terminal/vt/pty.ts +16 -2
  707. package/src/terminal-output/index.ts +6 -1
  708. package/src/three/canvas.ts +102 -8
  709. package/src/three/events.ts +49 -9
  710. package/src/tree/index.ts +19 -0
  711. package/dist/markdown/ast.d.ts.map +0 -1
  712. package/dist/markdown/ast.js +0 -17
  713. package/dist/markdown/ast.js.map +0 -1
  714. package/dist/markdown/parse.d.ts.map +0 -1
  715. package/dist/markdown/parse.js.map +0 -1
  716. package/dist/markdown/tags.d.ts.map +0 -1
  717. package/dist/markdown/tags.js.map +0 -1
  718. /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
  719. /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
  720. /package/dist/{markdown → internal/markdown}/tags.js +0 -0
  721. /package/src/{markdown → internal/markdown}/tags.ts +0 -0
package/dist/maps/node.js CHANGED
@@ -11,7 +11,8 @@
11
11
  // Three caches, and the map's whole performance argument is the way they
12
12
  // are layered:
13
13
  //
14
- // 1. **Tile data**, keyed on `source/z/x/y` and valid forever.
14
+ // 1. **Tile data**, keyed on the source object and `z/x/y`, and valid
15
+ // forever. The object, not its `id` — see `./tiles.ts`.
15
16
  // 2. **A rendered `Surface` per tile**, valid for a zoom *level* and a
16
17
  // style — not for a camera position. So a **pan** composites the same
17
18
  // surfaces at new offsets (and blits, so most of them are not even
@@ -29,13 +30,15 @@
29
30
  // nothing is ever rasterized during a drag.
30
31
  import { Node } from 'react-x11/node';
31
32
  import { Surface } from 'react-x11/ntk';
32
- import { GeometryBuffer } from './mvt.js';
33
+ import { MapController, quantizeZoom } from './controller.js';
33
34
  import { BATCH_VERTICES, DrawScratch, drawTileRun, isMapCanvas, now, prepareStyle, } from './paint.js';
34
- import { DEFAULT_TILE_SIZE, cameraForBounds, boundsOf, dataTileFor, subTileOf, projectLngLat, rasterFor, tileCover, transformFor, unprojectPoint, visibleBounds, } from './proj.js';
35
- import { TileCache, pyramid } from './tiles.js';
35
+ import { DEFAULT_TILE_SIZE, dataSquareOf, dataTileFor, subTileOf, rasterFor, tileCover, tileKey, transformFor, } from './proj.js';
36
+ import { TileCache, drawnFor, pyramid } from './tiles.js';
37
+ import { attributionOf } from './sources.js';
36
38
  import { shortbreadStyle } from './styles.js';
39
+ import { isDarkTheme, overlayPalette } from './theme.js';
37
40
  import { LabelShaper, collectLabels, drawLabels, placeLabels, } from './labels.js';
38
- import { drawMarkers, drawOverlays, markerAt, markerRect } from './overlay.js';
41
+ import { ATTRIBUTION_OPACITY, ATTRIBUTION_SIZE, attributionLayout, drawMarkers, drawOverlays, markerRect, } from './overlay.js';
39
42
  /** Registration key, `kind` and JSX tag, one string — react-x11 rejects a
40
43
  * node whose `kind` is not the name it was registered under, because
41
44
  * `kind` is what paint order, the test queries and the DEV style assertion
@@ -57,20 +60,6 @@ export const SELF_DAMAGED_PROPS = [
57
60
  'sources',
58
61
  'mapStyle',
59
62
  ];
60
- /** Screen pixels the pointer may travel before a press becomes a drag. */
61
- const DRAG_THRESHOLD = 3;
62
- /** How long after the last gesture step the map goes back to rasterizing.
63
- * Long enough that a wheel-zoom's many steps count as one gesture, short
64
- * enough that the map sharpens before the user has finished looking. */
65
- const SETTLE_MS = 140;
66
- /** Zoom is quantized to this, for the reason react-x11's docs/scale.md
67
- * gives about a gesture-driven `scale`: every distinct value is a distinct
68
- * set of font sizes to shape, and a wheel feeding a raw accumulator makes
69
- * a new set per frame. A sixteenth of a level is finer than the eye reads
70
- * as stepping. */
71
- const ZOOM_STEP = 1 / 16;
72
- /** A wheel notch is this much zoom. */
73
- const WHEEL_ZOOM = 1 / 2.5;
74
63
  /** The largest tile surface, per edge, in device pixels. An argb32 surface
75
64
  * is `4 × size²` bytes, so 2048 is 16 MB and is already more than any
76
65
  * pyramid justifies. */
@@ -101,26 +90,6 @@ function intersectRects(a, b) {
101
90
  return null;
102
91
  return { x, y, width: right - x, height: bottom - y };
103
92
  }
104
- const timers = globalThis;
105
- /**
106
- * The settle timer, unref'd where the runtime allows it.
107
- *
108
- * A map that has just been panned holds a 140 ms timer, and an unref'd one
109
- * does not keep a process alive on its own — which matters for a script or
110
- * a test that renders a map and expects to exit, and is the call core's
111
- * caret blink makes for the same reason.
112
- */
113
- function arm(tick) {
114
- const handle = timers.setTimeout?.(tick, SETTLE_MS) ?? null;
115
- handle?.unref?.();
116
- return handle;
117
- }
118
- function clamp(value, low, high) {
119
- return value < low ? low : value > high ? high : value;
120
- }
121
- function quantize(zoom) {
122
- return Math.round(zoom / ZOOM_STEP) * ZOOM_STEP;
123
- }
124
93
  /** A logical value put on the device grid — the same helper `src/flow/`
125
94
  * keeps, and for the same reason: ntk's fast paths for a blit and a
126
95
  * rounded box are gated on integral geometry, and `x * 1.5` is not always
@@ -136,38 +105,95 @@ function rectsOverlap(a, b) {
136
105
  a.y < b.y + b.height &&
137
106
  a.y + a.height > b.y);
138
107
  }
108
+ /** Whether `outer` covers all of `inner`. */
109
+ function containsRect(outer, inner) {
110
+ return (outer.x <= inner.x &&
111
+ outer.y <= inner.y &&
112
+ outer.x + outer.width >= inner.x + inner.width &&
113
+ outer.y + outer.height >= inner.y + inner.height);
114
+ }
115
+ function sameSources(a, b) {
116
+ return a.length === b.length && a.every((source, i) => source === b[i]);
117
+ }
118
+ /**
119
+ * How long a restyle may keep the previous style up once the view moves,
120
+ * in milliseconds the map was free to draw in.
121
+ *
122
+ * A still view is never cut short: its redraw is a fixed amount of work,
123
+ * and the swap waits for all of it. A moving one can bring tiles into view
124
+ * as fast as the old ones are finished — a pan with pauses in it, a camera
125
+ * an application animates — and would never swap. So once the camera has
126
+ * moved the wait is bounded, and a tile not yet redrawn when it runs out
127
+ * shows the background until it is. Frames a gesture holds rasterization
128
+ * off in do not count, since nothing is drawn in them.
129
+ */
130
+ const RESTYLE_WAIT_MS = 1500;
131
+ /**
132
+ * The clock {@link RESTYLE_WAIT_MS} is measured on: the raster budget's,
133
+ * read at the same moment, as a frame begins.
134
+ *
135
+ * One object, exported from this module though not from the package, so a
136
+ * test can stand in for it and move the bound's time on a step at a time,
137
+ * as `glideClock` in `./controller.ts` does for a glide. On the real clock a
138
+ * test finds the swap only once the step that made it is over, and on a
139
+ * loaded runner one step took long enough to put that two and a half
140
+ * seconds past the bound. It is not the budget's own `now`, because a test
141
+ * that held that one would hold the raster deadline with it: a frame would
142
+ * draw every tile to the end, and there would be no redraw left for the
143
+ * bound to cut short.
144
+ */
145
+ export const restyleClock = {
146
+ now() {
147
+ return now();
148
+ },
149
+ };
139
150
  export class MapViewNode extends Node {
140
151
  _cache;
141
152
  _scratch = new DrawScratch();
142
- _geometry = new GeometryBuffer();
143
153
  _shaper = null;
144
154
  /**
145
- * The camera this element owns, used whenever `props.camera` is absent.
155
+ * The camera and the gestures: the controller `<Map>` hands down, or this
156
+ * element's own when it is used bare. Both of `<Map>`'s renderers answer
157
+ * to one (`./controller.ts`), which is how a fallback from one to the
158
+ * other keeps the camera and the handle.
146
159
  *
147
- * The element keeping it — rather than the component above holding it in
148
- * `useState` — is what makes a pan cost nothing but a blit: a drag step
149
- * moves this number and claims a strip, and React is not involved at all.
150
- * Routed through state instead, every pointer step would be a render, a
151
- * commit and a full-pane claim, which is the shape `<Flow>` documents as
152
- * "the content lags and catches up".
160
+ * The camera being an object's rather than React state's is what makes a
161
+ * pan cost nothing but a blit: a drag step moves two numbers and claims a
162
+ * strip, and React is not involved at all. Routed through state instead,
163
+ * every pointer step would be a render, a commit and a full-pane claim,
164
+ * which is the shape `<Flow>` documents as "the content lags and catches
165
+ * up".
153
166
  */
154
- _camera = { center: { lon: 0, lat: 20 }, zoom: 2 };
167
+ _controller;
168
+ _ownsController;
169
+ /** This element, as its controller sees it. */
170
+ _view;
155
171
  _prepared = null;
156
172
  _preparedFrom = null;
157
173
  _defaultStyle = null;
158
174
  /** The placement, and what it was computed for. */
159
175
  _labels = [];
160
176
  _labelKey = '';
177
+ /** …and what it was placed from, all of which a restyle holds on to (see
178
+ * {@link Outgoing}): the quantized zoom, the sources on the map, their
179
+ * candidates per source, and the face they were shaped in. */
180
+ _labelZoom = 0;
181
+ _labelSources = [];
182
+ _labelCandidates = new Map();
183
+ _shapedFamily;
161
184
  /** Candidates per tile, so a pan that brings a tile back does not redo
162
185
  * the walk over its symbol layers. */
163
186
  _candidates = new Map();
164
- _gesture = null;
165
- _hover = null;
166
- /** Set while a gesture is in flight and for `SETTLE_MS` after it, which
167
- * is when rasterization is suspended. */
168
- _settleAt = 0;
169
- _settleTimer = null;
170
- _painting = false;
187
+ /** The previous style while a restyle draws the new one behind it, or
188
+ * null when what is on screen is the current generation. */
189
+ _outgoing = null;
190
+ /** The style background the last frame painted — what a restyle keeps
191
+ * painting. Remembered rather than recomputed, because `refresh()`
192
+ * follows an edit made to the very style object it would be read from. */
193
+ _paintedBackground;
194
+ /** Whether a frame has been painted: before one, a restyle has no
195
+ * previous picture to hold. */
196
+ _painted = false;
171
197
  /** Whether any tile has been rasterized in the frame being painted — the
172
198
  * forward-progress guarantee below. */
173
199
  _rastered = false;
@@ -176,11 +202,37 @@ export class MapViewNode extends Node {
176
202
  _sceneAnnounced = false;
177
203
  constructor(props, app) {
178
204
  super(ELEMENT, props, app);
179
- // Seeded once. `defaultCamera` is read here and never again, which is
180
- // what makes it a *default* rather than a second controlled prop.
205
+ const given = props.mapController;
206
+ // A bare element's own controller is seeded once: `defaultCamera` is
207
+ // read here and never again, which is what makes it a *default* rather
208
+ // than a second controlled prop. `<Map>` seeds the one it hands down.
181
209
  const seed = (props.camera ?? props.defaultCamera);
182
- if (seed)
183
- this._camera = { center: { ...seed.center }, zoom: seed.zoom };
210
+ this._controller = given ?? new MapController(seed);
211
+ this._ownsController = given === undefined;
212
+ if (this._ownsController) {
213
+ this._controller.setProps(props);
214
+ }
215
+ this._view = {
216
+ pane: () => this._pane(),
217
+ scale: () => this._scale,
218
+ moved: (previous, next, blit) => {
219
+ // A new pyramid level: different tiles, different labels.
220
+ if (Math.floor(next.zoom) !== Math.floor(previous.zoom)) {
221
+ this._labelKey = '';
222
+ }
223
+ if (!blit || !this._blitPan(previous, next))
224
+ this._repaint('scroll');
225
+ },
226
+ // The gesture is over: sharpen. A wake-up, not a repaint — nothing has
227
+ // moved since the last frame, so what is on screen is still right;
228
+ // what is needed is a frame to start rasterizing in, and each tile
229
+ // claims its own box as it lands.
230
+ settled: () => this._wake('content'),
231
+ refresh: () => this._restyle(),
232
+ stats: () => this._stats,
233
+ focus: () => this.focus(),
234
+ };
235
+ this._controller.attach(this._view);
184
236
  // A map is a thing you drive with the keyboard as well as the mouse:
185
237
  // arrows pan, +/- zoom. Without this it is never focused and no key
186
238
  // arrives.
@@ -191,9 +243,7 @@ export class MapViewNode extends Node {
191
243
  onError: (entry) => {
192
244
  this._prop('onTileError')?.(entry.error, {
193
245
  ...entry.tile,
194
- // The data entry is keyed per source, so the id is recoverable
195
- // from the key it was built with.
196
- sourceId: entry.key.slice(0, entry.key.lastIndexOf(':')),
246
+ sourceId: entry.sourceId,
197
247
  });
198
248
  },
199
249
  onChange: () => {
@@ -239,44 +289,20 @@ export class MapViewNode extends Node {
239
289
  return this._prepared;
240
290
  }
241
291
  _isDark() {
242
- const theme = this.theme;
243
- const background = theme?.background;
244
- if (typeof background !== 'string')
245
- return false;
246
- // The same reading `src/code-editor/`'s token themes make: luminance of
247
- // the surface the widget sits on, not a flag nobody sets.
248
- const hex = background.trim();
249
- if (!hex.startsWith('#') || hex.length < 7)
250
- return false;
251
- const r = parseInt(hex.slice(1, 3), 16);
252
- const g = parseInt(hex.slice(3, 5), 16);
253
- const b = parseInt(hex.slice(5, 7), 16);
254
- return 0.2126 * r + 0.7152 * g + 0.0722 * b < 128;
292
+ return isDarkTheme(this.theme);
255
293
  }
256
294
  _palette() {
257
- const theme = this.theme;
258
- return {
259
- accent: theme?.accent ?? '#2d6cdf',
260
- background: theme?.background ?? '#ffffff',
261
- text: theme?.text ?? '#111111',
262
- };
295
+ return overlayPalette(this.theme);
263
296
  }
264
297
  _sources() {
265
298
  const given = this._prop('sources');
266
299
  return given ? [...given] : [];
267
300
  }
301
+ /** What a source is called in its requests and its errors. Not what its
302
+ * tiles are cached under — the cache files them under the object. */
268
303
  _sourceId(source, index) {
269
304
  return source.id ?? `source-${index}`;
270
305
  }
271
- _minZoom() {
272
- return this._prop('minZoom') ?? 0;
273
- }
274
- _maxZoom() {
275
- return this._prop('maxZoom') ?? 22;
276
- }
277
- _interactive() {
278
- return this._prop('interactive') !== false;
279
- }
280
306
  _markers() {
281
307
  return this._prop('markers') ?? [];
282
308
  }
@@ -301,8 +327,7 @@ export class MapViewNode extends Node {
301
327
  return pane.width > 0 && pane.height > 0;
302
328
  }
303
329
  camera() {
304
- const given = this._prop('camera');
305
- return given ?? this._camera;
330
+ return this._controller.camera();
306
331
  }
307
332
  /** The camera resolved against the pane. */
308
333
  _transform(camera = this.camera()) {
@@ -344,46 +369,6 @@ export class MapViewNode extends Node {
344
369
  this.invalidate(false, { x: box.x, y: box.y, width: 1, height: 1 }, reason);
345
370
  }
346
371
  // --- camera --------------------------------------------------------------
347
- /**
348
- * Move the camera.
349
- *
350
- * The one place the camera changes, so the controlled/uncontrolled fork,
351
- * the clamping, the notification and — the interesting part — the
352
- * decision between a blit and a repaint all live together.
353
- */
354
- _applyCamera(next, blit = true) {
355
- const previous = this.camera();
356
- const zoom = clamp(next.zoom, this._minZoom(), this._maxZoom());
357
- // Latitude is clamped to what Web Mercator can represent; longitude is
358
- // not, because the map wraps and a camera just past the antimeridian is
359
- // a camera in the next copy of the world.
360
- const camera = {
361
- center: { lon: next.center.lon, lat: clamp(next.center.lat, -85, 85) },
362
- zoom,
363
- };
364
- if (camera.zoom === previous.zoom &&
365
- camera.center.lon === previous.center.lon &&
366
- camera.center.lat === previous.center.lat) {
367
- return;
368
- }
369
- if (this.props.camera === undefined)
370
- this._camera = camera;
371
- this._prop('onCameraChange')?.(camera);
372
- if (this._painting)
373
- return;
374
- // Every camera move defers rasterization, not just a pointer gesture.
375
- // An application animating a camera with `panBy` in a loop wants
376
- // exactly what a drag wants — composites while it moves, a sharpen when
377
- // it stops — and a single programmatic move only pays the settle delay,
378
- // which is a seventh of a second.
379
- this._touchGesture();
380
- if (Math.floor(camera.zoom) !== Math.floor(previous.zoom)) {
381
- // A new pyramid level: different tiles, different labels.
382
- this._labelKey = '';
383
- }
384
- if (!blit || !this._blitPan(previous, camera))
385
- this._repaint('scroll');
386
- }
387
372
  /**
388
373
  * A pan is a scroll in every way but the bookkeeping, and react-x11#303
389
374
  * made the bookkeeping public: `scrollContents` claims the pane, arms the
@@ -440,134 +425,108 @@ export class MapViewNode extends Node {
440
425
  }
441
426
  return true;
442
427
  }
443
- /** Zoom about a point that must not move — the pointer under a wheel,
444
- * the pane's centre for a key. */
445
- _zoomAbout(delta, screenX, screenY) {
446
- const camera = this.camera();
447
- const zoom = clamp(quantize(camera.zoom + delta), this._minZoom(), this._maxZoom());
448
- if (zoom === camera.zoom)
449
- return;
450
- const before = this._transform(camera);
451
- const anchor = unprojectPoint(before, screenX, screenY);
452
- const after = this._transform({ center: camera.center, zoom });
453
- // Where the anchor would land at the new zoom, and how far the centre
454
- // has to move so it lands where it already is.
455
- const moved = projectLngLat(after, anchor);
456
- const dx = (moved.x - screenX) / after.world;
457
- const dy = (moved.y - screenY) / after.world;
458
- this._applyCamera({
459
- zoom,
460
- center: unprojectPoint({
461
- ...after,
462
- centerX: after.centerX + dx,
463
- centerY: after.centerY + dy,
464
- }, after.paneX, after.paneY),
465
- }, false);
466
- }
467
- /** Suspend rasterization for the length of a gesture, and arrange for it
468
- * to resume. */
469
- _touchGesture() {
470
- // Wall-clock here, not the budget clock: this is a 140 ms window, and
471
- // it is compared inside a timer callback.
472
- this._settleAt = Date.now() + SETTLE_MS;
473
- if (this._settleTimer !== null)
474
- return;
475
- const tick = () => {
476
- this._settleTimer = null;
477
- if (Date.now() < this._settleAt) {
478
- this._settleTimer = arm(tick);
479
- return;
480
- }
481
- // The gesture is over: sharpen. A wake-up, not a repaint — nothing
482
- // has moved since the last frame, so what is on screen is still
483
- // right; what is needed is a frame to start rasterizing in, and each
484
- // tile claims its own box as it lands.
485
- this._prop('onMoveEnd')?.(this.camera());
486
- this._wake('content');
487
- };
488
- this._settleTimer = arm(tick);
489
- }
428
+ /** A gesture in flight, or the settle window after a camera move: while
429
+ * it lasts nothing is rasterized, so a drag or a wheel is composites
430
+ * only. The window is the controller's, and one for both renderers. */
490
431
  get _gesturing() {
491
- return this._gesture !== null || Date.now() < this._settleAt;
432
+ return this._controller.gesturing;
492
433
  }
493
434
  // --- the imperative surface ----------------------------------------------
435
+ //
436
+ // The controller's, for an application holding the element itself rather
437
+ // than `<Map>`'s handle.
494
438
  getCamera() {
495
- const camera = this.camera();
496
- return { center: { ...camera.center }, zoom: camera.zoom };
439
+ return this._controller.getCamera();
497
440
  }
498
441
  setCamera(camera) {
499
- const current = this.camera();
500
- this._applyCamera({
501
- center: camera.center ?? current.center,
502
- zoom: camera.zoom ?? current.zoom,
503
- }, false);
442
+ this._controller.setCamera(camera);
504
443
  }
505
444
  /** Move by a distance in pane-local logical pixels. */
506
445
  panBy(dx, dy) {
507
- const transform = this._transform();
508
- this._applyCamera({
509
- zoom: transform.zoom,
510
- center: unprojectPoint(transform, transform.paneX + dx, transform.paneY + dy),
511
- });
446
+ this._controller.panBy(dx, dy);
512
447
  }
513
448
  zoomIn(step = 1) {
514
- const pane = this._pane();
515
- this._zoomAbout(step, pane.width / 2, pane.height / 2);
449
+ this._controller.zoomIn(step);
516
450
  }
517
451
  zoomOut(step = 1) {
518
- this.zoomIn(-step);
452
+ this._controller.zoomOut(step);
519
453
  }
520
454
  zoomTo(zoom) {
521
- this.setCamera({ zoom });
455
+ this._controller.zoomTo(zoom);
522
456
  }
523
457
  fitBounds(bounds, options) {
524
- const pane = this._pane();
525
- if (pane.width <= 0 || pane.height <= 0) {
526
- // Asked before layout has run — which `fitBounds` in an effect always
527
- // is. Remembered and applied at the first paint that has a size.
528
- this._pendingFit = { bounds, options };
529
- return;
530
- }
531
- this._applyCamera(cameraForBounds(bounds, { width: pane.width, height: pane.height }, {
532
- padding: options?.padding ?? 24,
533
- tileSize: DEFAULT_TILE_SIZE,
534
- minZoom: this._minZoom(),
535
- maxZoom: options?.maxZoom ?? this._maxZoom(),
536
- }), false);
458
+ this._controller.fitBounds(bounds, options);
537
459
  }
538
460
  fitMarkers(ids, options) {
539
- const wanted = ids ? new Set(ids) : null;
540
- const positions = [];
541
- for (const marker of this._markers()) {
542
- if (wanted && !wanted.has(marker.id))
543
- continue;
544
- positions.push(marker.position);
545
- }
546
- const bounds = boundsOf(positions);
547
- if (bounds)
548
- this.fitBounds(bounds, options);
461
+ this._controller.fitMarkers(ids, options);
549
462
  }
550
463
  getBounds() {
551
- return visibleBounds(this._transform());
464
+ return this._controller.getBounds();
552
465
  }
553
466
  project(position) {
554
- return projectLngLat(this._transform(), position);
467
+ return this._controller.project(position);
555
468
  }
556
469
  unproject(x, y) {
557
- return unprojectPoint(this._transform(), x, y);
470
+ return this._controller.unproject(x, y);
558
471
  }
559
472
  markerAt(x, y) {
560
- return markerAt(this._markers(), this._transform(), x, y);
473
+ return this._controller.markerAt(x, y);
561
474
  }
562
475
  refresh() {
476
+ this._restyle();
477
+ }
478
+ /**
479
+ * Retire every rendered tile — for a new `mapStyle`, or `refresh()` after
480
+ * an edit made to the style in place. One path for both, and for anything
481
+ * else that ever needs the map redrawn in a new look.
482
+ *
483
+ * The compiled style and the label candidates go with the tiles: both are
484
+ * readings of the style, and `refresh()` exists because the object was
485
+ * edited under them. What does not go is the picture on screen. Unless
486
+ * there is none yet, or `progressive` asked to watch the repaint, it is
487
+ * held as {@link Outgoing} until the new one is ready to replace it.
488
+ *
489
+ * True when it is held. Then nothing on screen changes yet, so the only
490
+ * claim is the pixel that asks for a frame to start drawing in — and
491
+ * anything else a commit changed has to claim its own damage.
492
+ */
493
+ _restyle() {
494
+ this._prepared = null;
495
+ this._preparedFrom = null;
496
+ this._candidates.clear();
497
+ if (this._outgoing === null &&
498
+ this._painted &&
499
+ this._prop('progressive') !== true) {
500
+ this._outgoing = {
501
+ generation: this._cache.generation,
502
+ background: this._paintedBackground,
503
+ labels: this._labels,
504
+ labelZoom: this._labelZoom,
505
+ labelSources: this._labelSources,
506
+ candidates: this._labelCandidates,
507
+ family: this._shapedFamily,
508
+ view: this._viewKey(this._transform(), this._pane()),
509
+ moved: false,
510
+ waited: 0,
511
+ lastDrawn: null,
512
+ };
513
+ }
563
514
  this._cache.invalidateStyle();
564
515
  this._labelKey = '';
565
- this._repaint('content');
516
+ if (this._outgoing === null) {
517
+ this._repaint('props');
518
+ return false;
519
+ }
520
+ this._wake('props');
521
+ return true;
522
+ }
523
+ /** The camera and the pane, as a string that changes when either does. */
524
+ _viewKey(transform, pane) {
525
+ return `${transform.centerX},${transform.centerY},${transform.zoom},${pane.width}x${pane.height}`;
566
526
  }
567
527
  stats() {
568
528
  return this._stats;
569
529
  }
570
- _pendingFit = null;
571
530
  // --- painting ------------------------------------------------------------
572
531
  /** How tall the attribution strip is, in logical pixels — 0 when there is
573
532
  * nothing to say. Read by the blit as well as the paint, so the band it
@@ -576,16 +535,7 @@ export class MapViewNode extends Node {
576
535
  return this._attributionText() ? 16 : 0;
577
536
  }
578
537
  _attributionText() {
579
- const given = this._prop('attribution');
580
- if (given !== undefined)
581
- return given;
582
- const parts = [];
583
- for (const source of this._sources()) {
584
- if (source.attribution && !parts.includes(source.attribution)) {
585
- parts.push(source.attribution);
586
- }
587
- }
588
- return parts.join(' · ');
538
+ return attributionOf(this._prop('attribution'), this._sources());
589
539
  }
590
540
  /** How large a tile is rasterized, and how many surface pixels one
591
541
  * logical pixel is — the pair the rasterizer needs, and the pair that
@@ -619,7 +569,7 @@ export class MapViewNode extends Node {
619
569
  if (!this._visible() || !isMapCanvas(ctx))
620
570
  return;
621
571
  const started = now();
622
- this._painting = true;
572
+ const restyleAt = restyleClock.now();
623
573
  const scale = this._scale;
624
574
  this._frameClip = damage
625
575
  ? {
@@ -629,19 +579,17 @@ export class MapViewNode extends Node {
629
579
  height: damage.height / scale,
630
580
  }
631
581
  : null;
632
- if (this._pendingFit) {
633
- const { bounds, options } = this._pendingFit;
634
- this._pendingFit = null;
635
- this._painting = false;
636
- this.fitBounds(bounds, options);
637
- this._painting = true;
638
- }
582
+ // A fit asked for before layout had a size — `fitBounds` in an effect —
583
+ // lands in the first frame that has one, and this frame draws it.
584
+ this._controller.beginFrame();
585
+ this._controller.painting = true;
639
586
  const pane = this._pane();
640
587
  const camera = this.camera();
641
588
  const transform = this._transform(camera);
642
589
  const style = this._style();
643
590
  const frame = this._cache.beginFrame();
644
591
  const stats = {
592
+ renderer: 'retained',
645
593
  rasterMs: 0,
646
594
  drawMs: 0,
647
595
  tiles: 0,
@@ -649,6 +597,7 @@ export class MapViewNode extends Node {
649
597
  fromAncestor: 0,
650
598
  fromDescendant: 0,
651
599
  pending: 0,
600
+ restyling: false,
652
601
  labels: 0,
653
602
  errors: 0,
654
603
  surfaceBytes: 0,
@@ -677,17 +626,45 @@ export class MapViewNode extends Node {
677
626
  ? intersectRects(box, this._deviceRect(this._frameClip))
678
627
  : box;
679
628
  if (!clip) {
680
- this._painting = false;
629
+ this._controller.painting = false;
630
+ this._controller.paintEnded();
681
631
  this._frameClip = null;
682
632
  return;
683
633
  }
684
634
  ctx.rect(clip.x, clip.y, clip.width, clip.height);
685
635
  ctx.clip();
636
+ // Rasterization is suspended for the length of a gesture, so a drag or
637
+ // a wheel is composites only. `rasterBudgetMs` bounds the rest.
638
+ const budget = this._gesturing
639
+ ? 0
640
+ : (this._prop('rasterBudgetMs') ?? 8);
641
+ const deadline = started + budget;
642
+ this._rastered = false;
643
+ const progressive = this._prop('progressive') === true;
644
+ // **The work first, then the picture.** Every tile the cover wants is
645
+ // asked for, and the ones in view are drawn as far as the budget goes —
646
+ // into their own surfaces, so none of it is on screen yet. Which style
647
+ // this frame shows is decided only after that, because it depends on
648
+ // what the work just finished: a restyle swaps in the frame that finds
649
+ // the view redrawn, and has to know before the first pixel goes down.
650
+ const sources = this._sources();
651
+ const holding = this._outgoing !== null && !progressive;
652
+ const views = [];
653
+ for (let i = 0; i < sources.length; i++) {
654
+ views.push(this._workSource(sources[i], this._sourceId(sources[i], i), transform, pane, style, stats, budget > 0, deadline, holding));
655
+ }
656
+ this._settleRestyle(transform, pane, stats, budget, restyleAt, !damage || containsRect(damage, box), progressive);
657
+ const outgoing = this._outgoing;
658
+ stats.restyling = outgoing !== null;
686
659
  // The style's background under everything: it is what the parts of the
687
660
  // world with no tile yet look like, so it is most of what a map looks
688
- // like while it loads.
689
- const background = this.style.backgroundColor ??
690
- this._preparedBackground();
661
+ // like while it loads. The previous style's while a restyle holds it.
662
+ const styleBackground = outgoing
663
+ ? outgoing.background
664
+ : this._preparedBackground();
665
+ if (!outgoing)
666
+ this._paintedBackground = styleBackground;
667
+ const background = this.style.backgroundColor ?? styleBackground;
691
668
  if (background) {
692
669
  ctx.fillStyle = background;
693
670
  const region = this._frameClip
@@ -695,16 +672,16 @@ export class MapViewNode extends Node {
695
672
  : { x: box.x, y: box.y, width: box.width, height: box.height };
696
673
  ctx.fillRect(region.x, region.y, region.width, region.height);
697
674
  }
698
- // Rasterization is suspended for the length of a gesture, so a drag or
699
- // a wheel is composites only. `rasterBudgetMs` bounds the rest.
700
- const budget = this._gesturing
701
- ? 0
702
- : (this._prop('rasterBudgetMs') ?? 8);
703
- const deadline = started + budget;
704
- this._rastered = false;
705
- const sources = this._sources();
675
+ // Only pictures of the generation on screen are composited: the
676
+ // previous style's while a restyle holds it up, the current one's
677
+ // otherwise — so a tile last drawn before a switch is never shown in
678
+ // the style the map was switched away from. `progressive` shows
679
+ // whatever there is, which is its point.
680
+ const generation = progressive
681
+ ? undefined
682
+ : (outgoing?.generation ?? this._cache.generation);
706
683
  for (let i = 0; i < sources.length; i++) {
707
- this._paintSource(ctx, sources[i], this._sourceId(sources[i], i), transform, pane, style, stats, budget > 0, deadline);
684
+ this._drawSource(ctx, sources[i], views[i], stats, progressive, generation);
708
685
  }
709
686
  this._paintLabels(ctx, transform, pane, style, stats);
710
687
  const palette = this._palette();
@@ -716,7 +693,9 @@ export class MapViewNode extends Node {
716
693
  stats.surfaceBytes = this._cache.surfaceBytes;
717
694
  stats.drawMs = now() - started - stats.rasterMs;
718
695
  this._stats = stats;
719
- this._painting = false;
696
+ this._painted = true;
697
+ this._controller.painting = false;
698
+ this._controller.paintEnded();
720
699
  this._frameClip = null;
721
700
  void frame;
722
701
  this._prop('onFrame')?.(stats);
@@ -762,10 +741,83 @@ export class MapViewNode extends Node {
762
741
  height: Math.ceil((rect.y + rect.height) * s) - y,
763
742
  };
764
743
  }
765
- _paintSource(ctx, source, sourceId, transform, pane, style, stats,
744
+ /**
745
+ * Whether this frame keeps showing the previous style or swaps in the new
746
+ * one — see {@link Outgoing}.
747
+ *
748
+ * The swap waits for every tile in view that has data to have a finished
749
+ * rendering in the new style, which is `pending` reaching zero: a tile
750
+ * still loading has nothing to draw and does not hold it, and a tile that
751
+ * is done waits for the rest rather than going up alone. It is made only
752
+ * in a frame that repaints the whole pane, because a swap inside a partial
753
+ * frame would leave the rest of the pane in the old style — so a frame
754
+ * that finds the view redrawn but was clipped to its one pixel claims the
755
+ * pane, and the next frame swaps. That is the one full-pane claim a
756
+ * restyle makes.
757
+ *
758
+ * Or, once the view has moved, when {@link RESTYLE_WAIT_MS} of drawing
759
+ * time has gone by, with whatever is left undrawn.
760
+ */
761
+ _settleRestyle(transform, pane, stats, budget,
762
+ /** When this frame began, on {@link restyleClock}. */
763
+ began, whole, progressive) {
764
+ const outgoing = this._outgoing;
765
+ if (!outgoing)
766
+ return;
767
+ if (progressive) {
768
+ // Turned on while a restyle was held: the opt-in holds nothing.
769
+ this._swapStyle();
770
+ if (!whole)
771
+ this._repaint('content');
772
+ return;
773
+ }
774
+ if (!outgoing.moved && this._viewKey(transform, pane) !== outgoing.view) {
775
+ outgoing.moved = true;
776
+ }
777
+ // The clock runs across frames that could draw, two in a row, so the
778
+ // gap a gesture made — which drew nothing — is not counted when the
779
+ // next frame after it begins.
780
+ if (budget > 0) {
781
+ if (outgoing.lastDrawn !== null) {
782
+ outgoing.waited += began - outgoing.lastDrawn;
783
+ }
784
+ outgoing.lastDrawn = began;
785
+ }
786
+ else {
787
+ outgoing.lastDrawn = null;
788
+ }
789
+ const redrawn = stats.pending === 0;
790
+ // Never by the clock in the middle of a gesture: nothing is drawn
791
+ // during one, so the view would swap to holes it cannot fill until the
792
+ // gesture ends.
793
+ const overdue = budget > 0 && outgoing.moved && outgoing.waited >= RESTYLE_WAIT_MS;
794
+ if (!redrawn && !overdue)
795
+ return;
796
+ if (whole)
797
+ this._swapStyle();
798
+ else
799
+ this._repaint('content');
800
+ }
801
+ /** The new style goes on screen: every finished tile, its background and
802
+ * a label placement of its own, all in the frame being painted. */
803
+ _swapStyle() {
804
+ this._outgoing = null;
805
+ this._cache.swap();
806
+ this._labelKey = '';
807
+ }
808
+ /**
809
+ * A frame's work, for one source: want every tile of its cover, so that
810
+ * each loads and stays cached, and draw the ones in view into their own
811
+ * surfaces as far as the budget goes. Returns the ones in view, for
812
+ * {@link _drawSource} to composite once the frame knows which style it is
813
+ * showing.
814
+ */
815
+ _workSource(source, sourceId, transform, pane, style, stats,
766
816
  /** False for the length of a gesture, when nothing is rasterized. */
767
- mayRaster, deadline) {
768
- const scale = this._scale;
817
+ mayRaster, deadline,
818
+ /** A restyle is holding the previous style up, so a tile that finishes
819
+ * waits for the swap rather than going on screen alone. */
820
+ holding) {
769
821
  const p = pyramid(source);
770
822
  // The cover goes **deeper than the source cuts**, up to
771
823
  // `MAX_OVERZOOM` levels past it, and the data for those tiles comes
@@ -773,12 +825,18 @@ export class MapViewNode extends Node {
773
825
  // overzoomed map sharp: instead of one tile rasterized onto a surface
774
826
  // and stretched sixty-four times, there are two hundred and fifty-six
775
827
  // tiles sharing one fetch, each drawn at its own natural size, with
776
- // detail limited by the data rather than by a bitmap.
777
- const cover = tileCover({ ...transform, zoom: transform.zoom }, { ...p, maxZoom: p.maxZoom + MAX_OVERZOOM }, COVER_PADDING);
828
+ // detail limited by the data rather than by a bitmap. That is vector
829
+ // data. An image has no more detail than its pixels, so a raster tile
830
+ // past the cut is drawn as the whole of its data tile instead — see
831
+ // `_rasterSquares`.
832
+ const cover = this._rasterSquares(source, sourceId, tileCover({ ...transform, zoom: transform.zoom }, { ...p, maxZoom: p.maxZoom + MAX_OVERZOOM }, COVER_PADDING), p.maxZoom);
778
833
  const zoom = transform.zoom;
779
834
  const styleZoom = Math.floor(zoom);
780
- const progressive = this._prop('progressive') === true;
835
+ const visible = [];
781
836
  for (const entry of cover) {
837
+ // In the window's logical pixels, the space `pane` is in: what the
838
+ // overlap tests below, the claim when the tile lands and `_composite`
839
+ // all take. The pane's origin goes in here and nowhere after.
782
840
  const box = {
783
841
  x: pane.x + entry.x,
784
842
  y: pane.y + entry.y,
@@ -788,22 +846,27 @@ export class MapViewNode extends Node {
788
846
  // Two different questions, and conflating them was a bug worth
789
847
  // spelling out. **Whether to work on a tile** is about the pane: the
790
848
  // cover is padded, so some of it is off screen and those tiles are
791
- // wanted (so they load) but never drawn. **Whether to composite it**
849
+ // wanted (so they load) but never drawn. They are wanted on *every*
850
+ // frame, however small its damage rect, because the cache's `sweep`
851
+ // cancels any load a frame did not want. **Whether to composite it**
792
852
  // is about this pass's damage rect, which may be far smaller —
793
853
  // including the deliberately tiny claim a rasterization continuation
794
- // makes, which must still let the rasterizer run.
854
+ // makes, which must still let the rasterizer run. The first is asked
855
+ // here, and the second in `_drawSource`.
795
856
  const onScreen = rectsOverlap(box, pane);
796
- const inPass = this._frameClip === null || rectsOverlap(box, this._frameClip);
797
857
  const cached = this._cache.want(source, sourceId, entry.tile, dataTileFor(entry.tile, p.maxZoom), subTileOf(entry.tile, p.maxZoom));
798
858
  if (!onScreen)
799
859
  continue;
860
+ visible.push({ entry, box, cached });
800
861
  stats.tiles++;
801
862
  if (cached.status === 'error')
802
863
  stats.errors++;
803
864
  if (cached.status === 'ready') {
804
865
  const plan = this._rasterPlan(entry, p, zoom);
805
866
  const size = cached.raster ? cached.raster.width : plan.size;
806
- const drawing = this._cache.beginRender(cached, size, cached.raster ? 0 : styleZoom, (edge) => this._makeSurface(edge));
867
+ const drawing = this._cache.beginRender(cached, size, cached.raster ? 0 : styleZoom, (edge) => this._makeSurface(edge),
868
+ // A raster tile is the provider's image, the same in every style.
869
+ !cached.raster);
807
870
  if (drawing && drawing.progress !== -1) {
808
871
  if (cached.raster) {
809
872
  this._uploadRaster(cached, drawing);
@@ -823,18 +886,87 @@ export class MapViewNode extends Node {
823
886
  // goes blank between them. Claim the box it occupies, because
824
887
  // *that* is the pixel change this whole sequence of frames was
825
888
  // for; the frames before it claimed almost nothing.
826
- if (this._cache.promote(cached))
889
+ //
890
+ // Not while a restyle holds the previous style up, though: one
891
+ // tile in the new style among the rest in the old is the
892
+ // patchwork the hold is there to prevent. It waits, finished, for
893
+ // `_swapStyle` to put the whole view up at once. A raster tile
894
+ // does not wait — it is in no style, so it cannot make one.
895
+ if ((!holding || !drawing.styled) && this._cache.promote(cached)) {
827
896
  this._claim(box, 'content');
897
+ }
828
898
  }
829
899
  // "Pending" means *there is work left that this map could still
830
900
  // do*, and nothing weaker — because `paint` asks for another frame
831
- // while it is non-zero. A tile whose surface could not be made (a
832
- // backend that has none) never becomes drawable, and counting it
833
- // would spin the frame clock at the refresh rate forever,
834
- // repainting a map that cannot change.
835
- if (cached.drawing)
901
+ // while it is non-zero, and a restyle swaps when it reaches zero. A
902
+ // tile whose surface could not be made (a backend that has none)
903
+ // never becomes drawable, and counting it would spin the frame
904
+ // clock at the refresh rate forever, repainting a map that cannot
905
+ // change. A finished tile waiting for a restyle's swap has nothing
906
+ // left to do either.
907
+ if (cached.drawing && cached.drawing.progress !== -1)
836
908
  stats.pending++;
837
909
  }
910
+ }
911
+ return visible;
912
+ }
913
+ /**
914
+ * The cover, with every **raster** tile past the source's depth drawn as
915
+ * the whole of its data tile — once, however many of its cells there are.
916
+ *
917
+ * The cover goes deeper than the source cuts because that is what makes
918
+ * vector data sharp there: each cell is rasterized from its data tile's
919
+ * features, through the cell (`sub`, in `_rasterize`). An image has no
920
+ * detail finer than its pixels and no drawing it through a cell: a raster
921
+ * cell was drawn by uploading the image, all of it, so each cell showed
922
+ * its data tile whole, shrunk into its own square. Past a raster source's
923
+ * depth the map was a grid of miniatures of each tile — 2×2 one level
924
+ * past, 4×4 two — and a 256px source is read a level deeper than the
925
+ * view, so for `osmRasterSource`, which cuts at 19, that was every zoom
926
+ * from 19 up.
927
+ *
928
+ * A raster map past its provider's depth draws the deepest images larger,
929
+ * and so does this. The square is where the cover at the source's own
930
+ * depth puts the data tile, and it is drawn from that tile's own surface —
931
+ * the one the view at that depth draws, so crossing into overzoom uploads
932
+ * nothing.
933
+ *
934
+ * Which kind a tile is, is known once its data is in. Until then a cell
935
+ * stays a cell, and is covered from its neighbours in the pyramid like
936
+ * any other hole. Asking is a `want`, which the frame's own pass repeats
937
+ * for a cell kept here, and which is idempotent.
938
+ */
939
+ _rasterSquares(source, sourceId, cover, maxZoom) {
940
+ // A cover is one level, so every entry in it is past the cut or none is.
941
+ if (cover.length === 0 || cover[0].tile.z <= maxZoom)
942
+ return cover;
943
+ const out = [];
944
+ const squares = new Set();
945
+ for (const cell of cover) {
946
+ const cached = this._cache.want(source, sourceId, cell.tile, dataTileFor(cell.tile, maxZoom), subTileOf(cell.tile, maxZoom));
947
+ if (!cached.raster) {
948
+ out.push(cell);
949
+ continue;
950
+ }
951
+ const square = dataSquareOf(cell, maxZoom);
952
+ const key = `${square.worldCopy}:${tileKey(square.tile)}`;
953
+ if (squares.has(key))
954
+ continue;
955
+ squares.add(key);
956
+ out.push(square);
957
+ }
958
+ return out;
959
+ }
960
+ /**
961
+ * A frame's picture, for one source: composite each tile in view from its
962
+ * own finished rendering or, for a tile with none, from a finished
963
+ * ancestor or descendants. Only renderings of `generation` count, and
964
+ * `undefined` counts any — which is `progressive`.
965
+ */
966
+ _drawSource(ctx, source, visible, stats, progressive, generation) {
967
+ const scale = this._scale;
968
+ for (const { entry, box, cached } of visible) {
969
+ const inPass = this._frameClip === null || rectsOverlap(box, this._frameClip);
838
970
  // What is composited is `shown`, which is **finished by
839
971
  // construction** — a rendering only becomes `shown` when its last
840
972
  // style run is done. So a tile appears whole rather than as water,
@@ -845,22 +977,16 @@ export class MapViewNode extends Node {
845
977
  // `progressive` composites the draft instead, which is the old
846
978
  // behaviour and is honest about what the renderer is doing.
847
979
  const showing = progressive && cached.drawing ? cached.drawing : cached.shown;
848
- if (showing) {
980
+ if (showing && drawnFor(showing, generation)) {
849
981
  if (!inPass)
850
982
  continue;
851
- this._composite(ctx, showing.surface, showing.size, box, pane, scale, 0, 0, 1);
983
+ this._composite(ctx, showing.surface, showing.size, box, scale, 0, 0, 1);
852
984
  stats.ready++;
853
985
  continue;
854
986
  }
855
- // Nothing of this tile yet — a first load, which no buffering can
856
- // help. Borrow the ancestor that is already drawn, scaled up: that is
857
- // the difference between a map that fills in and one that flashes
858
- // empty on every zoom.
859
- if (!inPass)
860
- continue;
861
- // Nothing of this tile yet — a first load, which no buffering can
862
- // help. Two ways to cover it, and which is available says which way
863
- // the camera moved.
987
+ // Nothing of this tile to show — a first load, which no buffering can
988
+ // help, or a picture in a style the map is not showing. Two ways to
989
+ // cover it, and which is available says which way the camera moved.
864
990
  //
865
991
  // **Zooming in**, the tile already in hand is this one's *ancestor*:
866
992
  // one composite, scaled up, blurry but complete. **Zooming out**, the
@@ -870,35 +996,54 @@ export class MapViewNode extends Node {
870
996
  // background — with the labels and the markers still drawn over it —
871
997
  // until the coarser tile had been fetched, rasterized and composited.
872
998
  //
873
- // Descendants win when they cover the whole square, because they are
874
- // sharper and they are the level the user is coming *from*; the
875
- // ancestor wins when they do not, because a complete blurry picture
876
- // beats a sharp one with holes in it.
877
- const kids = this._cache.descendantsWithSurface(sourceId, entry.tile);
878
- const covered = kids.length > 0 && kids.length === kids[0].span * kids[0].span;
879
- const ancestor = covered
880
- ? null
881
- : this._cache.ancestorWithSurface(sourceId, entry.tile);
882
- if (ancestor?.shown) {
883
- const up = entry.tile.z - ancestor.tile.z;
884
- const span = 1 << up;
885
- const fx = entry.tile.x - (ancestor.tile.x << up);
886
- const fy = entry.tile.y - (ancestor.tile.y << up);
887
- this._composite(ctx, ancestor.shown.surface, ancestor.shown.size, box, pane, scale, fx, fy, span);
888
- stats.fromAncestor++;
999
+ // Both, in one square: the descendants wherever there are any, because
1000
+ // they are sharper and they are the level the user is coming *from*,
1001
+ // and the ancestor in the gaps between them. The first cut chose one
1002
+ // for the whole square — descendants only when they tiled it, and
1003
+ // only when no ancestor was in hand — and a zoom out lands at the
1004
+ // edges of the level it left, where they never tile it: that was the
1005
+ // background there, sharp tiles in hand or not.
1006
+ //
1007
+ // Looked up whether or not this pass draws the tile, because the
1008
+ // lookup is what stamps a piece as in use: a piece covering a hole is
1009
+ // on screen, and an unstamped entry is the first thing eviction takes
1010
+ // — which, in the one-pixel frames a redraw runs in, was every piece
1011
+ // but the one under that pixel. A restyle keeps such pieces on screen
1012
+ // for as long as its redraw takes.
1013
+ const below = this._cache.descendantsWithSurface(source, entry.tile, generation);
1014
+ const ancestor = below.gaps.length > 0
1015
+ ? this._cache.ancestorWithSurface(source, entry.tile, undefined, generation)
1016
+ : null;
1017
+ if (!inPass)
1018
+ continue;
1019
+ for (const piece of below.pieces) {
1020
+ const edge = entry.size / piece.span;
1021
+ this._composite(ctx, piece.value.shown.surface, piece.value.shown.size, {
1022
+ x: box.x + piece.x * edge,
1023
+ y: box.y + piece.y * edge,
1024
+ width: edge,
1025
+ height: edge,
1026
+ }, scale, 0, 0, 1);
889
1027
  }
890
- else if (kids.length > 0) {
891
- for (const kid of kids) {
892
- const piece = entry.size / kid.span;
893
- this._composite(ctx, kid.entry.shown.surface, kid.entry.shown.size, {
894
- x: box.x + kid.x * piece,
895
- y: box.y + kid.y * piece,
896
- width: piece,
897
- height: piece,
898
- }, pane, scale, 0, 0, 1);
899
- }
1028
+ if (below.pieces.length > 0)
900
1029
  stats.fromDescendant++;
1030
+ if (!ancestor?.shown)
1031
+ continue;
1032
+ for (const gap of below.gaps) {
1033
+ // The gap is a tile `log2(span)` levels under this one, and the
1034
+ // ancestor is `up` levels over that: the same sub-square arithmetic
1035
+ // as a whole tile, one level set deeper.
1036
+ const edge = entry.size / gap.span;
1037
+ const up = entry.tile.z + Math.log2(gap.span) - ancestor.tile.z;
1038
+ const span = 1 << up;
1039
+ this._composite(ctx, ancestor.shown.surface, ancestor.shown.size, {
1040
+ x: box.x + gap.x * edge,
1041
+ y: box.y + gap.y * edge,
1042
+ width: edge,
1043
+ height: edge,
1044
+ }, scale, entry.tile.x * gap.span + gap.x - (ancestor.tile.x << up), entry.tile.y * gap.span + gap.y - (ancestor.tile.y << up), span);
901
1045
  }
1046
+ stats.fromAncestor++;
902
1047
  }
903
1048
  }
904
1049
  /**
@@ -1030,18 +1175,28 @@ export class MapViewNode extends Node {
1030
1175
  * point. Clipping the destination and moving the source rectangle to
1031
1176
  * match keeps the scale factor `sw/dw` exactly what it was, so nothing
1032
1177
  * about the picture changes.
1178
+ *
1179
+ * **`dest` is already in the window's space**, the one `pane` is in — a
1180
+ * tile's `box` is `pane.x + entry.x` — so nothing is added to it here.
1181
+ * The pane's origin used to be added a second time, which drew every
1182
+ * tile that far right of and below where the markers, labels and
1183
+ * overlays put the same place. A constant offset on screen is a
1184
+ * different distance on the ground at every zoom, so it looked like a
1185
+ * marker sliding across the map as it zoomed, and like nothing at all as
1186
+ * it panned, where the blit moves both together. Every test mounted the
1187
+ * map at the window's origin, where the offset is zero.
1033
1188
  */
1034
1189
  _composite(ctx, surface, size,
1035
- /** Where it lands, in pane-local logical pixels. */
1036
- dest, pane, scale,
1190
+ /** Where it lands, in the window's logical pixels. */
1191
+ dest, scale,
1037
1192
  /** Which sub-square of the surface to take, in `subSpan`ths. */
1038
1193
  subX, subY, subSpan) {
1039
1194
  if (!ctx.drawImage || size <= 0)
1040
1195
  return;
1041
- const x0 = Math.round((pane.x + dest.x) * scale);
1042
- const y0 = Math.round((pane.y + dest.y) * scale);
1043
- const x1 = Math.round((pane.x + dest.x + dest.width) * scale);
1044
- const y1 = Math.round((pane.y + dest.y + dest.height) * scale);
1196
+ const x0 = Math.round(dest.x * scale);
1197
+ const y0 = Math.round(dest.y * scale);
1198
+ const x1 = Math.round((dest.x + dest.width) * scale);
1199
+ const y1 = Math.round((dest.y + dest.height) * scale);
1045
1200
  if (x1 <= x0 || y1 <= y0)
1046
1201
  return;
1047
1202
  // The clip is the content box — device pixels, like everything core
@@ -1083,7 +1238,11 @@ export class MapViewNode extends Node {
1083
1238
  if (!fonts)
1084
1239
  return; // headless: nothing to shape with
1085
1240
  const text = this.resolvedTextStyle();
1086
- const family = this._prop('mapStyle')?.fontFamily ??
1241
+ const outgoing = this._outgoing;
1242
+ // While a restyle holds the previous style up, its labels are set in
1243
+ // its face.
1244
+ const family = outgoing?.family ??
1245
+ this._prop('mapStyle')?.fontFamily ??
1087
1246
  this._defaultStyle?.fontFamily ??
1088
1247
  text.family;
1089
1248
  if (!this._shaper) {
@@ -1092,24 +1251,60 @@ export class MapViewNode extends Node {
1092
1251
  else {
1093
1252
  this._shaper.reconfigure(fonts, family, this._scale);
1094
1253
  }
1095
- const key = `${quantize(transform.zoom)}|${this._cache.generation}`;
1254
+ const clip = this._frameClip ? { ...this._frameClip } : null;
1255
+ if (outgoing) {
1256
+ // The labels that were on screen when the restyle began, and not the
1257
+ // new style's, which would go up over a map still drawn in the old
1258
+ // one. Re-placed only when they have to be — a placement is for one
1259
+ // zoom, and a source taken off the map takes its names with it — and
1260
+ // from what they were placed from, since the style that produced
1261
+ // that is being replaced, or was edited in place.
1262
+ const zoom = quantizeZoom(transform.zoom);
1263
+ const sources = this._sources();
1264
+ if (zoom !== outgoing.labelZoom ||
1265
+ !sameSources(sources, outgoing.labelSources)) {
1266
+ const candidates = [];
1267
+ for (const source of sources) {
1268
+ for (const candidate of outgoing.candidates.get(source) ?? []) {
1269
+ candidates.push(candidate);
1270
+ }
1271
+ }
1272
+ outgoing.labels = placeLabels(candidates, transform.world, this._shaper);
1273
+ outgoing.labelZoom = zoom;
1274
+ outgoing.labelSources = sources;
1275
+ }
1276
+ stats.labels = drawLabels(ctx, outgoing.labels, transform, pane, this._scale, clip, this._shaper);
1277
+ return;
1278
+ }
1279
+ this._shapedFamily = family;
1280
+ const key = `${quantizeZoom(transform.zoom)}|${this._cache.generation}`;
1096
1281
  if (key !== this._labelKey) {
1097
1282
  this._labelKey = key;
1098
1283
  const styleZoom = Math.floor(transform.zoom);
1099
1284
  // Labels come from the tile the **data** came from, at the depth that
1100
1285
  // source actually cuts — past which many renderings share one tile
1101
1286
  // and collecting per rendering would place every label `span²` times.
1102
- const wanted = new Set(this._sources().map((source) => Math.min(styleZoom, pyramid(source).maxZoom)));
1287
+ //
1288
+ // And only from the sources on the map now. The cache keeps a
1289
+ // provider's tiles after it is switched away, so that switching back
1290
+ // is free; collecting from all of them drew the old provider's place
1291
+ // names over the new one's map until eviction reached them.
1292
+ const sources = this._sources();
1293
+ const wanted = new Map();
1294
+ for (const source of sources) {
1295
+ wanted.set(source, Math.min(styleZoom, pyramid(source).maxZoom));
1296
+ }
1103
1297
  const candidates = [];
1298
+ const bySource = new Map();
1104
1299
  for (const cached of this._cache.dataEntries()) {
1105
1300
  if (cached.status !== 'ready' || !cached.vector)
1106
1301
  continue;
1107
- if (!wanted.has(cached.tile.z))
1302
+ if (wanted.get(cached.source) !== cached.tile.z)
1108
1303
  continue;
1109
1304
  const at = `${cached.key}|${styleZoom}`;
1110
1305
  let found = this._candidates.get(at);
1111
1306
  if (!found) {
1112
- found = collectLabels(cached.vector, cached.tile, style, styleZoom, this._geometry);
1307
+ found = collectLabels(cached.vector, cached.tile, style, styleZoom);
1113
1308
  // Bounded the way the shaper's cache is: a map panned across a
1114
1309
  // continent must not turn this into a leak, and rebuilding a
1115
1310
  // tile's candidates is one walk over its symbol layers.
@@ -1117,12 +1312,22 @@ export class MapViewNode extends Node {
1117
1312
  this._candidates.clear();
1118
1313
  this._candidates.set(at, found);
1119
1314
  }
1120
- for (const candidate of found)
1315
+ let own = bySource.get(cached.source);
1316
+ if (!own) {
1317
+ own = [];
1318
+ bySource.set(cached.source, own);
1319
+ }
1320
+ for (const candidate of found) {
1121
1321
  candidates.push(candidate);
1322
+ own.push(candidate);
1323
+ }
1122
1324
  }
1123
1325
  this._labels = placeLabels(candidates, transform.world, this._shaper);
1326
+ this._labelZoom = quantizeZoom(transform.zoom);
1327
+ this._labelSources = sources;
1328
+ this._labelCandidates = bySource;
1124
1329
  }
1125
- stats.labels = drawLabels(ctx, this._labels, transform, pane, this._scale, this._frameClip ? { ...this._frameClip } : null, this._shaper);
1330
+ stats.labels = drawLabels(ctx, this._labels, transform, pane, this._scale, clip, this._shaper);
1126
1331
  }
1127
1332
  /**
1128
1333
  * The attribution.
@@ -1137,152 +1342,60 @@ export class MapViewNode extends Node {
1137
1342
  const text = this._attributionText();
1138
1343
  if (!text || !this._shaper)
1139
1344
  return;
1140
- const shaped = this._shaper.shape(text, 9, palette.text);
1345
+ const shaped = this._shaper.shape(text, ATTRIBUTION_SIZE, palette.text);
1141
1346
  if (!shaped)
1142
1347
  return;
1143
- const scale = this._scale;
1144
- const padding = 4;
1145
- const width = shaped.width + padding * 2;
1146
- const height = shaped.height + padding;
1147
- const x = pane.x + pane.width - width;
1148
- const y = pane.y + pane.height - height;
1348
+ const at = attributionLayout(shaped, pane, this._scale);
1149
1349
  ctx.save();
1150
1350
  if (ctx.globalAlpha !== undefined)
1151
- ctx.globalAlpha = 0.72;
1351
+ ctx.globalAlpha = ATTRIBUTION_OPACITY;
1152
1352
  ctx.fillStyle = palette.background;
1153
- ctx.fillRect(Math.round(x * scale), Math.round(y * scale), Math.ceil(width * scale), Math.ceil(height * scale));
1353
+ ctx.fillRect(at.box.x, at.box.y, at.box.width, at.box.height);
1154
1354
  if (ctx.globalAlpha !== undefined)
1155
1355
  ctx.globalAlpha = 1;
1156
- shaped.layout.draw(ctx, Math.round((x + padding) * scale), Math.round((y + padding / 2) * scale));
1356
+ shaped.layout.draw(ctx, at.x, at.y);
1157
1357
  ctx.restore();
1158
1358
  }
1159
1359
  // --- behaviour -----------------------------------------------------------
1160
- /** An event's position in the pane's own logical pixels. A synthetic
1161
- * event's `x`/`y` are logical and relative to the window, so only the
1162
- * pane's own origin has to come off. */
1163
- _point(ev) {
1360
+ /**
1361
+ * An event as the controller reads it. A synthetic event's `x`/`y` are
1362
+ * logical and relative to the window, so only the pane's own origin has
1363
+ * to come off.
1364
+ */
1365
+ _input(ev) {
1164
1366
  const pane = this._pane();
1165
- return { x: ev.x - pane.x, y: ev.y - pane.y };
1166
- }
1167
- _pointerEvent(ev, marker) {
1168
- const point = this._point(ev);
1169
1367
  return {
1170
- lngLat: unprojectPoint(this._transform(), point.x, point.y),
1171
- x: point.x,
1172
- y: point.y,
1173
- marker,
1174
- shiftKey: ev.shiftKey ?? false,
1175
- ctrlKey: ev.ctrlKey ?? false,
1176
- altKey: ev.altKey ?? false,
1177
- metaKey: ev.metaKey ?? false,
1178
- button: ev.button ?? 1,
1368
+ x: ev.x - pane.x,
1369
+ y: ev.y - pane.y,
1370
+ button: ev.button,
1371
+ // The click count core puts on a press and on its release, which the
1372
+ // declarations do not name.
1373
+ detail: ev.detail,
1374
+ shiftKey: ev.shiftKey,
1375
+ ctrlKey: ev.ctrlKey,
1376
+ altKey: ev.altKey,
1377
+ metaKey: ev.metaKey,
1179
1378
  };
1180
1379
  }
1181
1380
  defaultMouseDown(ev) {
1182
- const point = this._point(ev);
1183
- const marker = markerAt(this._markers(), this._transform(), point.x, point.y);
1184
- if (marker) {
1185
- this._gesture = {
1186
- kind: 'marker',
1187
- id: marker.id,
1188
- startX: point.x,
1189
- startY: point.y,
1190
- };
1191
- ev.capturePointer?.();
1192
- return;
1193
- }
1194
- if (!this._interactive()) {
1195
- // Not a pan, but still a press: the release is what makes a click,
1196
- // and an application listening for one on a frozen map should get it.
1197
- this._gesture = {
1198
- kind: 'pan',
1199
- startX: point.x,
1200
- startY: point.y,
1201
- lastX: point.x,
1202
- lastY: point.y,
1203
- moved: false,
1204
- };
1205
- ev.capturePointer?.();
1206
- return;
1207
- }
1208
- this._gesture = {
1209
- kind: 'pan',
1210
- startX: point.x,
1211
- startY: point.y,
1212
- lastX: point.x,
1213
- lastY: point.y,
1214
- moved: false,
1215
- };
1216
- this.focus();
1381
+ // A pan takes the focus, so the arrows work next. A press on a marker,
1382
+ // or on a map that does not move, is captured all the same: its release
1383
+ // is a click, and an application listening for one should get it.
1384
+ if (this._controller.pointerDown(this._input(ev)) === 'pan')
1385
+ this.focus();
1217
1386
  ev.capturePointer?.();
1218
1387
  }
1219
1388
  defaultMouseDrag(ev) {
1220
- const gesture = this._gesture;
1221
- if (!gesture || gesture.kind !== 'pan')
1222
- return;
1223
- if (!this._interactive())
1224
- return;
1225
- const point = this._point(ev);
1226
- if (!gesture.moved &&
1227
- Math.abs(point.x - gesture.startX) < DRAG_THRESHOLD &&
1228
- Math.abs(point.y - gesture.startY) < DRAG_THRESHOLD) {
1229
- return;
1230
- }
1231
- gesture.moved = true;
1232
- // Whole device pixels, because that is what the blit can shift — a
1233
- // fractional pan would decline it every frame and repaint the pane.
1234
- const scale = this._scale;
1235
- const dx = Math.round((point.x - gesture.lastX) * scale) / scale;
1236
- const dy = Math.round((point.y - gesture.lastY) * scale) / scale;
1237
- if (dx === 0 && dy === 0)
1238
- return;
1239
- gesture.lastX += dx;
1240
- gesture.lastY += dy;
1241
- this._touchGesture();
1242
- this.panBy(-dx, -dy);
1389
+ this._controller.pointerDrag(this._input(ev));
1243
1390
  }
1244
1391
  defaultMouseUp(ev) {
1245
- const gesture = this._gesture;
1246
- this._gesture = null;
1247
- if (!gesture)
1248
- return;
1249
- const point = this._point(ev);
1250
- if (gesture.kind === 'marker') {
1251
- const marker = this._markers().find((m) => m.id === gesture.id);
1252
- if (marker &&
1253
- Math.abs(point.x - gesture.startX) < DRAG_THRESHOLD &&
1254
- Math.abs(point.y - gesture.startY) < DRAG_THRESHOLD) {
1255
- const event = this._pointerEvent(ev, marker);
1256
- this._prop('onMarkerClick')?.(marker, event);
1257
- this._prop('onMapClick')?.(event);
1258
- }
1259
- return;
1260
- }
1261
- if (gesture.moved) {
1262
- this._touchGesture();
1263
- return;
1264
- }
1265
- this._prop('onMapClick')?.(this._pointerEvent(ev, null));
1392
+ this._controller.pointerUp(this._input(ev));
1266
1393
  }
1267
1394
  defaultMouseMove(ev) {
1268
- const notify = this._prop('onMarkerHover');
1269
- if (!notify)
1270
- return;
1271
- const point = this._point(ev);
1272
- const marker = markerAt(this._markers(), this._transform(), point.x, point.y);
1273
- const id = marker?.id ?? null;
1274
- if (id === this._hover)
1275
- return;
1276
- this._hover = id;
1277
- notify(marker, this._pointerEvent(ev, marker));
1395
+ this._controller.pointerMove(this._input(ev));
1278
1396
  }
1279
1397
  defaultMouseLeave() {
1280
- if (this._hover === null)
1281
- return;
1282
- this._hover = null;
1283
- // No event: the pointer has left the map, so there is no position on it
1284
- // to report and inventing one would be worse than saying so.
1285
- this._prop('onMarkerHover')?.(null, null);
1398
+ this._controller.pointerLeave();
1286
1399
  }
1287
1400
  /**
1288
1401
  * The wheel is a zoom, not a scroll, so it is answered here rather than
@@ -1291,119 +1404,131 @@ export class MapViewNode extends Node {
1291
1404
  * scroll chain never hands over.
1292
1405
  */
1293
1406
  defaultWheel(ev) {
1294
- if (!this._interactive())
1295
- return;
1296
1407
  const pane = this._pane();
1297
- this._touchGesture();
1298
- this._zoomAbout(-(ev.deltaY ?? 0) * WHEEL_ZOOM * 0.02, ev.x - pane.x, ev.y - pane.y);
1299
- // Consumed whether or not the zoom moved: a wheel over a map is never
1300
- // meant for whatever is behind it.
1301
- ev.preventDefault();
1408
+ const point = { x: ev.x - pane.x, y: ev.y - pane.y };
1409
+ // Consumed whether or not the zoom moved: a wheel over a map that moves
1410
+ // is never meant for whatever is behind it.
1411
+ // `ev.smooth` is the device: a wheel that clicks whole notches, or a
1412
+ // touchpad whose valuators measured the scroll. The controller eases
1413
+ // the one and applies the other (`MapController.wheel`).
1414
+ if (this._controller.wheel(point, ev.deltaY ?? 0, ev.smooth === true)) {
1415
+ ev.preventDefault();
1416
+ }
1302
1417
  }
1303
1418
  defaultKeyDown(ev) {
1304
- // `Node` declares the default actions optional — an element that has
1305
- // no behaviour of its own simply has none — so calling up is an
1306
- // optional call rather than a plain one.
1307
- if (!this._interactive()) {
1308
- super.defaultKeyDown?.(ev);
1419
+ if (this._controller.keyDown(ev.keysym ?? 0, ev.shiftKey ?? false)) {
1420
+ ev.preventDefault();
1309
1421
  return;
1310
1422
  }
1311
- const pane = this._pane();
1312
- const step = ev.shiftKey ? 200 : 60;
1313
- switch (ev.keysym) {
1314
- case 0xff51: // XK_Left
1315
- this.panBy(-step, 0);
1316
- break;
1317
- case 0xff53: // XK_Right
1318
- this.panBy(step, 0);
1319
- break;
1320
- case 0xff52: // XK_Up
1321
- this.panBy(0, -step);
1322
- break;
1323
- case 0xff54: // XK_Down
1324
- this.panBy(0, step);
1325
- break;
1326
- case 0x002b: // XK_plus
1327
- case 0x003d: // XK_equal
1328
- case 0xffab: // XK_KP_Add
1329
- this._zoomAbout(1, pane.width / 2, pane.height / 2);
1330
- break;
1331
- case 0x002d: // XK_minus
1332
- case 0xffad: // XK_KP_Subtract
1333
- this._zoomAbout(-1, pane.width / 2, pane.height / 2);
1334
- break;
1335
- default:
1336
- // Everything else goes to the base class, which is what keeps the
1337
- // selection keys and Space/Enter-as-a-click working.
1338
- super.defaultKeyDown?.(ev);
1339
- return;
1340
- }
1341
- ev.preventDefault();
1423
+ // Everything else goes to the base class, which is what keeps the
1424
+ // selection keys and Space/Enter-as-a-click working. `Node` declares the
1425
+ // default actions optional — an element that has no behaviour of its
1426
+ // own simply has none — so calling up is an optional call.
1427
+ super.defaultKeyDown?.(ev);
1428
+ }
1429
+ /** What a screen reader meets: the markers in view, as buttons — the
1430
+ * controller's answer, which both renderers give. */
1431
+ a11yScene() {
1432
+ if (!this._visible())
1433
+ return [];
1434
+ return this._controller.markerScene();
1342
1435
  }
1436
+ /** Every source this element has been handed, and how many `sources`
1437
+ * changes in a row have made a slot anew — see `_noticeRemade`. */
1438
+ _seenSources = new WeakSet();
1439
+ _remade = 0;
1440
+ _warnedRemade = false;
1343
1441
  /**
1344
- * What a screen reader meets.
1442
+ * Say so, once, when the application makes a source anew on every
1443
+ * render.
1345
1444
  *
1346
- * A map is one painted rectangle to an assistive technology, and its
1347
- * markers are the only things in it that are *objects* rather than
1348
- * cartography — so those are the scene, and the map itself carries the
1349
- * camera in its description. Announcing every road would be worse than
1350
- * announcing none.
1445
+ * Tiles are cached per source object (see `./tiles.ts`), so a source
1446
+ * rebuilt on each render starts from an empty cache on each render: every
1447
+ * tile in view is fetched again, and the map is its background until they
1448
+ * land. Under a controlled camera that is a render per pan step — a drag
1449
+ * across a blank map that fetches the whole view on every step.
1450
+ *
1451
+ * What gives it away is a slot handed a source this element has never
1452
+ * had, looking like the one it replaces — the same id, pyramid and
1453
+ * attribution — on three `sources` changes in a row. A provider switch
1454
+ * does that once, and a switch back to a source already shown does not
1455
+ * count at all. Looking alike is only ever the basis of a warning, never
1456
+ * of a cache key: two providers can look exactly alike.
1457
+ *
1458
+ * `process` and `console` come off `globalThis` because `src/` compiles
1459
+ * with `types: []`, as for the terminal's warning.
1351
1460
  */
1352
- a11yScene() {
1353
- if (!this._visible())
1354
- return [];
1355
- const transform = this._transform();
1356
- const pane = this._pane();
1357
- const scale = this._scale;
1358
- const items = [];
1359
- for (const marker of this._markers()) {
1360
- const rect = markerRect(marker, transform);
1361
- if (rect.x + rect.width < 0 ||
1362
- rect.y + rect.height < 0 ||
1363
- rect.x > pane.width ||
1364
- rect.y > pane.height) {
1365
- continue;
1461
+ _noticeRemade(before, next) {
1462
+ if (this._warnedRemade)
1463
+ return;
1464
+ let slot = -1;
1465
+ if (before && next) {
1466
+ const length = Math.min(before.length, next.length);
1467
+ for (let i = 0; i < length && slot < 0; i++) {
1468
+ const was = before[i];
1469
+ const now = next[i];
1470
+ if (was &&
1471
+ now &&
1472
+ now !== was &&
1473
+ !this._seenSources.has(now) &&
1474
+ now.id === was.id &&
1475
+ now.tileSize === was.tileSize &&
1476
+ now.minZoom === was.minZoom &&
1477
+ now.maxZoom === was.maxZoom &&
1478
+ now.attribution === was.attribution) {
1479
+ slot = i;
1480
+ }
1366
1481
  }
1367
- items.push({
1368
- id: `marker:${marker.id}`,
1369
- // Device pixels in the owning window's coordinates, which is what
1370
- // an a11y scene rect is — the same space as `abs`.
1371
- rect: {
1372
- x: (pane.x + rect.x) * scale,
1373
- y: (pane.y + rect.y) * scale,
1374
- width: rect.width * scale,
1375
- height: rect.height * scale,
1376
- },
1377
- role: 'button',
1378
- name: marker.title ??
1379
- `${marker.position.lat.toFixed(4)}, ${marker.position.lon.toFixed(4)}`,
1380
- states: { selected: marker.selected ?? false },
1381
- });
1382
1482
  }
1383
- return items;
1483
+ for (const source of before ?? [])
1484
+ this._seenSources.add(source);
1485
+ for (const source of next ?? [])
1486
+ this._seenSources.add(source);
1487
+ this._remade = slot < 0 ? 0 : this._remade + 1;
1488
+ if (this._remade < 3)
1489
+ return;
1490
+ this._warnedRemade = true;
1491
+ const g = globalThis;
1492
+ if (g.process?.env?.NODE_ENV === 'production')
1493
+ return;
1494
+ g.console?.warn(`@react-x11/components: <Map> was handed a new sources[${slot}] on ` +
1495
+ 'three renders in a row. Tiles are cached per source object, so ' +
1496
+ 'each one started from an empty cache and fetched every tile in ' +
1497
+ 'view again. If it is the same provider each time, make it once — ' +
1498
+ 'at module scope, or with useMemo.');
1384
1499
  }
1385
1500
  applyProps(next, prev) {
1386
1501
  const before = prev ?? this.props;
1387
1502
  super.applyProps(next, prev);
1503
+ if (this._ownsController) {
1504
+ this._controller.setProps(this.props);
1505
+ }
1506
+ if (next.sources !== before.sources) {
1507
+ this._noticeRemade(before.sources, next.sources);
1508
+ }
1388
1509
  // Every one of these is in `selfDamagedProps`, so the commit claimed
1389
1510
  // nothing for them and this is the only claim there will be.
1390
- if (next.mapStyle !== before.mapStyle) {
1391
- this._prepared = null;
1392
- this._preparedFrom = null;
1393
- this._candidates.clear();
1394
- this._cache.invalidateStyle();
1395
- this._labelKey = '';
1396
- this._repaint('props');
1511
+ //
1512
+ // A restyle that repainted the pane covers everything else the commit
1513
+ // changed. One that is held claims a single pixel — nothing on screen
1514
+ // changes until the swap — so a camera or the markers moved in the same
1515
+ // commit still have to claim their own damage below.
1516
+ if (next.mapStyle !== before.mapStyle && !this._restyle())
1397
1517
  return;
1398
- }
1399
1518
  if (next.sources !== before.sources) {
1519
+ // Nothing to throw away here: tiles are filed under the source
1520
+ // object, so a new provider starts empty, and one switched away from
1521
+ // keeps the tiles it has for when it comes back — its loads still in
1522
+ // flight are cancelled by the next frame's `sweep`, since nothing asks
1523
+ // for them. Only the label placement is stale.
1400
1524
  this._labelKey = '';
1401
1525
  this._repaint('props');
1402
1526
  return;
1403
1527
  }
1404
1528
  if (next.camera !== before.camera && next.camera !== undefined) {
1405
1529
  const camera = next.camera;
1406
- const previous = before.camera ?? this._camera;
1530
+ const previous = before.camera ??
1531
+ this._controller.ownCamera();
1407
1532
  if (camera.zoom !== previous.zoom ||
1408
1533
  camera.center.lon !== previous.center.lon ||
1409
1534
  camera.center.lat !== previous.center.lat) {
@@ -1446,13 +1571,14 @@ export class MapViewNode extends Node {
1446
1571
  claim(after);
1447
1572
  }
1448
1573
  destroySubtree() {
1449
- if (this._settleTimer !== null) {
1450
- timers.clearTimeout?.(this._settleTimer);
1451
- this._settleTimer = null;
1452
- }
1574
+ this._controller.detach(this._view);
1575
+ if (this._ownsController)
1576
+ this._controller.dispose();
1453
1577
  this._cache.destroy();
1454
1578
  this._candidates.clear();
1455
1579
  this._labels = [];
1580
+ this._labelCandidates = new Map();
1581
+ this._outgoing = null;
1456
1582
  super.destroySubtree();
1457
1583
  }
1458
1584
  }