@react-x11/components 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/README.md +95 -34
  2. package/dist/charts/node.d.ts +1 -1
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +6 -2
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/embed/client.d.ts +48 -3
  7. package/dist/embed/client.d.ts.map +1 -1
  8. package/dist/embed/client.js +79 -5
  9. package/dist/embed/client.js.map +1 -1
  10. package/dist/embed/index.d.ts +1 -1
  11. package/dist/embed/index.d.ts.map +1 -1
  12. package/dist/embed/index.js +1 -1
  13. package/dist/embed/index.js.map +1 -1
  14. package/dist/html/node.d.ts +3 -1
  15. package/dist/html/node.d.ts.map +1 -1
  16. package/dist/html/node.js +7 -5
  17. package/dist/html/node.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
  23. package/dist/internal/markdown/ast.d.ts.map +1 -0
  24. package/dist/internal/markdown/ast.js +22 -0
  25. package/dist/internal/markdown/ast.js.map +1 -0
  26. package/dist/internal/markdown/parse.d.ts.map +1 -0
  27. package/dist/{markdown → internal/markdown}/parse.js +112 -22
  28. package/dist/internal/markdown/parse.js.map +1 -0
  29. package/dist/internal/markdown/stringify.d.ts +4 -0
  30. package/dist/internal/markdown/stringify.d.ts.map +1 -0
  31. package/dist/internal/markdown/stringify.js +684 -0
  32. package/dist/internal/markdown/stringify.js.map +1 -0
  33. package/dist/internal/markdown/tags.d.ts.map +1 -0
  34. package/dist/internal/markdown/tags.js.map +1 -0
  35. package/dist/internal/scroll.d.ts +3 -2
  36. package/dist/internal/scroll.d.ts.map +1 -1
  37. package/dist/internal/scroll.js +1 -0
  38. package/dist/internal/scroll.js.map +1 -1
  39. package/dist/maps/anchors.d.ts +98 -0
  40. package/dist/maps/anchors.d.ts.map +1 -0
  41. package/dist/maps/anchors.js +531 -0
  42. package/dist/maps/anchors.js.map +1 -0
  43. package/dist/maps/controller.d.ts +283 -0
  44. package/dist/maps/controller.d.ts.map +1 -0
  45. package/dist/maps/controller.js +743 -0
  46. package/dist/maps/controller.js.map +1 -0
  47. package/dist/maps/gl/buckets.d.ts +95 -0
  48. package/dist/maps/gl/buckets.d.ts.map +1 -0
  49. package/dist/maps/gl/buckets.js +362 -0
  50. package/dist/maps/gl/buckets.js.map +1 -0
  51. package/dist/maps/gl/build-worker.d.ts +13 -0
  52. package/dist/maps/gl/build-worker.d.ts.map +1 -0
  53. package/dist/maps/gl/build-worker.js +44 -0
  54. package/dist/maps/gl/build-worker.js.map +1 -0
  55. package/dist/maps/gl/color.d.ts +8 -0
  56. package/dist/maps/gl/color.d.ts.map +1 -0
  57. package/dist/maps/gl/color.js +57 -0
  58. package/dist/maps/gl/color.js.map +1 -0
  59. package/dist/maps/gl/cover.d.ts +51 -0
  60. package/dist/maps/gl/cover.d.ts.map +1 -0
  61. package/dist/maps/gl/cover.js +124 -0
  62. package/dist/maps/gl/cover.js.map +1 -0
  63. package/dist/maps/gl/index.d.ts +3 -0
  64. package/dist/maps/gl/index.d.ts.map +1 -0
  65. package/dist/maps/gl/index.js +20 -0
  66. package/dist/maps/gl/index.js.map +1 -0
  67. package/dist/maps/gl/markers.d.ts +28 -0
  68. package/dist/maps/gl/markers.d.ts.map +1 -0
  69. package/dist/maps/gl/markers.js +60 -0
  70. package/dist/maps/gl/markers.js.map +1 -0
  71. package/dist/maps/gl/overlays.d.ts +68 -0
  72. package/dist/maps/gl/overlays.d.ts.map +1 -0
  73. package/dist/maps/gl/overlays.js +300 -0
  74. package/dist/maps/gl/overlays.js.map +1 -0
  75. package/dist/maps/gl/pane.d.ts +12 -0
  76. package/dist/maps/gl/pane.d.ts.map +1 -0
  77. package/dist/maps/gl/pane.js +37 -0
  78. package/dist/maps/gl/pane.js.map +1 -0
  79. package/dist/maps/gl/placement.d.ts +111 -0
  80. package/dist/maps/gl/placement.d.ts.map +1 -0
  81. package/dist/maps/gl/placement.js +567 -0
  82. package/dist/maps/gl/placement.js.map +1 -0
  83. package/dist/maps/gl/renderer.d.ts +352 -0
  84. package/dist/maps/gl/renderer.d.ts.map +1 -0
  85. package/dist/maps/gl/renderer.js +1127 -0
  86. package/dist/maps/gl/renderer.js.map +1 -0
  87. package/dist/maps/gl/shaders.d.ts +120 -0
  88. package/dist/maps/gl/shaders.d.ts.map +1 -0
  89. package/dist/maps/gl/shaders.js +451 -0
  90. package/dist/maps/gl/shaders.js.map +1 -0
  91. package/dist/maps/gl/store.d.ts +115 -0
  92. package/dist/maps/gl/store.d.ts.map +1 -0
  93. package/dist/maps/gl/store.js +469 -0
  94. package/dist/maps/gl/store.js.map +1 -0
  95. package/dist/maps/gl/text.d.ts +154 -0
  96. package/dist/maps/gl/text.d.ts.map +1 -0
  97. package/dist/maps/gl/text.js +382 -0
  98. package/dist/maps/gl/text.js.map +1 -0
  99. package/dist/maps/gl/view.d.ts +54 -0
  100. package/dist/maps/gl/view.d.ts.map +1 -0
  101. package/dist/maps/gl/view.js +974 -0
  102. package/dist/maps/gl/view.js.map +1 -0
  103. package/dist/maps/index.d.ts +1 -1
  104. package/dist/maps/index.d.ts.map +1 -1
  105. package/dist/maps/index.js +216 -52
  106. package/dist/maps/index.js.map +1 -1
  107. package/dist/maps/labels.d.ts +37 -16
  108. package/dist/maps/labels.d.ts.map +1 -1
  109. package/dist/maps/labels.js +188 -119
  110. package/dist/maps/labels.js.map +1 -1
  111. package/dist/maps/node.d.ts +162 -56
  112. package/dist/maps/node.d.ts.map +1 -1
  113. package/dist/maps/node.js +607 -515
  114. package/dist/maps/node.js.map +1 -1
  115. package/dist/maps/overlay.d.ts +72 -9
  116. package/dist/maps/overlay.d.ts.map +1 -1
  117. package/dist/maps/overlay.js +108 -28
  118. package/dist/maps/overlay.js.map +1 -1
  119. package/dist/maps/paint.d.ts +3 -0
  120. package/dist/maps/paint.d.ts.map +1 -1
  121. package/dist/maps/paint.js.map +1 -1
  122. package/dist/maps/proj.d.ts +14 -0
  123. package/dist/maps/proj.d.ts.map +1 -1
  124. package/dist/maps/proj.js +28 -0
  125. package/dist/maps/proj.js.map +1 -1
  126. package/dist/maps/renderer.d.ts +69 -0
  127. package/dist/maps/renderer.d.ts.map +1 -0
  128. package/dist/maps/renderer.js +66 -0
  129. package/dist/maps/renderer.js.map +1 -0
  130. package/dist/maps/sources.d.ts +25 -3
  131. package/dist/maps/sources.d.ts.map +1 -1
  132. package/dist/maps/sources.js +17 -0
  133. package/dist/maps/sources.js.map +1 -1
  134. package/dist/maps/theme.d.ts +19 -0
  135. package/dist/maps/theme.d.ts.map +1 -0
  136. package/dist/maps/theme.js +44 -0
  137. package/dist/maps/theme.js.map +1 -0
  138. package/dist/maps/tiles.d.ts +117 -17
  139. package/dist/maps/tiles.d.ts.map +1 -1
  140. package/dist/maps/tiles.js +163 -30
  141. package/dist/maps/tiles.js.map +1 -1
  142. package/dist/maps/types.d.ts +188 -20
  143. package/dist/maps/types.d.ts.map +1 -1
  144. package/dist/markdown/expressions.d.ts +1 -1
  145. package/dist/markdown/expressions.d.ts.map +1 -1
  146. package/dist/markdown/expressions.js.map +1 -1
  147. package/dist/markdown/index.d.ts +7 -6
  148. package/dist/markdown/index.d.ts.map +1 -1
  149. package/dist/markdown/index.js +9 -7
  150. package/dist/markdown/index.js.map +1 -1
  151. package/dist/markdown/spans.d.ts +1 -1
  152. package/dist/markdown/spans.d.ts.map +1 -1
  153. package/dist/media-player/index.d.ts +11 -3
  154. package/dist/media-player/index.d.ts.map +1 -1
  155. package/dist/media-player/index.js +13 -2
  156. package/dist/media-player/index.js.map +1 -1
  157. package/dist/rich-text-editor/clipboard.d.ts +23 -0
  158. package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
  159. package/dist/rich-text-editor/clipboard.js +307 -0
  160. package/dist/rich-text-editor/clipboard.js.map +1 -0
  161. package/dist/rich-text-editor/collab.d.ts +32 -0
  162. package/dist/rich-text-editor/collab.d.ts.map +1 -0
  163. package/dist/rich-text-editor/collab.js +55 -0
  164. package/dist/rich-text-editor/collab.js.map +1 -0
  165. package/dist/rich-text-editor/commands.d.ts +50 -0
  166. package/dist/rich-text-editor/commands.d.ts.map +1 -0
  167. package/dist/rich-text-editor/commands.js +376 -0
  168. package/dist/rich-text-editor/commands.js.map +1 -0
  169. package/dist/rich-text-editor/drag.d.ts +68 -0
  170. package/dist/rich-text-editor/drag.d.ts.map +1 -0
  171. package/dist/rich-text-editor/drag.js +148 -0
  172. package/dist/rich-text-editor/drag.js.map +1 -0
  173. package/dist/rich-text-editor/html.d.ts +145 -0
  174. package/dist/rich-text-editor/html.d.ts.map +1 -0
  175. package/dist/rich-text-editor/html.js +516 -0
  176. package/dist/rich-text-editor/html.js.map +1 -0
  177. package/dist/rich-text-editor/index.d.ts +263 -0
  178. package/dist/rich-text-editor/index.d.ts.map +1 -0
  179. package/dist/rich-text-editor/index.js +822 -0
  180. package/dist/rich-text-editor/index.js.map +1 -0
  181. package/dist/rich-text-editor/inline.d.ts +94 -0
  182. package/dist/rich-text-editor/inline.d.ts.map +1 -0
  183. package/dist/rich-text-editor/inline.js +252 -0
  184. package/dist/rich-text-editor/inline.js.map +1 -0
  185. package/dist/rich-text-editor/keymap.d.ts +71 -0
  186. package/dist/rich-text-editor/keymap.d.ts.map +1 -0
  187. package/dist/rich-text-editor/keymap.js +393 -0
  188. package/dist/rich-text-editor/keymap.js.map +1 -0
  189. package/dist/rich-text-editor/keys.d.ts +29 -0
  190. package/dist/rich-text-editor/keys.d.ts.map +1 -0
  191. package/dist/rich-text-editor/keys.js +93 -0
  192. package/dist/rich-text-editor/keys.js.map +1 -0
  193. package/dist/rich-text-editor/look.d.ts +71 -0
  194. package/dist/rich-text-editor/look.d.ts.map +1 -0
  195. package/dist/rich-text-editor/look.js +342 -0
  196. package/dist/rich-text-editor/look.js.map +1 -0
  197. package/dist/rich-text-editor/markdown.d.ts +18 -0
  198. package/dist/rich-text-editor/markdown.d.ts.map +1 -0
  199. package/dist/rich-text-editor/markdown.js +496 -0
  200. package/dist/rich-text-editor/markdown.js.map +1 -0
  201. package/dist/rich-text-editor/nodes.d.ts +135 -0
  202. package/dist/rich-text-editor/nodes.d.ts.map +1 -0
  203. package/dist/rich-text-editor/nodes.js +397 -0
  204. package/dist/rich-text-editor/nodes.js.map +1 -0
  205. package/dist/rich-text-editor/render.d.ts +68 -0
  206. package/dist/rich-text-editor/render.d.ts.map +1 -0
  207. package/dist/rich-text-editor/render.js +500 -0
  208. package/dist/rich-text-editor/render.js.map +1 -0
  209. package/dist/rich-text-editor/schema.d.ts +16 -0
  210. package/dist/rich-text-editor/schema.d.ts.map +1 -0
  211. package/dist/rich-text-editor/schema.js +386 -0
  212. package/dist/rich-text-editor/schema.js.map +1 -0
  213. package/dist/rich-text-editor/store.d.ts +11 -0
  214. package/dist/rich-text-editor/store.d.ts.map +1 -0
  215. package/dist/rich-text-editor/store.js +43 -0
  216. package/dist/rich-text-editor/store.js.map +1 -0
  217. package/dist/rich-text-editor/suggest.d.ts +127 -0
  218. package/dist/rich-text-editor/suggest.d.ts.map +1 -0
  219. package/dist/rich-text-editor/suggest.js +0 -0
  220. package/dist/rich-text-editor/suggest.js.map +1 -0
  221. package/dist/rich-text-editor/tables.d.ts +41 -0
  222. package/dist/rich-text-editor/tables.d.ts.map +1 -0
  223. package/dist/rich-text-editor/tables.js +204 -0
  224. package/dist/rich-text-editor/tables.js.map +1 -0
  225. package/dist/rich-text-editor/toolbar.d.ts +58 -0
  226. package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
  227. package/dist/rich-text-editor/toolbar.js +548 -0
  228. package/dist/rich-text-editor/toolbar.js.map +1 -0
  229. package/dist/rich-text-editor/view.d.ts +368 -0
  230. package/dist/rich-text-editor/view.d.ts.map +1 -0
  231. package/dist/rich-text-editor/view.js +1968 -0
  232. package/dist/rich-text-editor/view.js.map +1 -0
  233. package/dist/rich-text-editor/virtual.d.ts +54 -0
  234. package/dist/rich-text-editor/virtual.d.ts.map +1 -0
  235. package/dist/rich-text-editor/virtual.js +262 -0
  236. package/dist/rich-text-editor/virtual.js.map +1 -0
  237. package/dist/richtext/node.d.ts +25 -2
  238. package/dist/richtext/node.d.ts.map +1 -1
  239. package/dist/richtext/node.js +46 -24
  240. package/dist/richtext/node.js.map +1 -1
  241. package/dist/terminal/backends.d.ts +4 -1
  242. package/dist/terminal/backends.d.ts.map +1 -1
  243. package/dist/terminal/backends.js.map +1 -1
  244. package/dist/terminal/index.d.ts +18 -7
  245. package/dist/terminal/index.d.ts.map +1 -1
  246. package/dist/terminal/index.js +28 -11
  247. package/dist/terminal/index.js.map +1 -1
  248. package/package.json +23 -4
  249. package/src/charts/node.ts +6 -2
  250. package/src/embed/client.ts +102 -8
  251. package/src/embed/index.ts +2 -0
  252. package/src/html/node.ts +7 -5
  253. package/src/index.ts +2 -0
  254. package/src/{markdown → internal/markdown}/ast.ts +24 -7
  255. package/src/{markdown → internal/markdown}/parse.ts +134 -24
  256. package/src/internal/markdown/stringify.ts +772 -0
  257. package/src/internal/scroll.ts +5 -3
  258. package/src/maps/anchors.ts +607 -0
  259. package/src/maps/controller.ts +999 -0
  260. package/src/maps/gl/buckets.ts +457 -0
  261. package/src/maps/gl/build-worker.ts +55 -0
  262. package/src/maps/gl/color.ts +62 -0
  263. package/src/maps/gl/cover.ts +170 -0
  264. package/src/maps/gl/index.ts +23 -0
  265. package/src/maps/gl/markers.ts +83 -0
  266. package/src/maps/gl/overlays.ts +379 -0
  267. package/src/maps/gl/pane.ts +42 -0
  268. package/src/maps/gl/placement.ts +816 -0
  269. package/src/maps/gl/renderer.ts +1576 -0
  270. package/src/maps/gl/shaders.ts +486 -0
  271. package/src/maps/gl/store.ts +566 -0
  272. package/src/maps/gl/text.ts +495 -0
  273. package/src/maps/gl/view.ts +1224 -0
  274. package/src/maps/index.ts +270 -81
  275. package/src/maps/labels.ts +214 -121
  276. package/src/maps/node.ts +763 -629
  277. package/src/maps/overlay.ts +148 -43
  278. package/src/maps/paint.ts +3 -0
  279. package/src/maps/proj.ts +31 -0
  280. package/src/maps/renderer.ts +155 -0
  281. package/src/maps/sources.ts +38 -3
  282. package/src/maps/theme.ts +51 -0
  283. package/src/maps/tiles.ts +191 -33
  284. package/src/maps/types.ts +188 -20
  285. package/src/markdown/expressions.ts +5 -1
  286. package/src/markdown/index.ts +14 -11
  287. package/src/markdown/spans.ts +1 -1
  288. package/src/media-player/index.ts +18 -4
  289. package/src/rich-text-editor/clipboard.ts +439 -0
  290. package/src/rich-text-editor/collab.ts +91 -0
  291. package/src/rich-text-editor/commands.ts +448 -0
  292. package/src/rich-text-editor/dom-globals.d.ts +23 -0
  293. package/src/rich-text-editor/drag.ts +211 -0
  294. package/src/rich-text-editor/html.ts +619 -0
  295. package/src/rich-text-editor/index.ts +1331 -0
  296. package/src/rich-text-editor/inline.ts +345 -0
  297. package/src/rich-text-editor/keymap.ts +510 -0
  298. package/src/rich-text-editor/keys.ts +119 -0
  299. package/src/rich-text-editor/look.ts +427 -0
  300. package/src/rich-text-editor/markdown.ts +653 -0
  301. package/src/rich-text-editor/nodes.ts +497 -0
  302. package/src/rich-text-editor/render.ts +800 -0
  303. package/src/rich-text-editor/schema.ts +431 -0
  304. package/src/rich-text-editor/store.ts +39 -0
  305. package/src/rich-text-editor/suggest.ts +597 -0
  306. package/src/rich-text-editor/tables.ts +272 -0
  307. package/src/rich-text-editor/toolbar.ts +724 -0
  308. package/src/rich-text-editor/view.ts +2287 -0
  309. package/src/rich-text-editor/virtual.ts +347 -0
  310. package/src/richtext/node.ts +59 -30
  311. package/src/terminal/backends.ts +4 -1
  312. package/src/terminal/index.ts +43 -15
  313. package/dist/markdown/ast.d.ts.map +0 -1
  314. package/dist/markdown/ast.js +0 -17
  315. package/dist/markdown/ast.js.map +0 -1
  316. package/dist/markdown/parse.d.ts.map +0 -1
  317. package/dist/markdown/parse.js.map +0 -1
  318. package/dist/markdown/tags.d.ts.map +0 -1
  319. package/dist/markdown/tags.js.map +0 -1
  320. /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
  321. /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
  322. /package/dist/{markdown → internal/markdown}/tags.js +0 -0
  323. /package/src/{markdown → internal/markdown}/tags.ts +0 -0
