@react-x11/components 0.4.0 → 0.5.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 (219) hide show
  1. package/README.md +125 -4
  2. package/dist/charts/components.d.ts +3 -1
  3. package/dist/charts/components.d.ts.map +1 -1
  4. package/dist/charts/components.js +8 -4
  5. package/dist/charts/components.js.map +1 -1
  6. package/dist/charts/node.d.ts +13 -6
  7. package/dist/charts/node.d.ts.map +1 -1
  8. package/dist/charts/node.js +75 -24
  9. package/dist/charts/node.js.map +1 -1
  10. package/dist/charts/render.d.ts +4 -0
  11. package/dist/charts/render.d.ts.map +1 -1
  12. package/dist/charts/render.js +11 -6
  13. package/dist/charts/render.js.map +1 -1
  14. package/dist/code-editor/node.d.ts +47 -16
  15. package/dist/code-editor/node.d.ts.map +1 -1
  16. package/dist/code-editor/node.js +121 -51
  17. package/dist/code-editor/node.js.map +1 -1
  18. package/dist/flow/draw.d.ts +19 -1
  19. package/dist/flow/draw.d.ts.map +1 -1
  20. package/dist/flow/draw.js +64 -35
  21. package/dist/flow/draw.js.map +1 -1
  22. package/dist/flow/index.d.ts.map +1 -1
  23. package/dist/flow/index.js +24 -8
  24. package/dist/flow/index.js.map +1 -1
  25. package/dist/flow/model.d.ts +4 -2
  26. package/dist/flow/model.d.ts.map +1 -1
  27. package/dist/flow/model.js +4 -2
  28. package/dist/flow/model.js.map +1 -1
  29. package/dist/flow/node.d.ts +40 -9
  30. package/dist/flow/node.d.ts.map +1 -1
  31. package/dist/flow/node.js +142 -53
  32. package/dist/flow/node.js.map +1 -1
  33. package/dist/flow/types.d.ts +46 -21
  34. package/dist/flow/types.d.ts.map +1 -1
  35. package/dist/formula/index.d.ts +4 -2
  36. package/dist/formula/index.d.ts.map +1 -1
  37. package/dist/formula/index.js.map +1 -1
  38. package/dist/formula/node.d.ts +5 -1
  39. package/dist/formula/node.d.ts.map +1 -1
  40. package/dist/formula/node.js +19 -3
  41. package/dist/formula/node.js.map +1 -1
  42. package/dist/html/controls.d.ts +4 -1
  43. package/dist/html/controls.d.ts.map +1 -1
  44. package/dist/html/controls.js.map +1 -1
  45. package/dist/html/css/cascade.d.ts +11 -5
  46. package/dist/html/css/cascade.d.ts.map +1 -1
  47. package/dist/html/css/cascade.js +18 -8
  48. package/dist/html/css/cascade.js.map +1 -1
  49. package/dist/html/css/parse.d.ts +15 -10
  50. package/dist/html/css/parse.d.ts.map +1 -1
  51. package/dist/html/css/parse.js +41 -16
  52. package/dist/html/css/parse.js.map +1 -1
  53. package/dist/html/css/style.d.ts +3 -0
  54. package/dist/html/css/style.d.ts.map +1 -1
  55. package/dist/html/css/style.js +3 -1
  56. package/dist/html/css/style.js.map +1 -1
  57. package/dist/html/css/values.d.ts +14 -1
  58. package/dist/html/css/values.d.ts.map +1 -1
  59. package/dist/html/css/values.js +11 -8
  60. package/dist/html/css/values.js.map +1 -1
  61. package/dist/html/index.d.ts.map +1 -1
  62. package/dist/html/index.js +1 -0
  63. package/dist/html/index.js.map +1 -1
  64. package/dist/html/layout/boxes.d.ts +12 -5
  65. package/dist/html/layout/boxes.d.ts.map +1 -1
  66. package/dist/html/layout/boxes.js +7 -4
  67. package/dist/html/layout/boxes.js.map +1 -1
  68. package/dist/html/layout/inline.d.ts.map +1 -1
  69. package/dist/html/layout/inline.js +22 -1
  70. package/dist/html/layout/inline.js.map +1 -1
  71. package/dist/html/node.d.ts +22 -8
  72. package/dist/html/node.d.ts.map +1 -1
  73. package/dist/html/node.js +97 -15
  74. package/dist/html/node.js.map +1 -1
  75. package/dist/html/paint.d.ts +3 -0
  76. package/dist/html/paint.d.ts.map +1 -1
  77. package/dist/html/paint.js +2 -2
  78. package/dist/html/paint.js.map +1 -1
  79. package/dist/index.d.ts +7 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +21 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/maps/clip.d.ts +43 -0
  84. package/dist/maps/clip.d.ts.map +1 -0
  85. package/dist/maps/clip.js +171 -0
  86. package/dist/maps/clip.js.map +1 -0
  87. package/dist/maps/gzip.d.ts +15 -0
  88. package/dist/maps/gzip.d.ts.map +1 -0
  89. package/dist/maps/gzip.js +56 -0
  90. package/dist/maps/gzip.js.map +1 -0
  91. package/dist/maps/index.d.ts +55 -0
  92. package/dist/maps/index.d.ts.map +1 -0
  93. package/dist/maps/index.js +141 -0
  94. package/dist/maps/index.js.map +1 -0
  95. package/dist/maps/labels.d.ts +123 -0
  96. package/dist/maps/labels.d.ts.map +1 -0
  97. package/dist/maps/labels.js +323 -0
  98. package/dist/maps/labels.js.map +1 -0
  99. package/dist/maps/mvt.d.ts +244 -0
  100. package/dist/maps/mvt.d.ts.map +1 -0
  101. package/dist/maps/mvt.js +820 -0
  102. package/dist/maps/mvt.js.map +1 -0
  103. package/dist/maps/node.d.ts +278 -0
  104. package/dist/maps/node.d.ts.map +1 -0
  105. package/dist/maps/node.js +1459 -0
  106. package/dist/maps/node.js.map +1 -0
  107. package/dist/maps/overlay.d.ts +157 -0
  108. package/dist/maps/overlay.d.ts.map +1 -0
  109. package/dist/maps/overlay.js +472 -0
  110. package/dist/maps/overlay.js.map +1 -0
  111. package/dist/maps/paint.d.ts +226 -0
  112. package/dist/maps/paint.d.ts.map +1 -0
  113. package/dist/maps/paint.js +705 -0
  114. package/dist/maps/paint.js.map +1 -0
  115. package/dist/maps/proj.d.ts +281 -0
  116. package/dist/maps/proj.d.ts.map +1 -0
  117. package/dist/maps/proj.js +451 -0
  118. package/dist/maps/proj.js.map +1 -0
  119. package/dist/maps/sources.d.ts +256 -0
  120. package/dist/maps/sources.d.ts.map +1 -0
  121. package/dist/maps/sources.js +210 -0
  122. package/dist/maps/sources.js.map +1 -0
  123. package/dist/maps/style.d.ts +141 -0
  124. package/dist/maps/style.d.ts.map +1 -0
  125. package/dist/maps/style.js +147 -0
  126. package/dist/maps/style.js.map +1 -0
  127. package/dist/maps/styles.d.ts +82 -0
  128. package/dist/maps/styles.d.ts.map +1 -0
  129. package/dist/maps/styles.js +898 -0
  130. package/dist/maps/styles.js.map +1 -0
  131. package/dist/maps/tiles.d.ts +283 -0
  132. package/dist/maps/tiles.d.ts.map +1 -0
  133. package/dist/maps/tiles.js +469 -0
  134. package/dist/maps/tiles.js.map +1 -0
  135. package/dist/maps/types.d.ts +244 -0
  136. package/dist/maps/types.d.ts.map +1 -0
  137. package/dist/maps/types.js +2 -0
  138. package/dist/maps/types.js.map +1 -0
  139. package/dist/reorder/hx.d.ts +18 -0
  140. package/dist/reorder/hx.d.ts.map +1 -0
  141. package/dist/reorder/hx.js +10 -0
  142. package/dist/reorder/hx.js.map +1 -0
  143. package/dist/reorder/index.d.ts +314 -0
  144. package/dist/reorder/index.d.ts.map +1 -0
  145. package/dist/reorder/index.js +1539 -0
  146. package/dist/reorder/index.js.map +1 -0
  147. package/dist/reorder/model.d.ts +107 -0
  148. package/dist/reorder/model.d.ts.map +1 -0
  149. package/dist/reorder/model.js +162 -0
  150. package/dist/reorder/model.js.map +1 -0
  151. package/dist/richtext/node.d.ts +15 -5
  152. package/dist/richtext/node.d.ts.map +1 -1
  153. package/dist/richtext/node.js +40 -11
  154. package/dist/richtext/node.js.map +1 -1
  155. package/dist/richtext/runs.d.ts +35 -13
  156. package/dist/richtext/runs.d.ts.map +1 -1
  157. package/dist/richtext/runs.js +83 -27
  158. package/dist/richtext/runs.js.map +1 -1
  159. package/dist/tabs/index.d.ts +17 -0
  160. package/dist/tabs/index.d.ts.map +1 -1
  161. package/dist/tabs/index.js +849 -186
  162. package/dist/tabs/index.js.map +1 -1
  163. package/dist/terminal/vt/fonts.d.ts +45 -15
  164. package/dist/terminal/vt/fonts.d.ts.map +1 -1
  165. package/dist/terminal/vt/fonts.js +89 -19
  166. package/dist/terminal/vt/fonts.js.map +1 -1
  167. package/dist/terminal/vt/node.d.ts +42 -0
  168. package/dist/terminal/vt/node.d.ts.map +1 -1
  169. package/dist/terminal/vt/node.js +95 -11
  170. package/dist/terminal/vt/node.js.map +1 -1
  171. package/dist/terminal/vt/renderer.d.ts.map +1 -1
  172. package/dist/terminal/vt/renderer.js +13 -3
  173. package/dist/terminal/vt/renderer.js.map +1 -1
  174. package/package.json +13 -3
  175. package/src/charts/components.ts +14 -7
  176. package/src/charts/node.ts +81 -26
  177. package/src/charts/render.ts +16 -6
  178. package/src/code-editor/node.ts +147 -53
  179. package/src/flow/draw.ts +104 -35
  180. package/src/flow/index.ts +33 -13
  181. package/src/flow/model.ts +4 -2
  182. package/src/flow/node.ts +158 -59
  183. package/src/flow/types.ts +46 -21
  184. package/src/formula/index.ts +4 -2
  185. package/src/formula/node.ts +24 -5
  186. package/src/html/controls.ts +4 -1
  187. package/src/html/css/cascade.ts +18 -7
  188. package/src/html/css/parse.ts +46 -21
  189. package/src/html/css/style.ts +6 -1
  190. package/src/html/css/values.ts +25 -9
  191. package/src/html/index.ts +1 -0
  192. package/src/html/layout/boxes.ts +19 -9
  193. package/src/html/layout/inline.ts +23 -1
  194. package/src/html/node.ts +110 -17
  195. package/src/html/paint.ts +17 -2
  196. package/src/index.ts +98 -0
  197. package/src/maps/clip.ts +184 -0
  198. package/src/maps/gzip.ts +66 -0
  199. package/src/maps/index.ts +316 -0
  200. package/src/maps/labels.ts +442 -0
  201. package/src/maps/mvt.ts +857 -0
  202. package/src/maps/node.ts +1819 -0
  203. package/src/maps/overlay.ts +639 -0
  204. package/src/maps/paint.ts +978 -0
  205. package/src/maps/proj.ts +641 -0
  206. package/src/maps/sources.ts +425 -0
  207. package/src/maps/style.ts +309 -0
  208. package/src/maps/styles.ts +1040 -0
  209. package/src/maps/tiles.ts +676 -0
  210. package/src/maps/types.ts +260 -0
  211. package/src/reorder/hx.ts +31 -0
  212. package/src/reorder/index.ts +2223 -0
  213. package/src/reorder/model.ts +232 -0
  214. package/src/richtext/node.ts +49 -14
  215. package/src/richtext/runs.ts +115 -30
  216. package/src/tabs/index.ts +1040 -204
  217. package/src/terminal/vt/fonts.ts +128 -36
  218. package/src/terminal/vt/node.ts +105 -12
  219. package/src/terminal/vt/renderer.ts +13 -3
