@tileflow/core 0.1.0-alpha.0 → 0.1.0-alpha.1

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.
package/README.md CHANGED
@@ -3,19 +3,28 @@
3
3
  Typed Tileflow config helpers and the MapLibre style compiler.
4
4
 
5
5
  ```ts
6
- import { defineTileflow } from "@tileflow/core";
6
+ import {defineTileflow, osm, roads} from '@tileflow/core';
7
7
 
8
8
  export default defineTileflow({
9
+ themes: {
10
+ light: {
11
+ mode: 'light',
12
+ colors: {
13
+ water: '#8ED6E8',
14
+ park: '#C3F1D5',
15
+ road: '#D8E0E7',
16
+ text: '#566371',
17
+ },
18
+ },
19
+ },
9
20
  maps: {
10
21
  madrid: {
11
- colors: {
12
- canvas: "#F8F6F1",
13
- land: "#F2EEE6",
14
- water: "#B6CBD8",
15
- nature: "#AEBB9B",
16
- road: "#DED6C8",
17
- text: "#2E2E2E",
18
- accent: "#C6A15B",
22
+ basemap: osm(),
23
+ theme: 'light',
24
+ modules: [roads({preset: 'modern', density: 'balanced'})],
25
+ view: {
26
+ center: [-3.7038, 40.4168],
27
+ zoom: 12,
19
28
  },
20
29
  },
21
30
  },
package/dist/index.d.ts CHANGED
@@ -1,7 +1,12 @@
1
- type TileflowDensity = "clean" | "balanced" | "dense";
2
- type TileflowRoads = "hidden" | "soft" | "standard" | "detailed";
3
- type TileflowLabels = "none" | "essential" | "balanced" | "full";
4
- type TileflowPoi = "none" | "minimal" | "balanced" | "full";
1
+ type TileflowDensity = 'clean' | 'balanced' | 'dense';
2
+ type TileflowRoads = 'hidden' | 'soft' | 'standard' | 'detailed';
3
+ type TileflowLabels = 'none' | 'essential' | 'balanced' | 'full';
4
+ type TileflowPoi = 'none' | 'minimal' | 'balanced' | 'full';
5
+ type TileflowTheme = 'standard' | 'light' | 'dark' | 'minimal' | 'osm-bright-2';
6
+ type TileflowBuildings = 'hidden' | 'flat' | '3d';
7
+ type TileflowTerrainMode = 'none' | 'hillshade' | '3d';
8
+ type TileflowTerrainEncoding = 'mapbox' | 'terrarium';
9
+ type TileflowThemeMode = 'light' | 'dark';
5
10
  type TileflowColors = {
6
11
  canvas: string;
7
12
  land: string;
@@ -15,13 +20,19 @@ type LegacyTileflowColorAliases = {
15
20
  background?: string;
16
21
  park?: string;
17
22
  building?: string;
23
+ roadCasing?: string;
18
24
  };
19
25
  type TileflowColorConfig = Partial<TileflowColors> & LegacyTileflowColorAliases;
20
- type TileflowFontWeight = "regular" | "medium" | "semibold" | "bold";
26
+ type TileflowFontWeight = 'regular' | 'medium' | 'semibold' | 'bold';
21
27
  type TileflowTypography = {
22
28
  font?: string;
23
29
  weight?: TileflowFontWeight;
24
30
  };
31
+ type TileflowThemeConfig = {
32
+ mode?: TileflowThemeMode;
33
+ colors?: TileflowColorConfig;
34
+ typography?: TileflowTypography;
35
+ };
25
36
  type TileflowSourceLayers = {
26
37
  landuse?: string;
27
38
  park?: string;
@@ -39,18 +50,59 @@ type TileflowTiles = {
39
50
  attribution?: string;
40
51
  sourceLayers?: TileflowSourceLayers;
41
52
  };
53
+ type TileflowTerrainConfig = {
54
+ attribution?: string;
55
+ encoding?: TileflowTerrainEncoding;
56
+ exaggeration?: number;
57
+ mode?: TileflowTerrainMode;
58
+ sourceId?: string;
59
+ tileset?: string;
60
+ url?: string;
61
+ };
62
+ type TileflowTerrain = TileflowTerrainMode | TileflowTerrainConfig;
63
+ type TileflowViewConfig = {
64
+ bearing?: number;
65
+ center?: readonly [number, number];
66
+ zoom?: number;
67
+ };
68
+ type TileflowOsmBasemapConfig = {
69
+ type: 'osm';
70
+ attribution?: string;
71
+ glyphs?: string;
72
+ sourceId?: string;
73
+ sourceLayers?: TileflowSourceLayers;
74
+ sprite?: string;
75
+ tileset?: string;
76
+ url?: string;
77
+ };
78
+ type TileflowBasemapConfig = TileflowOsmBasemapConfig;
79
+ type TileflowOsmBasemapOptions = Omit<TileflowOsmBasemapConfig, 'type'>;
80
+ type TileflowRoadsModulePreset = 'modern' | 'minimal';
81
+ type TileflowRoadsModuleConfig = {
82
+ type: 'roads';
83
+ density?: TileflowDensity;
84
+ preset?: TileflowRoadsModulePreset;
85
+ };
86
+ type TileflowRoadsModuleOptions = Omit<TileflowRoadsModuleConfig, 'type'>;
87
+ type TileflowModuleConfig = TileflowRoadsModuleConfig;
42
88
  type TileflowConfig = {
89
+ basemap?: TileflowBasemapConfig;
43
90
  name?: string;
44
91
  tileset?: string;
92
+ theme?: TileflowTheme | string | TileflowThemeConfig;
93
+ modules?: readonly TileflowModuleConfig[];
45
94
  colors?: TileflowColorConfig;
46
95
  typography?: TileflowTypography;
47
96
  density?: TileflowDensity;
48
97
  roads?: TileflowRoads;
49
98
  labels?: TileflowLabels;
50
99
  poi?: TileflowPoi;
100
+ buildings?: TileflowBuildings;
101
+ terrain?: TileflowTerrain;
51
102
  glyphs?: string;
52
103
  sprite?: string;
53
104
  tiles?: TileflowTiles;
105
+ view?: TileflowViewConfig;
54
106
  /**
55
107
  * Domains allowed to load this map in production (e.g. "example.com" or
56
108
  * "*.example.com"). When set, tile and session requests from other
@@ -67,10 +119,12 @@ type TileflowTilesetConfig = {
67
119
  sourceLayers?: TileflowSourceLayers;
68
120
  };
69
121
  type TileflowProjectConfig = {
122
+ themes?: Record<string, TileflowThemeConfig>;
70
123
  tilesets?: Record<string, TileflowTilesetConfig>;
71
124
  maps: Record<string, TileflowMapConfig>;
72
125
  };
73
126
  type TileflowStyleOptions = {
127
+ themes?: Record<string, TileflowThemeConfig>;
74
128
  tileBaseUrl?: string;
75
129
  tileset?: string;
76
130
  };
@@ -87,10 +141,11 @@ type MapLibreStyle = {
87
141
  sprite?: string;
88
142
  sources: Record<string, Record<string, unknown>>;
89
143
  layers: Array<Record<string, unknown>>;
144
+ terrain?: Record<string, unknown>;
90
145
  metadata?: Record<string, unknown>;
91
146
  };
92
147
  type ValidationMessage = {
93
- level: "error" | "warning";
148
+ level: 'error' | 'warning';
94
149
  path: string;
95
150
  message: string;
96
151
  };
@@ -98,8 +153,6 @@ type ValidationResult = {
98
153
  valid: boolean;
99
154
  messages: ValidationMessage[];
100
155
  };
101
- declare function defineTileflow(config: TileflowProjectConfig): TileflowProjectConfig;
102
- declare function validateConfig(input: unknown): ValidationResult;
103
156
  declare function createStyle(input?: TileflowConfig, options?: TileflowStyleOptions): MapLibreStyle;
104
157
  declare function createStyleFromProject(projectConfig: TileflowProjectConfig, mapName: string, options?: TileflowStyleOptions): MapLibreStyle;
105
158
  declare function createManifest(projectConfig: TileflowProjectConfig, options?: {
@@ -108,4 +161,12 @@ declare function createManifest(projectConfig: TileflowProjectConfig, options?:
108
161
  }): TileflowManifest;
109
162
  declare function getDefaultColors(): TileflowColors;
110
163
 
111
- export { type MapLibreStyle, type TileflowColorConfig, type TileflowColors, type TileflowConfig, type TileflowDensity, type TileflowFontWeight, type TileflowLabels, type TileflowManifest, type TileflowMapConfig, type TileflowPoi, type TileflowProjectConfig, type TileflowRoads, type TileflowSourceLayers, type TileflowStyleOptions, type TileflowTiles, type TileflowTilesetConfig, type TileflowTypography, type ValidationMessage, type ValidationResult, createManifest, createStyle, createStyleFromProject, defineTileflow, getDefaultColors, validateConfig };
164
+ declare function validateConfig(input: unknown): ValidationResult;
165
+
166
+ declare function defineTileflow(config: TileflowProjectConfig): TileflowProjectConfig;
167
+
168
+ declare function osm(options?: TileflowOsmBasemapOptions): TileflowOsmBasemapConfig;
169
+
170
+ declare function roads(options?: TileflowRoadsModuleOptions): TileflowRoadsModuleConfig;
171
+
172
+ export { type MapLibreStyle, type TileflowBasemapConfig, type TileflowBuildings, type TileflowColorConfig, type TileflowColors, type TileflowConfig, type TileflowDensity, type TileflowFontWeight, type TileflowLabels, type TileflowManifest, type TileflowMapConfig, type TileflowModuleConfig, type TileflowOsmBasemapConfig, type TileflowOsmBasemapOptions, type TileflowPoi, type TileflowProjectConfig, type TileflowRoads, type TileflowRoadsModuleConfig, type TileflowRoadsModuleOptions, type TileflowRoadsModulePreset, type TileflowSourceLayers, type TileflowStyleOptions, type TileflowTerrain, type TileflowTerrainConfig, type TileflowTerrainEncoding, type TileflowTerrainMode, type TileflowTheme, type TileflowThemeConfig, type TileflowThemeMode, type TileflowTiles, type TileflowTilesetConfig, type TileflowTypography, type TileflowViewConfig, type ValidationMessage, type ValidationResult, createManifest, createStyle, createStyleFromProject, defineTileflow, getDefaultColors, osm, roads, validateConfig };
package/dist/index.js CHANGED
@@ -1,31 +1,97 @@
1
- // src/index.ts
1
+ // src/basemaps.ts
2
+ function osm(options = {}) {
3
+ return {
4
+ type: "osm",
5
+ ...options
6
+ };
7
+ }
8
+
9
+ // src/schema.ts
2
10
  import { z } from "zod";
11
+ var legacyThemeNames = /* @__PURE__ */ new Set(["standard", "light", "dark", "minimal", "osm-bright-2"]);
3
12
  var hexColorSchema = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
4
- message: "Expected a hex color like #F8F6F1 or #F8F6F1FF"
13
+ message: "Expected a hex color like #F6F7F3 or #F6F7F3FF"
14
+ });
15
+ var colorConfigSchema = z.object({
16
+ canvas: hexColorSchema.optional(),
17
+ land: hexColorSchema.optional(),
18
+ background: hexColorSchema.optional(),
19
+ water: hexColorSchema.optional(),
20
+ nature: hexColorSchema.optional(),
21
+ text: hexColorSchema.optional(),
22
+ accent: hexColorSchema.optional(),
23
+ park: hexColorSchema.optional(),
24
+ road: hexColorSchema.optional(),
25
+ roadCasing: hexColorSchema.optional(),
26
+ building: hexColorSchema.optional()
27
+ });
28
+ var typographySchema = z.object({
29
+ font: z.string().trim().min(1).optional(),
30
+ weight: z.enum(["regular", "medium", "semibold", "bold"]).optional()
31
+ });
32
+ var themeConfigSchema = z.object({
33
+ mode: z.enum(["light", "dark"]).optional(),
34
+ colors: colorConfigSchema.optional(),
35
+ typography: typographySchema.optional()
36
+ });
37
+ var sourceLayersSchema = z.object({
38
+ landuse: z.string().optional(),
39
+ park: z.string().optional(),
40
+ water: z.string().optional(),
41
+ road: z.string().optional(),
42
+ building: z.string().optional(),
43
+ boundary: z.string().optional(),
44
+ place: z.string().optional(),
45
+ poi: z.string().optional()
46
+ });
47
+ var basemapSchema = z.object({
48
+ type: z.literal("osm"),
49
+ attribution: z.string().optional(),
50
+ glyphs: z.string().min(1).optional(),
51
+ sourceId: z.string().min(1).optional(),
52
+ sourceLayers: sourceLayersSchema.optional(),
53
+ sprite: z.string().min(1).optional(),
54
+ tileset: z.string().trim().min(1).optional(),
55
+ url: z.url().optional()
56
+ });
57
+ var roadsModuleSchema = z.object({
58
+ type: z.literal("roads"),
59
+ density: z.enum(["clean", "balanced", "dense"]).optional(),
60
+ preset: z.enum(["modern", "minimal"]).optional()
61
+ });
62
+ var moduleSchema = z.discriminatedUnion("type", [roadsModuleSchema]);
63
+ var viewSchema = z.object({
64
+ bearing: z.number().finite().min(-180).max(180).optional(),
65
+ center: z.tuple([z.number().finite(), z.number().finite()]).refine(([lng, lat]) => lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90, {
66
+ message: "Expected center as [lng, lat] within world bounds"
67
+ }).optional(),
68
+ zoom: z.number().finite().min(0).max(24).optional()
5
69
  });
6
70
  var configSchema = z.object({
71
+ basemap: basemapSchema.optional(),
7
72
  name: z.string().min(1).optional(),
8
73
  tileset: z.string().trim().min(1).optional(),
9
- colors: z.object({
10
- canvas: hexColorSchema.optional(),
11
- land: hexColorSchema.optional(),
12
- background: hexColorSchema.optional(),
13
- water: hexColorSchema.optional(),
14
- nature: hexColorSchema.optional(),
15
- text: hexColorSchema.optional(),
16
- accent: hexColorSchema.optional(),
17
- park: hexColorSchema.optional(),
18
- road: hexColorSchema.optional(),
19
- building: hexColorSchema.optional()
20
- }).optional(),
21
- typography: z.object({
22
- font: z.string().trim().min(1).optional(),
23
- weight: z.enum(["regular", "medium", "semibold", "bold"]).optional()
24
- }).optional(),
74
+ theme: z.union([z.string().trim().min(1), themeConfigSchema]).optional(),
75
+ modules: z.array(moduleSchema).optional(),
76
+ colors: colorConfigSchema.optional(),
77
+ typography: typographySchema.optional(),
25
78
  density: z.enum(["clean", "balanced", "dense"]).optional(),
26
79
  roads: z.enum(["hidden", "soft", "standard", "detailed"]).optional(),
27
80
  labels: z.enum(["none", "essential", "balanced", "full"]).optional(),
28
81
  poi: z.enum(["none", "minimal", "balanced", "full"]).optional(),
82
+ buildings: z.enum(["hidden", "flat", "3d"]).optional(),
83
+ terrain: z.union([
84
+ z.enum(["none", "hillshade", "3d"]),
85
+ z.object({
86
+ attribution: z.string().optional(),
87
+ encoding: z.enum(["mapbox", "terrarium"]).optional(),
88
+ exaggeration: z.number().min(0).max(10).optional(),
89
+ mode: z.enum(["none", "hillshade", "3d"]).optional(),
90
+ sourceId: z.string().min(1).optional(),
91
+ tileset: z.string().trim().min(1).optional(),
92
+ url: z.url().optional()
93
+ })
94
+ ]).optional(),
29
95
  glyphs: z.string().min(1).optional(),
30
96
  sprite: z.string().min(1).optional(),
31
97
  tiles: z.object({
@@ -33,40 +99,88 @@ var configSchema = z.object({
33
99
  tileset: z.string().trim().min(1).optional(),
34
100
  url: z.url().optional(),
35
101
  attribution: z.string().optional(),
36
- sourceLayers: z.object({
37
- landuse: z.string().optional(),
38
- park: z.string().optional(),
39
- water: z.string().optional(),
40
- road: z.string().optional(),
41
- building: z.string().optional(),
42
- boundary: z.string().optional(),
43
- place: z.string().optional(),
44
- poi: z.string().optional()
45
- }).optional()
102
+ sourceLayers: sourceLayersSchema.optional()
46
103
  }).optional(),
104
+ view: viewSchema.optional(),
47
105
  allowedOrigins: z.array(z.string().trim().min(1).max(253)).max(20).optional()
48
106
  });
49
107
  var tilesetConfigSchema = z.object({
50
108
  id: z.string().trim().min(1).optional(),
51
109
  name: z.string().trim().min(1).optional(),
52
110
  attribution: z.string().optional(),
53
- sourceLayers: z.object({
54
- landuse: z.string().optional(),
55
- park: z.string().optional(),
56
- water: z.string().optional(),
57
- road: z.string().optional(),
58
- building: z.string().optional(),
59
- boundary: z.string().optional(),
60
- place: z.string().optional(),
61
- poi: z.string().optional()
62
- }).optional()
111
+ sourceLayers: sourceLayersSchema.optional()
63
112
  });
64
113
  var projectConfigSchema = z.object({
114
+ themes: z.record(z.string(), themeConfigSchema).optional(),
65
115
  tilesets: z.record(z.string(), tilesetConfigSchema).optional(),
66
116
  maps: z.record(z.string(), configSchema).refine((maps) => {
67
117
  return Object.keys(maps).length > 0;
68
118
  }, "Expected at least one map")
69
119
  });
120
+ function validateConfig(input) {
121
+ if (isProjectConfigLike(input)) {
122
+ const parsed2 = projectConfigSchema.safeParse(input);
123
+ if (parsed2.success) {
124
+ const messages = validateProjectThemeReferences(parsed2.data);
125
+ if (messages.length > 0) {
126
+ return { valid: false, messages };
127
+ }
128
+ return { valid: true, messages: [] };
129
+ }
130
+ return {
131
+ valid: false,
132
+ messages: parsed2.error.issues.map((issue) => ({
133
+ level: "error",
134
+ path: issue.path.join(".") || "config",
135
+ message: issue.message
136
+ }))
137
+ };
138
+ }
139
+ const parsed = configSchema.safeParse(input);
140
+ if (parsed.success) {
141
+ const messages = validateMapThemeReference(parsed.data, void 0, "theme");
142
+ if (messages.length > 0) {
143
+ return { valid: false, messages };
144
+ }
145
+ return { valid: true, messages: [] };
146
+ }
147
+ return {
148
+ valid: false,
149
+ messages: parsed.error.issues.map((issue) => ({
150
+ level: "error",
151
+ path: issue.path.join(".") || "config",
152
+ message: issue.message
153
+ }))
154
+ };
155
+ }
156
+ function isProjectConfigLike(input) {
157
+ return Boolean(
158
+ input && typeof input === "object" && "maps" in input && typeof input.maps === "object"
159
+ );
160
+ }
161
+ function validateProjectThemeReferences(project) {
162
+ const themeNames = new Set(Object.keys(project.themes ?? {}));
163
+ return Object.entries(project.maps).flatMap(
164
+ ([mapName, mapConfig]) => validateMapThemeReference(mapConfig, themeNames, `maps.${mapName}.theme`)
165
+ );
166
+ }
167
+ function validateMapThemeReference(config, themeNames, path) {
168
+ if (typeof config.theme !== "string") {
169
+ return [];
170
+ }
171
+ if (legacyThemeNames.has(config.theme) || themeNames?.has(config.theme)) {
172
+ return [];
173
+ }
174
+ return [
175
+ {
176
+ level: "error",
177
+ path,
178
+ message: `Unknown Tileflow theme "${config.theme}"`
179
+ }
180
+ ];
181
+ }
182
+
183
+ // src/compiler.ts
70
184
  var defaultSourceLayers = {
71
185
  landuse: "landuse",
72
186
  park: "park",
@@ -78,20 +192,97 @@ var defaultSourceLayers = {
78
192
  poi: "poi"
79
193
  };
80
194
  var defaultColors = {
81
- canvas: "#F8F6F1",
82
- land: "#F2EEE6",
83
- water: "#B6CBD8",
84
- nature: "#AEBB9B",
85
- road: "#DED6C8",
86
- text: "#2E2E2E",
87
- accent: "#C6A15B"
195
+ canvas: "#F6F7F3",
196
+ land: "#F1F3ED",
197
+ water: "#A9D3F5",
198
+ nature: "#CDE8B5",
199
+ road: "#FFFFFF",
200
+ text: "#3C4043",
201
+ accent: "#F4C95D"
88
202
  };
89
203
  var defaultConfig = {
90
- density: "clean",
91
- roads: "soft",
92
- labels: "essential",
204
+ density: "balanced",
205
+ roads: "standard",
206
+ labels: "balanced",
93
207
  poi: "minimal"
94
208
  };
209
+ var themePresets = {
210
+ standard: {
211
+ colors: defaultColors,
212
+ density: "balanced",
213
+ labels: "balanced",
214
+ poi: "minimal",
215
+ roads: "standard"
216
+ },
217
+ light: {
218
+ colors: {
219
+ canvas: "#F9FAF8",
220
+ land: "#F6F7F3",
221
+ water: "#C5E1F5",
222
+ nature: "#DDEED2",
223
+ road: "#FFFFFF",
224
+ building: "#ECEAE4",
225
+ text: "#5F6368",
226
+ accent: "#C9D1D9"
227
+ },
228
+ density: "clean",
229
+ labels: "essential",
230
+ poi: "minimal",
231
+ roads: "standard"
232
+ },
233
+ dark: {
234
+ colors: {
235
+ canvas: "#1C2228",
236
+ land: "#232B32",
237
+ water: "#18384D",
238
+ nature: "#274230",
239
+ road: "#39434D",
240
+ building: "#2F363D",
241
+ text: "#D7DEE6",
242
+ accent: "#E4B857"
243
+ },
244
+ density: "balanced",
245
+ labels: "balanced",
246
+ poi: "minimal",
247
+ roads: "standard"
248
+ },
249
+ minimal: {
250
+ colors: {
251
+ canvas: "#FAFAF8",
252
+ land: "#F7F7F2",
253
+ water: "#D6EAF6",
254
+ nature: "#E5EEDB",
255
+ road: "#FFFFFF",
256
+ building: "#F0EEE8",
257
+ text: "#697079",
258
+ accent: "#B8C0CA"
259
+ },
260
+ density: "clean",
261
+ labels: "essential",
262
+ poi: "none",
263
+ roads: "soft"
264
+ },
265
+ // "OSM Bright 2" — Google Maps-inspired palette. Warm-neutral land, soft
266
+ // blue water, muted green parks, white roads on a light gray canvas, and a
267
+ // warm yellow accent that drives the highway color once the class-based road
268
+ // engine lands. Tuned to read as a familiar navigation basemap.
269
+ "osm-bright-2": {
270
+ colors: {
271
+ canvas: "#EBE9E4",
272
+ land: "#F5F4F1",
273
+ water: "#AADAFF",
274
+ nature: "#C7E6A8",
275
+ road: "#FFFFFF",
276
+ building: "#EFEDE7",
277
+ text: "#5F6368",
278
+ accent: "#F9C97C"
279
+ },
280
+ density: "balanced",
281
+ labels: "balanced",
282
+ poi: "balanced",
283
+ roads: "detailed"
284
+ }
285
+ };
95
286
  var defaultTypography = {
96
287
  font: "Noto Sans",
97
288
  weight: "regular"
@@ -104,23 +295,6 @@ var fontWeightLabels = {
104
295
  };
105
296
  var defaultTileBaseUrl = "https://api.tileflow.dev";
106
297
  var defaultTileset = "world";
107
- function defineTileflow(config) {
108
- return config;
109
- }
110
- function validateConfig(input) {
111
- const parsed = isProjectConfigLike(input) ? projectConfigSchema.safeParse(input) : configSchema.safeParse(input);
112
- if (parsed.success) {
113
- return { valid: true, messages: [] };
114
- }
115
- return {
116
- valid: false,
117
- messages: parsed.error.issues.map((issue) => ({
118
- level: "error",
119
- path: issue.path.join(".") || "config",
120
- message: issue.message
121
- }))
122
- };
123
- }
124
298
  function createStyle(input = {}, options = {}) {
125
299
  const parsed = configSchema.safeParse(input);
126
300
  if (!parsed.success) {
@@ -128,53 +302,83 @@ function createStyle(input = {}, options = {}) {
128
302
  throw new Error(`Invalid Tileflow config. ${message}`);
129
303
  }
130
304
  const config = parsed.data;
131
- const density = config.density ?? defaultConfig.density;
132
- const roads = config.roads ?? defaultConfig.roads;
133
- const labels = config.labels ?? defaultConfig.labels;
134
- const poi = config.poi ?? defaultConfig.poi;
135
- const colors = resolveColors(config.colors);
136
- const sourceId = config.tiles?.sourceId ?? "tileflow";
305
+ const basemap = config.basemap ?? osm();
306
+ const theme = resolveTheme(config.theme, options.themes);
307
+ const density = config.density ?? theme.preset.density ?? defaultConfig.density;
308
+ const roadModule = config.modules?.find(
309
+ (moduleConfig) => moduleConfig.type === "roads"
310
+ );
311
+ const roads2 = resolveRoads(config.roads, roadModule, theme.preset.roads ?? defaultConfig.roads);
312
+ const labels = config.labels ?? theme.preset.labels ?? defaultConfig.labels;
313
+ const poi = config.poi ?? theme.preset.poi ?? defaultConfig.poi;
314
+ const buildings = config.buildings ?? "flat";
315
+ const colors = resolveColors(config.colors, theme.colors);
316
+ const sourceId = config.tiles?.sourceId ?? basemap.sourceId ?? "tileflow";
137
317
  const tileBaseUrl = normalizeBaseUrl(options.tileBaseUrl);
138
- const tileset = options.tileset ?? config.tileset ?? config.tiles?.tileset ?? defaultTileset;
139
- const tilesUrl = config.tiles?.url ?? `${tileBaseUrl}/tiles/${tileset}/tiles.json`;
140
- const typography = resolveTypography(config.typography, defaultTypography);
318
+ const tileset = options.tileset ?? config.tileset ?? config.tiles?.tileset ?? basemap.tileset ?? defaultTileset;
319
+ const tilesUrl = config.tiles?.url ?? basemap.url ?? `${tileBaseUrl}/tiles/${tileset}/tiles.json`;
320
+ const typography = resolveTypography(config.typography, theme.typography);
321
+ const terrain = resolveTerrain(config.terrain, tileBaseUrl);
141
322
  const sourceLayers = resolveSourceLayers(
142
323
  {
143
324
  ...defaultSourceLayers,
325
+ ...basemap.sourceLayers,
144
326
  ...config.tiles?.sourceLayers
145
327
  },
146
328
  void 0
147
329
  );
148
330
  const layers = openMapTilesLayers(sourceId, sourceLayers, colors, {
331
+ buildings,
149
332
  density,
150
333
  labels,
151
334
  poi,
152
- roads,
335
+ roads: roads2,
153
336
  typography
154
337
  });
338
+ const resolvedLayers = terrain ? withTerrainLayers(layers, terrain) : layers;
155
339
  const style = {
156
340
  version: 8,
157
341
  name: config.name ?? "Tileflow",
158
- glyphs: config.glyphs ?? `${tileBaseUrl}/fonts/{fontstack}/{range}.pbf`,
159
- ...config.sprite ? { sprite: config.sprite } : {},
342
+ glyphs: config.glyphs ?? basemap.glyphs ?? `${tileBaseUrl}/fonts/{fontstack}/{range}.pbf`,
343
+ ...config.sprite || basemap.sprite ? { sprite: config.sprite ?? basemap.sprite } : {},
160
344
  sources: {
161
345
  [sourceId]: {
162
346
  type: "vector",
163
347
  url: tilesUrl,
164
- attribution: config.tiles?.attribution ?? "\xA9 OpenMapTiles \xA9 OpenStreetMap contributors"
165
- }
348
+ attribution: config.tiles?.attribution ?? basemap.attribution ?? "\xA9 OpenMapTiles \xA9 OpenStreetMap contributors"
349
+ },
350
+ ...terrain ? {
351
+ [terrain.sourceId]: terrain.source
352
+ } : {}
166
353
  },
167
- layers,
354
+ layers: resolvedLayers,
355
+ ...terrain?.mode === "3d" ? {
356
+ terrain: {
357
+ exaggeration: terrain.exaggeration,
358
+ source: terrain.sourceId
359
+ }
360
+ } : {},
168
361
  metadata: {
362
+ "tileflow:basemap": basemap.type,
363
+ "tileflow:theme": theme.name,
364
+ "tileflow:themeMode": theme.mode,
169
365
  "tileflow:density": density,
170
- "tileflow:roads": roads,
366
+ "tileflow:roads": roads2,
171
367
  "tileflow:labels": labels,
172
368
  "tileflow:poi": poi,
369
+ "tileflow:buildings": buildings,
370
+ "tileflow:modules": config.modules?.map((moduleConfig) => moduleConfig.type) ?? [],
371
+ "tileflow:terrain": terrain ? {
372
+ encoding: terrain.source.encoding,
373
+ mode: terrain.mode,
374
+ sourceId: terrain.sourceId
375
+ } : void 0,
173
376
  "tileflow:typography": typography,
174
377
  "tileflow:colors": colors,
175
378
  "tileflow:tileBaseUrl": tileBaseUrl,
176
379
  "tileflow:tileset": tileset,
177
- "tileflow:tiles": "tileflow"
380
+ "tileflow:tiles": "tileflow",
381
+ "tileflow:view": config.view
178
382
  }
179
383
  };
180
384
  return style;
@@ -184,25 +388,31 @@ function createStyleFromProject(projectConfig, mapName, options = {}) {
184
388
  if (!config) {
185
389
  throw new Error(`Unknown Tileflow map: ${mapName}`);
186
390
  }
187
- const tilesetName = options.tileset ?? config.tileset ?? config.tiles?.tileset;
391
+ const tilesetName = options.tileset ?? config.tileset ?? config.tiles?.tileset ?? config.basemap?.tileset;
188
392
  const tilesetConfig = tilesetName ? projectConfig.tilesets?.[tilesetName] : void 0;
189
393
  const resolvedTileset = tilesetConfig?.id ?? tilesetName ?? defaultTileset;
394
+ const baseBasemap = config.basemap ?? osm();
395
+ const { sourceLayers: tileSourceLayers, ...tileOptions } = config.tiles ?? {};
396
+ const resolvedTileSourceLayers = baseBasemap.sourceLayers || !tileSourceLayers ? void 0 : resolveSourceLayers(tileSourceLayers, tilesetConfig?.sourceLayers);
190
397
  return createStyle(
191
398
  {
192
399
  ...config,
193
400
  tileset: resolvedTileset,
401
+ basemap: {
402
+ ...baseBasemap,
403
+ tileset: resolvedTileset,
404
+ attribution: baseBasemap.attribution ?? tilesetConfig?.attribution,
405
+ sourceLayers: resolveSourceLayers(baseBasemap.sourceLayers, tilesetConfig?.sourceLayers)
406
+ },
194
407
  tiles: {
195
- ...config.tiles,
408
+ ...tileOptions,
196
409
  tileset: resolvedTileset,
197
- attribution: config.tiles?.attribution ?? tilesetConfig?.attribution,
198
- sourceLayers: resolveSourceLayers(
199
- config.tiles?.sourceLayers,
200
- tilesetConfig?.sourceLayers
201
- )
410
+ ...resolvedTileSourceLayers ? { sourceLayers: resolvedTileSourceLayers } : {}
202
411
  }
203
412
  },
204
413
  {
205
414
  ...options,
415
+ themes: projectConfig.themes,
206
416
  tileset: resolvedTileset
207
417
  }
208
418
  );
@@ -215,35 +425,96 @@ function createManifest(projectConfig, options = {}) {
215
425
  version: 1,
216
426
  tileBaseUrl,
217
427
  styles: Object.fromEntries(
218
- maps.map((mapName) => [
219
- mapName,
220
- `${styleBaseUrl}/styles/${mapName}.json`
221
- ])
428
+ maps.map((mapName) => [mapName, `${styleBaseUrl}/styles/${mapName}.json`])
222
429
  ),
223
430
  maps: Object.fromEntries(
224
- maps.map((mapName) => [
225
- mapName,
226
- `${styleBaseUrl}/styles/${mapName}.json`
227
- ])
431
+ maps.map((mapName) => [mapName, `${styleBaseUrl}/styles/${mapName}.json`])
228
432
  )
229
433
  };
230
434
  }
231
435
  function getDefaultColors() {
232
436
  return defaultColors;
233
437
  }
234
- function resolveColors(overrides = {}) {
438
+ function resolveTheme(theme, projectThemes) {
439
+ if (!theme) {
440
+ return resolveLegacyTheme("standard");
441
+ }
442
+ if (typeof theme === "string") {
443
+ const projectTheme = projectThemes?.[theme];
444
+ if (projectTheme) {
445
+ return resolveThemeConfig(theme, projectTheme);
446
+ }
447
+ if (isLegacyTheme(theme)) {
448
+ return resolveLegacyTheme(theme);
449
+ }
450
+ throw new Error(`Unknown Tileflow theme: ${theme}`);
451
+ }
452
+ return resolveThemeConfig("inline", theme);
453
+ }
454
+ function resolveLegacyTheme(theme) {
455
+ const preset = themePresets[theme];
456
+ return {
457
+ colors: preset.colors,
458
+ mode: theme === "dark" ? "dark" : "light",
459
+ name: theme,
460
+ preset,
461
+ typography: defaultTypography
462
+ };
463
+ }
464
+ function resolveThemeConfig(name, theme) {
465
+ const colors = normalizeColorAliases(theme.colors);
466
+ const preset = theme.mode === "dark" ? themePresets.dark : theme.mode === "light" ? themePresets.light : themePresets.standard;
467
+ return {
468
+ colors: {
469
+ ...preset.colors,
470
+ ...colors
471
+ },
472
+ mode: theme.mode,
473
+ name,
474
+ preset,
475
+ typography: resolveTypography(theme.typography, defaultTypography)
476
+ };
477
+ }
478
+ function isLegacyTheme(value) {
479
+ return value in themePresets;
480
+ }
481
+ function normalizeColorAliases(colors) {
482
+ if (!colors) {
483
+ return {};
484
+ }
485
+ return {
486
+ ...colors,
487
+ canvas: colors.canvas ?? colors.background,
488
+ nature: colors.nature ?? colors.park
489
+ };
490
+ }
491
+ function resolveRoads(legacyRoads, moduleConfig, fallback) {
492
+ if (!moduleConfig) {
493
+ return legacyRoads ?? fallback;
494
+ }
495
+ if (moduleConfig.preset === "minimal" || moduleConfig.density === "clean") {
496
+ return "soft";
497
+ }
498
+ if (moduleConfig.density === "dense") {
499
+ return "detailed";
500
+ }
501
+ return "standard";
502
+ }
503
+ function resolveColors(overrides = {}, defaults = defaultColors) {
235
504
  const colors = {
236
- ...defaultColors,
237
- canvas: overrides.canvas ?? overrides.background ?? defaultColors.canvas,
238
- land: overrides.land ?? defaultColors.land,
239
- water: overrides.water ?? defaultColors.water,
240
- nature: overrides.nature ?? overrides.park ?? defaultColors.nature,
241
- road: overrides.road ?? defaultColors.road,
242
- text: overrides.text ?? defaultColors.text,
243
- accent: overrides.accent ?? defaultColors.accent,
244
- building: overrides.building
505
+ ...defaults,
506
+ canvas: overrides.canvas ?? overrides.background ?? defaults.canvas ?? defaultColors.canvas,
507
+ land: overrides.land ?? defaults.land ?? defaultColors.land,
508
+ water: overrides.water ?? defaults.water ?? defaultColors.water,
509
+ nature: overrides.nature ?? overrides.park ?? defaults.nature ?? defaults.park ?? defaultColors.nature,
510
+ road: overrides.road ?? defaults.road ?? defaultColors.road,
511
+ text: overrides.text ?? defaults.text ?? defaultColors.text,
512
+ accent: overrides.accent ?? defaults.accent ?? defaultColors.accent,
513
+ building: overrides.building ?? defaults.building,
514
+ roadCasing: overrides.roadCasing ?? defaults.roadCasing
245
515
  };
246
- const building = colors.building ?? mix(colors.land, colors.road, 0.46);
516
+ const building = colors.building ?? mix(colors.land, colors.text, 0.08);
517
+ const roadCasing = colors.roadCasing ?? mix(colors.road, colors.text, 0.2);
247
518
  return {
248
519
  canvas: cssColor(colors.canvas),
249
520
  land: cssColor(colors.land),
@@ -253,13 +524,38 @@ function resolveColors(overrides = {}) {
253
524
  text: cssColor(colors.text),
254
525
  accent: cssColor(colors.accent),
255
526
  building: cssColor(building),
256
- roadCasing: mix(colors.road, colors.text, 0.2),
527
+ roadCasing: cssColor(roadCasing),
257
528
  roadMajor: mix(colors.road, colors.accent, 0.16),
258
529
  roadMinor: cssColor(colors.road),
259
530
  textHalo: alpha(colors.canvas, 0.84),
260
531
  textMuted: mix(colors.text, colors.canvas, 0.42)
261
532
  };
262
533
  }
534
+ function resolveTerrain(terrain, tileBaseUrl) {
535
+ if (!terrain || terrain === "none") {
536
+ return void 0;
537
+ }
538
+ const terrainConfig = typeof terrain === "string" ? { mode: terrain } : terrain;
539
+ const mode = terrainConfig.mode ?? "hillshade";
540
+ if (mode === "none") {
541
+ return void 0;
542
+ }
543
+ const sourceId = terrainConfig.sourceId ?? "tileflow-terrain";
544
+ const tileset = terrainConfig.tileset ?? "terrain";
545
+ const url = terrainConfig.url ?? `${tileBaseUrl}/tiles/${tileset}/tiles.json`;
546
+ const encoding = terrainConfig.encoding ?? "terrarium";
547
+ return {
548
+ exaggeration: terrainConfig.exaggeration ?? 1.2,
549
+ mode,
550
+ sourceId,
551
+ source: {
552
+ attribution: terrainConfig.attribution,
553
+ encoding,
554
+ type: "raster-dem",
555
+ url
556
+ }
557
+ };
558
+ }
263
559
  function resolveTypography(overrides = {}, defaults = defaultTypography) {
264
560
  return {
265
561
  ...defaults,
@@ -282,11 +578,6 @@ function normalizeStyleBaseUrl(value) {
282
578
  function normalizeUrl(value) {
283
579
  return value.replace(/\/+$/, "");
284
580
  }
285
- function isProjectConfigLike(input) {
286
- return Boolean(
287
- input && typeof input === "object" && "maps" in input && typeof input.maps === "object"
288
- );
289
- }
290
581
  function textFont(typography) {
291
582
  return [`${typography.font} ${fontWeightLabels[typography.weight]}`];
292
583
  }
@@ -312,34 +603,82 @@ function openMapTilesLayers(source, sourceLayers, colors, options) {
312
603
  }
313
604
  },
314
605
  fillLayer("landuse", source, sourceLayers.landuse, colors.land, 0.42),
315
- fillLayer(
316
- "parks",
317
- source,
318
- sourceLayers.park,
319
- colors.nature,
320
- parkOpacity(options.density)
321
- ),
606
+ fillLayer("parks", source, sourceLayers.park, colors.nature, parkOpacity(options.density)),
322
607
  fillLayer("water", source, sourceLayers.water, colors.water, 1),
323
- ...buildingLayers(source, sourceLayers.building, colors, options.density),
608
+ ...buildingLayers(source, sourceLayers.building, colors, options.density, options.buildings),
324
609
  ...roadLayers(source, sourceLayers.road, colors, options.roads),
325
610
  ...boundaryLayers(source, sourceLayers.boundary, colors),
326
- ...labelLayers(
327
- source,
328
- sourceLayers.place,
329
- colors,
330
- options.labels,
331
- options.typography
332
- ),
333
- ...poiLayers(
334
- source,
335
- sourceLayers.poi,
336
- colors,
337
- options.poi,
338
- options.typography
339
- )
611
+ ...labelLayers(source, sourceLayers.place, colors, options.labels, options.typography),
612
+ ...poiLayers(source, sourceLayers.poi, colors, options.poi, options.typography)
613
+ ];
614
+ }
615
+ function withTerrainLayers(layers, terrain) {
616
+ const hillshadeLayer = {
617
+ id: "terrain-hillshade",
618
+ type: "hillshade",
619
+ source: terrain.sourceId,
620
+ maxzoom: 15,
621
+ paint: {
622
+ "hillshade-accent-color": "rgba(255, 255, 255, 0.18)",
623
+ "hillshade-exaggeration": terrain.mode === "3d" ? 0.24 : 0.42,
624
+ "hillshade-highlight-color": "rgba(255, 255, 255, 0.28)",
625
+ "hillshade-shadow-color": "rgba(38, 44, 50, 0.34)"
626
+ }
627
+ };
628
+ const insertionIndex = layers.findIndex((layer) => layer.id === "water");
629
+ if (insertionIndex === -1) {
630
+ return [hillshadeLayer, ...layers];
631
+ }
632
+ return [
633
+ ...layers.slice(0, insertionIndex + 1),
634
+ hillshadeLayer,
635
+ ...layers.slice(insertionIndex + 1)
340
636
  ];
341
637
  }
342
- function buildingLayers(source, sourceLayer, colors, density) {
638
+ function buildingLayers(source, sourceLayer, colors, density, buildings) {
639
+ if (buildings === "hidden") {
640
+ return [];
641
+ }
642
+ if (buildings === "3d") {
643
+ return [
644
+ {
645
+ id: "buildings-3d",
646
+ type: "fill-extrusion",
647
+ source,
648
+ "source-layer": sourceLayer,
649
+ minzoom: 14,
650
+ filter: ["!=", ["get", "hide_3d"], true],
651
+ paint: {
652
+ "fill-extrusion-base": [
653
+ "case",
654
+ ["has", "render_min_height"],
655
+ ["to-number", ["get", "render_min_height"]],
656
+ ["has", "min_height"],
657
+ ["to-number", ["get", "min_height"]],
658
+ 0
659
+ ],
660
+ "fill-extrusion-color": colors.building,
661
+ "fill-extrusion-height": [
662
+ "interpolate",
663
+ ["linear"],
664
+ ["zoom"],
665
+ 14,
666
+ 0,
667
+ 15,
668
+ [
669
+ "case",
670
+ ["has", "render_height"],
671
+ ["to-number", ["get", "render_height"]],
672
+ ["has", "height"],
673
+ ["to-number", ["get", "height"]],
674
+ 18
675
+ ]
676
+ ],
677
+ "fill-extrusion-opacity": 0.72
678
+ }
679
+ }
680
+ ];
681
+ }
343
682
  if (density === "clean") {
344
683
  return [
345
684
  {
@@ -381,12 +720,12 @@ function buildingLayers(source, sourceLayer, colors, density) {
381
720
  }
382
721
  ];
383
722
  }
384
- function roadLayers(source, sourceLayer, colors, roads) {
385
- if (roads === "hidden") {
723
+ function roadLayers(source, sourceLayer, colors, roads2) {
724
+ if (roads2 === "hidden") {
386
725
  return [];
387
726
  }
388
- const opacity = roads === "soft" ? 0.72 : roads === "standard" ? 0.84 : 0.96;
389
- const casingOpacity = roads === "detailed" ? 0.3 : 0.16;
727
+ const opacity = roads2 === "soft" ? 0.72 : roads2 === "standard" ? 0.84 : 0.96;
728
+ const casingOpacity = roads2 === "detailed" ? 0.3 : 0.16;
390
729
  const majorRoadFilter = [
391
730
  "match",
392
731
  ["get", "class"],
@@ -395,7 +734,22 @@ function roadLayers(source, sourceLayer, colors, roads) {
395
734
  false
396
735
  ];
397
736
  const minorRoadFilter = ["!", majorRoadFilter];
737
+ const nonTunnelRoadFilter = ["!=", ["get", "brunnel"], "tunnel"];
398
738
  return [
739
+ {
740
+ id: "roads-tunnels-casing",
741
+ type: "line",
742
+ source,
743
+ "source-layer": sourceLayer,
744
+ minzoom: 10,
745
+ filter: ["==", ["get", "brunnel"], "tunnel"],
746
+ paint: {
747
+ "line-color": colors.roadCasing,
748
+ "line-dasharray": [0.6, 0.8],
749
+ "line-opacity": 0.28,
750
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.5, 14, 1.7, 16, 3.8]
751
+ }
752
+ },
399
753
  {
400
754
  id: "roads-tunnels",
401
755
  type: "line",
@@ -406,18 +760,8 @@ function roadLayers(source, sourceLayer, colors, roads) {
406
760
  paint: {
407
761
  "line-color": colors.roadMinor,
408
762
  "line-dasharray": [0.6, 0.8],
409
- "line-opacity": opacity * 0.62,
410
- "line-width": [
411
- "interpolate",
412
- ["linear"],
413
- ["zoom"],
414
- 10,
415
- 0.25,
416
- 14,
417
- 1,
418
- 16,
419
- 2.4
420
- ]
763
+ "line-opacity": 0.2,
764
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.25, 14, 1, 16, 2.4]
421
765
  }
422
766
  },
423
767
  {
@@ -426,20 +770,11 @@ function roadLayers(source, sourceLayer, colors, roads) {
426
770
  source,
427
771
  "source-layer": sourceLayer,
428
772
  minzoom: 6,
773
+ filter: nonTunnelRoadFilter,
429
774
  paint: {
430
775
  "line-color": colors.roadCasing,
431
776
  "line-opacity": casingOpacity,
432
- "line-width": [
433
- "interpolate",
434
- ["linear"],
435
- ["zoom"],
436
- 6,
437
- 0.4,
438
- 12,
439
- 1.2,
440
- 16,
441
- 4
442
- ]
777
+ "line-width": ["interpolate", ["linear"], ["zoom"], 6, 0.4, 12, 1.2, 16, 4]
443
778
  }
444
779
  },
445
780
  {
@@ -448,21 +783,11 @@ function roadLayers(source, sourceLayer, colors, roads) {
448
783
  source,
449
784
  "source-layer": sourceLayer,
450
785
  minzoom: 6,
451
- filter: minorRoadFilter,
786
+ filter: ["all", nonTunnelRoadFilter, minorRoadFilter],
452
787
  paint: {
453
788
  "line-color": colors.roadMinor,
454
789
  "line-opacity": opacity * 0.88,
455
- "line-width": [
456
- "interpolate",
457
- ["linear"],
458
- ["zoom"],
459
- 6,
460
- 0.2,
461
- 12,
462
- 0.8,
463
- 16,
464
- 3
465
- ]
790
+ "line-width": ["interpolate", ["linear"], ["zoom"], 6, 0.2, 12, 0.8, 16, 3]
466
791
  }
467
792
  },
468
793
  {
@@ -471,21 +796,11 @@ function roadLayers(source, sourceLayer, colors, roads) {
471
796
  source,
472
797
  "source-layer": sourceLayer,
473
798
  minzoom: 5,
474
- filter: majorRoadFilter,
799
+ filter: ["all", nonTunnelRoadFilter, majorRoadFilter],
475
800
  paint: {
476
801
  "line-color": colors.roadMajor,
477
802
  "line-opacity": opacity,
478
- "line-width": [
479
- "interpolate",
480
- ["linear"],
481
- ["zoom"],
482
- 5,
483
- 0.35,
484
- 12,
485
- 1.2,
486
- 16,
487
- 4
488
- ]
803
+ "line-width": ["interpolate", ["linear"], ["zoom"], 5, 0.35, 12, 1.2, 16, 4]
489
804
  }
490
805
  },
491
806
  {
@@ -498,17 +813,7 @@ function roadLayers(source, sourceLayer, colors, roads) {
498
813
  paint: {
499
814
  "line-color": mix(colors.roadMajor, colors.canvas, 0.14),
500
815
  "line-opacity": opacity,
501
- "line-width": [
502
- "interpolate",
503
- ["linear"],
504
- ["zoom"],
505
- 10,
506
- 0.45,
507
- 14,
508
- 1.6,
509
- 16,
510
- 4.4
511
- ]
816
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.45, 14, 1.6, 16, 4.4]
512
817
  }
513
818
  }
514
819
  ];
@@ -545,17 +850,7 @@ function labelLayers(source, sourceLayer, colors, labels, typography) {
545
850
  layout: {
546
851
  "text-field": ["coalesce", ["get", "name:en"], ["get", "name"]],
547
852
  "text-font": textFont(typography),
548
- "text-size": [
549
- "interpolate",
550
- ["linear"],
551
- ["zoom"],
552
- 3,
553
- 10,
554
- 8,
555
- 13,
556
- 14,
557
- 17
558
- ],
853
+ "text-size": ["interpolate", ["linear"], ["zoom"], 3, 10, 8, 13, 14, 17],
559
854
  "text-letter-spacing": 0,
560
855
  "text-padding": 4
561
856
  },
@@ -648,11 +943,27 @@ function roundAlpha(value) {
648
943
  function rgbToHex(r, g, b) {
649
944
  return `#${[r, g, b].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
650
945
  }
946
+
947
+ // src/config.ts
948
+ function defineTileflow(config) {
949
+ return config;
950
+ }
951
+
952
+ // src/modules.ts
953
+ function roads(options = {}) {
954
+ return {
955
+ type: "roads",
956
+ preset: options.preset ?? "modern",
957
+ ...options.density ? { density: options.density } : {}
958
+ };
959
+ }
651
960
  export {
652
961
  createManifest,
653
962
  createStyle,
654
963
  createStyleFromProject,
655
964
  defineTileflow,
656
965
  getDefaultColors,
966
+ osm,
967
+ roads,
657
968
  validateConfig
658
969
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/core",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.1",
4
4
  "description": "Typed Tileflow config and MapLibre style compiler.",
5
5
  "type": "module",
6
6
  "homepage": "https://tileflow.dev",
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsup src/index.ts --format esm --dts --clean",
42
+ "verify": "tsx scripts/verify.ts",
42
43
  "typecheck": "tsc -p tsconfig.json --noEmit",
43
44
  "lint": "tsc -p tsconfig.json --noEmit"
44
45
  }