@@ -18,7 +18,7 @@ import { clipOf, clipRing, clipSegment } from './clip.js';
18
18
  import type { ClipRect } from './clip.js';
19
19
  import type { MapCanvas } from './paint.js';
20
20
  import { projectLngLat } from './proj.js';
21
- import type { LngLat, Transform } from './proj.js';
21
+ import type { LngLat, ScreenRect, Transform } from './proj.js';
22
22
 
23
23
  /** A point on the map the user can click. */
24
24
  export interface MapMarker {
@@ -37,9 +37,11 @@ export interface MapMarker {
37
37
  /** The ring around it, so a marker stays visible on any basemap. The
38
38
  * theme's background by default. */
39
39
  outline?: string;
40
- /** Drawn above unselected markers, and given the selected ring. */
40
+ /** Drawn above the unselected markers of its `zIndex`, and given the
41
+ * selected ring: 2.5 logical pixels of the theme's text colour. */
41
42
  selected?: boolean;
42
- /** Higher draws later. Ties break on array order. */
43
+ /** Higher draws later, and is hit first. Ties break on `selected`, then
44
+ * on array order. */
43
45
  zIndex?: number;
44
46
  /** Skipped by hit testing — for a marker that is decoration. */
45
47
  interactive?: boolean;
@@ -104,12 +106,56 @@ export interface OverlayPalette {
104
106
  text: string;
105
107
  }
106
108
 
109
+ /** The attribution's text size, in logical pixels. */
110
+ export const ATTRIBUTION_SIZE = 9;
111
+ /** The opacity of the box of the theme's background the attribution sits
112
+ * in — enough to read it over any basemap, not so much that it hides one. */
113
+ export const ATTRIBUTION_OPACITY = 0.72;
114
+
115
+ /**
116
+ * Where the attribution goes: a box in the pane's bottom-right corner with 4
117
+ * logical pixels of padding either side of the text and 2 above and below
118
+ * it, and the text's top-left corner inside it — in device pixels, on whole
119
+ * ones. Both renderers lay it out with this, so it sits on the same pixels
120
+ * whichever of them draws the map.
121
+ *
122
+ * `text` is the set string's box and `pane` the pane, in logical pixels.
123
+ */
124
+ export function attributionLayout(
125
+ text: { width: number; height: number },
126
+ pane: ScreenRect,
127
+ scale: number,
128
+ ): { box: ScreenRect; x: number; y: number } {
129
+ const padding = 4;
130
+ const width = text.width + padding * 2;
131
+ const height = text.height + padding;
132
+ const x = pane.x + pane.width - width;
133
+ const y = pane.y + pane.height - height;
134
+ return {
135
+ box: {
136
+ x: Math.round(x * scale),
137
+ y: Math.round(y * scale),
138
+ width: Math.ceil(width * scale),
139
+ height: Math.ceil(height * scale),
140
+ },
141
+ x: Math.round((x + padding) * scale),
142
+ y: Math.round((y + padding / 2) * scale),
143
+ };
144
+ }
145
+
107
146
  function byZ<T extends { zIndex?: number }>(items: readonly T[]): T[] {
108
147
  // A stable sort — the language guarantees it — so items with no `zIndex`
109
148
  // keep the order the application listed them in.
110
149
  return [...items].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
111
150
  }
112
151
 
152
+ /** The order overlays are drawn in, bottom first: by `zIndex`, and
153
+ * otherwise the order the application listed them in. Both renderers
154
+ * draw in it. */
155
+ export function overlayOrder(overlays: readonly MapOverlay[]): MapOverlay[] {
156
+ return byZ(overlays);
157
+ }
158
+
113
159
  /**
114
160
  * Draw the overlays.
115
161
  *
@@ -314,22 +360,87 @@ function appendRing(ctx: MapCanvas, ring: readonly number[]): boolean {
314
360
  return true;
315
361
  }
316
362
 
317
- /** Where a marker's ink lands, in pane-local logical pixels. Shared by the
318
- * drawing and the hit test, so the two cannot disagree — the bug that
319
- * makes a marker unclickable a few pixels from where it looks. */
320
- export function markerRect(
363
+ /** A marker's size when it names none, in logical pixels. */
364
+ export const MARKER_SIZE = 14;
365
+ /** A pin is this many times as tall as it is wide. */
366
+ export const PIN_HEIGHT = 1.4;
367
+
368
+ /**
369
+ * The order markers are drawn in, bottom first: by `zIndex`, a selected
370
+ * marker above the unselected ones of its `zIndex`, and otherwise the order
371
+ * the application listed them in. The hit test walks it from the top, so a
372
+ * press lands on the marker drawn over the others. Both renderers draw in
373
+ * it.
374
+ */
375
+ export function markerOrder(markers: readonly MapMarker[]): MapMarker[] {
376
+ // A stable sort, so equal keys keep the application's order.
377
+ return [...markers].sort(
378
+ (a, b) =>
379
+ (a.zIndex ?? 0) - (b.zIndex ?? 0) ||
380
+ Number(a.selected === true) - Number(b.selected === true),
381
+ );
382
+ }
383
+
384
+ /** A marker's colours and ring, the theme's where it names none — what
385
+ * both renderers paint it with. `ringWidth` is in device pixels. */
386
+ export function markerPaint(
321
387
  marker: MapMarker,
322
- transform: Transform,
323
- ): {
388
+ palette: OverlayPalette,
389
+ scale: number,
390
+ ): { fill: string; ring: string; ringWidth: number } {
391
+ return {
392
+ fill: marker.color ?? palette.accent,
393
+ ring: marker.selected
394
+ ? palette.text
395
+ : (marker.outline ?? palette.background),
396
+ ringWidth: Math.max(1, (marker.selected ? 2.5 : 1.5) * scale),
397
+ };
398
+ }
399
+
400
+ /** Where a marker's ink lands: its box, and the point it marks. */
401
+ export interface MarkerRect {
324
402
  x: number;
325
403
  y: number;
326
404
  width: number;
327
405
  height: number;
328
406
  tipX: number;
329
407
  tipY: number;
330
- } {
408
+ }
409
+
410
+ /**
411
+ * The markers a pane shows, bottom first, with where each one's ink lands
412
+ * — what both renderers draw. A marker wholly outside the pane is left out.
413
+ */
414
+ export function markersInView(
415
+ markers: readonly MapMarker[],
416
+ transform: Transform,
417
+ pane: { width: number; height: number },
418
+ ): { marker: MapMarker; rect: MarkerRect }[] {
419
+ const shown: { marker: MapMarker; rect: MarkerRect }[] = [];
420
+ for (const marker of markerOrder(markers)) {
421
+ const rect = markerRect(marker, transform);
422
+ if (
423
+ rect.x + rect.width < 0 ||
424
+ rect.y + rect.height < 0 ||
425
+ rect.x > pane.width ||
426
+ rect.y > pane.height
427
+ ) {
428
+ continue;
429
+ }
430
+ shown.push({ marker, rect });
431
+ }
432
+ return shown;
433
+ }
434
+
435
+ /** Where a marker's ink lands, in pane-local logical pixels. Shared by the
436
+ * drawing and the hit test, so the two cannot disagree — the bug that
437
+ * makes a marker unclickable a few pixels from where it looks. */
438
+ export function markerRect(
439
+ marker: MapMarker,
440
+ transform: Transform,
441
+ ): MarkerRect {
331
442
  const point = projectLngLat(transform, marker.position);
332
- const size = marker.size ?? 14;
443
+ const size = marker.size ?? MARKER_SIZE;
333
444
  if ((marker.shape ?? 'pin') === 'circle') {
334
445
  return {
335
446
  x: point.x - size / 2,
@@ -343,7 +454,7 @@ export function markerRect(
343
454
  // A pin *stands on* its position: the point is the tip, and the head is
344
455
  // above it. Getting this the other way round puts every marker half its
345
456
  // own height north of where it belongs, which on a city map is a street.
346
- const height = size * 1.4;
457
+ const height = size * PIN_HEIGHT;
347
458
  return {
348
459
  x: point.x - size / 2,
349
460
  y: point.y - height,
@@ -354,7 +465,7 @@ export function markerRect(
354
465
  };
355
466
  }
356
467
 
357
- /** Draw the markers, lowest `zIndex` first. */
468
+ /** Draw the markers, in {@link markerOrder}. */
358
469
  export function drawMarkers(
359
470
  ctx: MapCanvas,
360
471
  markers: readonly MapMarker[],
@@ -363,27 +474,17 @@ export function drawMarkers(
363
474
  scale: number,
364
475
  palette: OverlayPalette,
365
476
  ): number {
366
- let drawn = 0;
367
- for (const marker of byZ(markers)) {
368
- const rect = markerRect(marker, transform);
369
- if (
370
- rect.x + rect.width < 0 ||
371
- rect.y + rect.height < 0 ||
372
- rect.x > pane.width ||
373
- rect.y > pane.height
374
- ) {
375
- continue;
376
- }
377
- const size = marker.size ?? 14;
378
- const fill = marker.color ?? palette.accent;
379
- const ring = marker.outline ?? palette.background;
477
+ const shown = markersInView(markers, transform, pane);
478
+ for (const { marker, rect } of shown) {
479
+ const size = marker.size ?? MARKER_SIZE;
480
+ const paint = markerPaint(marker, palette, scale);
380
481
  const x = (pane.x + rect.tipX) * scale;
381
482
  const y = (pane.y + rect.tipY) * scale;
382
483
  const r = (size / 2) * scale;
383
484
  ctx.save();
384
- ctx.lineWidth = Math.max(1, (marker.selected ? 2.5 : 1.5) * scale);
385
- ctx.strokeStyle = marker.selected ? palette.text : ring;
386
- ctx.fillStyle = fill;
485
+ ctx.lineWidth = paint.ringWidth;
486
+ ctx.strokeStyle = paint.ring;
487
+ ctx.fillStyle = paint.fill;
387
488
  if ((marker.shape ?? 'pin') === 'circle') {
388
489
  ctx.beginPath();
389
490
  ctx.moveTo(x + r, y);
@@ -391,12 +492,14 @@ export function drawMarkers(
391
492
  ctx.fill();
392
493
  ctx.stroke();
393
494
  } else {
394
- // A teardrop: a circle for the head and two lines down to the tip.
395
- // The tangent angle is where the straight sides meet the circle, so
396
- // the outline is smooth rather than two lines crossing an arc.
397
- const cy = y - size * 1.4 * scale + r;
495
+ // A teardrop: a circle for the head and two straight sides down to
496
+ // the tip, meeting the circle where they are tangent to it — at
497
+ // acos(r / d) either side of the line to the tip, seen from the
498
+ // centre — so the outline is smooth rather than two lines crossing
499
+ // an arc.
500
+ const cy = y - size * PIN_HEIGHT * scale + r;
398
501
  const d = y - cy;
399
- const angle = Math.asin(Math.min(1, r / d));
502
+ const angle = Math.acos(Math.min(1, r / d));
400
503
  ctx.beginPath();
401
504
  ctx.arc(x, cy, r, Math.PI / 2 + angle, Math.PI / 2 - angle);
402
505
  ctx.lineTo(x, y);
@@ -405,9 +508,8 @@ export function drawMarkers(
405
508
  ctx.stroke();
406
509
  }
407
510
  ctx.restore();
408
- drawn++;
409
511
  }
410
- return drawn;
512
+ return shown.length;
411
513
  }
412
514
 
413
515
  /** The marker under a pane-local logical point, topmost first, or null. */
@@ -418,7 +520,7 @@ export function markerAt(
418
520
  y: number,
419
521
  slop = 2,
420
522
  ): MapMarker | null {
421
- const ordered = byZ(markers);
523
+ const ordered = markerOrder(markers);
422
524
  for (let i = ordered.length - 1; i >= 0; i--) {
423
525
  const marker = ordered[i];
424
526
  if (marker.interactive === false) continue;
@@ -623,13 +725,16 @@ export function geoJsonOverlays(
623
725
  */
624
726
  const MAX_ARC_RADIUS = 8192;
625
727
 
728
+ /** How many segments a circle of this radius, in pixels, is drawn with as
729
+ * a ring: enough to hold the sagitta under half a pixel. */
730
+ export function circleSegments(radius: number): number {
731
+ const step = 2 * Math.acos(Math.max(-1, 1 - 0.5 / radius));
732
+ return Math.min(4096, Math.max(24, Math.ceil((Math.PI * 2) / step)));
733
+ }
734
+
626
735
  /** A circle as a ring, with the sagitta held under half a pixel. */
627
736
  function circleRing(cx: number, cy: number, radius: number): number[] {
628
- const step = 2 * Math.acos(Math.max(-1, 1 - 0.5 / radius));
629
- const segments = Math.min(
630
- 4096,
631
- Math.max(24, Math.ceil((Math.PI * 2) / step)),
632
- );
737
+ const segments = circleSegments(radius);
633
738
  const out: number[] = [];
634
739
  for (let i = 0; i < segments; i++) {
635
740
  const angle = (i / segments) * Math.PI * 2;
package/src/maps/paint.ts CHANGED
@@ -72,6 +72,9 @@ export interface MapCanvas {
72
72
  * scale bar are drawn with. */
73
73
  fillRects?(rects: readonly number[]): void;
74
74
  setLineDash?(segments: readonly number[]): void;
75
+ /** What a street name set along its street is drawn with. */
76
+ translate?(x: number, y: number): void;
77
+ rotate?(angle: number): void;
75
78
  roundRect?(x: number, y: number, w: number, h: number, radii: number): void;
76
79
  drawImage?(image: unknown, ...args: number[]): void;
77
80
  /** Straight RGBA in, for a raster tile the application decoded. */
package/src/maps/proj.ts CHANGED
@@ -441,6 +441,37 @@ export function subTileOf(
441
441
  };
442
442
  }
443
443
 
444
+ /**
445
+ * Where the data tile that serves a cover entry lands on screen: the entry
446
+ * itself until the cover goes deeper than the source cuts, and past that
447
+ * the whole of the ancestor it is one cell of — `span` times its size,
448
+ * starting `sub.x` cells to its left and `sub.y` above it.
449
+ *
450
+ * What a **raster** tile is drawn over past its source's depth. A vector
451
+ * cell is rasterized from its data tile's features through the cell, which
452
+ * is where overzoom's sharpness comes from; an image has nothing finer in
453
+ * it, so it is drawn whole, once, over this square. Placed from the entry
454
+ * rather than from the camera, and it lands where {@link tileCover} at the
455
+ * source's own depth puts the same tile.
456
+ */
457
+ export function dataSquareOf(
458
+ entry: TileCoverEntry,
459
+ maxZoom: number,
460
+ ): TileCoverEntry {
461
+ const sub = subTileOf(entry.tile, maxZoom);
462
+ if (sub.span === 1) return entry;
463
+ return {
464
+ tile: dataTileFor(entry.tile, maxZoom),
465
+ worldCopy: entry.worldCopy,
466
+ x: entry.x - sub.x * entry.size,
467
+ y: entry.y - sub.y * entry.size,
468
+ size: entry.size * sub.span,
469
+ // The cell's: a square takes its place in the cover's order from the
470
+ // cell it was reached through.
471
+ distance: entry.distance,
472
+ };
473
+ }
474
+
444
475
  /**
445
476
  * How large to rasterize a tile whose pyramid level is `z` at camera `zoom`.
446
477
  *
@@ -0,0 +1,155 @@
1
+ // Which of `<Map>`'s two renderers draws a map, and bringing the GL one in.
2
+ //
3
+ // The retained renderer (`./node.ts`) runs everywhere a 2D context does —
4
+ // indirect GLX, a remote X server, Xvfb in CI, the headless harness — and
5
+ // is visible to window capture. The GL renderer (`./gl/`) needs direct GL,
6
+ // and draws every frame from vector data at the display's rate. Neither is
7
+ // deprecated in favour of the other: `docs/prd-maps-gl.md`, "One `<Map>`,
8
+ // two renderers", has why the retained one stays, and this file is the
9
+ // decision between them — pure, so the whole of it is a unit test.
10
+ //
11
+ // `'auto'` decides in this order:
12
+ //
13
+ // 1. An explicit `renderer`, or the environment's, over both props and
14
+ // policy — the way `REACT_X11_BACKEND` works for the backend.
15
+ // 2. Whether this connection draws through the direct backend at all —
16
+ // `useSupports('shaders')`, which on X11 needs `glPolicy: 'auto'` and a
17
+ // DRI3 or Apple-DRI server, and on the Cocoa backend is always true once
18
+ // its probe has answered.
19
+ // 3. At run time, a GL failure moves the map to the retained renderer, with
20
+ // its camera and its handle intact. That one is sticky.
21
+ //
22
+ // A map that asked for `'gl'` by name never falls back: it gets GL, or it
23
+ // gets `onError`.
24
+ import type {
25
+ MapRenderer,
26
+ MapRendererReason,
27
+ MapRendererRequest,
28
+ } from './types.js';
29
+
30
+ /** The environment variable that overrides every map's renderer. */
31
+ export const RENDERER_ENV = 'REACT_X11_MAP_RENDERER';
32
+
33
+ /**
34
+ * What a map asks for when it does not say: GL where this connection draws
35
+ * through the direct backend, the retained renderer everywhere else — which
36
+ * is still every indirect GLX connection, every remote X server and every
37
+ * headless test run. `'retained'` until the GL renderer had soaked on both
38
+ * backends (`docs/prd-maps-gl.md`, "The soak").
39
+ */
40
+ export const DEFAULT_RENDERER: MapRendererRequest = 'auto';
41
+
42
+ const REQUESTS: readonly string[] = ['auto', 'gl', 'retained'];
43
+
44
+ /** Everything the decision reads. */
45
+ export interface RendererInput {
46
+ /** The `renderer` prop. */
47
+ requested?: MapRendererRequest;
48
+ /** `REACT_X11_MAP_RENDERER`, as the environment has it — anything but
49
+ * the three names is ignored. */
50
+ env?: string;
51
+ /** `useSupports('shaders')`: does this connection draw through the
52
+ * direct backend? */
53
+ shaders: boolean;
54
+ /**
55
+ * Whether the answer to that is still to come. The Cocoa backend settles
56
+ * it on the first ask rather than at connect, so the first render of a map
57
+ * there reads `false` for an answer that is about to be `true`; drawing
58
+ * the retained renderer for those few milliseconds would fetch the view's
59
+ * tiles twice. A map in that state shows its background and waits.
60
+ */
61
+ probing?: boolean;
62
+ /** Whether GL has already failed on this map. */
63
+ failed: boolean;
64
+ }
65
+
66
+ /** The decision: a renderer — or `'pending'` while the connection's answer
67
+ * is still to come — and why, when it is not what was asked for. */
68
+ export interface RendererChoice {
69
+ renderer: MapRenderer | 'pending';
70
+ /** Null when the map got what it asked for. */
71
+ reason: MapRendererReason | null;
72
+ /** What was asked for, the environment's word included. */
73
+ asked: MapRendererRequest;
74
+ }
75
+
76
+ function requestOf(value: string | undefined): MapRendererRequest | null {
77
+ const v = value?.trim().toLowerCase();
78
+ return v && REQUESTS.includes(v) ? (v as MapRendererRequest) : null;
79
+ }
80
+
81
+ /**
82
+ * What a map asks for: the environment's word over the prop's, the prop's
83
+ * over the default — and whether the environment is what changed it.
84
+ */
85
+ export function rendererRequest(
86
+ requested: MapRendererRequest | undefined,
87
+ env: string | undefined,
88
+ ): { asked: MapRendererRequest; forced: boolean } {
89
+ const fromEnv = requestOf(env);
90
+ const own = requested ?? DEFAULT_RENDERER;
91
+ return { asked: fromEnv ?? own, forced: fromEnv !== null && fromEnv !== own };
92
+ }
93
+
94
+ /** Choose a map's renderer. */
95
+ export function chooseRenderer(input: RendererInput): RendererChoice {
96
+ const { asked, forced: byEnv } = rendererRequest(input.requested, input.env);
97
+ if (asked === 'retained') {
98
+ return { renderer: 'retained', reason: byEnv ? 'forced' : null, asked };
99
+ }
100
+ if (asked === 'gl') {
101
+ return { renderer: 'gl', reason: byEnv ? 'forced' : null, asked };
102
+ }
103
+ if (input.failed) return { renderer: 'retained', reason: 'gl-failed', asked };
104
+ if (!input.shaders) {
105
+ return input.probing
106
+ ? { renderer: 'pending', reason: null, asked }
107
+ : { renderer: 'retained', reason: 'no-direct-gl', asked };
108
+ }
109
+ return { renderer: 'gl', reason: null, asked };
110
+ }
111
+
112
+ /** The environment's override, read off `globalThis` because `src/` compiles
113
+ * with `types: []`. */
114
+ export function rendererFromEnv(): string | undefined {
115
+ const g = globalThis as {
116
+ process?: { env?: Record<string, string | undefined> };
117
+ };
118
+ return g.process?.env?.[RENDERER_ENV];
119
+ }
120
+
121
+ // --- the GL renderer, loaded when it is chosen --------------------------------
122
+
123
+ /**
124
+ * The GL renderer's module, once it has loaded.
125
+ *
126
+ * Reached by dynamic `import()` and never statically: a static import from
127
+ * `<Map>` would put all of `./gl/` — the shaders, the bucket builder, the
128
+ * label atlas — into every bundle that uses a map, which is the tree-shaking
129
+ * promise broken for an application that never draws one through GL.
130
+ * `test/treeshake.test.ts` holds it to that.
131
+ */
132
+ export type GlRendererModule = typeof import('./gl/index.js');
133
+
134
+ let loaded: GlRendererModule | null = null;
135
+ let loading: Promise<GlRendererModule> | null = null;
136
+
137
+ /** The GL renderer, if it has loaded — so a second map mounts it at once. */
138
+ export function glRendererModule(): GlRendererModule | null {
139
+ return loaded;
140
+ }
141
+
142
+ /** Load the GL renderer — once per process, however many maps ask. */
143
+ export function loadGlRenderer(): Promise<GlRendererModule> {
144
+ loading ??= import('./gl/index.js').then(
145
+ (module) => (loaded = module),
146
+ (error: unknown) => {
147
+ // Asked again next time rather than failing forever on a cached
148
+ // rejection: a load that failed once is most likely to fail again,
149
+ // and the map that asked has already fallen back or reported it.
150
+ loading = null;
151
+ throw error;
152
+ },
153
+ );
154
+ return loading;
155
+ }
@@ -27,7 +27,13 @@ export interface TileRequest extends TileId {
27
27
  * loader. */
28
28
  sourceId: string;
29
29
  /**
30
- * Aborted when the tile leaves the view before it arrives.
30
+ * Aborted when the map stops wanting the tile before it has arrived: it
31
+ * has been panned or zoomed out of what the map is loading (the view and
32
+ * a margin around it), its source has been taken out of `sources`, or the
33
+ * map has unmounted. Whatever an aborted load answers after that, an
34
+ * `AbortError` included, is ignored rather than reported to
35
+ * `onTileError`, and a tile that comes back is asked for again with a new
36
+ * signal.
31
37
  *
32
38
  * **This is a real `AbortSignal`** on every runtime that has one, which
33
39
  * is what makes `fetch(url, { signal })` work — `fetch` rejects anything
@@ -63,8 +69,17 @@ export type TileData =
63
69
 
64
70
  /** A tile source. */
65
71
  export interface MapSource {
66
- /** Distinguishes this source's cache entries from another's. Defaults to
67
- * the source's position in the `sources` array. */
72
+ /**
73
+ * What this source is called: the `sourceId` its `load` requests carry
74
+ * and `onTileError` reports. `source-<index>` by default.
75
+ *
76
+ * **Not** what its tiles are cached under — that is the source object
77
+ * itself, because a name is not a provider: two sources may share one,
78
+ * and a map switched between them must not draw one's tiles as the
79
+ * other's. The flip side is that a source made anew on every render
80
+ * starts from an empty cache on every render, so make it once — at
81
+ * module scope, or in `useMemo`.
82
+ */
68
83
  id?: string;
69
84
  /** Fetch, or answer from a cache, or decline. May be async. */
70
85
  load(request: TileRequest): TileData | Promise<TileData>;
@@ -87,6 +102,26 @@ export interface MapSource {
87
102
  attribution?: string;
88
103
  }
89
104
 
105
+ /**
106
+ * What a map says in its corner: the `attribution` prop when it is given —
107
+ * `''` is the application saying it has put the attribution somewhere else —
108
+ * and otherwise every source's own, each once, in order. Both renderers draw
109
+ * this string, because for open data it is a licence condition.
110
+ */
111
+ export function attributionOf(
112
+ attribution: string | undefined,
113
+ sources: readonly MapSource[],
114
+ ): string {
115
+ if (attribution !== undefined) return attribution;
116
+ const parts: string[] = [];
117
+ for (const source of sources) {
118
+ if (source.attribution && !parts.includes(source.attribution)) {
119
+ parts.push(source.attribution);
120
+ }
121
+ }
122
+ return parts.join(' · ');
123
+ }
124
+
90
125
  /** A source's pyramid, with the defaults filled in. */
91
126
  export function pyramidOf(source: MapSource): {
92
127
  minZoom: number;
@@ -0,0 +1,51 @@
1
+ // The theme's say in how a map looks: which of the default style's palettes
2
+ // it gets, and the colours markers, overlays and the attribution fall back
3
+ // to. Both renderers read the theme through these, so a map in a dark
4
+ // application is dark whichever of them draws it.
5
+ import type { OverlayPalette } from './overlay.js';
6
+ import type { MapStyle } from './style.js';
7
+ import { shortbreadStyle } from './styles.js';
8
+
9
+ const defaults = new Map<boolean, MapStyle>();
10
+
11
+ /**
12
+ * The style a map with no `mapStyle` is drawn in: `shortbreadStyle()` in the
13
+ * theme's light or dark palette. Made once each — a style's identity is
14
+ * what decides whether the GL renderer rebuilds its tiles.
15
+ */
16
+ export function defaultStyleFor(dark: boolean): MapStyle {
17
+ let style = defaults.get(dark);
18
+ if (!style) {
19
+ style = shortbreadStyle({ dark });
20
+ defaults.set(dark, style);
21
+ }
22
+ return style;
23
+ }
24
+
25
+ /**
26
+ * Whether a theme is dark — the luminance of the surface the map sits on,
27
+ * the same reading `src/code-editor/`'s token themes make, rather than a flag
28
+ * nobody sets. A map inside a dark application that stays light is the thing
29
+ * everyone notices first.
30
+ */
31
+ export function isDarkTheme(theme: unknown): boolean {
32
+ const background = (theme as Record<string, unknown> | undefined)?.background;
33
+ if (typeof background !== 'string') return false;
34
+ const hex = background.trim();
35
+ if (!hex.startsWith('#') || hex.length < 7) return false;
36
+ const r = parseInt(hex.slice(1, 3), 16);
37
+ const g = parseInt(hex.slice(3, 5), 16);
38
+ const b = parseInt(hex.slice(5, 7), 16);
39
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b < 128;
40
+ }
41
+
42
+ /** The colours a marker, an overlay and the attribution use where they do
43
+ * not name one. */
44
+ export function overlayPalette(theme: unknown): OverlayPalette {
45
+ const t = theme as Record<string, unknown> | undefined;
46
+ return {
47
+ accent: (t?.accent as string) ?? '#2d6cdf',
48
+ background: (t?.background as string) ?? '#ffffff',
49
+ text: (t?.text as string) ?? '#111111',
50
+ };
51
+ }