@@ -0,0 +1,1040 @@
1
+ // A default style, so that `<Map>` with a source and nothing else looks
2
+ // like a map.
3
+ //
4
+ // Written against **Shortbread 1.1**, the schema OpenStreetMap's own vector
5
+ // tiles are cut in (`vector.openstreetmap.org`) — chosen because it is the
6
+ // one open schema with an open, keyless, canonical server behind it, which
7
+ // is what makes `<Map source={osmVectorSource()} />` a line that works.
8
+ //
9
+ // The cartography is deliberately close to a familiar one (OSM Carto's
10
+ // hues for the light palette, a desaturated dark counterpart), because a
11
+ // map that looks unlike every other map reads as broken rather than as
12
+ // styled. Both palettes are here rather than derived from the theme: a
13
+ // map's colours are a *scheme* — water against land against parkland
14
+ // against six classes of road — and interpolating that out of an
15
+ // application's accent colour produces mud. What the theme does decide is
16
+ // which of the two is used, and the ink and halo of the labels, so labels
17
+ // on a map agree with text beside it.
18
+ //
19
+ // Every layer's `id` is stable, because that is what an application filters
20
+ // on to hide one (`style.layers.filter((l) => l.id !== 'buildings')`) and
21
+ // what a `<Map>` event names.
22
+ import type { MapStyle, MapStyleLayer, Zoomed } from './style.js';
23
+
24
+ /** The tags Shortbread puts a road's class in, grouped as a style wants
25
+ * them rather than as the schema lists them. */
26
+ const MOTORWAY = ['motorway'] as const;
27
+ const TRUNK = ['trunk'] as const;
28
+ const PRIMARY = ['primary'] as const;
29
+ const SECONDARY = ['secondary'] as const;
30
+ const TERTIARY = ['tertiary'] as const;
31
+ const MINOR = [
32
+ 'unclassified',
33
+ 'residential',
34
+ 'living_street',
35
+ 'pedestrian',
36
+ 'busway',
37
+ ] as const;
38
+ const SERVICE = ['service', 'track', 'ford'] as const;
39
+ const PATH = ['path', 'footway', 'cycleway', 'steps', 'bridleway'] as const;
40
+ const RAIL = [
41
+ 'rail',
42
+ 'narrow_gauge',
43
+ 'light_rail',
44
+ 'subway',
45
+ 'tram',
46
+ 'monorail',
47
+ 'funicular',
48
+ ] as const;
49
+
50
+ /** Landuse kinds that read as green space. */
51
+ const GREEN = [
52
+ 'park',
53
+ 'garden',
54
+ 'grass',
55
+ 'meadow',
56
+ 'forest',
57
+ 'wood',
58
+ 'scrub',
59
+ 'heath',
60
+ 'orchard',
61
+ 'vineyard',
62
+ 'village_green',
63
+ 'recreation_ground',
64
+ 'golf_course',
65
+ 'cemetery',
66
+ 'allotments',
67
+ 'farmland',
68
+ 'farmyard',
69
+ ] as const;
70
+
71
+ /** …and the ones that read as built-up. */
72
+ const BUILT = [
73
+ 'residential',
74
+ 'commercial',
75
+ 'retail',
76
+ 'industrial',
77
+ 'brownfield',
78
+ 'greenfield',
79
+ 'railway',
80
+ 'garages',
81
+ 'quarry',
82
+ 'construction',
83
+ 'landfill',
84
+ ] as const;
85
+
86
+ /** One palette. Every colour a {@link shortbreadStyle} layer uses comes
87
+ * from here, so a third palette is a literal rather than a fork. */
88
+ export interface MapPalette {
89
+ land: string;
90
+ ocean: string;
91
+ water: string;
92
+ green: string;
93
+ built: string;
94
+ site: string;
95
+ building: string;
96
+ buildingEdge: string;
97
+ motorway: string;
98
+ motorwayCasing: string;
99
+ trunk: string;
100
+ primary: string;
101
+ secondary: string;
102
+ minor: string;
103
+ minorCasing: string;
104
+ path: string;
105
+ rail: string;
106
+ boundary: string;
107
+ pier: string;
108
+ text: string;
109
+ textMinor: string;
110
+ halo: string;
111
+ }
112
+
113
+ /** OSM Carto's hues, near enough that a user's eye reads the map as the one
114
+ * they already know. */
115
+ export const LIGHT_PALETTE: MapPalette = {
116
+ land: '#f2efe9',
117
+ ocean: '#aad3df',
118
+ water: '#aad3df',
119
+ green: '#cfe8b4',
120
+ built: '#e8e2dc',
121
+ site: '#f0e7db',
122
+ building: '#dfd7cd',
123
+ buildingEdge: '#cec4b8',
124
+ motorway: '#f9b29c',
125
+ motorwayCasing: '#d97f63',
126
+ trunk: '#fcd6a4',
127
+ primary: '#fcd6a4',
128
+ secondary: '#f7fabf',
129
+ minor: '#ffffff',
130
+ minorCasing: '#cfcdca',
131
+ path: '#d4b8a8',
132
+ rail: '#b0aeab',
133
+ boundary: '#a4a1ae',
134
+ pier: '#f2efe9',
135
+ text: '#33322e',
136
+ textMinor: '#6b6862',
137
+ halo: 'rgba(255,255,255,0.85)',
138
+ };
139
+
140
+ /** The dark counterpart. Desaturated rather than inverted: an inverted map
141
+ * puts white water under dark land, which reads as a negative. */
142
+ export const DARK_PALETTE: MapPalette = {
143
+ land: '#191b1f',
144
+ ocean: '#0f1c28',
145
+ water: '#12222f',
146
+ green: '#1a2620',
147
+ built: '#1f2126',
148
+ site: '#20232a',
149
+ building: '#23262c',
150
+ buildingEdge: '#2b2f36',
151
+ motorway: '#5d4b38',
152
+ motorwayCasing: '#7a6244',
153
+ trunk: '#4c4232',
154
+ primary: '#43403a',
155
+ secondary: '#3b3a35',
156
+ minor: '#33363c',
157
+ minorCasing: '#22242a',
158
+ path: '#3a352f',
159
+ rail: '#33363c',
160
+ boundary: '#4a4757',
161
+ pier: '#22252b',
162
+ text: '#d3d7de',
163
+ textMinor: '#9299a3',
164
+ halo: 'rgba(12,14,18,0.85)',
165
+ };
166
+
167
+ /** How wide a road class is, at each zoom. */
168
+ function width(stops: readonly (readonly [number, number])[]): Zoomed<number> {
169
+ return { stops };
170
+ }
171
+
172
+ export interface ShortbreadStyleOptions {
173
+ /** `false` (the default) for the light palette. */
174
+ dark?: boolean;
175
+ /** Swap individual colours without forking the layer list. */
176
+ palette?: Partial<MapPalette>;
177
+ /**
178
+ * Which name tag labels read.
179
+ *
180
+ * Shortbread carries `name` (the local name) plus `name_<lang>` for the
181
+ * languages a feature has one in. `'name'` is the honest default —
182
+ * Tokyo's streets are labelled in Japanese on a Japanese map — and an
183
+ * application that knows its user's locale passes `name_en`, `name_de`
184
+ * and so on. A missing translation falls back to `name`.
185
+ */
186
+ nameField?: string;
187
+ /** Leave the label layers out — for a map an application labels itself,
188
+ * or a background under a heavy data overlay. */
189
+ labels?: boolean;
190
+ /** Leave building footprints out. They are the densest polygon layer at
191
+ * high zoom and the first thing to drop on a slow machine. */
192
+ buildings?: boolean;
193
+ }
194
+
195
+ /**
196
+ * The default style for Shortbread tiles.
197
+ *
198
+ * Twenty-six layers, in paint order. The order is the cartography: sea,
199
+ * then landuse, then water, then sites and buildings, then road *casings*
200
+ * as one pass and road *fills* as another — which is what makes a junction
201
+ * look like a junction rather than two roads crossing — then rail,
202
+ * boundaries, and labels last.
203
+ */
204
+ export function shortbreadStyle(
205
+ options: ShortbreadStyleOptions = {},
206
+ ): MapStyle {
207
+ const p: MapPalette = {
208
+ ...(options.dark ? DARK_PALETTE : LIGHT_PALETTE),
209
+ ...options.palette,
210
+ };
211
+ const name = options.nameField ?? 'name';
212
+ const layers: MapStyleLayer[] = [
213
+ { id: 'ocean', type: 'fill', sourceLayer: 'ocean', color: p.ocean },
214
+ {
215
+ id: 'landuse-green',
216
+ type: 'fill',
217
+ sourceLayer: 'land',
218
+ filter: ['in', 'kind', ...GREEN],
219
+ color: p.green,
220
+ },
221
+ {
222
+ id: 'landuse-built',
223
+ type: 'fill',
224
+ sourceLayer: 'land',
225
+ filter: ['in', 'kind', ...BUILT],
226
+ color: p.built,
227
+ },
228
+ {
229
+ id: 'sites',
230
+ type: 'fill',
231
+ sourceLayer: 'sites',
232
+ color: p.site,
233
+ minZoom: 13,
234
+ },
235
+ {
236
+ id: 'water-polygons',
237
+ type: 'fill',
238
+ sourceLayer: 'water_polygons',
239
+ color: p.water,
240
+ },
241
+ {
242
+ id: 'water-lines',
243
+ type: 'line',
244
+ sourceLayer: 'water_lines',
245
+ color: p.water,
246
+ width: width([
247
+ [6, 0.5],
248
+ [10, 1.2],
249
+ [14, 3],
250
+ [18, 8],
251
+ ]),
252
+ },
253
+ {
254
+ id: 'dams',
255
+ type: 'fill',
256
+ sourceLayer: 'dam_polygons',
257
+ color: p.built,
258
+ minZoom: 13,
259
+ },
260
+ {
261
+ id: 'piers',
262
+ type: 'fill',
263
+ sourceLayer: 'pier_polygons',
264
+ color: p.pier,
265
+ minZoom: 13,
266
+ },
267
+ ];
268
+
269
+ if (options.buildings !== false) {
270
+ layers.push({
271
+ id: 'buildings',
272
+ type: 'fill',
273
+ sourceLayer: 'buildings',
274
+ minZoom: 14,
275
+ color: p.building,
276
+ // The edge only once a footprint is big enough for it to be an edge
277
+ // rather than a darkening of the whole polygon.
278
+ outlineColor: p.buildingEdge,
279
+ });
280
+ }
281
+
282
+ // Casings first, every class in one pass, so that the fills above them
283
+ // knit together at every junction. This is the one thing that separates a
284
+ // road network from a pile of coloured lines.
285
+ const roadClasses: {
286
+ id: string;
287
+ kinds: readonly string[];
288
+ fill: string;
289
+ casing: string;
290
+ fillWidth: Zoomed<number>;
291
+ minZoom?: number;
292
+ }[] = [
293
+ {
294
+ id: 'motorway',
295
+ kinds: MOTORWAY,
296
+ fill: p.motorway,
297
+ casing: p.motorwayCasing,
298
+ fillWidth: width([
299
+ [5, 0.6],
300
+ [8, 1.4],
301
+ [10, 2.4],
302
+ [13, 5],
303
+ [16, 12],
304
+ [20, 40],
305
+ ]),
306
+ },
307
+ {
308
+ id: 'trunk',
309
+ kinds: TRUNK,
310
+ fill: p.trunk,
311
+ casing: p.motorwayCasing,
312
+ fillWidth: width([
313
+ [6, 0.5],
314
+ [9, 1.2],
315
+ [12, 2.6],
316
+ [16, 9],
317
+ [20, 30],
318
+ ]),
319
+ },
320
+ {
321
+ id: 'primary',
322
+ kinds: PRIMARY,
323
+ fill: p.primary,
324
+ casing: p.minorCasing,
325
+ fillWidth: width([
326
+ [7, 0.5],
327
+ [10, 1.3],
328
+ [13, 3],
329
+ [16, 8],
330
+ [20, 26],
331
+ ]),
332
+ minZoom: 7,
333
+ },
334
+ {
335
+ id: 'secondary',
336
+ kinds: SECONDARY,
337
+ fill: p.secondary,
338
+ casing: p.minorCasing,
339
+ fillWidth: width([
340
+ [9, 0.5],
341
+ [12, 1.6],
342
+ [15, 5],
343
+ [20, 20],
344
+ ]),
345
+ minZoom: 9,
346
+ },
347
+ {
348
+ id: 'tertiary',
349
+ kinds: TERTIARY,
350
+ fill: p.minor,
351
+ casing: p.minorCasing,
352
+ fillWidth: width([
353
+ [11, 0.6],
354
+ [14, 2.4],
355
+ [17, 7],
356
+ [20, 18],
357
+ ]),
358
+ minZoom: 11,
359
+ },
360
+ {
361
+ id: 'minor',
362
+ kinds: MINOR,
363
+ fill: p.minor,
364
+ casing: p.minorCasing,
365
+ fillWidth: width([
366
+ [12, 0.5],
367
+ [14, 1.8],
368
+ [17, 6],
369
+ [20, 16],
370
+ ]),
371
+ minZoom: 12,
372
+ },
373
+ {
374
+ id: 'service',
375
+ kinds: SERVICE,
376
+ fill: p.minor,
377
+ casing: p.minorCasing,
378
+ fillWidth: width([
379
+ [14, 0.8],
380
+ [17, 3],
381
+ [20, 9],
382
+ ]),
383
+ minZoom: 14,
384
+ },
385
+ ];
386
+
387
+ for (const road of roadClasses) {
388
+ layers.push({
389
+ id: `${road.id}-casing`,
390
+ type: 'line',
391
+ sourceLayer: 'streets',
392
+ minZoom: road.minZoom,
393
+ filter: ['all', ['in', 'kind', ...road.kinds], ['!=', 'rail', true]],
394
+ color: road.casing,
395
+ // A casing is the fill plus two logical pixels, which is what makes
396
+ // the outline a constant width at every zoom rather than a ratio that
397
+ // disappears when the road is thin.
398
+ width: scaleWidth(road.fillWidth, 2),
399
+ cap: 'round',
400
+ join: 'round',
401
+ });
402
+ }
403
+ for (const road of roadClasses) {
404
+ layers.push({
405
+ id: road.id,
406
+ type: 'line',
407
+ sourceLayer: 'streets',
408
+ minZoom: road.minZoom,
409
+ filter: ['all', ['in', 'kind', ...road.kinds], ['!=', 'rail', true]],
410
+ color: road.fill,
411
+ width: road.fillWidth,
412
+ cap: 'round',
413
+ join: 'round',
414
+ });
415
+ }
416
+
417
+ layers.push(
418
+ {
419
+ id: 'paths',
420
+ type: 'line',
421
+ sourceLayer: 'streets',
422
+ minZoom: 15,
423
+ filter: ['in', 'kind', ...PATH],
424
+ color: p.path,
425
+ width: width([
426
+ [15, 0.8],
427
+ [18, 1.6],
428
+ [20, 3],
429
+ ]),
430
+ dash: [3, 2],
431
+ },
432
+ {
433
+ id: 'rail',
434
+ type: 'line',
435
+ sourceLayer: 'streets',
436
+ minZoom: 9,
437
+ filter: ['in', 'kind', ...RAIL],
438
+ color: p.rail,
439
+ width: width([
440
+ [9, 0.5],
441
+ [13, 1],
442
+ [17, 2],
443
+ [20, 3],
444
+ ]),
445
+ },
446
+ {
447
+ id: 'ferries',
448
+ type: 'line',
449
+ sourceLayer: 'ferries',
450
+ minZoom: 9,
451
+ color: p.boundary,
452
+ width: 1,
453
+ dash: [4, 3],
454
+ },
455
+ {
456
+ id: 'boundaries-country',
457
+ type: 'line',
458
+ sourceLayer: 'boundaries',
459
+ color: p.boundary,
460
+ filter: ['<=', 'admin_level', 2],
461
+ width: width([
462
+ [2, 0.6],
463
+ [6, 1.1],
464
+ [12, 1.6],
465
+ ]),
466
+ dash: [5, 3],
467
+ },
468
+ {
469
+ // Regional borders only once there is room for them to mean
470
+ // something — at zoom 3 they are noise over the country outlines.
471
+ id: 'boundaries-region',
472
+ type: 'line',
473
+ sourceLayer: 'boundaries',
474
+ minZoom: 6,
475
+ color: p.boundary,
476
+ filter: ['>', 'admin_level', 2],
477
+ width: width([
478
+ [6, 0.5],
479
+ [12, 1],
480
+ ]),
481
+ dash: [3, 3],
482
+ },
483
+ );
484
+
485
+ if (options.labels !== false) {
486
+ layers.push(
487
+ // Places, split by kind rather than ranked by a field — see
488
+ // `SymbolLayer.rank` for why. The split is also what lets each class
489
+ // have its own size and its own zoom, which is most of what makes a
490
+ // label layer read as cartography.
491
+ {
492
+ id: 'place-labels-city',
493
+ type: 'symbol',
494
+ sourceLayer: 'place_labels',
495
+ filter: ['in', 'kind', 'capital', 'city'],
496
+ textField: name,
497
+ textColor: p.text,
498
+ textHaloColor: p.halo,
499
+ textHaloWidth: 1.5,
500
+ textSize: {
501
+ stops: [
502
+ [3, 12],
503
+ [8, 15],
504
+ [12, 18],
505
+ [16, 20],
506
+ ],
507
+ },
508
+ rank: 100,
509
+ },
510
+ {
511
+ id: 'place-labels-town',
512
+ type: 'symbol',
513
+ sourceLayer: 'place_labels',
514
+ minZoom: 7,
515
+ filter: ['in', 'kind', 'town', 'borough'],
516
+ textField: name,
517
+ textColor: p.text,
518
+ textHaloColor: p.halo,
519
+ textHaloWidth: 1.5,
520
+ textSize: {
521
+ stops: [
522
+ [7, 11],
523
+ [12, 14],
524
+ [16, 16],
525
+ ],
526
+ },
527
+ rank: 80,
528
+ },
529
+ {
530
+ id: 'place-labels-suburb',
531
+ type: 'symbol',
532
+ sourceLayer: 'place_labels',
533
+ minZoom: 11,
534
+ filter: ['in', 'kind', 'suburb', 'quarter', 'neighbourhood', 'village'],
535
+ textField: name,
536
+ textColor: p.textMinor,
537
+ textHaloColor: p.halo,
538
+ textHaloWidth: 1.2,
539
+ textSize: {
540
+ stops: [
541
+ [11, 10],
542
+ [15, 13],
543
+ ],
544
+ },
545
+ rank: 60,
546
+ },
547
+ {
548
+ id: 'place-labels-locality',
549
+ type: 'symbol',
550
+ sourceLayer: 'place_labels',
551
+ minZoom: 13,
552
+ filter: [
553
+ 'in',
554
+ 'kind',
555
+ 'hamlet',
556
+ 'locality',
557
+ 'isolated_dwelling',
558
+ 'farm',
559
+ 'island',
560
+ 'islet',
561
+ ],
562
+ textField: name,
563
+ textColor: p.textMinor,
564
+ textHaloColor: p.halo,
565
+ textHaloWidth: 1.2,
566
+ textSize: 10,
567
+ rank: 40,
568
+ },
569
+ {
570
+ id: 'water-labels',
571
+ type: 'symbol',
572
+ sourceLayer: 'water_polygons_labels',
573
+ minZoom: 9,
574
+ textField: name,
575
+ textColor: p.textMinor,
576
+ textHaloColor: p.halo,
577
+ textHaloWidth: 1,
578
+ textSize: 11,
579
+ rank: 30,
580
+ },
581
+ {
582
+ id: 'street-labels',
583
+ type: 'symbol',
584
+ sourceLayer: 'street_labels',
585
+ minZoom: 14,
586
+ filter: ['!in', 'kind', ...RAIL],
587
+ textField: name,
588
+ textColor: p.textMinor,
589
+ textHaloColor: p.halo,
590
+ textHaloWidth: 1,
591
+ textSize: 11,
592
+ // Under every place name: a suburb's name is what orients someone,
593
+ // a street's name is what they read once they are oriented.
594
+ rank: 20,
595
+ },
596
+ {
597
+ id: 'transport-labels',
598
+ type: 'symbol',
599
+ sourceLayer: 'public_transport',
600
+ minZoom: 15,
601
+ textField: name,
602
+ textColor: p.textMinor,
603
+ textHaloColor: p.halo,
604
+ textHaloWidth: 1,
605
+ textSize: 10,
606
+ rank: 10,
607
+ },
608
+ );
609
+ }
610
+
611
+ return { background: p.land, layers };
612
+ }
613
+
614
+ /** A width ramp plus a constant — how a casing is derived from its fill so
615
+ * the two cannot drift apart when one of them is retuned. */
616
+ function scaleWidth(base: Zoomed<number>, add: number): Zoomed<number> {
617
+ if (typeof base === 'number') return base + add;
618
+ return { stops: base.stops.map(([z, v]) => [z, v + add] as const) };
619
+ }
620
+
621
+ // --- OpenMapTiles -----------------------------------------------------------
622
+
623
+ /**
624
+ * Road classes as **OpenMapTiles** names them.
625
+ *
626
+ * Not the same words as Shortbread's, which is the whole reason this second
627
+ * style exists: `transportation.class` is `minor` where Shortbread says
628
+ * `residential`, `unclassified` and `living_street`, and rail lives in the
629
+ * same layer under `class: 'rail'` rather than beside the roads. Point
630
+ * {@link shortbreadStyle} at an OpenMapTiles source and it matches nothing
631
+ * and draws an empty map.
632
+ */
633
+ const OMT_RAIL = ['rail', 'transit'] as const;
634
+
635
+ /** Landcover classes that read as green space. */
636
+ const OMT_GREEN = ['wood', 'grass', 'farmland', 'wetland'] as const;
637
+
638
+ /** Landuse classes that read as built-up. */
639
+ const OMT_BUILT = [
640
+ 'residential',
641
+ 'commercial',
642
+ 'industrial',
643
+ 'retail',
644
+ 'railway',
645
+ 'cemetery',
646
+ 'quarry',
647
+ ] as const;
648
+
649
+ /** Landuse classes for an institutional site. */
650
+ const OMT_SITES = [
651
+ 'hospital',
652
+ 'school',
653
+ 'university',
654
+ 'college',
655
+ 'kindergarten',
656
+ 'stadium',
657
+ 'pitch',
658
+ 'playground',
659
+ ] as const;
660
+
661
+ export interface OpenMapTilesStyleOptions extends ShortbreadStyleOptions {}
662
+
663
+ /**
664
+ * The same cartography over the **OpenMapTiles** schema.
665
+ *
666
+ * OpenMapTiles is the other open schema, and between them the two cover
667
+ * nearly every provider worth pointing this at: Shortbread is what
668
+ * OpenStreetMap's own server and VersaTiles cut, OpenMapTiles is what
669
+ * MapTiler, Stadia, Geoapify, OpenFreeMap and most self-hosted planets cut.
670
+ *
671
+ * Deliberately the same palette, the same layer ids where they mean the
672
+ * same thing, and the same casing-then-fill ordering, so switching a source
673
+ * between the two schemas changes which style you pass and nothing else
674
+ * about how the map looks.
675
+ */
676
+ export function openMapTilesStyle(
677
+ options: OpenMapTilesStyleOptions = {},
678
+ ): MapStyle {
679
+ const p: MapPalette = {
680
+ ...(options.dark ? DARK_PALETTE : LIGHT_PALETTE),
681
+ ...options.palette,
682
+ };
683
+ const name = options.nameField ?? 'name';
684
+ const layers: MapStyleLayer[] = [
685
+ // One `water` layer for everything wet, with `class` telling the sea
686
+ // from a lake — where Shortbread has `ocean` and `water_polygons`.
687
+ { id: 'ocean', type: 'fill', sourceLayer: 'water', color: p.water },
688
+ {
689
+ id: 'landuse-green',
690
+ type: 'fill',
691
+ sourceLayer: 'landcover',
692
+ filter: ['in', 'class', ...OMT_GREEN],
693
+ color: p.green,
694
+ },
695
+ { id: 'park', type: 'fill', sourceLayer: 'park', color: p.green },
696
+ {
697
+ id: 'landuse-built',
698
+ type: 'fill',
699
+ sourceLayer: 'landuse',
700
+ filter: ['in', 'class', ...OMT_BUILT],
701
+ color: p.built,
702
+ },
703
+ {
704
+ id: 'sites',
705
+ type: 'fill',
706
+ sourceLayer: 'landuse',
707
+ minZoom: 13,
708
+ filter: ['in', 'class', ...OMT_SITES],
709
+ color: p.site,
710
+ },
711
+ {
712
+ id: 'water-lines',
713
+ type: 'line',
714
+ sourceLayer: 'waterway',
715
+ color: p.water,
716
+ width: width([
717
+ [6, 0.5],
718
+ [10, 1.2],
719
+ [14, 3],
720
+ [18, 8],
721
+ ]),
722
+ },
723
+ ];
724
+
725
+ if (options.buildings !== false) {
726
+ layers.push({
727
+ id: 'buildings',
728
+ type: 'fill',
729
+ sourceLayer: 'building',
730
+ minZoom: 14,
731
+ color: p.building,
732
+ outlineColor: p.buildingEdge,
733
+ });
734
+ }
735
+
736
+ const roads: {
737
+ id: string;
738
+ classes: readonly string[];
739
+ fill: string;
740
+ casing: string;
741
+ fillWidth: Zoomed<number>;
742
+ minZoom?: number;
743
+ }[] = [
744
+ {
745
+ id: 'motorway',
746
+ classes: ['motorway'],
747
+ fill: p.motorway,
748
+ casing: p.motorwayCasing,
749
+ fillWidth: width([
750
+ [5, 0.6],
751
+ [8, 1.4],
752
+ [10, 2.4],
753
+ [13, 5],
754
+ [16, 12],
755
+ [20, 40],
756
+ ]),
757
+ },
758
+ {
759
+ id: 'trunk',
760
+ classes: ['trunk'],
761
+ fill: p.trunk,
762
+ casing: p.motorwayCasing,
763
+ fillWidth: width([
764
+ [6, 0.5],
765
+ [9, 1.2],
766
+ [12, 2.6],
767
+ [16, 9],
768
+ [20, 30],
769
+ ]),
770
+ },
771
+ {
772
+ id: 'primary',
773
+ classes: ['primary'],
774
+ fill: p.primary,
775
+ casing: p.minorCasing,
776
+ minZoom: 7,
777
+ fillWidth: width([
778
+ [7, 0.5],
779
+ [10, 1.3],
780
+ [13, 3],
781
+ [16, 8],
782
+ [20, 26],
783
+ ]),
784
+ },
785
+ {
786
+ id: 'secondary',
787
+ classes: ['secondary'],
788
+ fill: p.secondary,
789
+ casing: p.minorCasing,
790
+ minZoom: 9,
791
+ fillWidth: width([
792
+ [9, 0.5],
793
+ [12, 1.6],
794
+ [15, 5],
795
+ [20, 20],
796
+ ]),
797
+ },
798
+ {
799
+ id: 'tertiary',
800
+ classes: ['tertiary'],
801
+ fill: p.minor,
802
+ casing: p.minorCasing,
803
+ minZoom: 11,
804
+ fillWidth: width([
805
+ [11, 0.6],
806
+ [14, 2.4],
807
+ [17, 7],
808
+ [20, 18],
809
+ ]),
810
+ },
811
+ {
812
+ // `minor` is OpenMapTiles' one word for residential, unclassified and
813
+ // living_street, which Shortbread keeps apart.
814
+ id: 'minor',
815
+ classes: ['minor'],
816
+ fill: p.minor,
817
+ casing: p.minorCasing,
818
+ minZoom: 12,
819
+ fillWidth: width([
820
+ [12, 0.5],
821
+ [14, 1.8],
822
+ [17, 6],
823
+ [20, 16],
824
+ ]),
825
+ },
826
+ {
827
+ id: 'service',
828
+ classes: ['service', 'track'],
829
+ fill: p.minor,
830
+ casing: p.minorCasing,
831
+ minZoom: 14,
832
+ fillWidth: width([
833
+ [14, 0.8],
834
+ [17, 3],
835
+ [20, 9],
836
+ ]),
837
+ },
838
+ ];
839
+
840
+ for (const road of roads) {
841
+ layers.push({
842
+ id: `${road.id}-casing`,
843
+ type: 'line',
844
+ sourceLayer: 'transportation',
845
+ minZoom: road.minZoom,
846
+ filter: ['in', 'class', ...road.classes],
847
+ color: road.casing,
848
+ width: scaleWidth(road.fillWidth, 2),
849
+ cap: 'round',
850
+ join: 'round',
851
+ });
852
+ }
853
+ for (const road of roads) {
854
+ layers.push({
855
+ id: road.id,
856
+ type: 'line',
857
+ sourceLayer: 'transportation',
858
+ minZoom: road.minZoom,
859
+ filter: ['in', 'class', ...road.classes],
860
+ color: road.fill,
861
+ width: road.fillWidth,
862
+ cap: 'round',
863
+ join: 'round',
864
+ });
865
+ }
866
+
867
+ layers.push(
868
+ {
869
+ id: 'paths',
870
+ type: 'line',
871
+ sourceLayer: 'transportation',
872
+ minZoom: 15,
873
+ filter: ['in', 'class', 'path', 'pedestrian'],
874
+ color: p.path,
875
+ width: width([
876
+ [15, 0.8],
877
+ [18, 1.6],
878
+ [20, 3],
879
+ ]),
880
+ dash: [3, 2],
881
+ },
882
+ {
883
+ id: 'rail',
884
+ type: 'line',
885
+ sourceLayer: 'transportation',
886
+ minZoom: 9,
887
+ filter: ['in', 'class', ...OMT_RAIL],
888
+ color: p.rail,
889
+ width: width([
890
+ [9, 0.5],
891
+ [13, 1],
892
+ [17, 2],
893
+ [20, 3],
894
+ ]),
895
+ },
896
+ {
897
+ id: 'ferries',
898
+ type: 'line',
899
+ sourceLayer: 'transportation',
900
+ minZoom: 9,
901
+ filter: ['==', 'class', 'ferry'],
902
+ color: p.boundary,
903
+ width: 1,
904
+ dash: [4, 3],
905
+ },
906
+ {
907
+ id: 'boundaries-country',
908
+ type: 'line',
909
+ sourceLayer: 'boundary',
910
+ filter: ['<=', 'admin_level', 2],
911
+ color: p.boundary,
912
+ width: width([
913
+ [2, 0.6],
914
+ [6, 1.1],
915
+ [12, 1.6],
916
+ ]),
917
+ dash: [5, 3],
918
+ },
919
+ {
920
+ id: 'boundaries-region',
921
+ type: 'line',
922
+ sourceLayer: 'boundary',
923
+ minZoom: 6,
924
+ filter: ['>', 'admin_level', 2],
925
+ color: p.boundary,
926
+ width: width([
927
+ [6, 0.5],
928
+ [12, 1],
929
+ ]),
930
+ dash: [3, 3],
931
+ },
932
+ );
933
+
934
+ if (options.labels !== false) {
935
+ layers.push(
936
+ {
937
+ id: 'place-labels-city',
938
+ type: 'symbol',
939
+ sourceLayer: 'place',
940
+ filter: ['in', 'class', 'city'],
941
+ textField: name,
942
+ textColor: p.text,
943
+ textHaloColor: p.halo,
944
+ textHaloWidth: 1.5,
945
+ textSize: {
946
+ stops: [
947
+ [3, 12],
948
+ [8, 15],
949
+ [12, 18],
950
+ [16, 20],
951
+ ],
952
+ },
953
+ rank: 100,
954
+ },
955
+ {
956
+ id: 'place-labels-town',
957
+ type: 'symbol',
958
+ sourceLayer: 'place',
959
+ minZoom: 7,
960
+ filter: ['in', 'class', 'town'],
961
+ textField: name,
962
+ textColor: p.text,
963
+ textHaloColor: p.halo,
964
+ textHaloWidth: 1.5,
965
+ textSize: {
966
+ stops: [
967
+ [7, 11],
968
+ [12, 14],
969
+ [16, 16],
970
+ ],
971
+ },
972
+ rank: 80,
973
+ },
974
+ {
975
+ id: 'place-labels-suburb',
976
+ type: 'symbol',
977
+ sourceLayer: 'place',
978
+ minZoom: 11,
979
+ filter: [
980
+ 'in',
981
+ 'class',
982
+ 'suburb',
983
+ 'quarter',
984
+ 'neighbourhood',
985
+ 'village',
986
+ ],
987
+ textField: name,
988
+ textColor: p.textMinor,
989
+ textHaloColor: p.halo,
990
+ textHaloWidth: 1.2,
991
+ textSize: {
992
+ stops: [
993
+ [11, 10],
994
+ [15, 13],
995
+ ],
996
+ },
997
+ rank: 60,
998
+ },
999
+ {
1000
+ id: 'place-labels-locality',
1001
+ type: 'symbol',
1002
+ sourceLayer: 'place',
1003
+ minZoom: 13,
1004
+ filter: ['in', 'class', 'hamlet', 'isolated_dwelling', 'island'],
1005
+ textField: name,
1006
+ textColor: p.textMinor,
1007
+ textHaloColor: p.halo,
1008
+ textHaloWidth: 1.2,
1009
+ textSize: 10,
1010
+ rank: 40,
1011
+ },
1012
+ {
1013
+ id: 'water-labels',
1014
+ type: 'symbol',
1015
+ sourceLayer: 'water_name',
1016
+ minZoom: 9,
1017
+ textField: name,
1018
+ textColor: p.textMinor,
1019
+ textHaloColor: p.halo,
1020
+ textHaloWidth: 1,
1021
+ textSize: 11,
1022
+ rank: 30,
1023
+ },
1024
+ {
1025
+ id: 'street-labels',
1026
+ type: 'symbol',
1027
+ sourceLayer: 'transportation_name',
1028
+ minZoom: 14,
1029
+ textField: name,
1030
+ textColor: p.textMinor,
1031
+ textHaloColor: p.halo,
1032
+ textHaloWidth: 1,
1033
+ textSize: 11,
1034
+ rank: 20,
1035
+ },
1036
+ );
1037
+ }
1038
+
1039
+ return { background: p.land, layers };
1040
+ }