@tileflow/core 0.1.0-alpha.43 → 0.1.0-alpha.45
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 +103 -56
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/dist/{3d-CW5Ls0An.d.ts → 3d-EHN80Dhw.d.ts} +16 -1
- package/dist/3d.d.ts +1 -1
- package/dist/3d.js +436 -180
- package/dist/browser.d.ts +180 -9
- package/dist/browser.js +4877 -1372
- package/dist/build.d.ts +15 -5
- package/dist/build.js +1161 -167
- package/dist/config-reference.json +93 -44
- package/dist/{icon-composition-Bbw9umUA.d.ts → icon-composition-Dph-24oC.d.ts} +11 -2
- package/dist/index.d.ts +7 -7
- package/dist/index.js +1020 -486
- package/dist/{manifest-types-CwjHd4qc.d.ts → manifest-types-GeYn0SQM.d.ts} +102 -2
- package/dist/manifest.d.ts +2 -2
- package/dist/manifest.js +13 -1
- package/dist/native-profile.d.ts +4 -0
- package/dist/native.d.ts +8 -1
- package/dist/native.js +66 -7
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +3079 -27
- package/dist/worker/camera-tile-worker.global.js +2 -2
- package/package.json +2 -2
package/dist/runtime.js
CHANGED
|
@@ -86,13 +86,2398 @@ var init_time_of_day = __esm({
|
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
// src/cartography/expression-operators.ts
|
|
90
|
+
function isMapLibreExpressionOperator(value) {
|
|
91
|
+
return typeof value === "string" && mapLibreExpressionOperators.has(value);
|
|
92
|
+
}
|
|
93
|
+
var mapLibreExpressionOperators;
|
|
94
|
+
var init_expression_operators = __esm({
|
|
95
|
+
"src/cartography/expression-operators.ts"() {
|
|
96
|
+
"use strict";
|
|
97
|
+
mapLibreExpressionOperators = /* @__PURE__ */ new Set([
|
|
98
|
+
"!",
|
|
99
|
+
"!=",
|
|
100
|
+
"%",
|
|
101
|
+
"*",
|
|
102
|
+
"+",
|
|
103
|
+
"-",
|
|
104
|
+
"/",
|
|
105
|
+
"<",
|
|
106
|
+
"<=",
|
|
107
|
+
"==",
|
|
108
|
+
">",
|
|
109
|
+
">=",
|
|
110
|
+
"^",
|
|
111
|
+
"abs",
|
|
112
|
+
"accumulated",
|
|
113
|
+
"acos",
|
|
114
|
+
"all",
|
|
115
|
+
"any",
|
|
116
|
+
"array",
|
|
117
|
+
"asin",
|
|
118
|
+
"at",
|
|
119
|
+
"atan",
|
|
120
|
+
"boolean",
|
|
121
|
+
"case",
|
|
122
|
+
"ceil",
|
|
123
|
+
"coalesce",
|
|
124
|
+
"collator",
|
|
125
|
+
"concat",
|
|
126
|
+
"cos",
|
|
127
|
+
"distance",
|
|
128
|
+
"downcase",
|
|
129
|
+
"e",
|
|
130
|
+
"elevation",
|
|
131
|
+
"error",
|
|
132
|
+
"feature-state",
|
|
133
|
+
"filter-<",
|
|
134
|
+
"filter-<=",
|
|
135
|
+
"filter-==",
|
|
136
|
+
"filter->",
|
|
137
|
+
"filter->=",
|
|
138
|
+
"filter-has",
|
|
139
|
+
"filter-has-id",
|
|
140
|
+
"filter-id-<",
|
|
141
|
+
"filter-id-<=",
|
|
142
|
+
"filter-id-==",
|
|
143
|
+
"filter-id->",
|
|
144
|
+
"filter-id->=",
|
|
145
|
+
"filter-id-in",
|
|
146
|
+
"filter-in-large",
|
|
147
|
+
"filter-in-small",
|
|
148
|
+
"filter-type-==",
|
|
149
|
+
"filter-type-in",
|
|
150
|
+
"floor",
|
|
151
|
+
"format",
|
|
152
|
+
"geometry-type",
|
|
153
|
+
"get",
|
|
154
|
+
"global-state",
|
|
155
|
+
"has",
|
|
156
|
+
"heatmap-density",
|
|
157
|
+
"id",
|
|
158
|
+
"image",
|
|
159
|
+
"in",
|
|
160
|
+
"index-of",
|
|
161
|
+
"interpolate",
|
|
162
|
+
"interpolate-hcl",
|
|
163
|
+
"interpolate-lab",
|
|
164
|
+
"is-supported-script",
|
|
165
|
+
"join",
|
|
166
|
+
"length",
|
|
167
|
+
"let",
|
|
168
|
+
"line-progress",
|
|
169
|
+
"literal",
|
|
170
|
+
"ln",
|
|
171
|
+
"ln2",
|
|
172
|
+
"log10",
|
|
173
|
+
"log2",
|
|
174
|
+
"match",
|
|
175
|
+
"max",
|
|
176
|
+
"min",
|
|
177
|
+
"number",
|
|
178
|
+
"number-format",
|
|
179
|
+
"object",
|
|
180
|
+
"pi",
|
|
181
|
+
"properties",
|
|
182
|
+
"resolved-locale",
|
|
183
|
+
"rgb",
|
|
184
|
+
"rgba",
|
|
185
|
+
"round",
|
|
186
|
+
"sin",
|
|
187
|
+
"slice",
|
|
188
|
+
"split",
|
|
189
|
+
"sqrt",
|
|
190
|
+
"step",
|
|
191
|
+
"string",
|
|
192
|
+
"tan",
|
|
193
|
+
"to-boolean",
|
|
194
|
+
"to-color",
|
|
195
|
+
"to-number",
|
|
196
|
+
"to-rgba",
|
|
197
|
+
"to-string",
|
|
198
|
+
"typeof",
|
|
199
|
+
"upcase",
|
|
200
|
+
"var",
|
|
201
|
+
"within",
|
|
202
|
+
"zoom"
|
|
203
|
+
]);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// src/cartography/values.ts
|
|
208
|
+
function isTileflowThemeTokenReference(value) {
|
|
209
|
+
return isRecord(value) && hasExactOwnKeys(value, ["category", "kind", "token"]) && value.kind === "theme-token" && typeof value.token === "string" && /^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)*$/.test(value.token) && tileflowThemeTokenCategories.includes(value.category);
|
|
210
|
+
}
|
|
211
|
+
function isTileflowFixedValue(value) {
|
|
212
|
+
return isRecord(value) && hasExactOwnKeys(value, ["kind", "reason", "value"]) && value.kind === "theme-fixed" && typeof value.reason === "string" && Boolean(value.reason.trim()) && Object.hasOwn(value, "value");
|
|
213
|
+
}
|
|
214
|
+
function isTileflowThemeColorOperation(value) {
|
|
215
|
+
if (!isRecord(value) || value.kind !== "theme-color") return false;
|
|
216
|
+
if (value.operation === "alpha") {
|
|
217
|
+
return hasExactOwnKeys(value, ["color", "kind", "opacity", "operation"]) && Object.hasOwn(value, "color") && Object.hasOwn(value, "opacity");
|
|
218
|
+
}
|
|
219
|
+
return value.operation === "mix" && hasExactOwnKeys(value, ["amount", "from", "kind", "operation", "space", "to"]) && value.space === "oklch" && Object.hasOwn(value, "amount") && Object.hasOwn(value, "from") && Object.hasOwn(value, "to");
|
|
220
|
+
}
|
|
221
|
+
function isRecord(value) {
|
|
222
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
223
|
+
}
|
|
224
|
+
function hasExactOwnKeys(value, expectedKeys) {
|
|
225
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
226
|
+
return ownKeys.length === expectedKeys.length && ownKeys.every((key) => typeof key === "string" && expectedKeys.includes(key));
|
|
227
|
+
}
|
|
228
|
+
var tileflowThemeTokenCategories;
|
|
229
|
+
var init_values = __esm({
|
|
230
|
+
"src/cartography/values.ts"() {
|
|
231
|
+
"use strict";
|
|
232
|
+
tileflowThemeTokenCategories = Object.freeze([
|
|
233
|
+
"color",
|
|
234
|
+
"font",
|
|
235
|
+
"image",
|
|
236
|
+
"number"
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// src/maps/operations.ts
|
|
242
|
+
function isTileflowReset(value) {
|
|
243
|
+
if (!isPlainRecord(value)) return false;
|
|
244
|
+
return value.$tileflow === "reset" && Object.keys(value).length === 1;
|
|
245
|
+
}
|
|
246
|
+
function deepFreeze(value) {
|
|
247
|
+
if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
248
|
+
Object.freeze(value);
|
|
249
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
function isPlainRecord(value) {
|
|
253
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
254
|
+
const prototype = Object.getPrototypeOf(value);
|
|
255
|
+
return prototype === Object.prototype || prototype === null;
|
|
256
|
+
}
|
|
257
|
+
var resetValue, disableValue;
|
|
258
|
+
var init_operations = __esm({
|
|
259
|
+
"src/maps/operations.ts"() {
|
|
260
|
+
"use strict";
|
|
261
|
+
resetValue = deepFreeze({ $tileflow: "reset" });
|
|
262
|
+
disableValue = deepFreeze({ op: "disable" });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// src/theme-blend.ts
|
|
267
|
+
function planTileflowThemeBlend(styles, options = {}) {
|
|
268
|
+
if (styles.length < 2)
|
|
269
|
+
throw new TileflowThemeBlendError("A theme blend needs two or more themes.");
|
|
270
|
+
const [base] = styles;
|
|
271
|
+
assertSameStructure(styles, base);
|
|
272
|
+
const mixedImages = new Set(imageProperties);
|
|
273
|
+
if (options.iconImages === "switch") mixedImages.delete("icon-image");
|
|
274
|
+
const paints = [];
|
|
275
|
+
const switches = [];
|
|
276
|
+
const imageGroups = collectImageNames(styles, base, mixedImages);
|
|
277
|
+
const symbolLayers = /* @__PURE__ */ new Set();
|
|
278
|
+
const images = /* @__PURE__ */ new Map();
|
|
279
|
+
let splitLayers = 0;
|
|
280
|
+
let copiedLayers = 0;
|
|
281
|
+
let switchedLayers = 0;
|
|
282
|
+
const layers = [];
|
|
283
|
+
for (const [index, layer] of base.layers.entries()) {
|
|
284
|
+
const variants = styles.map((style) => style.layers[index]);
|
|
285
|
+
layers.push(...planLayer(variants, String(layer.type)));
|
|
286
|
+
}
|
|
287
|
+
const switchesByLayer = /* @__PURE__ */ new Map();
|
|
288
|
+
for (const entry of switches) {
|
|
289
|
+
const list = switchesByLayer.get(entry.layer) ?? [];
|
|
290
|
+
list.push(entry);
|
|
291
|
+
switchesByLayer.set(entry.layer, list);
|
|
292
|
+
}
|
|
293
|
+
const sky = differingRoot(styles, "sky");
|
|
294
|
+
const light = differingRoot(styles, "light");
|
|
295
|
+
const metadata = alignInteractionManifest(base.metadata, layers);
|
|
296
|
+
return {
|
|
297
|
+
themes: styles.length,
|
|
298
|
+
paints,
|
|
299
|
+
images: [...images.values()],
|
|
300
|
+
switches,
|
|
301
|
+
...sky === void 0 ? {} : { sky },
|
|
302
|
+
...light === void 0 ? {} : { light },
|
|
303
|
+
summary: { splitLayers, copiedLayers, switchedLayers, switchedValues: switches.length },
|
|
304
|
+
styleAt(position) {
|
|
305
|
+
const at = clampPosition(position, styles.length);
|
|
306
|
+
const dominant = Math.round(at);
|
|
307
|
+
const style = {
|
|
308
|
+
...base,
|
|
309
|
+
...metadata === void 0 ? {} : { metadata },
|
|
310
|
+
// Symbols show the nearest theme, as the label cross-fade leaves them between changes.
|
|
311
|
+
layers: layers.map(
|
|
312
|
+
(layer) => fillLayer(
|
|
313
|
+
layer,
|
|
314
|
+
symbolLayers.has(String(layer.id)) ? dominant : at,
|
|
315
|
+
dominant,
|
|
316
|
+
switchesByLayer.get(String(layer.id))
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
};
|
|
320
|
+
if (sky !== void 0) style.sky = fillTemplate(sky, at);
|
|
321
|
+
if (light !== void 0) style.light = fillTemplate(light, at);
|
|
322
|
+
return style;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
function planLayer(variants, type) {
|
|
326
|
+
const [layer] = variants;
|
|
327
|
+
const id = String(layer.id);
|
|
328
|
+
const paint = { ...layer.paint };
|
|
329
|
+
const layout = { ...layer.layout };
|
|
330
|
+
const follows = type === "symbol" ? "dominant" : "position";
|
|
331
|
+
const ownPaints = [];
|
|
332
|
+
const ownSwitches = [];
|
|
333
|
+
const driven = [];
|
|
334
|
+
const paintOf = (variant) => variant.paint ?? {};
|
|
335
|
+
for (const group of ["layout", "paint"]) {
|
|
336
|
+
const target = group === "paint" ? paint : layout;
|
|
337
|
+
for (const property of propertyNames(variants, group)) {
|
|
338
|
+
const values = variants.map(
|
|
339
|
+
(variant) => variant[group]?.[property]
|
|
340
|
+
);
|
|
341
|
+
if (values.every((value) => same2(value, values[0]))) continue;
|
|
342
|
+
if (mixedImages.has(property)) {
|
|
343
|
+
const tuples = imageTuples(values);
|
|
344
|
+
if (tuples && tuples.every((names) => imageGroups.get(names[0])?.size === 1)) {
|
|
345
|
+
for (const names of tuples) {
|
|
346
|
+
if (names.every((name) => name === names[0])) continue;
|
|
347
|
+
images.set(JSON.stringify(names), {
|
|
348
|
+
names,
|
|
349
|
+
follows: property === "icon-image" ? "dominant" : "position"
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (group === "layout" || values.includes(void 0) || imageProperties.has(property)) {
|
|
356
|
+
ownSwitches.push({ group, property, values });
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (values.some(isDataDriven)) {
|
|
360
|
+
driven.push(property);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const template = tryMixTemplate(values);
|
|
364
|
+
if (template === void 0) {
|
|
365
|
+
ownSwitches.push({ group, property, values });
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
ownPaints.push({ property, template });
|
|
369
|
+
target[property] = template;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const planned = { ...layer, paint, layout };
|
|
373
|
+
let results = [{ layer: planned, extra: [] }];
|
|
374
|
+
if (driven.length > 0) {
|
|
375
|
+
const split = type === "symbol" ? void 0 : splitLayer();
|
|
376
|
+
const copies = split ?? (type === "symbol" ? void 0 : copyLayer());
|
|
377
|
+
if (copies) results = copies;
|
|
378
|
+
else
|
|
379
|
+
for (const property of driven)
|
|
380
|
+
ownSwitches.push({
|
|
381
|
+
group: "paint",
|
|
382
|
+
property,
|
|
383
|
+
values: variants.map((variant) => paintOf(variant)[property])
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
if (options.featureSwitches === "layers" && results.length === 1 && ownSwitches.some((entry) => entry.values.some(isDataDriven)))
|
|
387
|
+
results = switchLayer();
|
|
388
|
+
for (const result of results) {
|
|
389
|
+
const resultId = String(result.layer.id);
|
|
390
|
+
if (type === "symbol") symbolLayers.add(resultId);
|
|
391
|
+
const overridden = new Set(result.extra.map((entry) => entry.property));
|
|
392
|
+
for (const entry of [
|
|
393
|
+
...ownPaints.filter((own) => !overridden.has(own.property)),
|
|
394
|
+
...result.extra
|
|
395
|
+
])
|
|
396
|
+
paints.push({ layer: resultId, property: entry.property, template: entry.template, follows });
|
|
397
|
+
if (result.visibility)
|
|
398
|
+
switches.push({
|
|
399
|
+
layer: resultId,
|
|
400
|
+
group: "layout",
|
|
401
|
+
property: "visibility",
|
|
402
|
+
values: result.visibility
|
|
403
|
+
});
|
|
404
|
+
else for (const entry of ownSwitches) switches.push({ layer: resultId, ...entry });
|
|
405
|
+
}
|
|
406
|
+
return results.map((result) => result.layer);
|
|
407
|
+
function switchLayer() {
|
|
408
|
+
switchedLayers += variants.length;
|
|
409
|
+
return variants.map((_, theme) => {
|
|
410
|
+
const copyPaint = { ...paint };
|
|
411
|
+
const copyLayout = { ...layout };
|
|
412
|
+
for (const entry of ownSwitches) {
|
|
413
|
+
const target = entry.group === "paint" ? copyPaint : copyLayout;
|
|
414
|
+
const value = entry.values[theme];
|
|
415
|
+
if (value === void 0) delete target[entry.property];
|
|
416
|
+
else target[entry.property] = value;
|
|
417
|
+
}
|
|
418
|
+
const shown = copyLayout.visibility === "none" ? "none" : "visible";
|
|
419
|
+
return {
|
|
420
|
+
layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint, layout: copyLayout },
|
|
421
|
+
extra: [],
|
|
422
|
+
visibility: variants.map((__, nearest) => nearest === theme ? shown : "none")
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
function splitLayer() {
|
|
427
|
+
if (isLegacyFilter(layer.filter)) return void 0;
|
|
428
|
+
const values = driven.map(
|
|
429
|
+
(property) => [
|
|
430
|
+
property,
|
|
431
|
+
variants.map((variant) => inlineBranchBindings(paintOf(variant)[property]))
|
|
432
|
+
]
|
|
433
|
+
);
|
|
434
|
+
if (values.some(([, entries]) => entries.some((entry) => !entry))) return void 0;
|
|
435
|
+
const branches = splitBranches(
|
|
436
|
+
Object.fromEntries(
|
|
437
|
+
values.map(([property, entries]) => [property, entries.map((entry) => entry.value)])
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
if (!branches) return void 0;
|
|
441
|
+
const groups = /* @__PURE__ */ new Map();
|
|
442
|
+
for (const branch of branches) {
|
|
443
|
+
const key = JSON.stringify(branch.valuesByProperty);
|
|
444
|
+
let group = groups.get(key);
|
|
445
|
+
if (!group) {
|
|
446
|
+
const templates = {};
|
|
447
|
+
for (const [property, values2] of Object.entries(branch.valuesByProperty)) {
|
|
448
|
+
const template = tryMixTemplate(values2);
|
|
449
|
+
if (template === void 0) return void 0;
|
|
450
|
+
templates[property] = template;
|
|
451
|
+
}
|
|
452
|
+
group = { templates, conditions: [] };
|
|
453
|
+
groups.set(key, group);
|
|
454
|
+
}
|
|
455
|
+
group.conditions.push(
|
|
456
|
+
branch.conditions.length === 1 ? branch.conditions[0] : ["all", ...branch.conditions]
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
splitLayers += groups.size;
|
|
460
|
+
return [...groups.values()].map((group, number) => {
|
|
461
|
+
const chosen = group.conditions.length === 1 ? group.conditions[0] : ["any", ...group.conditions];
|
|
462
|
+
return {
|
|
463
|
+
layer: {
|
|
464
|
+
...planned,
|
|
465
|
+
id: `${id}::branch-${number}`,
|
|
466
|
+
filter: layer.filter === void 0 ? chosen : ["all", layer.filter, chosen],
|
|
467
|
+
paint: { ...paint, ...group.templates }
|
|
468
|
+
},
|
|
469
|
+
extra: Object.entries(group.templates).filter(([, template]) => containsLeaf(template)).map(([property, template]) => ({ property, template }))
|
|
470
|
+
};
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function copyLayer() {
|
|
474
|
+
const opacity = opacityProperties[type];
|
|
475
|
+
if (!opacity || variants.some((variant) => isDataDriven(paintOf(variant)[opacity])))
|
|
476
|
+
return void 0;
|
|
477
|
+
copiedLayers += variants.length;
|
|
478
|
+
return variants.map((variant, theme) => {
|
|
479
|
+
const weight = new BlendLeaf(variants.map((_, other) => other === theme ? 1 : 0));
|
|
480
|
+
const template = scaleByWeight(paint[opacity] ?? 1, weight);
|
|
481
|
+
const copyPaint = { ...paint, [opacity]: template };
|
|
482
|
+
for (const property of driven) copyPaint[property] = paintOf(variant)[property];
|
|
483
|
+
return {
|
|
484
|
+
layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint },
|
|
485
|
+
extra: [{ property: opacity, template }]
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function fillLayer(layer, position, dominant, layerSwitches) {
|
|
492
|
+
const filled = { ...layer };
|
|
493
|
+
for (const group of ["layout", "paint"]) {
|
|
494
|
+
const values = layer[group];
|
|
495
|
+
if (!values) continue;
|
|
496
|
+
const out = {};
|
|
497
|
+
for (const [key, value] of Object.entries(values)) out[key] = fillTemplate(value, position);
|
|
498
|
+
for (const entry of layerSwitches ?? []) {
|
|
499
|
+
if (entry.group !== group) continue;
|
|
500
|
+
const value = entry.values[dominant];
|
|
501
|
+
if (value === void 0) delete out[entry.property];
|
|
502
|
+
else out[entry.property] = value;
|
|
503
|
+
}
|
|
504
|
+
if (Object.keys(out).length > 0) filled[group] = out;
|
|
505
|
+
else delete filled[group];
|
|
506
|
+
}
|
|
507
|
+
return filled;
|
|
508
|
+
}
|
|
509
|
+
function fillTemplate(node, position) {
|
|
510
|
+
if (node instanceof BlendLeaf) return mixLeaf(node.values, position);
|
|
511
|
+
if (Array.isArray(node)) return node.map((item) => fillTemplate(item, position));
|
|
512
|
+
if (node && typeof node === "object" && containsLeaf(node)) {
|
|
513
|
+
return Object.fromEntries(
|
|
514
|
+
Object.entries(node).map(([key, value]) => [key, fillTemplate(value, position)])
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
return node;
|
|
518
|
+
}
|
|
519
|
+
function containsLeaf(node) {
|
|
520
|
+
if (node instanceof BlendLeaf) return true;
|
|
521
|
+
if (Array.isArray(node)) return node.some(containsLeaf);
|
|
522
|
+
if (node && typeof node === "object") return Object.values(node).some(containsLeaf);
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
function mixLeaf(values, position) {
|
|
526
|
+
const at = clampPosition(position, values.length);
|
|
527
|
+
const index = Math.min(values.length - 2, Math.floor(at));
|
|
528
|
+
const t = at - index;
|
|
529
|
+
const from = values[index];
|
|
530
|
+
const to = values[index + 1];
|
|
531
|
+
if (t <= 0 || from === to) return from;
|
|
532
|
+
if (t >= 1) return to;
|
|
533
|
+
if (typeof from === "number" && typeof to === "number") return from + (to - from) * t;
|
|
534
|
+
const a = oklab(String(from));
|
|
535
|
+
const b = oklab(String(to));
|
|
536
|
+
if (!a || !b) return t < 0.5 ? from : to;
|
|
537
|
+
return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
|
|
538
|
+
}
|
|
539
|
+
function clampPosition(position, themes) {
|
|
540
|
+
if (!Number.isFinite(position)) return 0;
|
|
541
|
+
return Math.min(themes - 1, Math.max(0, position));
|
|
542
|
+
}
|
|
543
|
+
function assertSameStructure(styles, base) {
|
|
544
|
+
const ids = (style) => style.layers.map((layer) => layer.id);
|
|
545
|
+
for (const style of styles) {
|
|
546
|
+
if (!same2(ids(style), ids(base)))
|
|
547
|
+
throw new TileflowThemeBlendError(
|
|
548
|
+
"Blended themes must have the same layers in the same order."
|
|
549
|
+
);
|
|
550
|
+
if (!same2(style.sources, base.sources))
|
|
551
|
+
throw new TileflowThemeBlendError("Blended themes must use the same sources.");
|
|
552
|
+
if (!same2(style.sprite, base.sprite) || !same2(style.glyphs, base.glyphs))
|
|
553
|
+
throw new TileflowThemeBlendError("Blended themes must use the same sprite and glyphs.");
|
|
554
|
+
for (const key of ["projection", "terrain"])
|
|
555
|
+
if (!same2(style[key], base[key]))
|
|
556
|
+
throw new TileflowThemeBlendError(`Blended themes must use the same ${key}.`);
|
|
557
|
+
for (const [index, layer] of style.layers.entries()) {
|
|
558
|
+
const other = base.layers[index];
|
|
559
|
+
for (const key of ["type", "source", "source-layer", "filter", "minzoom", "maxzoom"])
|
|
560
|
+
if (!same2(layer[key], other[key]))
|
|
561
|
+
throw new TileflowThemeBlendError(
|
|
562
|
+
`Blended themes differ in ${key} of layer ${JSON.stringify(layer.id)}.`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function propertyNames(variants, group) {
|
|
568
|
+
const names = /* @__PURE__ */ new Set();
|
|
569
|
+
for (const variant of variants)
|
|
570
|
+
for (const name of Object.keys(variant[group] ?? {})) names.add(name);
|
|
571
|
+
return [...names];
|
|
572
|
+
}
|
|
573
|
+
function differingRoot(styles, key) {
|
|
574
|
+
const values = styles.map((style) => style[key]);
|
|
575
|
+
if (values.every((value) => same2(value, values[0]))) return void 0;
|
|
576
|
+
return tryMixTemplate(values);
|
|
577
|
+
}
|
|
578
|
+
function collectImageNames(styles, base, properties) {
|
|
579
|
+
const uses = /* @__PURE__ */ new Map();
|
|
580
|
+
for (const [index] of base.layers.entries()) {
|
|
581
|
+
for (const group of ["layout", "paint"]) {
|
|
582
|
+
for (const property of properties) {
|
|
583
|
+
const values = styles.map(
|
|
584
|
+
(style) => style.layers[index]?.[group]?.[property]
|
|
585
|
+
);
|
|
586
|
+
if (values[0] === void 0) continue;
|
|
587
|
+
for (const names of imageTuples(values) ?? []) {
|
|
588
|
+
const set = uses.get(names[0]) ?? /* @__PURE__ */ new Set();
|
|
589
|
+
set.add(JSON.stringify(names));
|
|
590
|
+
uses.set(names[0], set);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return uses;
|
|
596
|
+
}
|
|
597
|
+
function alignInteractionManifest(metadata, layers) {
|
|
598
|
+
if (!isRecord2(metadata)) return void 0;
|
|
599
|
+
const planned = /* @__PURE__ */ new Map();
|
|
600
|
+
for (const [index, layer] of layers.entries()) {
|
|
601
|
+
const id = String(layer.id);
|
|
602
|
+
const original = /^(.+)::(?:branch|theme)-\d+$/u.exec(id)?.[1] ?? id;
|
|
603
|
+
planned.set(original, [...planned.get(original) ?? [], { id, index }]);
|
|
604
|
+
}
|
|
605
|
+
let aligned;
|
|
606
|
+
const interactions = metadata["tileflow:interaction-manifest"];
|
|
607
|
+
const domains = isRecord2(interactions) ? interactions.domains : void 0;
|
|
608
|
+
if (isRecord2(interactions) && isRecord2(domains) && isRecord2(domains.poi)) {
|
|
609
|
+
const poi = domains.poi;
|
|
610
|
+
if (Array.isArray(poi.layers)) {
|
|
611
|
+
const poiLayers = poi.layers.flatMap((entry) => {
|
|
612
|
+
if (!isRecord2(entry) || typeof entry.layerId !== "string") return [entry];
|
|
613
|
+
const copies = planned.get(entry.layerId);
|
|
614
|
+
if (!copies) return [entry];
|
|
615
|
+
return copies.map(({ id, index }) => ({ ...entry, layerId: id, priority: index }));
|
|
616
|
+
});
|
|
617
|
+
aligned = {
|
|
618
|
+
...metadata,
|
|
619
|
+
"tileflow:interaction-manifest": {
|
|
620
|
+
...interactions,
|
|
621
|
+
domains: { ...domains, poi: { ...poi, layers: poiLayers } }
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const overlays = metadata["tileflow:overlay-placement-manifest"];
|
|
627
|
+
if (isRecord2(overlays) && isRecord2(overlays.anchors)) {
|
|
628
|
+
const anchors = Object.fromEntries(
|
|
629
|
+
Object.entries(overlays.anchors).map(([placement, anchor]) => [
|
|
630
|
+
placement,
|
|
631
|
+
typeof anchor === "string" ? planned.get(anchor)?.[0]?.id ?? anchor : anchor
|
|
632
|
+
])
|
|
633
|
+
);
|
|
634
|
+
aligned = {
|
|
635
|
+
...aligned ?? metadata,
|
|
636
|
+
"tileflow:overlay-placement-manifest": { ...overlays, anchors }
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
return aligned;
|
|
640
|
+
}
|
|
641
|
+
function imageTuples(values, out = []) {
|
|
642
|
+
const [first] = values;
|
|
643
|
+
if (values.every((value) => typeof value === "string")) {
|
|
644
|
+
out.push(values);
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
647
|
+
if (!Array.isArray(first) || typeof first[0] !== "string") {
|
|
648
|
+
return values.every((value) => same2(value, first)) ? out : void 0;
|
|
649
|
+
}
|
|
650
|
+
const operator = first[0];
|
|
651
|
+
if (!values.every(
|
|
652
|
+
(value) => Array.isArray(value) && value[0] === operator && value.length === first.length
|
|
653
|
+
)) {
|
|
654
|
+
return void 0;
|
|
655
|
+
}
|
|
656
|
+
const at = (index) => values.map((value) => value[index]);
|
|
657
|
+
const sameAt = (index) => at(index).every((value) => same2(value, first[index]));
|
|
658
|
+
const outputs = [];
|
|
659
|
+
if (operator === "literal" || operator === "image") outputs.push(1);
|
|
660
|
+
else if (operator === "coalesce")
|
|
661
|
+
for (let index = 1; index < first.length; index += 1) outputs.push(index);
|
|
662
|
+
else if (operator === "match") {
|
|
663
|
+
if (!sameAt(1)) return void 0;
|
|
664
|
+
for (let index = 2; index < first.length - 1; index += 2) {
|
|
665
|
+
if (!sameAt(index)) return void 0;
|
|
666
|
+
outputs.push(index + 1);
|
|
667
|
+
}
|
|
668
|
+
outputs.push(first.length - 1);
|
|
669
|
+
} else if (operator === "case") {
|
|
670
|
+
for (let index = 1; index < first.length - 1; index += 2) {
|
|
671
|
+
if (!sameAt(index)) return void 0;
|
|
672
|
+
outputs.push(index + 1);
|
|
673
|
+
}
|
|
674
|
+
outputs.push(first.length - 1);
|
|
675
|
+
} else if (operator === "step") {
|
|
676
|
+
if (!sameAt(1)) return void 0;
|
|
677
|
+
outputs.push(2);
|
|
678
|
+
for (let index = 3; index < first.length; index += 2) {
|
|
679
|
+
if (!sameAt(index)) return void 0;
|
|
680
|
+
outputs.push(index + 1);
|
|
681
|
+
}
|
|
682
|
+
} else {
|
|
683
|
+
return values.every((value) => same2(value, first)) ? out : void 0;
|
|
684
|
+
}
|
|
685
|
+
for (const index of outputs) if (!imageTuples(at(index), out)) return void 0;
|
|
686
|
+
return out;
|
|
687
|
+
}
|
|
688
|
+
function tryMixTemplate(values) {
|
|
689
|
+
const [first] = values;
|
|
690
|
+
if (values.every((value) => same2(value, first))) return first;
|
|
691
|
+
if (Array.isArray(first) && first[0] !== "literal" && values.every((value) => Array.isArray(value) && value.length === first.length)) {
|
|
692
|
+
const items = [];
|
|
693
|
+
for (let index = 0; index < first.length; index += 1) {
|
|
694
|
+
const item = tryMixTemplate(values.map((value) => value[index]));
|
|
695
|
+
if (item === void 0 && first[index] !== void 0) return void 0;
|
|
696
|
+
items.push(item);
|
|
697
|
+
}
|
|
698
|
+
return items;
|
|
699
|
+
}
|
|
700
|
+
if (first && typeof first === "object" && !Array.isArray(first)) {
|
|
701
|
+
const keys = Object.keys(first);
|
|
702
|
+
if (!values.every((value) => value && typeof value === "object" && same2(Object.keys(value), keys)))
|
|
703
|
+
return void 0;
|
|
704
|
+
const out = {};
|
|
705
|
+
for (const key of keys) {
|
|
706
|
+
const item = tryMixTemplate(values.map((value) => value[key]));
|
|
707
|
+
if (item === void 0) return void 0;
|
|
708
|
+
out[key] = item;
|
|
709
|
+
}
|
|
710
|
+
return out;
|
|
711
|
+
}
|
|
712
|
+
if (values.every((value) => typeof value === "number")) return new BlendLeaf(values);
|
|
713
|
+
if (values.every((value) => typeof value === "string" && oklab(value)))
|
|
714
|
+
return new BlendLeaf(values);
|
|
715
|
+
return void 0;
|
|
716
|
+
}
|
|
717
|
+
function isDataDriven(value) {
|
|
718
|
+
if (!Array.isArray(value)) return false;
|
|
719
|
+
if (typeof value[0] === "string" && featureOperators.has(value[0])) return true;
|
|
720
|
+
return value.some(isDataDriven);
|
|
721
|
+
}
|
|
722
|
+
function decisionOf(node) {
|
|
723
|
+
if (!Array.isArray(node) || typeof node[0] !== "string") return void 0;
|
|
724
|
+
const [operator] = node;
|
|
725
|
+
if (operator === "match" && isDataDriven(node[1])) {
|
|
726
|
+
const labels = [];
|
|
727
|
+
const outputs = [];
|
|
728
|
+
for (let index = 2; index < node.length - 1; index += 2) {
|
|
729
|
+
labels.push(node[index]);
|
|
730
|
+
outputs.push(node[index + 1]);
|
|
731
|
+
}
|
|
732
|
+
outputs.push(node.at(-1));
|
|
733
|
+
return {
|
|
734
|
+
index: ["match", node[1], ...labels.flatMap((label, index) => [label, index]), labels.length],
|
|
735
|
+
outputs
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
if (operator === "case") {
|
|
739
|
+
const conditions = [];
|
|
740
|
+
const outputs = [];
|
|
741
|
+
for (let index = 1; index < node.length - 1; index += 2) {
|
|
742
|
+
conditions.push(node[index]);
|
|
743
|
+
outputs.push(node[index + 1]);
|
|
744
|
+
}
|
|
745
|
+
outputs.push(node.at(-1));
|
|
746
|
+
if (!conditions.some(isDataDriven)) return void 0;
|
|
747
|
+
return {
|
|
748
|
+
index: [
|
|
749
|
+
"case",
|
|
750
|
+
...conditions.flatMap((condition, index) => [condition, index]),
|
|
751
|
+
conditions.length
|
|
752
|
+
],
|
|
753
|
+
outputs
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
if (operator === "step" && isDataDriven(node[1])) {
|
|
757
|
+
const outputs = [node[2]];
|
|
758
|
+
const stops = [];
|
|
759
|
+
for (let index = 3; index < node.length; index += 2) {
|
|
760
|
+
stops.push(node[index]);
|
|
761
|
+
outputs.push(node[index + 1]);
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
index: ["step", node[1], 0, ...stops.flatMap((stop, index) => [stop, index + 1])],
|
|
765
|
+
outputs
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return void 0;
|
|
769
|
+
}
|
|
770
|
+
function firstDecision(value) {
|
|
771
|
+
const decision = decisionOf(value);
|
|
772
|
+
if (decision) return decision;
|
|
773
|
+
if (!Array.isArray(value) || value[0] === "literal") return void 0;
|
|
774
|
+
for (const item of value) {
|
|
775
|
+
const found = firstDecision(item);
|
|
776
|
+
if (found) return found;
|
|
777
|
+
}
|
|
778
|
+
return void 0;
|
|
779
|
+
}
|
|
780
|
+
function choose(value, key, branch) {
|
|
781
|
+
const decision = decisionOf(value);
|
|
782
|
+
if (decision && JSON.stringify(decision.index) === key)
|
|
783
|
+
return choose(decision.outputs[branch], key, branch);
|
|
784
|
+
if (!Array.isArray(value) || value[0] === "literal") return value;
|
|
785
|
+
return value.map((item) => choose(item, key, branch));
|
|
786
|
+
}
|
|
787
|
+
function splitBranches(valuesByProperty, conditions = []) {
|
|
788
|
+
let decision;
|
|
789
|
+
for (const values of Object.values(valuesByProperty)) {
|
|
790
|
+
decision = firstDecision(values[0]);
|
|
791
|
+
if (decision) break;
|
|
792
|
+
}
|
|
793
|
+
if (!decision) {
|
|
794
|
+
const residual = Object.values(valuesByProperty).some((values) => values.some(isDataDriven));
|
|
795
|
+
return residual ? void 0 : [{ conditions, valuesByProperty }];
|
|
796
|
+
}
|
|
797
|
+
if (reads(decision.index, "zoom") || reads(decision.index, "feature-state")) return void 0;
|
|
798
|
+
const key = JSON.stringify(decision.index);
|
|
799
|
+
const branches = [];
|
|
800
|
+
for (let branch = 0; branch < decision.outputs.length; branch += 1) {
|
|
801
|
+
const chosen = Object.fromEntries(
|
|
802
|
+
Object.entries(valuesByProperty).map(([property, values]) => [
|
|
803
|
+
property,
|
|
804
|
+
values.map((value) => choose(value, key, branch))
|
|
805
|
+
])
|
|
806
|
+
);
|
|
807
|
+
const nested = splitBranches(chosen, [...conditions, ["==", decision.index, branch]]);
|
|
808
|
+
if (!nested) return void 0;
|
|
809
|
+
branches.push(...nested);
|
|
810
|
+
if (branches.length > maximumBranches) return void 0;
|
|
811
|
+
}
|
|
812
|
+
return branches;
|
|
813
|
+
}
|
|
814
|
+
function inlineBranchBindings(input) {
|
|
815
|
+
const limit = 2e4;
|
|
816
|
+
const visit = (node, scope, depth) => {
|
|
817
|
+
if (depth > 96) throw new RangeError("Expression nesting limit");
|
|
818
|
+
if (!Array.isArray(node) || node[0] === "literal" || isColorProgramMarker(node))
|
|
819
|
+
return { value: node, size: 1 };
|
|
820
|
+
if (node[0] === "var") {
|
|
821
|
+
const bound = scope.get(String(node[1]));
|
|
822
|
+
if (!bound) throw new TypeError("Unbound branch variable");
|
|
823
|
+
return bound;
|
|
824
|
+
}
|
|
825
|
+
if (node[0] === "let") {
|
|
826
|
+
const inner = new Map(scope);
|
|
827
|
+
for (let index = 1; index < node.length - 1; index += 2)
|
|
828
|
+
inner.set(String(node[index]), visit(node[index + 1], scope, depth + 1));
|
|
829
|
+
return visit(node.at(-1), inner, depth + 1);
|
|
830
|
+
}
|
|
831
|
+
let size = 1;
|
|
832
|
+
const value = node.map((child) => {
|
|
833
|
+
const result = visit(child, scope, depth + 1);
|
|
834
|
+
size += result.size;
|
|
835
|
+
if (size > limit) throw new RangeError("Expanded branch limit");
|
|
836
|
+
return result.value;
|
|
837
|
+
});
|
|
838
|
+
return { value, size };
|
|
839
|
+
};
|
|
840
|
+
try {
|
|
841
|
+
return { value: visit(input, /* @__PURE__ */ new Map(), 0).value };
|
|
842
|
+
} catch {
|
|
843
|
+
return void 0;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function scaleByWeight(opacity, weight) {
|
|
847
|
+
const input = Array.isArray(opacity) ? opacity[0] === "interpolate" ? opacity[2] : opacity[1] : void 0;
|
|
848
|
+
if (Array.isArray(opacity) && (opacity[0] === "interpolate" || opacity[0] === "step") && reads(input, "zoom")) {
|
|
849
|
+
const head = opacity[0] === "interpolate" ? 3 : 2;
|
|
850
|
+
return opacity.map(
|
|
851
|
+
(item, index) => index >= head && (index - head) % 2 === (opacity[0] === "interpolate" ? 1 : 0) ? scaleByWeight(item, weight) : item
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
if (typeof opacity === "number")
|
|
855
|
+
return new BlendLeaf(weight.values.map((value) => opacity * Number(value)));
|
|
856
|
+
return ["*", opacity, weight];
|
|
857
|
+
}
|
|
858
|
+
function same2(a, b) {
|
|
859
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
860
|
+
}
|
|
861
|
+
function oklab(colour) {
|
|
862
|
+
const cached = oklabCache.get(colour);
|
|
863
|
+
if (cached !== void 0) return cached;
|
|
864
|
+
const rgba = parseColour(colour);
|
|
865
|
+
let result = null;
|
|
866
|
+
if (rgba) {
|
|
867
|
+
const [r, g, b] = rgba.slice(0, 3).map((channel) => {
|
|
868
|
+
const value = channel / 255;
|
|
869
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
870
|
+
});
|
|
871
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
872
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
873
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
874
|
+
result = [
|
|
875
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
876
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
877
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
|
878
|
+
rgba[3]
|
|
879
|
+
];
|
|
880
|
+
}
|
|
881
|
+
if (oklabCache.size > 4096) oklabCache.clear();
|
|
882
|
+
oklabCache.set(colour, result);
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
885
|
+
function colourFromOklab([lightness, a, b, alpha]) {
|
|
886
|
+
const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
887
|
+
const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
888
|
+
const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
889
|
+
const [red, green, blue] = [
|
|
890
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
891
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
892
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
893
|
+
].map((value) => {
|
|
894
|
+
const clamped = Math.min(1, Math.max(0, value));
|
|
895
|
+
const encoded = clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
|
|
896
|
+
return Math.round(encoded * 255);
|
|
897
|
+
});
|
|
898
|
+
return `rgba(${red}, ${green}, ${blue}, ${Number(alpha.toFixed(3))})`;
|
|
899
|
+
}
|
|
900
|
+
function parseColour(colour) {
|
|
901
|
+
const text = colour.trim().toLowerCase();
|
|
902
|
+
const hex = /^#([\da-f]{3,8})$/u.exec(text)?.[1];
|
|
903
|
+
if (hex && [3, 4, 6, 8].includes(hex.length)) {
|
|
904
|
+
const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
|
|
905
|
+
const channels = full.match(/../gu).map((pair) => Number.parseInt(pair, 16));
|
|
906
|
+
return [channels[0], channels[1], channels[2], channels.length > 3 ? channels[3] / 255 : 1];
|
|
907
|
+
}
|
|
908
|
+
const functional = /^(rgba?|hsla?)\(([^)]+)\)$/u.exec(text);
|
|
909
|
+
if (!functional) return null;
|
|
910
|
+
const parts = functional[2].split(/[\s,/]+/u).filter(Boolean);
|
|
911
|
+
if (parts.length < 3 || parts.length > 4) return null;
|
|
912
|
+
const alpha = parts[3] === void 0 ? 1 : parseUnit(parts[3], 1);
|
|
913
|
+
if (functional[1].startsWith("rgb")) {
|
|
914
|
+
const [r, g, b] = parts.slice(0, 3).map((part) => parseUnit(part, 255));
|
|
915
|
+
if ([r, g, b, alpha].some((value) => value === null)) return null;
|
|
916
|
+
return [r, g, b, alpha];
|
|
917
|
+
}
|
|
918
|
+
const hue = Number.parseFloat(parts[0]);
|
|
919
|
+
const saturation = parseUnit(parts[1], 1);
|
|
920
|
+
const light = parseUnit(parts[2], 1);
|
|
921
|
+
if (!Number.isFinite(hue) || saturation === null || light === null || alpha === null) return null;
|
|
922
|
+
const chroma = (1 - Math.abs(2 * light - 1)) * saturation;
|
|
923
|
+
const sector = (hue % 360 + 360) % 360 / 60;
|
|
924
|
+
const x = chroma * (1 - Math.abs(sector % 2 - 1));
|
|
925
|
+
const [r1, g1, b1] = sector < 1 ? [chroma, x, 0] : sector < 2 ? [x, chroma, 0] : sector < 3 ? [0, chroma, x] : sector < 4 ? [0, x, chroma] : sector < 5 ? [x, 0, chroma] : [chroma, 0, x];
|
|
926
|
+
const offset = light - chroma / 2;
|
|
927
|
+
return [(r1 + offset) * 255, (g1 + offset) * 255, (b1 + offset) * 255, alpha];
|
|
928
|
+
}
|
|
929
|
+
function parseUnit(part, scale) {
|
|
930
|
+
const percentage = part.endsWith("%");
|
|
931
|
+
const value = Number.parseFloat(part);
|
|
932
|
+
if (!Number.isFinite(value)) return null;
|
|
933
|
+
return percentage ? value / 100 * scale : value;
|
|
934
|
+
}
|
|
935
|
+
var TileflowThemeBlendError, BlendLeaf, imageProperties, opacityProperties, featureOperators, maximumBranches, isRecord2, reads, isLegacyFilter, oklabCache;
|
|
936
|
+
var init_theme_blend = __esm({
|
|
937
|
+
"src/theme-blend.ts"() {
|
|
938
|
+
"use strict";
|
|
939
|
+
init_color_program();
|
|
940
|
+
TileflowThemeBlendError = class extends Error {
|
|
941
|
+
name = "TileflowThemeBlendError";
|
|
942
|
+
};
|
|
943
|
+
BlendLeaf = class {
|
|
944
|
+
constructor(values) {
|
|
945
|
+
this.values = values;
|
|
946
|
+
}
|
|
947
|
+
values;
|
|
948
|
+
};
|
|
949
|
+
imageProperties = /* @__PURE__ */ new Set([
|
|
950
|
+
"icon-image",
|
|
951
|
+
"fill-pattern",
|
|
952
|
+
"line-pattern",
|
|
953
|
+
"background-pattern",
|
|
954
|
+
"fill-extrusion-pattern"
|
|
955
|
+
]);
|
|
956
|
+
opacityProperties = {
|
|
957
|
+
background: "background-opacity",
|
|
958
|
+
circle: "circle-opacity",
|
|
959
|
+
fill: "fill-opacity",
|
|
960
|
+
"fill-extrusion": "fill-extrusion-opacity",
|
|
961
|
+
heatmap: "heatmap-opacity",
|
|
962
|
+
line: "line-opacity",
|
|
963
|
+
raster: "raster-opacity"
|
|
964
|
+
};
|
|
965
|
+
featureOperators = /* @__PURE__ */ new Set([
|
|
966
|
+
"feature-state",
|
|
967
|
+
"geometry-type",
|
|
968
|
+
"get",
|
|
969
|
+
"has",
|
|
970
|
+
"id",
|
|
971
|
+
"in",
|
|
972
|
+
"properties"
|
|
973
|
+
]);
|
|
974
|
+
maximumBranches = 24;
|
|
975
|
+
isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
976
|
+
reads = (value, operator) => Array.isArray(value) && (value[0] === operator || value.some((item) => reads(item, operator)));
|
|
977
|
+
isLegacyFilter = (filter) => Array.isArray(filter) && typeof filter[1] === "string" && !Array.isArray(filter[1]);
|
|
978
|
+
oklabCache = /* @__PURE__ */ new Map();
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
// src/sun.ts
|
|
983
|
+
function normalizeTileflowAzimuth(azimuth) {
|
|
984
|
+
const turned = azimuth % 360;
|
|
985
|
+
return turned < 0 ? turned + 360 : turned === 0 ? 0 : turned;
|
|
986
|
+
}
|
|
987
|
+
function tileflowSunLight(direction, base) {
|
|
988
|
+
const radial = base?.position?.[0] ?? 1.15;
|
|
989
|
+
return {
|
|
990
|
+
...base ?? {},
|
|
991
|
+
anchor: "map",
|
|
992
|
+
position: [
|
|
993
|
+
radial,
|
|
994
|
+
Number(normalizeTileflowAzimuth(direction.azimuth).toFixed(3)),
|
|
995
|
+
// MapLibre lights the extrusions from above the horizon only.
|
|
996
|
+
Number(Math.min(90, Math.max(0, 90 - direction.altitude)).toFixed(3))
|
|
997
|
+
]
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
var tileflowDefaultSunPreset, degrees;
|
|
1001
|
+
var init_sun = __esm({
|
|
1002
|
+
"src/sun.ts"() {
|
|
1003
|
+
"use strict";
|
|
1004
|
+
tileflowDefaultSunPreset = Object.freeze({
|
|
1005
|
+
azimuth: 210,
|
|
1006
|
+
altitude: 60,
|
|
1007
|
+
color: "#ffffff",
|
|
1008
|
+
intensity: 0.2,
|
|
1009
|
+
shadowIntensity: 1,
|
|
1010
|
+
ambient: Object.freeze({ color: "#ffffff", intensity: 0.8 })
|
|
1011
|
+
});
|
|
1012
|
+
degrees = Math.PI / 180;
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
// src/themes/visual-semantics.ts
|
|
1017
|
+
function isTileflowVisualColorCollection(property) {
|
|
1018
|
+
return property === "colors" || property.endsWith("Colors") || property.endsWith("-colors");
|
|
1019
|
+
}
|
|
1020
|
+
function classifyTileflowVisualProperty(property, _value) {
|
|
1021
|
+
if (property === "color" || property.endsWith("Color") || property.endsWith("-color") || isTileflowVisualColorCollection(property)) {
|
|
1022
|
+
return "color";
|
|
1023
|
+
}
|
|
1024
|
+
if (property === "font" || property === "fallbacks" || property.endsWith("-font")) return "font";
|
|
1025
|
+
if (property === "image" || property === "pattern" || property.endsWith("-image") || property.endsWith("-pattern")) {
|
|
1026
|
+
return "image";
|
|
1027
|
+
}
|
|
1028
|
+
return visualNumberProperties.has(property) ? "number" : void 0;
|
|
1029
|
+
}
|
|
1030
|
+
var visualNumberProperties;
|
|
1031
|
+
var init_visual_semantics = __esm({
|
|
1032
|
+
"src/themes/visual-semantics.ts"() {
|
|
1033
|
+
"use strict";
|
|
1034
|
+
visualNumberProperties = /* @__PURE__ */ new Set([
|
|
1035
|
+
"angle",
|
|
1036
|
+
"base",
|
|
1037
|
+
"blur",
|
|
1038
|
+
"brightnessScale",
|
|
1039
|
+
"crownScale",
|
|
1040
|
+
"crownAnchor",
|
|
1041
|
+
"sizeAnchor",
|
|
1042
|
+
"sizeCompression",
|
|
1043
|
+
"contrast",
|
|
1044
|
+
"dash",
|
|
1045
|
+
"exaggeration",
|
|
1046
|
+
"emissiveStrength",
|
|
1047
|
+
"emissiveVariation",
|
|
1048
|
+
"emissiveHeight",
|
|
1049
|
+
"colorMix",
|
|
1050
|
+
"colorScale",
|
|
1051
|
+
"metallic",
|
|
1052
|
+
"roughness",
|
|
1053
|
+
"horizonBlend",
|
|
1054
|
+
"hue",
|
|
1055
|
+
"saturation",
|
|
1056
|
+
"lightness",
|
|
1057
|
+
"fadeDuration",
|
|
1058
|
+
"gapWidth",
|
|
1059
|
+
"groundOcclusion",
|
|
1060
|
+
"groundOcclusionRadius",
|
|
1061
|
+
"haloBlur",
|
|
1062
|
+
"haloWidth",
|
|
1063
|
+
"height",
|
|
1064
|
+
"heightScale",
|
|
1065
|
+
"illuminationDirection",
|
|
1066
|
+
"intensity",
|
|
1067
|
+
"letterSpacing",
|
|
1068
|
+
"lineHeight",
|
|
1069
|
+
"maxAngle",
|
|
1070
|
+
"maxWidth",
|
|
1071
|
+
"miterLimit",
|
|
1072
|
+
"modelOcclusion",
|
|
1073
|
+
"offset",
|
|
1074
|
+
"opacity",
|
|
1075
|
+
"padding",
|
|
1076
|
+
"patternWidths",
|
|
1077
|
+
"radius",
|
|
1078
|
+
"radialOffset",
|
|
1079
|
+
"rotate",
|
|
1080
|
+
"roundLimit",
|
|
1081
|
+
"size",
|
|
1082
|
+
"spacing",
|
|
1083
|
+
"starIntensity",
|
|
1084
|
+
"strokeOpacity",
|
|
1085
|
+
"strokeWidth",
|
|
1086
|
+
"textFitPadding",
|
|
1087
|
+
"translate",
|
|
1088
|
+
"width",
|
|
1089
|
+
"widthScale",
|
|
1090
|
+
"background-emissive-strength",
|
|
1091
|
+
"background-opacity",
|
|
1092
|
+
"circle-blur",
|
|
1093
|
+
"circle-emissive-strength",
|
|
1094
|
+
"circle-opacity",
|
|
1095
|
+
"circle-radius",
|
|
1096
|
+
"circle-stroke-opacity",
|
|
1097
|
+
"circle-stroke-width",
|
|
1098
|
+
"circle-translate",
|
|
1099
|
+
"color-relief-opacity",
|
|
1100
|
+
"fill-emissive-strength",
|
|
1101
|
+
"fill-extrusion-ambient-occlusion-ground-attenuation",
|
|
1102
|
+
"fill-extrusion-ambient-occlusion-ground-radius",
|
|
1103
|
+
"fill-extrusion-ambient-occlusion-intensity",
|
|
1104
|
+
"fill-extrusion-ambient-occlusion-radius",
|
|
1105
|
+
"fill-extrusion-base",
|
|
1106
|
+
"fill-extrusion-emissive-strength",
|
|
1107
|
+
"fill-extrusion-flood-light-ground-attenuation",
|
|
1108
|
+
"fill-extrusion-flood-light-ground-radius",
|
|
1109
|
+
"fill-extrusion-flood-light-intensity",
|
|
1110
|
+
"fill-extrusion-flood-light-wall-radius",
|
|
1111
|
+
"fill-extrusion-height",
|
|
1112
|
+
"fill-extrusion-opacity",
|
|
1113
|
+
"fill-extrusion-translate",
|
|
1114
|
+
"fill-extrusion-vertical-scale",
|
|
1115
|
+
"fill-opacity",
|
|
1116
|
+
"fill-translate",
|
|
1117
|
+
"heatmap-intensity",
|
|
1118
|
+
"heatmap-opacity",
|
|
1119
|
+
"heatmap-radius",
|
|
1120
|
+
"heatmap-weight",
|
|
1121
|
+
"hillshade-exaggeration",
|
|
1122
|
+
"hillshade-emissive-strength",
|
|
1123
|
+
"hillshade-illumination-direction",
|
|
1124
|
+
"icon-emissive-strength",
|
|
1125
|
+
"icon-halo-blur",
|
|
1126
|
+
"icon-halo-width",
|
|
1127
|
+
"icon-opacity",
|
|
1128
|
+
"icon-occlusion-opacity",
|
|
1129
|
+
"icon-offset",
|
|
1130
|
+
"icon-padding",
|
|
1131
|
+
"icon-size",
|
|
1132
|
+
"icon-text-fit-padding",
|
|
1133
|
+
"icon-translate",
|
|
1134
|
+
"line-blur",
|
|
1135
|
+
"line-dasharray",
|
|
1136
|
+
"line-emissive-strength",
|
|
1137
|
+
"line-gap-width",
|
|
1138
|
+
"line-miter-limit",
|
|
1139
|
+
"line-opacity",
|
|
1140
|
+
"line-offset",
|
|
1141
|
+
"line-round-limit",
|
|
1142
|
+
"line-translate",
|
|
1143
|
+
"line-width",
|
|
1144
|
+
"raster-brightness-max",
|
|
1145
|
+
"raster-brightness-min",
|
|
1146
|
+
"raster-contrast",
|
|
1147
|
+
"raster-emissive-strength",
|
|
1148
|
+
"raster-fade-duration",
|
|
1149
|
+
"raster-hue-rotate",
|
|
1150
|
+
"raster-opacity",
|
|
1151
|
+
"raster-saturation",
|
|
1152
|
+
"text-emissive-strength",
|
|
1153
|
+
"text-halo-blur",
|
|
1154
|
+
"text-halo-width",
|
|
1155
|
+
"text-letter-spacing",
|
|
1156
|
+
"text-line-height",
|
|
1157
|
+
"text-max-angle",
|
|
1158
|
+
"text-max-width",
|
|
1159
|
+
"text-offset",
|
|
1160
|
+
"text-occlusion-opacity",
|
|
1161
|
+
"text-opacity",
|
|
1162
|
+
"text-padding",
|
|
1163
|
+
"text-radial-offset",
|
|
1164
|
+
"text-rotate",
|
|
1165
|
+
"text-size",
|
|
1166
|
+
"text-translate",
|
|
1167
|
+
"symbol-spacing"
|
|
1168
|
+
]);
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
// src/themes/model.ts
|
|
1173
|
+
function resolveTileflowTheme(theme) {
|
|
1174
|
+
assertThemeDocument(theme);
|
|
1175
|
+
const resolver = new ThemeValueResolver(theme);
|
|
1176
|
+
const tokens = {
|
|
1177
|
+
color: resolver.resolveCategory("color"),
|
|
1178
|
+
font: resolver.resolveCategory("font"),
|
|
1179
|
+
image: resolver.resolveCategory("image"),
|
|
1180
|
+
number: resolver.resolveCategory("number")
|
|
1181
|
+
};
|
|
1182
|
+
const typography = resolver.resolveTypography(theme.typography);
|
|
1183
|
+
const { light: lighting, sun } = resolver.resolveLighting(theme.lighting);
|
|
1184
|
+
return {
|
|
1185
|
+
colorScheme: theme.colorScheme,
|
|
1186
|
+
id: theme.id,
|
|
1187
|
+
lighting,
|
|
1188
|
+
...sun ? { sun } : {},
|
|
1189
|
+
tokens,
|
|
1190
|
+
typography,
|
|
1191
|
+
version: theme.version
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
function resolveThemeValues(value, theme, path = "value") {
|
|
1195
|
+
const resolver = new ThemeValueResolver(theme);
|
|
1196
|
+
resolver.resolveAllTokens();
|
|
1197
|
+
const resolved = resolver.resolveTree(value, path);
|
|
1198
|
+
const errors = auditTileflowThemeValues(value, path).filter(({ severity }) => severity === "error");
|
|
1199
|
+
if (errors.length > 0) throw new TileflowThemeAuditError(errors);
|
|
1200
|
+
return resolved;
|
|
1201
|
+
}
|
|
1202
|
+
function auditTileflowThemeValues(value, path = "value", context = inferAuditContext(path)) {
|
|
1203
|
+
const diagnostics = [];
|
|
1204
|
+
visitAuditableValue(value, path, void 0, {
|
|
1205
|
+
context,
|
|
1206
|
+
diagnostics,
|
|
1207
|
+
resolvingVariables: /* @__PURE__ */ new Set(),
|
|
1208
|
+
variables: /* @__PURE__ */ new Map()
|
|
1209
|
+
});
|
|
1210
|
+
return diagnostics.sort(compareAuditDiagnostics);
|
|
1211
|
+
}
|
|
1212
|
+
function visitThemeIntentNode(value, path, expectedCategory, state) {
|
|
1213
|
+
const tokenReference = isTileflowThemeTokenReference(value) ? value : void 0;
|
|
1214
|
+
const fixedValue = isTileflowFixedValue(value) ? value : void 0;
|
|
1215
|
+
const colorOperation = isTileflowThemeColorOperation(value) ? value : void 0;
|
|
1216
|
+
if (!tokenReference && !fixedValue && !colorOperation) {
|
|
1217
|
+
addThemeNodeDiagnostic(
|
|
1218
|
+
expectedCategory ?? inferThemeNodeCategory(value),
|
|
1219
|
+
path,
|
|
1220
|
+
state,
|
|
1221
|
+
themeNodeDisplayValue(value),
|
|
1222
|
+
`Malformed Tileflow ${String(value.kind)} node cannot establish explicit theme intent.`,
|
|
1223
|
+
"Use token.*, fixed(value, {reason}), or color.* constructors without extra fields."
|
|
1224
|
+
);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const actualCategory = tokenReference?.category ?? (colorOperation ? "color" : void 0);
|
|
1228
|
+
if (!expectedCategory) {
|
|
1229
|
+
addThemeNodeDiagnostic(
|
|
1230
|
+
actualCategory ?? inferThemeNodeCategory(value),
|
|
1231
|
+
path,
|
|
1232
|
+
state,
|
|
1233
|
+
themeNodeDisplayValue(value),
|
|
1234
|
+
"Tileflow theme values may only be consumed by a categorized visual property.",
|
|
1235
|
+
"Move the value to a color, font, image, or visual-number field; keep structure literal."
|
|
1236
|
+
);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (actualCategory && actualCategory !== expectedCategory) {
|
|
1240
|
+
addThemeNodeDiagnostic(
|
|
1241
|
+
expectedCategory,
|
|
1242
|
+
path,
|
|
1243
|
+
state,
|
|
1244
|
+
themeNodeDisplayValue(value),
|
|
1245
|
+
`Tileflow theme value category mismatch: expected ${expectedCategory}, received ${actualCategory}.`,
|
|
1246
|
+
`Use token.${expectedCategory}(...) or an explicit fixed ${expectedCategory} value.`
|
|
1247
|
+
);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (fixedValue) {
|
|
1251
|
+
if (!isThemePrimitiveForCategory(fixedValue.value, expectedCategory)) {
|
|
1252
|
+
addThemeNodeDiagnostic(
|
|
1253
|
+
expectedCategory,
|
|
1254
|
+
path,
|
|
1255
|
+
state,
|
|
1256
|
+
themeNodeDisplayValue(value),
|
|
1257
|
+
`Tileflow fixed value category mismatch: expected ${expectedCategory}.`,
|
|
1258
|
+
`Use a primitive ${expectedCategory} value inside fixed(value, {reason}).`
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (!colorOperation) return;
|
|
1264
|
+
if (colorOperation.operation === "alpha") {
|
|
1265
|
+
visitAuditableValue(colorOperation.color, `${path}.color`, "color", state, true);
|
|
1266
|
+
visitAuditableValue(colorOperation.opacity, `${path}.opacity`, "number", state, true);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
visitAuditableValue(colorOperation.from, `${path}.from`, "color", state, true);
|
|
1270
|
+
visitAuditableValue(colorOperation.to, `${path}.to`, "color", state, true);
|
|
1271
|
+
visitAuditableValue(colorOperation.amount, `${path}.amount`, "number", state, true);
|
|
1272
|
+
}
|
|
1273
|
+
function addThemeNodeDiagnostic(category, path, state, value, message, suggestion) {
|
|
1274
|
+
state.diagnostics.push({
|
|
1275
|
+
category,
|
|
1276
|
+
code: "THEME_IMPLICIT_FIXED",
|
|
1277
|
+
message,
|
|
1278
|
+
...state.context.owner ? { owner: state.context.owner } : {},
|
|
1279
|
+
path,
|
|
1280
|
+
phase: "theme-audit",
|
|
1281
|
+
scope: state.context.scope,
|
|
1282
|
+
severity: "error",
|
|
1283
|
+
suggestion,
|
|
1284
|
+
...state.context.target ? { target: state.context.target } : {},
|
|
1285
|
+
value
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
function inferThemeNodeCategory(value) {
|
|
1289
|
+
if (isThemeTokenCategory(value.category)) return value.category;
|
|
1290
|
+
if (value.kind === "theme-color") return "color";
|
|
1291
|
+
return typeof value.value === "number" ? "number" : "color";
|
|
1292
|
+
}
|
|
1293
|
+
function themeNodeDisplayValue(value) {
|
|
1294
|
+
if (typeof value.token === "string") return value.token;
|
|
1295
|
+
if (typeof value.value === "string" || typeof value.value === "number") return value.value;
|
|
1296
|
+
if (typeof value.operation === "string") return value.operation;
|
|
1297
|
+
return String(value.kind);
|
|
1298
|
+
}
|
|
1299
|
+
function isThemePrimitiveForCategory(value, category) {
|
|
1300
|
+
return category === "number" ? typeof value === "number" && Number.isFinite(value) || Array.isArray(value) && value.every((entry) => typeof entry === "number" && Number.isFinite(entry)) : typeof value === "string" && value.length > 0;
|
|
1301
|
+
}
|
|
1302
|
+
function isThemeValueCandidate(value) {
|
|
1303
|
+
return isRecord3(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
|
|
1304
|
+
}
|
|
1305
|
+
function visitAuditableValue(value, path, category, state, intentRequired = false) {
|
|
1306
|
+
if (isThemeStructuralPath(path)) category = void 0;
|
|
1307
|
+
if (isThemeValueCandidate(value)) {
|
|
1308
|
+
visitThemeIntentNode(value, path, category, state);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
1312
|
+
addAuditDiagnostic(value, path, category, state, intentRequired);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (Array.isArray(value)) {
|
|
1316
|
+
visitAuditableArray(value, path, category, state, false, intentRequired);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (!isRecord3(value)) return;
|
|
1320
|
+
if (value.kind === "expression" && Array.isArray(value.value)) {
|
|
1321
|
+
visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (value.kind === "zoom" && Array.isArray(value.stops)) {
|
|
1325
|
+
for (const [index, stop] of value.stops.entries()) {
|
|
1326
|
+
if (Array.isArray(stop)) {
|
|
1327
|
+
visitAuditableValue(stop[0], `${path}.stops[${index}][0]`, void 0, state);
|
|
1328
|
+
visitAuditableValue(
|
|
1329
|
+
stop[1],
|
|
1330
|
+
`${path}.stops[${index}][1]`,
|
|
1331
|
+
category,
|
|
1332
|
+
state,
|
|
1333
|
+
category !== void 0 && category !== "number"
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
const collectionCategory = isTileflowVisualColorCollection(lastPathKey(path)) ? category : void 0;
|
|
1340
|
+
for (const key of Object.keys(value).sort(compareCodeUnits)) {
|
|
1341
|
+
visitAuditableValue(
|
|
1342
|
+
value[key],
|
|
1343
|
+
`${path}.${key}`,
|
|
1344
|
+
classifyTileflowVisualProperty(key, value[key]) ?? collectionCategory,
|
|
1345
|
+
state
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
function lastPathKey(path) {
|
|
1350
|
+
return path.endsWith("]") ? "" : path.slice(path.lastIndexOf(".") + 1);
|
|
1351
|
+
}
|
|
1352
|
+
function visitAuditableArray(value, path, category, state, forceExpression = false, intentRequired = false) {
|
|
1353
|
+
const operator = typeof value[0] === "string" ? value[0] : void 0;
|
|
1354
|
+
const isExpression = forceExpression || isMapLibreExpressionOperator(operator);
|
|
1355
|
+
if (!isExpression) {
|
|
1356
|
+
for (const [index, entry] of value.entries()) {
|
|
1357
|
+
visitAuditableValue(entry, `${path}[${index}]`, category, state, intentRequired);
|
|
1358
|
+
}
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
visitExpressionOutputs(value, path, category, state, intentRequired);
|
|
1362
|
+
}
|
|
1363
|
+
function visitExpressionOutputs(value, path, category, state, intentRequired) {
|
|
1364
|
+
const operator = typeof value[0] === "string" ? value[0] : void 0;
|
|
1365
|
+
if (!operator) return;
|
|
1366
|
+
if (operator === "let") {
|
|
1367
|
+
const variables = new Map(state.variables);
|
|
1368
|
+
for (let index = 1; index < value.length - 1; index += 2) {
|
|
1369
|
+
const name = value[index];
|
|
1370
|
+
if (typeof name === "string") {
|
|
1371
|
+
variables.set(name, { path: `${path}[${index + 1}]`, value: value[index + 1] });
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
if (value.length > 1) {
|
|
1375
|
+
visitAuditableValue(
|
|
1376
|
+
value.at(-1),
|
|
1377
|
+
`${path}[${value.length - 1}]`,
|
|
1378
|
+
category,
|
|
1379
|
+
{ ...state, variables },
|
|
1380
|
+
intentRequired || category !== void 0 && category !== "number"
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
if (operator === "var") {
|
|
1386
|
+
const name = value[1];
|
|
1387
|
+
if (typeof name !== "string" || state.resolvingVariables.has(name)) {
|
|
1388
|
+
addDynamicExpressionDiagnostic(
|
|
1389
|
+
operator,
|
|
1390
|
+
path,
|
|
1391
|
+
category,
|
|
1392
|
+
state,
|
|
1393
|
+
intentRequired || category !== void 0 && category !== "number"
|
|
1394
|
+
);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
const variable = state.variables.get(name);
|
|
1398
|
+
if (!variable) {
|
|
1399
|
+
addDynamicExpressionDiagnostic(
|
|
1400
|
+
operator,
|
|
1401
|
+
path,
|
|
1402
|
+
category,
|
|
1403
|
+
state,
|
|
1404
|
+
intentRequired || category !== void 0 && category !== "number"
|
|
1405
|
+
);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
visitAuditableValue(
|
|
1409
|
+
variable.value,
|
|
1410
|
+
variable.path,
|
|
1411
|
+
category,
|
|
1412
|
+
{ ...state, resolvingVariables: /* @__PURE__ */ new Set([...state.resolvingVariables, name]) },
|
|
1413
|
+
intentRequired
|
|
1414
|
+
);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
if (operator === "literal") {
|
|
1418
|
+
if (value.length > 1) {
|
|
1419
|
+
visitLiteralOutput(value[1], `${path}[1]`, category, state, intentRequired);
|
|
1420
|
+
}
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
if (expressionDataOperators.has(operator)) {
|
|
1424
|
+
for (let index = 1; index < value.length; index += 1) {
|
|
1425
|
+
visitAuditableValue(value[index], `${path}[${index}]`, void 0, state);
|
|
1426
|
+
}
|
|
1427
|
+
addDynamicExpressionDiagnostic(
|
|
1428
|
+
operator,
|
|
1429
|
+
path,
|
|
1430
|
+
category,
|
|
1431
|
+
state,
|
|
1432
|
+
intentRequired || category !== void 0 && category !== "number"
|
|
1433
|
+
);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
if (operator === "rgb" || operator === "rgba") {
|
|
1437
|
+
for (let index = 1; index < value.length; index += 1) {
|
|
1438
|
+
visitAuditableValue(value[index], `${path}[${index}]`, void 0, state);
|
|
1439
|
+
}
|
|
1440
|
+
addDynamicExpressionDiagnostic(operator, path, category, state, true);
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
const outputIndices = new Set(expressionOutputIndices(operator, value.length));
|
|
1444
|
+
for (let index = 1; index < value.length; index += 1) {
|
|
1445
|
+
const isOutput = outputIndices.has(index);
|
|
1446
|
+
visitAuditableValue(
|
|
1447
|
+
value[index],
|
|
1448
|
+
`${path}[${index}]`,
|
|
1449
|
+
isOutput ? category : void 0,
|
|
1450
|
+
state,
|
|
1451
|
+
isOutput && (intentRequired || category !== void 0 && category !== "number")
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function visitLiteralOutput(value, path, category, state, intentRequired) {
|
|
1456
|
+
if (Array.isArray(value)) {
|
|
1457
|
+
for (const [index, entry] of value.entries()) {
|
|
1458
|
+
visitLiteralOutput(entry, `${path}[${index}]`, category, state, intentRequired);
|
|
1459
|
+
}
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
visitAuditableValue(value, path, category, state, intentRequired);
|
|
1463
|
+
}
|
|
1464
|
+
function addAuditDiagnostic(value, path, category, state, blocking = false) {
|
|
1465
|
+
if (!category) return;
|
|
1466
|
+
if (category === "number") {
|
|
1467
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return;
|
|
1468
|
+
} else {
|
|
1469
|
+
if (typeof value !== "string") return;
|
|
1470
|
+
}
|
|
1471
|
+
state.diagnostics.push({
|
|
1472
|
+
category,
|
|
1473
|
+
code: "THEME_IMPLICIT_FIXED",
|
|
1474
|
+
message: `Visual ${category} literal is implicitly fixed; use token.${category}(...) or fixed(value, {reason}).`,
|
|
1475
|
+
...state.context.owner ? { owner: state.context.owner } : {},
|
|
1476
|
+
path,
|
|
1477
|
+
phase: "theme-audit",
|
|
1478
|
+
scope: state.context.scope,
|
|
1479
|
+
severity: category === "number" && !blocking ? "warning" : "error",
|
|
1480
|
+
suggestion: `Replace the literal with token.${category}(...) or document the invariant with fixed(value, {reason}).`,
|
|
1481
|
+
...state.context.target ? { target: state.context.target } : {},
|
|
1482
|
+
value
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
function addDynamicExpressionDiagnostic(operator, path, category, state, intentRequired) {
|
|
1486
|
+
if (!category || category === "number" || !intentRequired) return;
|
|
1487
|
+
state.diagnostics.push({
|
|
1488
|
+
category,
|
|
1489
|
+
code: "THEME_IMPLICIT_FIXED",
|
|
1490
|
+
message: `Visual ${category} expression has a dynamic output from ${operator}; every possible output must be token.${category}(...), fixed(value, {reason}), or a valid theme color operation.`,
|
|
1491
|
+
...state.context.owner ? { owner: state.context.owner } : {},
|
|
1492
|
+
path,
|
|
1493
|
+
phase: "theme-audit",
|
|
1494
|
+
scope: state.context.scope,
|
|
1495
|
+
severity: "error",
|
|
1496
|
+
suggestion: `Make every ${category} output branch carry explicit theme intent.`,
|
|
1497
|
+
...state.context.target ? { target: state.context.target } : {},
|
|
1498
|
+
value: operator
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
function expressionOutputIndices(operator, length) {
|
|
1502
|
+
switch (operator) {
|
|
1503
|
+
case "case":
|
|
1504
|
+
return uniqueIndices([...range(2, length - 1, 2), length - 1]);
|
|
1505
|
+
case "match":
|
|
1506
|
+
return uniqueIndices([...range(3, length - 1, 2), length - 1]);
|
|
1507
|
+
case "step":
|
|
1508
|
+
return range(2, length, 2);
|
|
1509
|
+
case "interpolate":
|
|
1510
|
+
case "interpolate-hcl":
|
|
1511
|
+
case "interpolate-lab":
|
|
1512
|
+
return range(4, length, 2);
|
|
1513
|
+
case "format":
|
|
1514
|
+
return range(1, length, 2);
|
|
1515
|
+
case "at":
|
|
1516
|
+
return length > 2 ? [2] : [];
|
|
1517
|
+
case "array":
|
|
1518
|
+
return length > 1 ? [length - 1] : [];
|
|
1519
|
+
case "image":
|
|
1520
|
+
return length > 1 ? [1] : [];
|
|
1521
|
+
case "coalesce":
|
|
1522
|
+
case "concat":
|
|
1523
|
+
case "join":
|
|
1524
|
+
case "boolean":
|
|
1525
|
+
case "number":
|
|
1526
|
+
case "object":
|
|
1527
|
+
case "string":
|
|
1528
|
+
case "to-boolean":
|
|
1529
|
+
case "to-color":
|
|
1530
|
+
case "to-number":
|
|
1531
|
+
case "to-string":
|
|
1532
|
+
return range(1, length, 1);
|
|
1533
|
+
default:
|
|
1534
|
+
return range(1, length, 1);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
function uniqueIndices(indices) {
|
|
1538
|
+
return [...new Set(indices)];
|
|
1539
|
+
}
|
|
1540
|
+
function inferAuditContext(path) {
|
|
1541
|
+
if (path === "terrain" || path.startsWith("terrain.")) return { scope: "terrain" };
|
|
1542
|
+
if (path === "modules" || path.startsWith("modules.")) {
|
|
1543
|
+
const owner = path.match(/^modules\.([^.[]+)/u)?.[1];
|
|
1544
|
+
return { scope: "module", ...owner ? { owner } : {} };
|
|
1545
|
+
}
|
|
1546
|
+
return { scope: "value" };
|
|
1547
|
+
}
|
|
1548
|
+
function compareAuditDiagnostics(left, right) {
|
|
1549
|
+
const leftKey = [
|
|
1550
|
+
left.scope,
|
|
1551
|
+
left.owner ?? "",
|
|
1552
|
+
left.target ?? "",
|
|
1553
|
+
left.path,
|
|
1554
|
+
left.category,
|
|
1555
|
+
typeof left.value,
|
|
1556
|
+
String(left.value)
|
|
1557
|
+
];
|
|
1558
|
+
const rightKey = [
|
|
1559
|
+
right.scope,
|
|
1560
|
+
right.owner ?? "",
|
|
1561
|
+
right.target ?? "",
|
|
1562
|
+
right.path,
|
|
1563
|
+
right.category,
|
|
1564
|
+
typeof right.value,
|
|
1565
|
+
String(right.value)
|
|
1566
|
+
];
|
|
1567
|
+
for (let index = 0; index < leftKey.length; index += 1) {
|
|
1568
|
+
const compared = compareCodeUnits(leftKey[index], rightKey[index]);
|
|
1569
|
+
if (compared !== 0) return compared;
|
|
1570
|
+
}
|
|
1571
|
+
return 0;
|
|
1572
|
+
}
|
|
1573
|
+
function range(start, end, step) {
|
|
1574
|
+
const values = [];
|
|
1575
|
+
for (let index = start; index < end; index += step) values.push(index);
|
|
1576
|
+
return values;
|
|
1577
|
+
}
|
|
1578
|
+
function assertThemeIdentity(theme) {
|
|
1579
|
+
if (!isThemeName(theme.id)) {
|
|
1580
|
+
throw new Error(
|
|
1581
|
+
`Invalid Tileflow theme id ${JSON.stringify(theme.id)}; expected a portable identifier.`
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
if (!Number.isSafeInteger(theme.version) || theme.version < 1) {
|
|
1585
|
+
throw new Error(`Tileflow theme "${theme.id}" version must be a positive integer.`);
|
|
1586
|
+
}
|
|
1587
|
+
if (theme.colorScheme !== "light" && theme.colorScheme !== "dark") {
|
|
1588
|
+
throw new Error(`Tileflow theme "${theme.id}" colorScheme must be light or dark.`);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
function assertThemeDocument(theme) {
|
|
1592
|
+
const record3 = assertPlainThemeRecord(theme, "theme");
|
|
1593
|
+
assertExactThemeKeys(record3, "theme", themeDocumentKeys);
|
|
1594
|
+
assertRequiredThemeKeys(record3, "theme", themeDocumentKeys);
|
|
1595
|
+
assertThemeIdentity(theme);
|
|
1596
|
+
assertThemeTokens(record3.tokens, "theme.tokens", true);
|
|
1597
|
+
assertThemeTypography(record3.typography, "theme.typography");
|
|
1598
|
+
assertThemeLighting(record3.lighting, "theme.lighting");
|
|
1599
|
+
cloneJson(theme);
|
|
1600
|
+
}
|
|
1601
|
+
function assertThemeTokens(value, path, complete) {
|
|
1602
|
+
const tokens = assertPlainThemeRecord(value, path);
|
|
1603
|
+
assertExactThemeKeys(tokens, path, tileflowThemeTokenCategories);
|
|
1604
|
+
if (complete) assertRequiredThemeKeys(tokens, path, tileflowThemeTokenCategories);
|
|
1605
|
+
for (const category of tileflowThemeTokenCategories) {
|
|
1606
|
+
if (!Object.hasOwn(tokens, category)) continue;
|
|
1607
|
+
const values = assertPlainThemeRecord(tokens[category], `${path}.${category}`);
|
|
1608
|
+
for (const name of Reflect.ownKeys(values)) {
|
|
1609
|
+
if (typeof name !== "string") {
|
|
1610
|
+
throw new Error(
|
|
1611
|
+
`Invalid Tileflow ${category} token key ${String(name)} at ${path}.${category}.`
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
assertTokenName(name, category);
|
|
1615
|
+
assertThemeCategoryValue(values[name], category, `${path}.${category}.${name}`);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
function assertThemeTypography(value, path) {
|
|
1620
|
+
const typography = assertPlainThemeRecord(value, path);
|
|
1621
|
+
assertExactThemeKeys(typography, path, themeTypographyKeys);
|
|
1622
|
+
assertThemeTypographyStyleFields(typography, path);
|
|
1623
|
+
for (const domain of themeTypographyDomains) {
|
|
1624
|
+
if (!Object.hasOwn(typography, domain)) continue;
|
|
1625
|
+
const stylePath = `${path}.${domain}`;
|
|
1626
|
+
const style = assertPlainThemeRecord(typography[domain], stylePath);
|
|
1627
|
+
assertExactThemeKeys(style, stylePath, themeTypographyStyleKeys);
|
|
1628
|
+
assertThemeTypographyStyleFields(style, stylePath);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function assertThemeTypographyStyleFields(style, path) {
|
|
1632
|
+
if (Object.hasOwn(style, "font")) {
|
|
1633
|
+
assertThemeCategoryValue(style.font, "font", `${path}.font`);
|
|
1634
|
+
}
|
|
1635
|
+
if (Object.hasOwn(style, "fallbacks")) {
|
|
1636
|
+
if (!Array.isArray(style.fallbacks)) {
|
|
1637
|
+
throw themeTypeError(`${path}.fallbacks`, "font array", style.fallbacks);
|
|
1638
|
+
}
|
|
1639
|
+
for (const [index, fallback] of style.fallbacks.entries()) {
|
|
1640
|
+
assertThemeCategoryValue(fallback, "font", `${path}.fallbacks[${index}]`);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
if (Object.hasOwn(style, "letterSpacing")) {
|
|
1644
|
+
assertThemeCategoryValue(style.letterSpacing, "number", `${path}.letterSpacing`);
|
|
1645
|
+
}
|
|
1646
|
+
if (Object.hasOwn(style, "transform") && !["lowercase", "none", "uppercase"].includes(String(style.transform))) {
|
|
1647
|
+
throw themeTypeError(`${path}.transform`, "text transform", style.transform);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
function assertThemeLighting(value, path, allowReset = false) {
|
|
1651
|
+
if (allowReset && isTileflowReset(value)) return;
|
|
1652
|
+
const authored = assertPlainThemeRecord(value, path);
|
|
1653
|
+
assertExactThemeKeys(authored, path, themeLightingKeys);
|
|
1654
|
+
const lighting = allowReset ? Object.fromEntries(Object.entries(authored).filter(([, entry]) => !isTileflowReset(entry))) : authored;
|
|
1655
|
+
if (Object.hasOwn(lighting, "sun")) {
|
|
1656
|
+
const sun = assertPlainThemeRecord(lighting.sun, `${path}.sun`);
|
|
1657
|
+
assertExactThemeKeys(sun, `${path}.sun`, themeSunKeys);
|
|
1658
|
+
for (const key of ["altitude", "azimuth", "intensity", "shadowIntensity"]) {
|
|
1659
|
+
if (Object.hasOwn(sun, key))
|
|
1660
|
+
assertThemeCategoryValue(sun[key], "number", `${path}.sun.${key}`);
|
|
1661
|
+
}
|
|
1662
|
+
if (Object.hasOwn(sun, "color"))
|
|
1663
|
+
assertThemeCategoryValue(sun.color, "color", `${path}.sun.color`);
|
|
1664
|
+
}
|
|
1665
|
+
if (Object.hasOwn(lighting, "ambient")) {
|
|
1666
|
+
const ambient = assertPlainThemeRecord(lighting.ambient, `${path}.ambient`);
|
|
1667
|
+
assertExactThemeKeys(ambient, `${path}.ambient`, themeAmbientKeys);
|
|
1668
|
+
if (Object.hasOwn(ambient, "intensity"))
|
|
1669
|
+
assertThemeCategoryValue(ambient.intensity, "number", `${path}.ambient.intensity`);
|
|
1670
|
+
if (Object.hasOwn(ambient, "color"))
|
|
1671
|
+
assertThemeCategoryValue(ambient.color, "color", `${path}.ambient.color`);
|
|
1672
|
+
}
|
|
1673
|
+
if (Object.hasOwn(lighting, "anchor") && lighting.anchor !== "map" && lighting.anchor !== "viewport") {
|
|
1674
|
+
throw themeTypeError(`${path}.anchor`, "map or viewport", lighting.anchor);
|
|
1675
|
+
}
|
|
1676
|
+
if (Object.hasOwn(lighting, "color")) {
|
|
1677
|
+
assertThemeCategoryValue(lighting.color, "color", `${path}.color`);
|
|
1678
|
+
}
|
|
1679
|
+
if (Object.hasOwn(lighting, "intensity")) {
|
|
1680
|
+
assertThemeCategoryValue(lighting.intensity, "number", `${path}.intensity`);
|
|
1681
|
+
}
|
|
1682
|
+
if (Object.hasOwn(lighting, "position")) {
|
|
1683
|
+
if (!Array.isArray(lighting.position) || lighting.position.length !== 3) {
|
|
1684
|
+
throw themeTypeError(`${path}.position`, "three number values", lighting.position);
|
|
1685
|
+
}
|
|
1686
|
+
for (const [index, position] of lighting.position.entries()) {
|
|
1687
|
+
assertThemeCategoryValue(position, "number", `${path}.position[${index}]`);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
function assertThemeCategoryValue(value, expectedCategory, path) {
|
|
1692
|
+
if (!isRecord3(value)) {
|
|
1693
|
+
assertTokenPrimitive(expectedCategory, value, path);
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
if (value.kind === "theme-token") {
|
|
1697
|
+
assertThemeTokenReferenceNode(value, expectedCategory, path);
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
if (value.kind === "theme-fixed") {
|
|
1701
|
+
assertThemeFixedNode(value, expectedCategory, path);
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
if (value.kind === "theme-color") {
|
|
1705
|
+
if (expectedCategory !== "color") {
|
|
1706
|
+
throw themeTypeError(path, `${expectedCategory} value`, value);
|
|
1707
|
+
}
|
|
1708
|
+
assertThemeColorOperationNode(value, path);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
throw themeTypeError(path, `${expectedCategory} value`, value);
|
|
1712
|
+
}
|
|
1713
|
+
function assertThemeTokenReferenceNode(reference, expectedCategory, path) {
|
|
1714
|
+
assertExactThemeKeys(reference, path, ["category", "kind", "token"]);
|
|
1715
|
+
assertRequiredThemeKeys(reference, path, ["category", "kind", "token"]);
|
|
1716
|
+
if (!isThemeTokenCategory(reference.category)) {
|
|
1717
|
+
throw themeTypeError(`${path}.category`, "color, font, image, or number", reference.category);
|
|
1718
|
+
}
|
|
1719
|
+
if (typeof reference.token !== "string") {
|
|
1720
|
+
throw themeTypeError(`${path}.token`, "token name", reference.token);
|
|
1721
|
+
}
|
|
1722
|
+
assertTokenName(reference.token, reference.category);
|
|
1723
|
+
if (reference.category !== expectedCategory) {
|
|
1724
|
+
throw new Error(
|
|
1725
|
+
`Tileflow theme token type mismatch at ${path}; expected ${expectedCategory}, received ${reference.category} token "${reference.token}".`
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
function assertThemeFixedNode(fixedValue, expectedCategory, path) {
|
|
1730
|
+
assertExactThemeKeys(fixedValue, path, ["kind", "reason", "value"]);
|
|
1731
|
+
assertRequiredThemeKeys(fixedValue, path, ["kind", "reason", "value"]);
|
|
1732
|
+
if (typeof fixedValue.reason !== "string" || !fixedValue.reason.trim()) {
|
|
1733
|
+
throw new Error(`Tileflow fixed value at ${path} requires a non-empty reason.`);
|
|
1734
|
+
}
|
|
1735
|
+
assertTokenPrimitive(expectedCategory, fixedValue.value, `${path}.value`);
|
|
1736
|
+
}
|
|
1737
|
+
function assertThemeColorOperationNode(operation, path) {
|
|
1738
|
+
if (operation.operation === "alpha") {
|
|
1739
|
+
assertExactThemeKeys(operation, path, ["color", "kind", "opacity", "operation"]);
|
|
1740
|
+
assertRequiredThemeKeys(operation, path, ["color", "kind", "opacity", "operation"]);
|
|
1741
|
+
assertThemeCategoryValue(operation.color, "color", `${path}.color`);
|
|
1742
|
+
assertThemeCategoryValue(operation.opacity, "number", `${path}.opacity`);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
if (operation.operation === "mix") {
|
|
1746
|
+
assertExactThemeKeys(operation, path, ["amount", "from", "kind", "operation", "space", "to"]);
|
|
1747
|
+
assertRequiredThemeKeys(operation, path, [
|
|
1748
|
+
"amount",
|
|
1749
|
+
"from",
|
|
1750
|
+
"kind",
|
|
1751
|
+
"operation",
|
|
1752
|
+
"space",
|
|
1753
|
+
"to"
|
|
1754
|
+
]);
|
|
1755
|
+
if (operation.space !== "oklch") {
|
|
1756
|
+
throw new Error(`Tileflow theme color mix at ${path} must use the oklch color space.`);
|
|
1757
|
+
}
|
|
1758
|
+
assertThemeCategoryValue(operation.from, "color", `${path}.from`);
|
|
1759
|
+
assertThemeCategoryValue(operation.to, "color", `${path}.to`);
|
|
1760
|
+
assertThemeCategoryValue(operation.amount, "number", `${path}.amount`);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
throw themeTypeError(`${path}.operation`, "alpha or mix", operation.operation);
|
|
1764
|
+
}
|
|
1765
|
+
function assertPlainThemeRecord(value, path) {
|
|
1766
|
+
if (!isPlainRecord2(value)) throw themeTypeError(path, "plain object", value);
|
|
1767
|
+
return value;
|
|
1768
|
+
}
|
|
1769
|
+
function assertExactThemeKeys(value, path, allowedKeys) {
|
|
1770
|
+
const allowed = new Set(allowedKeys);
|
|
1771
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
1772
|
+
if (typeof key !== "string" || !allowed.has(key)) {
|
|
1773
|
+
throw new Error(`Unknown Tileflow theme field ${JSON.stringify(String(key))} at ${path}.`);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
function assertRequiredThemeKeys(value, path, requiredKeys) {
|
|
1778
|
+
for (const key of requiredKeys) {
|
|
1779
|
+
if (!Object.hasOwn(value, key)) {
|
|
1780
|
+
throw new Error(`Missing Tileflow theme field ${JSON.stringify(key)} at ${path}.`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
function isThemeTokenCategory(value) {
|
|
1785
|
+
return tileflowThemeTokenCategories.includes(value);
|
|
1786
|
+
}
|
|
1787
|
+
function assertTokenPrimitive(category, value, path) {
|
|
1788
|
+
if (category === "number") {
|
|
1789
|
+
assertFiniteNumber(value, path);
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1793
|
+
throw themeTypeError(path, category, value);
|
|
1794
|
+
}
|
|
1795
|
+
if (category === "color") assertColor(value, path);
|
|
1796
|
+
if (category === "font") assertFontName(value, path);
|
|
1797
|
+
if (category === "image") assertImageName(value, path);
|
|
1798
|
+
}
|
|
1799
|
+
function assertThemeNodeResolutionContext(value, expectedCategory, path) {
|
|
1800
|
+
if (value.kind === "theme-token") {
|
|
1801
|
+
if (!isThemeTokenCategory(value.category)) {
|
|
1802
|
+
assertExactThemeKeys(value, path, ["category", "kind", "token"]);
|
|
1803
|
+
assertRequiredThemeKeys(value, path, ["category", "kind", "token"]);
|
|
1804
|
+
throw themeTypeError(`${path}.category`, "color, font, image, or number", value.category);
|
|
1805
|
+
}
|
|
1806
|
+
assertThemeTokenReferenceNode(value, expectedCategory ?? value.category, path);
|
|
1807
|
+
if (!expectedCategory) throwThemeValueOutsideVisualField(path);
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
if (value.kind === "theme-fixed") {
|
|
1811
|
+
assertExactThemeKeys(value, path, ["kind", "reason", "value"]);
|
|
1812
|
+
assertRequiredThemeKeys(value, path, ["kind", "reason", "value"]);
|
|
1813
|
+
if (typeof value.reason !== "string" || !value.reason.trim()) {
|
|
1814
|
+
throw new Error(`Tileflow fixed value at ${path} requires a non-empty reason.`);
|
|
1815
|
+
}
|
|
1816
|
+
if (!expectedCategory) throwThemeValueOutsideVisualField(path);
|
|
1817
|
+
if (!isThemePrimitiveForCategory(value.value, expectedCategory)) {
|
|
1818
|
+
throw themeTypeError(path, `${expectedCategory} value`, value.value);
|
|
1819
|
+
}
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (value.kind === "theme-color") {
|
|
1823
|
+
assertThemeColorOperationNode(value, path);
|
|
1824
|
+
if (!expectedCategory) throwThemeValueOutsideVisualField(path);
|
|
1825
|
+
if (expectedCategory !== "color") {
|
|
1826
|
+
throw themeTypeError(path, `${expectedCategory} value`, value);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
function throwThemeValueOutsideVisualField(path) {
|
|
1831
|
+
throw new Error(
|
|
1832
|
+
`Tileflow theme value at ${path} is outside a categorized visual property; theme values cannot control filters, zoom bounds, visibility, metadata, or structure.`
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
function inferThemeCategoryFromPath(path) {
|
|
1836
|
+
const tokenCategory = path.match(/^theme\.tokens\.(color|font|image|number)(?:\.|$)/u)?.[1];
|
|
1837
|
+
if (isThemeTokenCategory(tokenCategory)) return tokenCategory;
|
|
1838
|
+
if (/^theme\.lighting\.position(?:\[\d+\])?$/u.test(path)) return "number";
|
|
1839
|
+
if (/^theme\.lighting\.sun\.(?:altitude|azimuth|shadowIntensity)$/u.test(path)) return "number";
|
|
1840
|
+
const segments = path.replace(/\[\d+\]/g, "").split(".");
|
|
1841
|
+
return [...segments].reverse().map((segment) => classifyTileflowVisualProperty(segment)).find((category) => category !== void 0);
|
|
1842
|
+
}
|
|
1843
|
+
function isThemeStructuralPath(path) {
|
|
1844
|
+
const segments = path.replace(/\[\d+\]/g, "").split(".");
|
|
1845
|
+
const modulesIndex = segments.indexOf("modules");
|
|
1846
|
+
const dataIndex = segments.indexOf("data");
|
|
1847
|
+
if (dataIndex >= 0 && segments[dataIndex + 1] === "schema" && ["fields", "layers"].includes(segments[dataIndex + 2] ?? "")) {
|
|
1848
|
+
return true;
|
|
1849
|
+
}
|
|
1850
|
+
if (modulesIndex >= 0 && segments[modulesIndex + 1] === "poi" && segments.length === modulesIndex + 3 && segments[modulesIndex + 2] === "color") {
|
|
1851
|
+
return true;
|
|
1852
|
+
}
|
|
1853
|
+
const terrainIndex = segments.indexOf("terrain");
|
|
1854
|
+
const firstPropertyIndex = modulesIndex >= 0 ? modulesIndex + 2 : terrainIndex >= 0 ? terrainIndex + 1 : segments[0] === "map" ? 2 : 0;
|
|
1855
|
+
return segments.slice(firstPropertyIndex).some((segment) => themeStructuralPathSegments.has(segment));
|
|
1856
|
+
}
|
|
1857
|
+
function assertTokenName(name, category) {
|
|
1858
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)*$/.test(name)) {
|
|
1859
|
+
throw new Error(
|
|
1860
|
+
`Invalid Tileflow ${category} token ${JSON.stringify(name)}; expected a portable dot-separated semantic name.`
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
function isThemeName(value) {
|
|
1865
|
+
return isTileflowThemeName(value);
|
|
1866
|
+
}
|
|
1867
|
+
function mixOklch(from, to, amount) {
|
|
1868
|
+
const a = rgbaToOklch(parseColor(from));
|
|
1869
|
+
const b = rgbaToOklch(parseColor(to));
|
|
1870
|
+
let hueDelta = b.h - a.h;
|
|
1871
|
+
if (hueDelta > 180) hueDelta -= 360;
|
|
1872
|
+
if (hueDelta < -180) hueDelta += 360;
|
|
1873
|
+
const rgba = oklchToRgba({
|
|
1874
|
+
c: interpolate(a.c, b.c, amount),
|
|
1875
|
+
h: (a.h + hueDelta * amount + 360) % 360,
|
|
1876
|
+
l: interpolate(a.l, b.l, amount)
|
|
1877
|
+
});
|
|
1878
|
+
const alpha = interpolate(parseColor(from).a, parseColor(to).a, amount);
|
|
1879
|
+
return formatColor({ ...rgba, a: alpha });
|
|
1880
|
+
}
|
|
1881
|
+
function applyAlpha(value, opacity) {
|
|
1882
|
+
const rgba = parseColor(value);
|
|
1883
|
+
return formatSrgbColor({ ...rgba, a: rgba.a * opacity });
|
|
1884
|
+
}
|
|
1885
|
+
function parseColor(value) {
|
|
1886
|
+
const hex = value.match(/^#([0-9a-f]{3,8})$/i)?.[1];
|
|
1887
|
+
if (hex) {
|
|
1888
|
+
const expanded = hex.length === 3 || hex.length === 4 ? [...hex].map((entry) => entry + entry).join("") : hex;
|
|
1889
|
+
if (expanded.length === 6 || expanded.length === 8) {
|
|
1890
|
+
return {
|
|
1891
|
+
a: expanded.length === 8 ? Number.parseInt(expanded.slice(6), 16) / 255 : 1,
|
|
1892
|
+
b: Number.parseInt(expanded.slice(4, 6), 16) / 255,
|
|
1893
|
+
g: Number.parseInt(expanded.slice(2, 4), 16) / 255,
|
|
1894
|
+
r: Number.parseInt(expanded.slice(0, 2), 16) / 255
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
const rgb = value.match(
|
|
1899
|
+
/^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/i
|
|
1900
|
+
);
|
|
1901
|
+
if (rgb) {
|
|
1902
|
+
const result = {
|
|
1903
|
+
a: Number(rgb[4] ?? 1),
|
|
1904
|
+
b: Number(rgb[3]) / 255,
|
|
1905
|
+
g: Number(rgb[2]) / 255,
|
|
1906
|
+
r: Number(rgb[1]) / 255
|
|
1907
|
+
};
|
|
1908
|
+
if (Object.values(result).every((entry) => Number.isFinite(entry) && entry >= 0 && entry <= 1)) {
|
|
1909
|
+
return result;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
throw new Error(`Invalid Tileflow theme color ${JSON.stringify(value)}.`);
|
|
1913
|
+
}
|
|
1914
|
+
function rgbaToOklch({ b, g, r }) {
|
|
1915
|
+
const linear = [r, g, b].map(srgbToLinear);
|
|
1916
|
+
const l = 0.4122214708 * linear[0] + 0.5363325363 * linear[1] + 0.0514459929 * linear[2];
|
|
1917
|
+
const m = 0.2119034982 * linear[0] + 0.6806995451 * linear[1] + 0.1073969566 * linear[2];
|
|
1918
|
+
const s = 0.0883024619 * linear[0] + 0.2817188376 * linear[1] + 0.6299787005 * linear[2];
|
|
1919
|
+
const lRoot = Math.cbrt(l);
|
|
1920
|
+
const mRoot = Math.cbrt(m);
|
|
1921
|
+
const sRoot = Math.cbrt(s);
|
|
1922
|
+
const labA = 1.9779984951 * lRoot - 2.428592205 * mRoot + 0.4505937099 * sRoot;
|
|
1923
|
+
const labB = 0.0259040371 * lRoot + 0.7827717662 * mRoot - 0.808675766 * sRoot;
|
|
1924
|
+
return {
|
|
1925
|
+
c: Math.hypot(labA, labB),
|
|
1926
|
+
h: Math.atan2(labB, labA) * 180 / Math.PI + 360,
|
|
1927
|
+
l: 0.2104542553 * lRoot + 0.793617785 * mRoot - 0.0040720468 * sRoot
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
function oklchToRgba({ c, h, l }) {
|
|
1931
|
+
const radians = h * Math.PI / 180;
|
|
1932
|
+
const labA = c * Math.cos(radians);
|
|
1933
|
+
const labB = c * Math.sin(radians);
|
|
1934
|
+
const lRoot = l + 0.3963377774 * labA + 0.2158037573 * labB;
|
|
1935
|
+
const mRoot = l - 0.1055613458 * labA - 0.0638541728 * labB;
|
|
1936
|
+
const sRoot = l - 0.0894841775 * labA - 1.291485548 * labB;
|
|
1937
|
+
const lLinear = lRoot ** 3;
|
|
1938
|
+
const mLinear = mRoot ** 3;
|
|
1939
|
+
const sLinear = sRoot ** 3;
|
|
1940
|
+
return {
|
|
1941
|
+
a: 1,
|
|
1942
|
+
b: clamp01(-0.0041960863 * lLinear - 0.7034186147 * mLinear + 1.707614701 * sLinear),
|
|
1943
|
+
g: clamp01(-1.2684380046 * lLinear + 2.6097574011 * mLinear - 0.3413193965 * sLinear),
|
|
1944
|
+
r: clamp01(4.0767416621 * lLinear - 3.3077115913 * mLinear + 0.2309699292 * sLinear)
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
function formatColor({ a, b, g, r }) {
|
|
1948
|
+
const channels = [r, g, b].map((entry) => Math.round(linearToSrgb(entry) * 255));
|
|
1949
|
+
if (a >= 0.9995) {
|
|
1950
|
+
return `#${channels.map((entry) => entry.toString(16).padStart(2, "0")).join("")}`;
|
|
1951
|
+
}
|
|
1952
|
+
return `rgba(${channels[0]}, ${channels[1]}, ${channels[2]}, ${round(a, 4)})`;
|
|
1953
|
+
}
|
|
1954
|
+
function formatSrgbColor({ a, b, g, r }) {
|
|
1955
|
+
const channels = [r, g, b].map((entry) => Math.round(clamp01(entry) * 255));
|
|
1956
|
+
if (a >= 0.9995) {
|
|
1957
|
+
return `#${channels.map((entry) => entry.toString(16).padStart(2, "0")).join("")}`;
|
|
1958
|
+
}
|
|
1959
|
+
return `rgba(${channels[0]}, ${channels[1]}, ${channels[2]}, ${round(a, 4)})`;
|
|
1960
|
+
}
|
|
1961
|
+
function srgbToLinear(value) {
|
|
1962
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
1963
|
+
}
|
|
1964
|
+
function linearToSrgb(value) {
|
|
1965
|
+
const clamped = clamp01(value);
|
|
1966
|
+
return clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
|
|
1967
|
+
}
|
|
1968
|
+
function assertColor(value, path) {
|
|
1969
|
+
if (typeof value !== "string") throw themeTypeError(path, "color", value);
|
|
1970
|
+
try {
|
|
1971
|
+
parseColor(value);
|
|
1972
|
+
} catch {
|
|
1973
|
+
throw new Error(`Invalid Tileflow theme color ${JSON.stringify(value)} at ${path}.`);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
function assertFiniteNumber(value, path) {
|
|
1977
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1978
|
+
throw themeTypeError(path, "finite number", value);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
function assertFontName(value, path) {
|
|
1982
|
+
if (value.length > 100 || value !== value.trim() || value !== value.normalize("NFC") || /[\p{Cc}\\]/u.test(value)) {
|
|
1983
|
+
throw new Error(
|
|
1984
|
+
`Invalid Tileflow font ${JSON.stringify(value)} at ${path}; expected an exact NFC face name without whitespace, controls, or backslashes.`
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
function assertImageName(value, path) {
|
|
1989
|
+
if (value.length > 128 || value !== value.trim() || value !== value.normalize("NFC") || /[\p{Cc}\\]/u.test(value)) {
|
|
1990
|
+
throw new Error(`Invalid Tileflow image token value ${JSON.stringify(value)} at ${path}.`);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
function assertUnitInterval(value, path) {
|
|
1994
|
+
assertFiniteNumber(value, path);
|
|
1995
|
+
if (value < 0 || value > 1) {
|
|
1996
|
+
throw new Error(`Tileflow theme value at ${path} must be between 0 and 1.`);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
function themeTypeError(path, expected, value) {
|
|
2000
|
+
return new Error(
|
|
2001
|
+
`Invalid Tileflow theme value at ${path}; expected ${expected}, received ${describe(value)}.`
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
function describe(value) {
|
|
2005
|
+
if (value === null) return "null";
|
|
2006
|
+
if (Array.isArray(value)) return "array";
|
|
2007
|
+
return typeof value;
|
|
2008
|
+
}
|
|
2009
|
+
function interpolate(from, to, amount) {
|
|
2010
|
+
return from + (to - from) * amount;
|
|
2011
|
+
}
|
|
2012
|
+
function clamp01(value) {
|
|
2013
|
+
return Math.max(0, Math.min(1, value));
|
|
2014
|
+
}
|
|
2015
|
+
function round(value, digits) {
|
|
2016
|
+
const power = 10 ** digits;
|
|
2017
|
+
return Math.round(value * power) / power;
|
|
2018
|
+
}
|
|
2019
|
+
function cloneJson(value) {
|
|
2020
|
+
const serialized = JSON.stringify(value);
|
|
2021
|
+
if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
|
|
2022
|
+
return JSON.parse(serialized);
|
|
2023
|
+
}
|
|
2024
|
+
function isRecord3(value) {
|
|
2025
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2026
|
+
}
|
|
2027
|
+
function isPlainRecord2(value) {
|
|
2028
|
+
if (!isRecord3(value)) return false;
|
|
2029
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2030
|
+
return prototype === Object.prototype || prototype === null;
|
|
2031
|
+
}
|
|
2032
|
+
function compareCodeUnits(left, right) {
|
|
2033
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
2034
|
+
}
|
|
2035
|
+
var tileflowThemeLimits, TileflowThemeAuditError, themeDocumentKeys, themeTypographyStyleKeys, themeTypographyKeys, themeTypographyDomains, themeLightingKeys, themeSunKeys, themeAmbientKeys, expressionDataOperators, ThemeValueResolver, themeStructuralPathSegments;
|
|
2036
|
+
var init_model = __esm({
|
|
2037
|
+
"src/themes/model.ts"() {
|
|
2038
|
+
"use strict";
|
|
2039
|
+
init_expression_operators();
|
|
2040
|
+
init_values();
|
|
2041
|
+
init_operations();
|
|
2042
|
+
init_portable_identity_rules();
|
|
2043
|
+
init_sun();
|
|
2044
|
+
init_visual_semantics();
|
|
2045
|
+
tileflowThemeLimits = Object.freeze({ maxThemes: 64 });
|
|
2046
|
+
TileflowThemeAuditError = class extends Error {
|
|
2047
|
+
diagnostics;
|
|
2048
|
+
constructor(diagnostics) {
|
|
2049
|
+
const errors = diagnostics.filter(({ severity }) => severity === "error").sort(compareAuditDiagnostics).map((diagnostic) => ({ ...diagnostic }));
|
|
2050
|
+
const first = errors[0];
|
|
2051
|
+
super(
|
|
2052
|
+
`Tileflow theme audit failed with ${errors.length} blocking diagnostic${errors.length === 1 ? "" : "s"}${first ? `; first error at ${first.path}` : ""}.`
|
|
2053
|
+
);
|
|
2054
|
+
this.name = "TileflowThemeAuditError";
|
|
2055
|
+
this.diagnostics = errors;
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
themeDocumentKeys = [
|
|
2059
|
+
"colorScheme",
|
|
2060
|
+
"id",
|
|
2061
|
+
"lighting",
|
|
2062
|
+
"tokens",
|
|
2063
|
+
"typography",
|
|
2064
|
+
"version"
|
|
2065
|
+
];
|
|
2066
|
+
themeTypographyStyleKeys = ["fallbacks", "font", "letterSpacing", "transform"];
|
|
2067
|
+
themeTypographyKeys = [
|
|
2068
|
+
...themeTypographyStyleKeys,
|
|
2069
|
+
"places",
|
|
2070
|
+
"poi",
|
|
2071
|
+
"roads",
|
|
2072
|
+
"water"
|
|
2073
|
+
];
|
|
2074
|
+
themeTypographyDomains = ["places", "poi", "roads", "water"];
|
|
2075
|
+
themeLightingKeys = ["ambient", "anchor", "color", "intensity", "position", "sun"];
|
|
2076
|
+
themeSunKeys = ["altitude", "azimuth", "color", "intensity", "shadowIntensity"];
|
|
2077
|
+
themeAmbientKeys = ["color", "intensity"];
|
|
2078
|
+
expressionDataOperators = /* @__PURE__ */ new Set([
|
|
2079
|
+
"accumulated",
|
|
2080
|
+
"collator",
|
|
2081
|
+
"distance",
|
|
2082
|
+
"elevation",
|
|
2083
|
+
"error",
|
|
2084
|
+
"feature-state",
|
|
2085
|
+
"geometry-type",
|
|
2086
|
+
"get",
|
|
2087
|
+
"global-state",
|
|
2088
|
+
"has",
|
|
2089
|
+
"heatmap-density",
|
|
2090
|
+
"id",
|
|
2091
|
+
"in",
|
|
2092
|
+
"index-of",
|
|
2093
|
+
"is-supported-script",
|
|
2094
|
+
"length",
|
|
2095
|
+
"line-progress",
|
|
2096
|
+
"properties",
|
|
2097
|
+
"resolved-locale",
|
|
2098
|
+
"typeof",
|
|
2099
|
+
"within",
|
|
2100
|
+
"zoom"
|
|
2101
|
+
]);
|
|
2102
|
+
ThemeValueResolver = class {
|
|
2103
|
+
#cache = /* @__PURE__ */ new Map();
|
|
2104
|
+
#stack = [];
|
|
2105
|
+
#theme;
|
|
2106
|
+
constructor(theme) {
|
|
2107
|
+
assertThemeDocument(theme);
|
|
2108
|
+
this.#theme = theme;
|
|
2109
|
+
}
|
|
2110
|
+
resolveAllTokens() {
|
|
2111
|
+
for (const category of tileflowThemeTokenCategories) {
|
|
2112
|
+
this.resolveCategory(category);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
resolveCategory(category) {
|
|
2116
|
+
return Object.fromEntries(
|
|
2117
|
+
Object.keys(this.#theme.tokens[category]).sort(compareCodeUnits).map((name) => [
|
|
2118
|
+
name,
|
|
2119
|
+
this.#resolveToken(category, name, `theme.tokens.${category}.${name}`)
|
|
2120
|
+
])
|
|
2121
|
+
);
|
|
2122
|
+
}
|
|
2123
|
+
resolveTree(value, path, expectedCategory) {
|
|
2124
|
+
const category = isThemeStructuralPath(path) ? void 0 : expectedCategory ?? inferThemeCategoryFromPath(path);
|
|
2125
|
+
if (isThemeValueCandidate(value)) {
|
|
2126
|
+
assertThemeNodeResolutionContext(value, category, path);
|
|
2127
|
+
}
|
|
2128
|
+
if (isTileflowThemeTokenReference(value)) {
|
|
2129
|
+
return this.#resolveReference(value, path);
|
|
2130
|
+
}
|
|
2131
|
+
if (isTileflowFixedValue(value)) {
|
|
2132
|
+
return this.resolveTree(value.value, `${path}.value`, category);
|
|
2133
|
+
}
|
|
2134
|
+
if (isTileflowThemeColorOperation(value)) return this.#resolveColorOperation(value, path);
|
|
2135
|
+
if (Array.isArray(value)) {
|
|
2136
|
+
return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
|
|
2137
|
+
}
|
|
2138
|
+
if (!isRecord3(value)) return value;
|
|
2139
|
+
if (value.kind === "expression" && Array.isArray(value.value)) {
|
|
2140
|
+
return {
|
|
2141
|
+
...value,
|
|
2142
|
+
value: this.resolveTree(value.value, `${path}.value`, category)
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
if (value.kind === "zoom" && Array.isArray(value.stops)) {
|
|
2146
|
+
return {
|
|
2147
|
+
...value,
|
|
2148
|
+
stops: value.stops.map(
|
|
2149
|
+
(stop, index) => Array.isArray(stop) ? [
|
|
2150
|
+
this.resolveTree(stop[0], `${path}.stops[${index}][0]`),
|
|
2151
|
+
this.resolveTree(stop[1], `${path}.stops[${index}][1]`, category)
|
|
2152
|
+
] : this.resolveTree(stop, `${path}.stops[${index}]`)
|
|
2153
|
+
)
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
const result = {};
|
|
2157
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
2158
|
+
const nextPath = typeof key === "symbol" ? `${path}.[${String(key)}]` : `${path}.${String(key)}`;
|
|
2159
|
+
const nextValue = value[key];
|
|
2160
|
+
const nextCategory = typeof key === "string" ? classifyTileflowVisualProperty(key, nextValue) : void 0;
|
|
2161
|
+
result[key] = this.resolveTree(nextValue, nextPath, nextCategory);
|
|
2162
|
+
}
|
|
2163
|
+
return result;
|
|
2164
|
+
}
|
|
2165
|
+
resolveTypography(typography) {
|
|
2166
|
+
const global = this.#resolveTypographyStyle(typography, "theme.typography", {
|
|
2167
|
+
font: "Noto Sans Regular"
|
|
2168
|
+
});
|
|
2169
|
+
return {
|
|
2170
|
+
...global,
|
|
2171
|
+
places: this.#resolveTypographyStyle(typography.places, "theme.typography.places", global),
|
|
2172
|
+
poi: this.#resolveTypographyStyle(typography.poi, "theme.typography.poi", global),
|
|
2173
|
+
roads: this.#resolveTypographyStyle(typography.roads, "theme.typography.roads", global),
|
|
2174
|
+
water: this.#resolveTypographyStyle(typography.water, "theme.typography.water", global)
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
resolveLighting(lighting) {
|
|
2178
|
+
const { ambient, sun, ...flat } = lighting;
|
|
2179
|
+
const light = this.#resolveFlatLight(flat);
|
|
2180
|
+
if (sun === void 0 && ambient === void 0) return { light };
|
|
2181
|
+
const resolvedSun = this.resolveTree(sun ?? {}, "theme.lighting.sun");
|
|
2182
|
+
const resolvedAmbient = this.resolveTree(ambient ?? {}, "theme.lighting.ambient");
|
|
2183
|
+
const defaults = tileflowDefaultSunPreset;
|
|
2184
|
+
const number = (value, path, fallback, range2) => {
|
|
2185
|
+
if (value === void 0) return fallback;
|
|
2186
|
+
assertFiniteNumber(value, path);
|
|
2187
|
+
if (range2 && (value < range2[0] || value > range2[1])) {
|
|
2188
|
+
throw new Error(`Tileflow ${path} must be between ${range2[0]} and ${range2[1]}.`);
|
|
2189
|
+
}
|
|
2190
|
+
return value;
|
|
2191
|
+
};
|
|
2192
|
+
const colour = (value, path, fallback) => {
|
|
2193
|
+
if (value === void 0) return fallback;
|
|
2194
|
+
assertColor(value, path);
|
|
2195
|
+
return value;
|
|
2196
|
+
};
|
|
2197
|
+
const preset = {
|
|
2198
|
+
azimuth: normalizeTileflowAzimuth(
|
|
2199
|
+
number(resolvedSun.azimuth, "theme.lighting.sun.azimuth", defaults.azimuth)
|
|
2200
|
+
),
|
|
2201
|
+
altitude: number(
|
|
2202
|
+
resolvedSun.altitude,
|
|
2203
|
+
"theme.lighting.sun.altitude",
|
|
2204
|
+
defaults.altitude,
|
|
2205
|
+
[-90, 90]
|
|
2206
|
+
),
|
|
2207
|
+
color: colour(resolvedSun.color, "theme.lighting.sun.color", defaults.color),
|
|
2208
|
+
intensity: number(
|
|
2209
|
+
resolvedSun.intensity,
|
|
2210
|
+
"theme.lighting.sun.intensity",
|
|
2211
|
+
defaults.intensity,
|
|
2212
|
+
[0, 1]
|
|
2213
|
+
),
|
|
2214
|
+
shadowIntensity: number(
|
|
2215
|
+
resolvedSun.shadowIntensity,
|
|
2216
|
+
"theme.lighting.sun.shadowIntensity",
|
|
2217
|
+
defaults.shadowIntensity,
|
|
2218
|
+
[0, 1]
|
|
2219
|
+
),
|
|
2220
|
+
ambient: {
|
|
2221
|
+
color: colour(
|
|
2222
|
+
resolvedAmbient.color,
|
|
2223
|
+
"theme.lighting.ambient.color",
|
|
2224
|
+
defaults.ambient.color
|
|
2225
|
+
),
|
|
2226
|
+
intensity: number(
|
|
2227
|
+
resolvedAmbient.intensity,
|
|
2228
|
+
"theme.lighting.ambient.intensity",
|
|
2229
|
+
defaults.ambient.intensity,
|
|
2230
|
+
[0, 1]
|
|
2231
|
+
)
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
return { light: sun === void 0 ? light : tileflowSunLight(preset, light), sun: preset };
|
|
2235
|
+
}
|
|
2236
|
+
#resolveFlatLight(lighting) {
|
|
2237
|
+
const result = this.resolveTree(lighting, "theme.lighting");
|
|
2238
|
+
if (result.anchor !== void 0 && result.anchor !== "map" && result.anchor !== "viewport") {
|
|
2239
|
+
throw themeTypeError("theme.lighting.anchor", "map or viewport", result.anchor);
|
|
2240
|
+
}
|
|
2241
|
+
if (result.color !== void 0) assertColor(result.color, "theme.lighting.color");
|
|
2242
|
+
if (result.intensity !== void 0) {
|
|
2243
|
+
assertFiniteNumber(result.intensity, "theme.lighting.intensity");
|
|
2244
|
+
if (result.intensity < 0 || result.intensity > 1) {
|
|
2245
|
+
throw new Error("Tileflow theme.lighting.intensity must be between 0 and 1.");
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
if (result.position !== void 0) {
|
|
2249
|
+
if (!Array.isArray(result.position) || result.position.length !== 3 || result.position.some((entry) => typeof entry !== "number" || !Number.isFinite(entry))) {
|
|
2250
|
+
throw themeTypeError("theme.lighting.position", "three finite numbers", result.position);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
return result;
|
|
2254
|
+
}
|
|
2255
|
+
#resolveTypographyStyle(style, path, defaults) {
|
|
2256
|
+
const resolved = this.resolveTree(style ?? {}, path);
|
|
2257
|
+
const font = resolved.font ?? defaults.font;
|
|
2258
|
+
if (typeof font !== "string" || !font) throw themeTypeError(`${path}.font`, "font", font);
|
|
2259
|
+
assertFontName(font, `${path}.font`);
|
|
2260
|
+
const fallbacks = resolved.fallbacks ?? defaults.fallbacks;
|
|
2261
|
+
if (fallbacks !== void 0 && (!Array.isArray(fallbacks) || fallbacks.some((entry) => typeof entry !== "string" || !entry))) {
|
|
2262
|
+
throw themeTypeError(`${path}.fallbacks`, "font array", fallbacks);
|
|
2263
|
+
}
|
|
2264
|
+
if (Array.isArray(fallbacks)) {
|
|
2265
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2266
|
+
for (const [index, fallback] of fallbacks.entries()) {
|
|
2267
|
+
assertFontName(fallback, `${path}.fallbacks[${index}]`);
|
|
2268
|
+
if (seen.has(fallback)) {
|
|
2269
|
+
throw new Error(
|
|
2270
|
+
`Duplicate Tileflow font fallback ${JSON.stringify(fallback)} at ${path}.fallbacks[${index}].`
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
seen.add(fallback);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const letterSpacing = resolved.letterSpacing ?? defaults.letterSpacing;
|
|
2277
|
+
if (letterSpacing !== void 0) assertFiniteNumber(letterSpacing, `${path}.letterSpacing`);
|
|
2278
|
+
const transform = resolved.transform ?? defaults.transform;
|
|
2279
|
+
if (transform !== void 0 && !["lowercase", "none", "uppercase"].includes(String(transform))) {
|
|
2280
|
+
throw themeTypeError(`${path}.transform`, "text transform", transform);
|
|
2281
|
+
}
|
|
2282
|
+
return {
|
|
2283
|
+
font,
|
|
2284
|
+
...fallbacks === void 0 ? {} : { fallbacks: [...fallbacks] },
|
|
2285
|
+
...letterSpacing === void 0 ? {} : { letterSpacing },
|
|
2286
|
+
...transform === void 0 ? {} : { transform }
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
#resolveReference(reference, path) {
|
|
2290
|
+
return this.#resolveToken(reference.category, reference.token, path);
|
|
2291
|
+
}
|
|
2292
|
+
#resolveToken(category, name, consumedAt) {
|
|
2293
|
+
const cacheKey = `${category}:${name}`;
|
|
2294
|
+
const cached = this.#cache.get(cacheKey);
|
|
2295
|
+
if (cached !== void 0) return cached;
|
|
2296
|
+
const tokens = this.#theme.tokens[category];
|
|
2297
|
+
if (!Object.hasOwn(tokens, name)) {
|
|
2298
|
+
throw new Error(
|
|
2299
|
+
`Unknown Tileflow ${category} token "${name}" at ${consumedAt} in theme "${this.#theme.id}".`
|
|
2300
|
+
);
|
|
2301
|
+
}
|
|
2302
|
+
const cycleStart = this.#stack.indexOf(cacheKey);
|
|
2303
|
+
if (cycleStart >= 0) {
|
|
2304
|
+
const cycle = [...this.#stack.slice(cycleStart), cacheKey].map((key) => key.replace(":", ".")).join(" -> ");
|
|
2305
|
+
throw new Error(`Circular Tileflow theme token reference: ${cycle}.`);
|
|
2306
|
+
}
|
|
2307
|
+
this.#stack.push(cacheKey);
|
|
2308
|
+
try {
|
|
2309
|
+
const path = `theme.tokens.${category}.${name}`;
|
|
2310
|
+
const value = this.resolveTree(tokens[name], path, category);
|
|
2311
|
+
assertTokenPrimitive(category, value, path);
|
|
2312
|
+
this.#cache.set(cacheKey, value);
|
|
2313
|
+
return value;
|
|
2314
|
+
} finally {
|
|
2315
|
+
this.#stack.pop();
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
#resolveColorOperation(operation, path) {
|
|
2319
|
+
if (operation.operation === "alpha") {
|
|
2320
|
+
const source = this.resolveTree(operation.color, `${path}.color`, "color");
|
|
2321
|
+
const opacity = this.resolveTree(operation.opacity, `${path}.opacity`, "number");
|
|
2322
|
+
assertColor(source, `${path}.color`);
|
|
2323
|
+
assertUnitInterval(opacity, `${path}.opacity`);
|
|
2324
|
+
return applyAlpha(source, opacity);
|
|
2325
|
+
}
|
|
2326
|
+
const from = this.resolveTree(operation.from, `${path}.from`, "color");
|
|
2327
|
+
const to = this.resolveTree(operation.to, `${path}.to`, "color");
|
|
2328
|
+
const amount = this.resolveTree(operation.amount, `${path}.amount`, "number");
|
|
2329
|
+
assertColor(from, `${path}.from`);
|
|
2330
|
+
assertColor(to, `${path}.to`);
|
|
2331
|
+
assertUnitInterval(amount, `${path}.amount`);
|
|
2332
|
+
if (operation.space !== "oklch") {
|
|
2333
|
+
throw new Error(`Tileflow theme color mix at ${path} must use the oklch color space.`);
|
|
2334
|
+
}
|
|
2335
|
+
return mixOklch(from, to, amount);
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
themeStructuralPathSegments = /* @__PURE__ */ new Set([
|
|
2339
|
+
"after",
|
|
2340
|
+
"before",
|
|
2341
|
+
"filter",
|
|
2342
|
+
"id",
|
|
2343
|
+
"maxZoom",
|
|
2344
|
+
"maxzoom",
|
|
2345
|
+
"metadata",
|
|
2346
|
+
"minZoom",
|
|
2347
|
+
"minzoom",
|
|
2348
|
+
"owner",
|
|
2349
|
+
"placement",
|
|
2350
|
+
"requires",
|
|
2351
|
+
"source",
|
|
2352
|
+
"source-layer",
|
|
2353
|
+
"sourceLayer",
|
|
2354
|
+
"target",
|
|
2355
|
+
"type",
|
|
2356
|
+
"visibility"
|
|
2357
|
+
]);
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
|
|
2361
|
+
// src/color-program.ts
|
|
2362
|
+
function colorProgramMarker(value, program) {
|
|
2363
|
+
return [
|
|
2364
|
+
"let",
|
|
2365
|
+
marker,
|
|
2366
|
+
["literal", program],
|
|
2367
|
+
"__tileflow_color_value",
|
|
2368
|
+
value,
|
|
2369
|
+
["var", "__tileflow_color_value"]
|
|
2370
|
+
];
|
|
2371
|
+
}
|
|
2372
|
+
function isColorProgramMarker(value) {
|
|
2373
|
+
return Array.isArray(value) && value.length === 6 && value[0] === "let" && value[1] === marker && Array.isArray(value[2]) && value[2][0] === "literal";
|
|
2374
|
+
}
|
|
2375
|
+
function colorProgramValue(value) {
|
|
2376
|
+
return isColorProgramMarker(value) ? value[4] : value;
|
|
2377
|
+
}
|
|
2378
|
+
function colorProgramOf(value) {
|
|
2379
|
+
return isColorProgramMarker(value) ? value[2][1] : value;
|
|
2380
|
+
}
|
|
2381
|
+
function extractTileflowColorPrograms(style, tokens) {
|
|
2382
|
+
const programs = [];
|
|
2383
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2384
|
+
const bindings = [];
|
|
2385
|
+
const visit = (node, path) => {
|
|
2386
|
+
if (record2(node) && node.kind === "expression" && isColorProgramMarker(node.value))
|
|
2387
|
+
return visit(node.value, path);
|
|
2388
|
+
if (isColorProgramMarker(node)) {
|
|
2389
|
+
const program = colorProgramOf(node);
|
|
2390
|
+
const key = JSON.stringify(program);
|
|
2391
|
+
let id = ids.get(key);
|
|
2392
|
+
if (id === void 0) {
|
|
2393
|
+
id = programs.length;
|
|
2394
|
+
ids.set(key, id);
|
|
2395
|
+
programs.push(program);
|
|
2396
|
+
}
|
|
2397
|
+
bindings.push([
|
|
2398
|
+
path[0] === "layers" && path[2] === "paint" ? [path[1], ...path.slice(3)] : path,
|
|
2399
|
+
id
|
|
2400
|
+
]);
|
|
2401
|
+
return colorProgramValue(node);
|
|
2402
|
+
}
|
|
2403
|
+
if (Array.isArray(node)) return node.map((v, i) => visit(v, [...path, i]));
|
|
2404
|
+
if (record2(node))
|
|
2405
|
+
return Object.fromEntries(
|
|
2406
|
+
Object.entries(node).filter(([key]) => key !== tileflowColorMetadataKey).map(([key, v]) => [key, visit(v, [...path, key])])
|
|
2407
|
+
);
|
|
2408
|
+
return node;
|
|
2409
|
+
};
|
|
2410
|
+
const result = visit(style, []);
|
|
2411
|
+
if (bindings.length)
|
|
2412
|
+
result.metadata = {
|
|
2413
|
+
...result.metadata,
|
|
2414
|
+
// Keep the declared catalog shape across presets. Per-preset pruning makes a shared
|
|
2415
|
+
// object structurally different and repeats the entire catalog in the transport.
|
|
2416
|
+
[tileflowColorMetadataKey]: { version: 1, programs, bindings, tokens }
|
|
2417
|
+
};
|
|
2418
|
+
return result;
|
|
2419
|
+
}
|
|
2420
|
+
function restoreTileflowColorPrograms(style) {
|
|
2421
|
+
const result = structuredClone(style);
|
|
2422
|
+
const metadata = result.metadata?.[tileflowColorMetadataKey];
|
|
2423
|
+
if (!metadata) return result;
|
|
2424
|
+
validateBindings(metadata);
|
|
2425
|
+
for (const [path, id] of metadata.bindings)
|
|
2426
|
+
updatePath(result, path, (value) => colorProgramMarker(value, metadata.programs[id]));
|
|
2427
|
+
return result;
|
|
2428
|
+
}
|
|
2429
|
+
function updatePath(root, inputPath, change) {
|
|
2430
|
+
const path = typeof inputPath[0] === "number" ? ["layers", inputPath[0], "paint", ...inputPath.slice(1)] : inputPath;
|
|
2431
|
+
if (!path.length || path.length > 96 || path.some((key) => ["__proto__", "constructor", "prototype"].includes(String(key))))
|
|
2432
|
+
throw new TypeError("Invalid colour binding path.");
|
|
2433
|
+
let node = root;
|
|
2434
|
+
for (const key of path.slice(0, -1)) {
|
|
2435
|
+
if (!node || typeof node !== "object" || !Object.hasOwn(node, key))
|
|
2436
|
+
throw new TypeError("Missing colour binding path.");
|
|
2437
|
+
node = node[key];
|
|
2438
|
+
}
|
|
2439
|
+
const last = path.at(-1);
|
|
2440
|
+
if (!node || !Object.hasOwn(node, last)) throw new TypeError("Missing colour binding value.");
|
|
2441
|
+
node[last] = change(node[last]);
|
|
2442
|
+
}
|
|
2443
|
+
function validateBindings(value) {
|
|
2444
|
+
if (value.version !== 1 || !Array.isArray(value.programs) || value.programs.length > 4096 || !Array.isArray(value.bindings) || value.bindings.length > 16384 || !record2(value.tokens?.color) || !record2(value.tokens?.number))
|
|
2445
|
+
throw new TypeError("Invalid colour program catalog.");
|
|
2446
|
+
for (const [path, id] of value.bindings) {
|
|
2447
|
+
if (!Array.isArray(path) || !path.length || path.some((key) => typeof key !== "string" && !Number.isSafeInteger(key)) || !Number.isSafeInteger(id) || id < 0 || id >= value.programs.length || !(typeof path[0] === "number" || ["metadata", "light", "sky"].includes(String(path[0]))))
|
|
2448
|
+
throw new TypeError("Invalid colour program binding.");
|
|
2449
|
+
if (typeof path[0] === "number" && (path[0] < 0 || !String(path[1]).endsWith("color")))
|
|
2450
|
+
throw new TypeError("Colour programs can only change paint colours.");
|
|
2451
|
+
if (path[0] === "metadata" && (path[1] === tileflowColorMetadataKey || ![
|
|
2452
|
+
"tileflow:buildings",
|
|
2453
|
+
"tileflow:vegetation",
|
|
2454
|
+
"tileflow:sun",
|
|
2455
|
+
"tileflow:sky",
|
|
2456
|
+
"tileflow:route"
|
|
2457
|
+
].includes(String(path[1]))))
|
|
2458
|
+
throw new TypeError("Invalid colour metadata binding.");
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
function record2(value) {
|
|
2462
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2463
|
+
}
|
|
2464
|
+
var tileflowColorMetadataKey, marker;
|
|
2465
|
+
var init_color_program = __esm({
|
|
2466
|
+
"src/color-program.ts"() {
|
|
2467
|
+
"use strict";
|
|
2468
|
+
tileflowColorMetadataKey = "tileflow:colors";
|
|
2469
|
+
marker = "__tileflow_color_program";
|
|
2470
|
+
}
|
|
2471
|
+
});
|
|
2472
|
+
|
|
89
2473
|
// src/manifest-types.ts
|
|
90
|
-
var tileflowRuntimeManifestVersion, tileflowTimeOfDayManifestVersion, tileflowRuntimeManifestLimits;
|
|
2474
|
+
var tileflowRuntimeManifestVersion, tileflowTimeOfDayManifestVersion, tileflowFamilyManifestVersion, tileflowRuntimeManifestLimits;
|
|
91
2475
|
var init_manifest_types = __esm({
|
|
92
2476
|
"src/manifest-types.ts"() {
|
|
93
2477
|
"use strict";
|
|
94
2478
|
tileflowRuntimeManifestVersion = 1;
|
|
95
2479
|
tileflowTimeOfDayManifestVersion = 2;
|
|
2480
|
+
tileflowFamilyManifestVersion = 3;
|
|
96
2481
|
tileflowRuntimeManifestLimits = Object.freeze({ maximumBytes: 1024 * 1024 });
|
|
97
2482
|
}
|
|
98
2483
|
});
|
|
@@ -154,23 +2539,6 @@ var init_runtime_view_schema = __esm({
|
|
|
154
2539
|
}
|
|
155
2540
|
});
|
|
156
2541
|
|
|
157
|
-
// src/themes/model.ts
|
|
158
|
-
var tileflowThemeLimits, themeTypographyStyleKeys, themeTypographyKeys;
|
|
159
|
-
var init_model = __esm({
|
|
160
|
-
"src/themes/model.ts"() {
|
|
161
|
-
"use strict";
|
|
162
|
-
tileflowThemeLimits = Object.freeze({ maxThemes: 64 });
|
|
163
|
-
themeTypographyStyleKeys = ["fallbacks", "font", "letterSpacing", "transform"];
|
|
164
|
-
themeTypographyKeys = [
|
|
165
|
-
...themeTypographyStyleKeys,
|
|
166
|
-
"places",
|
|
167
|
-
"poi",
|
|
168
|
-
"roads",
|
|
169
|
-
"water"
|
|
170
|
-
];
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
|
|
174
2542
|
// src/time-of-day-schema.ts
|
|
175
2543
|
function createTileflowTimeOfDaySchema(operations, themeName) {
|
|
176
2544
|
return operations.record(
|
|
@@ -236,6 +2604,7 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
|
|
|
236
2604
|
apiUrl: operations.optional(apiOriginSchema),
|
|
237
2605
|
defaultTheme: tileflowThemeNameSchema2,
|
|
238
2606
|
environment: operations.optional(operations.maxLength(safeTextSchema, 128)),
|
|
2607
|
+
familyUrl: operations.optional(publicResourceUrlSchema),
|
|
239
2608
|
mapId: operations.optional(operations.maxLength(safeTextSchema, 128)),
|
|
240
2609
|
systemThemes: operations.optional(
|
|
241
2610
|
operations.object({
|
|
@@ -317,11 +2686,20 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
|
|
|
317
2686
|
maps: operations.record(tileflowPortableIdSchema2, manifestMapSchema),
|
|
318
2687
|
version: operations.union(
|
|
319
2688
|
operations.literal(tileflowRuntimeManifestVersion),
|
|
320
|
-
operations.literal(2)
|
|
2689
|
+
operations.union(operations.literal(2), operations.literal(3))
|
|
321
2690
|
)
|
|
322
2691
|
}),
|
|
323
2692
|
(manifest, context) => {
|
|
324
2693
|
const mapCount = Object.keys(manifest.maps).length;
|
|
2694
|
+
if (manifest.version !== 3)
|
|
2695
|
+
for (const [name, map] of Object.entries(manifest.maps)) {
|
|
2696
|
+
if (map.familyUrl !== void 0)
|
|
2697
|
+
context.addIssue({
|
|
2698
|
+
code: "custom",
|
|
2699
|
+
message: "familyUrl requires manifest version 3",
|
|
2700
|
+
path: ["maps", name, "familyUrl"]
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
325
2703
|
if (manifest.version === 1) {
|
|
326
2704
|
for (const [name, map] of Object.entries(manifest.maps)) {
|
|
327
2705
|
if (map.timeOfDay !== void 0)
|
|
@@ -534,6 +2912,7 @@ var manifest_exports = {};
|
|
|
534
2912
|
__export(manifest_exports, {
|
|
535
2913
|
parseTileflowRuntimeManifest: () => parseTileflowRuntimeManifest,
|
|
536
2914
|
safeParseTileflowRuntimeManifest: () => safeParseTileflowRuntimeManifest,
|
|
2915
|
+
tileflowFamilyManifestVersion: () => tileflowFamilyManifestVersion,
|
|
537
2916
|
tileflowRuntimeManifestLimits: () => tileflowRuntimeManifestLimits,
|
|
538
2917
|
tileflowRuntimeManifestSchema: () => tileflowRuntimeManifestSchema,
|
|
539
2918
|
tileflowRuntimeManifestVersion: () => tileflowRuntimeManifestVersion,
|
|
@@ -567,6 +2946,664 @@ var init_manifest = __esm({
|
|
|
567
2946
|
// src/runtime.ts
|
|
568
2947
|
init_portable_identity_rules();
|
|
569
2948
|
init_time_of_day();
|
|
2949
|
+
|
|
2950
|
+
// src/style-family.ts
|
|
2951
|
+
init_portable_identity_rules();
|
|
2952
|
+
var tileflowStyleFamilyLimits = Object.freeze({
|
|
2953
|
+
themes: 64,
|
|
2954
|
+
documentBytes: 16 * 1024 * 1024,
|
|
2955
|
+
expandedStyleBytes: 8 * 1024 * 1024,
|
|
2956
|
+
expandedFamilyBytes: 32 * 1024 * 1024,
|
|
2957
|
+
layers: 4096,
|
|
2958
|
+
nodes: 5e5,
|
|
2959
|
+
depth: 96
|
|
2960
|
+
});
|
|
2961
|
+
function createTileflowStyleFamily(styles) {
|
|
2962
|
+
const themes = Object.keys(styles).sort();
|
|
2963
|
+
if (!themes.length || themes.length > tileflowStyleFamilyLimits.themes || themes.some((name) => !isTileflowThemeName(name)))
|
|
2964
|
+
fail("Invalid theme names.");
|
|
2965
|
+
const parameters = [];
|
|
2966
|
+
const columns = /* @__PURE__ */ new Map();
|
|
2967
|
+
const add = (values) => {
|
|
2968
|
+
const key = serializeCanonicalJson(values);
|
|
2969
|
+
let index = columns.get(key);
|
|
2970
|
+
if (index === void 0) {
|
|
2971
|
+
index = parameters.length;
|
|
2972
|
+
columns.set(key, index);
|
|
2973
|
+
parameters.push(values);
|
|
2974
|
+
}
|
|
2975
|
+
return { $variant: index };
|
|
2976
|
+
};
|
|
2977
|
+
const factor = (values) => {
|
|
2978
|
+
const first = values[0];
|
|
2979
|
+
if (values.some((value) => isReference(value) || isConstantReference(value)))
|
|
2980
|
+
return add(values);
|
|
2981
|
+
if (Array.isArray(first) && values.every((v) => Array.isArray(v) && v.length === first.length))
|
|
2982
|
+
return first.map((_, index) => factor(values.map((v) => v[index])));
|
|
2983
|
+
if (record(first) && values.every((v) => record(v) && same(Object.keys(v).sort(), Object.keys(first).sort())))
|
|
2984
|
+
return Object.fromEntries(
|
|
2985
|
+
Object.keys(first).sort().map((key) => [key, factor(values.map((v) => v[key]))])
|
|
2986
|
+
);
|
|
2987
|
+
if (values.every((v) => same(v, first))) return first;
|
|
2988
|
+
return add(values);
|
|
2989
|
+
};
|
|
2990
|
+
const documents = themes.map((name) => JSON.parse(serializeCanonicalJson(styles[name])));
|
|
2991
|
+
for (const document2 of documents) assertStyle(document2);
|
|
2992
|
+
const template = factor(documents);
|
|
2993
|
+
const packed = shareConstants(template);
|
|
2994
|
+
const family = {
|
|
2995
|
+
kind: "tileflow-style-family",
|
|
2996
|
+
version: 1,
|
|
2997
|
+
themes,
|
|
2998
|
+
template: packed.template,
|
|
2999
|
+
constants: packed.constants,
|
|
3000
|
+
parameters,
|
|
3001
|
+
updates: planUpdates(template)
|
|
3002
|
+
};
|
|
3003
|
+
return parseTileflowStyleFamily(family);
|
|
3004
|
+
}
|
|
3005
|
+
function parseTileflowStyleFamily(input) {
|
|
3006
|
+
assertJsonBudget(input);
|
|
3007
|
+
if (!record(input) || !same(Object.keys(input).sort(), [
|
|
3008
|
+
"constants",
|
|
3009
|
+
"kind",
|
|
3010
|
+
"parameters",
|
|
3011
|
+
"template",
|
|
3012
|
+
"themes",
|
|
3013
|
+
"updates",
|
|
3014
|
+
"version"
|
|
3015
|
+
]) || input.kind !== "tileflow-style-family" || input.version !== 1 || !Array.isArray(input.themes) || !input.themes.length || input.themes.length > tileflowStyleFamilyLimits.themes || input.themes.some((name) => typeof name !== "string" || !isTileflowThemeName(name)) || new Set(input.themes).size !== input.themes.length || !Array.isArray(input.parameters) || !Array.isArray(input.constants))
|
|
3016
|
+
fail("Invalid family document.");
|
|
3017
|
+
const family = input;
|
|
3018
|
+
if (bytes(input) > tileflowStyleFamilyLimits.documentBytes) fail("Family document is too large.");
|
|
3019
|
+
for (const values of family.parameters)
|
|
3020
|
+
if (!Array.isArray(values) || values.length !== family.themes.length)
|
|
3021
|
+
fail("Invalid parameter column.");
|
|
3022
|
+
walk(family.template, (value) => {
|
|
3023
|
+
if (isReference(value) && (!Number.isSafeInteger(value.$variant) || value.$variant < 0 || value.$variant >= family.parameters.length))
|
|
3024
|
+
fail("Invalid parameter reference.");
|
|
3025
|
+
if (isConstantReference(value) && (!Number.isSafeInteger(value.$constant) || value.$constant < 0 || value.$constant >= family.constants.length))
|
|
3026
|
+
fail("Invalid constant reference.");
|
|
3027
|
+
});
|
|
3028
|
+
let total = 0;
|
|
3029
|
+
const sizes = family.parameters.map((column) => column.map(bytes));
|
|
3030
|
+
const constants = family.constants.map(bytes);
|
|
3031
|
+
for (let index = 0; index < family.themes.length; index += 1) {
|
|
3032
|
+
const size = expandedBytes(family.template, sizes, constants, index);
|
|
3033
|
+
total += size;
|
|
3034
|
+
if (size > tileflowStyleFamilyLimits.expandedStyleBytes || total > tileflowStyleFamilyLimits.expandedFamilyBytes)
|
|
3035
|
+
fail("Expanded family is too large.");
|
|
3036
|
+
const style = materialize(family.template, family, index);
|
|
3037
|
+
assertJsonBudget(style);
|
|
3038
|
+
assertStyle(style);
|
|
3039
|
+
}
|
|
3040
|
+
if (!same(family.updates, planUpdates(expandTileflowFamilyTemplate(family))))
|
|
3041
|
+
fail("Invalid update plan.");
|
|
3042
|
+
return family;
|
|
3043
|
+
}
|
|
3044
|
+
function materializeTileflowStyleFamily(family, theme) {
|
|
3045
|
+
const index = family.themes.indexOf(theme);
|
|
3046
|
+
if (index < 0) fail(`Unknown family theme: ${theme}.`);
|
|
3047
|
+
return materialize(family.template, family, index);
|
|
3048
|
+
}
|
|
3049
|
+
function expandTileflowStyleFamily(input) {
|
|
3050
|
+
const family = parseTileflowStyleFamily(input);
|
|
3051
|
+
return Object.fromEntries(
|
|
3052
|
+
family.themes.map((name) => [name, materializeTileflowStyleFamily(family, name)])
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
function readTileflowFamilyValue(family, path, theme) {
|
|
3056
|
+
const index = family.themes.indexOf(theme);
|
|
3057
|
+
if (index < 0) fail(`Unknown family theme: ${theme}.`);
|
|
3058
|
+
let node = family.template;
|
|
3059
|
+
for (const key of path) {
|
|
3060
|
+
if (isReference(node)) node = family.parameters[node.$variant][index];
|
|
3061
|
+
else if (isConstantReference(node)) node = family.constants[node.$constant];
|
|
3062
|
+
node = node && typeof node === "object" ? node[key] : void 0;
|
|
3063
|
+
}
|
|
3064
|
+
return node === void 0 ? void 0 : materialize(node, family, index);
|
|
3065
|
+
}
|
|
3066
|
+
function materialize(node, family, index) {
|
|
3067
|
+
if (isReference(node)) return structuredClone(family.parameters[node.$variant][index]);
|
|
3068
|
+
if (isConstantReference(node)) return structuredClone(family.constants[node.$constant]);
|
|
3069
|
+
if (Array.isArray(node)) return node.map((value) => materialize(value, family, index));
|
|
3070
|
+
if (record(node))
|
|
3071
|
+
return Object.fromEntries(
|
|
3072
|
+
Object.entries(node).map(([key, value]) => [key, materialize(value, family, index)])
|
|
3073
|
+
);
|
|
3074
|
+
return node;
|
|
3075
|
+
}
|
|
3076
|
+
function planUpdates(template) {
|
|
3077
|
+
let appearanceOnly = true;
|
|
3078
|
+
const paint = /* @__PURE__ */ new Map();
|
|
3079
|
+
const roots = /* @__PURE__ */ new Set();
|
|
3080
|
+
walk(template, (node, path) => {
|
|
3081
|
+
if (!isReference(node)) return;
|
|
3082
|
+
if (path[0] === "name") return;
|
|
3083
|
+
if (path[0] === "metadata") {
|
|
3084
|
+
const key = path[1];
|
|
3085
|
+
if ([
|
|
3086
|
+
"tileflow:colors",
|
|
3087
|
+
"tileflow:family-image-theme",
|
|
3088
|
+
"tileflow:theme",
|
|
3089
|
+
"tileflow:colorScheme",
|
|
3090
|
+
"tileflow:sun",
|
|
3091
|
+
"tileflow:route",
|
|
3092
|
+
"tileflow:sky",
|
|
3093
|
+
"tileflow:sourceRequirements",
|
|
3094
|
+
"tileflow:dataRequirements"
|
|
3095
|
+
].includes(String(key)))
|
|
3096
|
+
return;
|
|
3097
|
+
if (key === "tileflow:buildings" && ["color", "modelColors", "modelLighting", "floodLight"].includes(String(path[2])))
|
|
3098
|
+
return;
|
|
3099
|
+
if (key === "tileflow:vegetation" && path[2] === "colors") return;
|
|
3100
|
+
appearanceOnly = false;
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
if (path[0] === "layers" && path[2] === "layout" && path[3] === "icon-image") return;
|
|
3104
|
+
if (path[0] === "sky" || path[0] === "light") {
|
|
3105
|
+
roots.add(path[0]);
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
if (path[0] === "layers" && typeof path[1] === "number" && path[2] === "paint" && typeof path[3] === "string") {
|
|
3109
|
+
paint.set(`${path[1]}/${path[3]}`, [path[1], path[3]]);
|
|
3110
|
+
} else appearanceOnly = false;
|
|
3111
|
+
});
|
|
3112
|
+
if (record(template) && record(template.metadata) && template.metadata["tileflow:colors"] && Array.isArray(template.layers))
|
|
3113
|
+
template.layers.forEach((layer, index) => {
|
|
3114
|
+
if (record(layer) && record(layer.paint)) {
|
|
3115
|
+
for (const property of Object.keys(layer.paint))
|
|
3116
|
+
if (property.endsWith("color")) paint.set(`${index}/${property}`, [index, property]);
|
|
3117
|
+
}
|
|
3118
|
+
});
|
|
3119
|
+
return { appearanceOnly, paint: [...paint.values()], roots: [...roots].sort() };
|
|
3120
|
+
}
|
|
3121
|
+
function expandedBytes(node, sizes, constants, index) {
|
|
3122
|
+
if (isReference(node)) return sizes[node.$variant][index];
|
|
3123
|
+
if (isConstantReference(node)) return constants[node.$constant];
|
|
3124
|
+
if (Array.isArray(node))
|
|
3125
|
+
return 2 + Math.max(0, node.length - 1) + node.reduce((n, v) => n + expandedBytes(v, sizes, constants, index), 0);
|
|
3126
|
+
if (record(node))
|
|
3127
|
+
return 2 + Math.max(0, Object.keys(node).length - 1) + Object.entries(node).reduce(
|
|
3128
|
+
(n, [key, v]) => n + bytes(key) + 1 + expandedBytes(v, sizes, constants, index),
|
|
3129
|
+
0
|
|
3130
|
+
);
|
|
3131
|
+
return bytes(node);
|
|
3132
|
+
}
|
|
3133
|
+
function assertStyle(style) {
|
|
3134
|
+
if (!record(style) || style.version !== 8 || !record(style.sources) || !Array.isArray(style.layers) || style.layers.length > tileflowStyleFamilyLimits.layers)
|
|
3135
|
+
fail("Invalid expanded MapLibre style.");
|
|
3136
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3137
|
+
for (const layer of style.layers) {
|
|
3138
|
+
if (!record(layer) || typeof layer.id !== "string" || ids.has(layer.id) || typeof layer.type !== "string")
|
|
3139
|
+
fail("Invalid or duplicate layer.");
|
|
3140
|
+
ids.add(layer.id);
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
function assertJsonBudget(input) {
|
|
3144
|
+
let nodes = 0;
|
|
3145
|
+
const stack = /* @__PURE__ */ new Set();
|
|
3146
|
+
const visit = (value, depth) => {
|
|
3147
|
+
if (++nodes > tileflowStyleFamilyLimits.nodes || depth > tileflowStyleFamilyLimits.depth)
|
|
3148
|
+
fail("Family complexity limit exceeded.");
|
|
3149
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value))
|
|
3150
|
+
return;
|
|
3151
|
+
if (typeof value !== "object" || !value || stack.has(value)) fail("Expected acyclic JSON.");
|
|
3152
|
+
stack.add(value);
|
|
3153
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
|
3154
|
+
fail("Expected JSON objects.");
|
|
3155
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3156
|
+
if (["__proto__", "constructor", "prototype"].includes(key)) fail("Unsafe object key.");
|
|
3157
|
+
visit(item, depth + 1);
|
|
3158
|
+
}
|
|
3159
|
+
stack.delete(value);
|
|
3160
|
+
};
|
|
3161
|
+
visit(input, 0);
|
|
3162
|
+
}
|
|
3163
|
+
function walk(node, visit, path = []) {
|
|
3164
|
+
visit(node, path);
|
|
3165
|
+
if (isReference(node) || isConstantReference(node)) return;
|
|
3166
|
+
if (Array.isArray(node)) node.forEach((value, i) => walk(value, visit, [...path, i]));
|
|
3167
|
+
else if (record(node))
|
|
3168
|
+
Object.entries(node).forEach(([key, value]) => walk(value, visit, [...path, key]));
|
|
3169
|
+
}
|
|
3170
|
+
function isReference(node) {
|
|
3171
|
+
return record(node) && Object.keys(node).length === 1 && Object.hasOwn(node, "$variant");
|
|
3172
|
+
}
|
|
3173
|
+
function isConstantReference(node) {
|
|
3174
|
+
return record(node) && Object.keys(node).length === 1 && Object.hasOwn(node, "$constant");
|
|
3175
|
+
}
|
|
3176
|
+
function expandTileflowFamilyTemplate(family) {
|
|
3177
|
+
const visit = (node) => {
|
|
3178
|
+
if (isConstantReference(node)) return structuredClone(family.constants[node.$constant]);
|
|
3179
|
+
if (isReference(node)) return node;
|
|
3180
|
+
if (Array.isArray(node)) return node.map(visit);
|
|
3181
|
+
if (record(node))
|
|
3182
|
+
return Object.fromEntries(
|
|
3183
|
+
Object.entries(node).map(([key, value]) => [key, visit(value)])
|
|
3184
|
+
);
|
|
3185
|
+
return node;
|
|
3186
|
+
};
|
|
3187
|
+
return visit(family.template);
|
|
3188
|
+
}
|
|
3189
|
+
function withTileflowFamilyTemplate(family, template) {
|
|
3190
|
+
assertJsonBudget(template);
|
|
3191
|
+
const packed = shareConstants(template);
|
|
3192
|
+
return parseTileflowStyleFamily({
|
|
3193
|
+
...family,
|
|
3194
|
+
template: packed.template,
|
|
3195
|
+
constants: packed.constants
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
function shareConstants(template) {
|
|
3199
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3200
|
+
const keys = /* @__PURE__ */ new WeakMap();
|
|
3201
|
+
const inspect = (node) => {
|
|
3202
|
+
if (isReference(node) || isConstantReference(node)) return false;
|
|
3203
|
+
if (!node || typeof node !== "object") return true;
|
|
3204
|
+
const children = Array.isArray(node) ? node : Object.values(node);
|
|
3205
|
+
const constant = children.map(inspect).every(Boolean);
|
|
3206
|
+
if (constant) {
|
|
3207
|
+
const key = JSON.stringify(node);
|
|
3208
|
+
if (key.length >= 96) {
|
|
3209
|
+
keys.set(node, key);
|
|
3210
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
return constant;
|
|
3214
|
+
};
|
|
3215
|
+
inspect(template);
|
|
3216
|
+
const constants = [];
|
|
3217
|
+
const ids = /* @__PURE__ */ new Map();
|
|
3218
|
+
const pack = (node) => {
|
|
3219
|
+
if (!node || typeof node !== "object" || isReference(node)) return node;
|
|
3220
|
+
const key = keys.get(node);
|
|
3221
|
+
if (key && counts.get(key) > 1) {
|
|
3222
|
+
let index = ids.get(key);
|
|
3223
|
+
if (index === void 0) {
|
|
3224
|
+
index = constants.length;
|
|
3225
|
+
ids.set(key, index);
|
|
3226
|
+
constants.push(node);
|
|
3227
|
+
}
|
|
3228
|
+
return { $constant: index };
|
|
3229
|
+
}
|
|
3230
|
+
if (Array.isArray(node)) return node.map(pack);
|
|
3231
|
+
return Object.fromEntries(
|
|
3232
|
+
Object.entries(node).map(([key2, value]) => [key2, pack(value)])
|
|
3233
|
+
);
|
|
3234
|
+
};
|
|
3235
|
+
return { template: pack(template), constants };
|
|
3236
|
+
}
|
|
3237
|
+
function record(node) {
|
|
3238
|
+
return !!node && typeof node === "object" && !Array.isArray(node);
|
|
3239
|
+
}
|
|
3240
|
+
function same(a, b) {
|
|
3241
|
+
return serializeCanonicalJson(a) === serializeCanonicalJson(b);
|
|
3242
|
+
}
|
|
3243
|
+
function serializeCanonicalJson(value) {
|
|
3244
|
+
const sort = (node) => Array.isArray(node) ? node.map(sort) : record(node) ? Object.fromEntries(
|
|
3245
|
+
Object.keys(node).sort().map((key) => [key, sort(node[key])])
|
|
3246
|
+
) : node;
|
|
3247
|
+
return JSON.stringify(sort(value));
|
|
3248
|
+
}
|
|
3249
|
+
function bytes(value) {
|
|
3250
|
+
return new TextEncoder().encode(JSON.stringify(value)).length;
|
|
3251
|
+
}
|
|
3252
|
+
function fail(message) {
|
|
3253
|
+
throw new TypeError(`Tileflow style family: ${message}`);
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
// src/style-family-loader.ts
|
|
3257
|
+
async function loadTileflowStyleFamily(url, options = {}) {
|
|
3258
|
+
const request = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3259
|
+
const response = await request(url, { credentials: "same-origin", signal: options.signal });
|
|
3260
|
+
if (!response.ok) throw new Error(`Tileflow style family request failed: ${response.status}`);
|
|
3261
|
+
const maximum = tileflowStyleFamilyLimits.documentBytes;
|
|
3262
|
+
const length = response.headers.get("content-length");
|
|
3263
|
+
if (length && Number(length) > maximum) {
|
|
3264
|
+
await response.body?.cancel();
|
|
3265
|
+
throw new TypeError("Tileflow style family is too large.");
|
|
3266
|
+
}
|
|
3267
|
+
const reader = response.body?.getReader();
|
|
3268
|
+
if (!reader) throw new TypeError("Tileflow style family response has no body.");
|
|
3269
|
+
const chunks = [];
|
|
3270
|
+
let total = 0;
|
|
3271
|
+
try {
|
|
3272
|
+
for (; ; ) {
|
|
3273
|
+
const { done, value } = await reader.read();
|
|
3274
|
+
if (done) break;
|
|
3275
|
+
total += value.byteLength;
|
|
3276
|
+
if (total > maximum) {
|
|
3277
|
+
await reader.cancel();
|
|
3278
|
+
throw new TypeError("Tileflow style family is too large.");
|
|
3279
|
+
}
|
|
3280
|
+
chunks.push(value);
|
|
3281
|
+
}
|
|
3282
|
+
} finally {
|
|
3283
|
+
reader.releaseLock();
|
|
3284
|
+
}
|
|
3285
|
+
const bytes2 = new Uint8Array(total);
|
|
3286
|
+
let offset = 0;
|
|
3287
|
+
for (const chunk of chunks) {
|
|
3288
|
+
bytes2.set(chunk, offset);
|
|
3289
|
+
offset += chunk.length;
|
|
3290
|
+
}
|
|
3291
|
+
return parseTileflowStyleFamily(
|
|
3292
|
+
JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2))
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
// src/color-program-runtime.ts
|
|
3297
|
+
init_color_program();
|
|
3298
|
+
|
|
3299
|
+
// src/themes/index.ts
|
|
3300
|
+
init_color_program();
|
|
3301
|
+
init_model();
|
|
3302
|
+
init_visual_semantics();
|
|
3303
|
+
var baseThemeColorRoles = {
|
|
3304
|
+
background: "surface.background",
|
|
3305
|
+
boundary: "boundaries.default",
|
|
3306
|
+
building: "surface.building",
|
|
3307
|
+
land: "surface.land",
|
|
3308
|
+
park: "surface.park",
|
|
3309
|
+
road: "roads.default",
|
|
3310
|
+
roadCasing: "roads.casing",
|
|
3311
|
+
roadMajor: "roads.major",
|
|
3312
|
+
text: "labels.primary",
|
|
3313
|
+
textHalo: "labels.halo",
|
|
3314
|
+
textMuted: "labels.muted",
|
|
3315
|
+
water: "surface.water"
|
|
3316
|
+
};
|
|
3317
|
+
var tileflowRequiredThemeColorRoles = Object.freeze(Object.values(baseThemeColorRoles));
|
|
3318
|
+
var baseThemeColorRoleSet = new Set(tileflowRequiredThemeColorRoles);
|
|
3319
|
+
function mix(hexA, hexB, amount) {
|
|
3320
|
+
if (isColorProgramMarker(hexA) || isColorProgramMarker(hexB))
|
|
3321
|
+
return colorProgramMarker(
|
|
3322
|
+
mix(colorProgramValue(hexA), colorProgramValue(hexB), amount),
|
|
3323
|
+
["srgb", colorProgramOf(hexA), colorProgramOf(hexB), amount]
|
|
3324
|
+
);
|
|
3325
|
+
const a = hexToRgb(hexA);
|
|
3326
|
+
const b = hexToRgb(hexB);
|
|
3327
|
+
const r = Math.round(a.r + (b.r - a.r) * amount);
|
|
3328
|
+
const g = Math.round(a.g + (b.g - a.g) * amount);
|
|
3329
|
+
const bValue = Math.round(a.b + (b.b - a.b) * amount);
|
|
3330
|
+
return rgbToHex(r, g, bValue);
|
|
3331
|
+
}
|
|
3332
|
+
function hexToRgb(hex) {
|
|
3333
|
+
const { b, g, r } = hexToRgba(hex);
|
|
3334
|
+
return { b, g, r };
|
|
3335
|
+
}
|
|
3336
|
+
function hexToRgba(hex) {
|
|
3337
|
+
hex = colorProgramValue(hex);
|
|
3338
|
+
const rgbMatch = hex.match(
|
|
3339
|
+
/^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/
|
|
3340
|
+
);
|
|
3341
|
+
if (rgbMatch) {
|
|
3342
|
+
return {
|
|
3343
|
+
a: roundAlpha(Number(rgbMatch[4] ?? 1)),
|
|
3344
|
+
r: Math.round(Number(rgbMatch[1])),
|
|
3345
|
+
g: Math.round(Number(rgbMatch[2])),
|
|
3346
|
+
b: Math.round(Number(rgbMatch[3]))
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
const value = hex.replace("#", "");
|
|
3350
|
+
const normalized = value.length === 3 || value.length === 4 ? value.split("").map((character) => character + character).join("") : value;
|
|
3351
|
+
return {
|
|
3352
|
+
a: normalized.length === 8 ? roundAlpha(Number.parseInt(normalized.slice(6, 8), 16) / 255) : 1,
|
|
3353
|
+
r: Number.parseInt(normalized.slice(0, 2), 16),
|
|
3354
|
+
g: Number.parseInt(normalized.slice(2, 4), 16),
|
|
3355
|
+
b: Number.parseInt(normalized.slice(4, 6), 16)
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
function roundAlpha(value) {
|
|
3359
|
+
return Math.round(value * 1e3) / 1e3;
|
|
3360
|
+
}
|
|
3361
|
+
function rgbToHex(r, g, b) {
|
|
3362
|
+
return `#${[r, g, b].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
// src/color-program-runtime.ts
|
|
3366
|
+
init_model();
|
|
3367
|
+
function applyTileflowColorOverrides(style, overrides) {
|
|
3368
|
+
const keys = Object.keys(overrides);
|
|
3369
|
+
if (!keys.length) return style;
|
|
3370
|
+
const catalog = style.metadata?.[tileflowColorMetadataKey];
|
|
3371
|
+
if (!catalog)
|
|
3372
|
+
throw new TypeError(
|
|
3373
|
+
"This style has no runtime colour bindings. Rebuild with runtimeColors enabled."
|
|
3374
|
+
);
|
|
3375
|
+
validateBindings(catalog);
|
|
3376
|
+
if (keys.length > 1024 || keys.some(
|
|
3377
|
+
(key) => !Object.hasOwn(catalog.tokens.color, key) || typeof overrides[key] !== "string" || overrides[key].length > 128
|
|
3378
|
+
))
|
|
3379
|
+
throw new TypeError("Unknown colour token or invalid override.");
|
|
3380
|
+
const theme = {
|
|
3381
|
+
id: "runtime",
|
|
3382
|
+
version: 1,
|
|
3383
|
+
colorScheme: "light",
|
|
3384
|
+
lighting: {},
|
|
3385
|
+
typography: { font: "Noto Sans Regular" },
|
|
3386
|
+
tokens: {
|
|
3387
|
+
...catalog.tokens,
|
|
3388
|
+
color: { ...catalog.tokens.color, ...overrides },
|
|
3389
|
+
font: {},
|
|
3390
|
+
image: {}
|
|
3391
|
+
}
|
|
3392
|
+
};
|
|
3393
|
+
const resolved = resolveTileflowTheme(theme);
|
|
3394
|
+
const concrete = {
|
|
3395
|
+
...theme,
|
|
3396
|
+
tokens: { ...theme.tokens, color: resolved.tokens.color, number: resolved.tokens.number }
|
|
3397
|
+
};
|
|
3398
|
+
const evaluate = (program) => {
|
|
3399
|
+
if (typeof program === "string") return program;
|
|
3400
|
+
if (Array.isArray(program) && program[0] === "srgb" && program.length === 4 && typeof program[3] === "number" && program[3] >= 0 && program[3] <= 1)
|
|
3401
|
+
return mix(evaluate(program[1]), evaluate(program[2]), program[3]);
|
|
3402
|
+
if (program && typeof program === "object" && !Array.isArray(program)) {
|
|
3403
|
+
const node = program;
|
|
3404
|
+
if (node.kind === "theme-color" && node.operation === "alpha")
|
|
3405
|
+
program = { ...node, color: evaluate(node.color) };
|
|
3406
|
+
else if (node.kind === "theme-color" && node.operation === "mix")
|
|
3407
|
+
program = { ...node, from: evaluate(node.from), to: evaluate(node.to) };
|
|
3408
|
+
}
|
|
3409
|
+
return resolveThemeValues({ color: program }, concrete).color;
|
|
3410
|
+
};
|
|
3411
|
+
const affected = (node, seen = /* @__PURE__ */ new Set()) => {
|
|
3412
|
+
if (!node || typeof node !== "object") return false;
|
|
3413
|
+
const ref = node;
|
|
3414
|
+
if (ref.kind === "theme-token" && ref.category === "color" && typeof ref.token === "string") {
|
|
3415
|
+
if (Object.hasOwn(overrides, ref.token)) return true;
|
|
3416
|
+
if (seen.has(ref.token)) return false;
|
|
3417
|
+
return affected(catalog.tokens.color[ref.token], /* @__PURE__ */ new Set([...seen, ref.token]));
|
|
3418
|
+
}
|
|
3419
|
+
return Object.values(node).some((value) => affected(value, seen));
|
|
3420
|
+
};
|
|
3421
|
+
const values = catalog.programs.map(
|
|
3422
|
+
(program) => affected(program) ? evaluate(program) : void 0
|
|
3423
|
+
);
|
|
3424
|
+
const result = structuredClone(style);
|
|
3425
|
+
for (const [path, id] of catalog.bindings)
|
|
3426
|
+
if (values[id] !== void 0) updatePath(result, path, () => colorProgramValue(values[id]));
|
|
3427
|
+
return result;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
// src/style-family-render.ts
|
|
3431
|
+
init_color_program();
|
|
3432
|
+
init_theme_blend();
|
|
3433
|
+
var preparedFamilies = /* @__PURE__ */ new WeakMap();
|
|
3434
|
+
function prepareTileflowStyleFamilyForRendering(family) {
|
|
3435
|
+
const cached = preparedFamilies.get(family);
|
|
3436
|
+
if (cached) return cached;
|
|
3437
|
+
if (readTileflowFamilyValue(family, ["metadata", "tileflow:family-prepared"], family.themes[0]) === 1)
|
|
3438
|
+
return family;
|
|
3439
|
+
if (!family.updates.appearanceOnly || family.themes.length < 2) return family;
|
|
3440
|
+
const styles = expandTileflowStyleFamily(family);
|
|
3441
|
+
const plan = planTileflowThemeBlend(
|
|
3442
|
+
family.themes.map((name) => restoreTileflowColorPrograms(styles[name])),
|
|
3443
|
+
{ iconImages: "mix" }
|
|
3444
|
+
);
|
|
3445
|
+
const first = plan.styleAt(0);
|
|
3446
|
+
const descendants = (id) => first.layers.map((layer) => String(layer.id)).filter(
|
|
3447
|
+
(name) => name === id || name.startsWith(`${id}::branch-`) || name.startsWith(`${id}::theme-`)
|
|
3448
|
+
);
|
|
3449
|
+
const prepared = createTileflowStyleFamily(
|
|
3450
|
+
Object.fromEntries(
|
|
3451
|
+
family.themes.map((name, index) => {
|
|
3452
|
+
const style = plan.styleAt(index);
|
|
3453
|
+
const metadata = {
|
|
3454
|
+
...styles[name].metadata,
|
|
3455
|
+
"tileflow:family-prepared": 1,
|
|
3456
|
+
...plan.images.length ? { "tileflow:family-images": plan.images, "tileflow:family-image-theme": name } : {},
|
|
3457
|
+
// A split physical layer must retain the planner's interaction mapping.
|
|
3458
|
+
...style.metadata?.["tileflow:interaction-manifest"] ? { "tileflow:interaction-manifest": style.metadata["tileflow:interaction-manifest"] } : {},
|
|
3459
|
+
...style.metadata?.["tileflow:overlay-placement-manifest"] ? {
|
|
3460
|
+
"tileflow:overlay-placement-manifest": style.metadata["tileflow:overlay-placement-manifest"]
|
|
3461
|
+
} : {}
|
|
3462
|
+
};
|
|
3463
|
+
const vegetation = metadata["tileflow:vegetation"];
|
|
3464
|
+
if (Array.isArray(vegetation?.flatLayers))
|
|
3465
|
+
metadata["tileflow:vegetation"] = {
|
|
3466
|
+
...vegetation,
|
|
3467
|
+
flatLayers: vegetation.flatLayers.flatMap(descendants)
|
|
3468
|
+
};
|
|
3469
|
+
const display = metadata["tileflow:vegetation-display"];
|
|
3470
|
+
if (Array.isArray(display?.flatLayers))
|
|
3471
|
+
metadata["tileflow:vegetation-display"] = {
|
|
3472
|
+
...display,
|
|
3473
|
+
flatLayers: display.flatLayers.flatMap(
|
|
3474
|
+
(layer) => descendants(layer.id).map((id) => ({ ...layer, id }))
|
|
3475
|
+
)
|
|
3476
|
+
};
|
|
3477
|
+
const colors = styles[name].metadata?.[tileflowColorMetadataKey];
|
|
3478
|
+
const preparedStyle = { ...style, transition: { duration: 0, delay: 0 }, metadata };
|
|
3479
|
+
if (colors) {
|
|
3480
|
+
const restoredMetadata = restoreTileflowColorPrograms(styles[name]).metadata;
|
|
3481
|
+
for (const key of Object.keys(restoredMetadata))
|
|
3482
|
+
if (key !== "tileflow:interaction-manifest" && key !== "tileflow:overlay-placement-manifest" && key !== "tileflow:vegetation-display")
|
|
3483
|
+
preparedStyle.metadata[key] = key === "tileflow:vegetation" ? {
|
|
3484
|
+
...restoredMetadata[key],
|
|
3485
|
+
flatLayers: metadata[key].flatLayers
|
|
3486
|
+
} : restoredMetadata[key];
|
|
3487
|
+
}
|
|
3488
|
+
return [
|
|
3489
|
+
name,
|
|
3490
|
+
colors ? extractTileflowColorPrograms(preparedStyle, colors.tokens) : preparedStyle
|
|
3491
|
+
];
|
|
3492
|
+
})
|
|
3493
|
+
)
|
|
3494
|
+
);
|
|
3495
|
+
if (!prepared.updates.appearanceOnly)
|
|
3496
|
+
throw new TypeError("Family preparation changed its structural contract.");
|
|
3497
|
+
preparedFamilies.set(family, prepared);
|
|
3498
|
+
preparedFamilies.set(prepared, prepared);
|
|
3499
|
+
return prepared;
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
// src/style-family-runtime.ts
|
|
3503
|
+
function resolveTileflowStyleResourceUrls(style, documentUrl) {
|
|
3504
|
+
const result = structuredClone(style);
|
|
3505
|
+
const absolute = (value) => (
|
|
3506
|
+
// URL encodes path placeholders. Query values may contain a nested URL whose
|
|
3507
|
+
// escaped placeholders must remain opaque to the outer tile substitution.
|
|
3508
|
+
new URL(value, documentUrl).href.replace(
|
|
3509
|
+
/^[^?#]+/u,
|
|
3510
|
+
(path) => path.replaceAll(/%7B([^%]+)%7D/giu, "{$1}")
|
|
3511
|
+
)
|
|
3512
|
+
);
|
|
3513
|
+
const property = (record3, key) => {
|
|
3514
|
+
if (typeof record3[key] === "string") record3[key] = absolute(record3[key]);
|
|
3515
|
+
};
|
|
3516
|
+
property(result, "glyphs");
|
|
3517
|
+
if (typeof result.sprite === "string") result.sprite = absolute(result.sprite);
|
|
3518
|
+
else if (Array.isArray(result.sprite))
|
|
3519
|
+
for (const sprite of result.sprite) property(sprite, "url");
|
|
3520
|
+
for (const source of Object.values(result.sources)) {
|
|
3521
|
+
property(source, "url");
|
|
3522
|
+
if (source.type === "geojson") property(source, "data");
|
|
3523
|
+
if (Array.isArray(source.tiles))
|
|
3524
|
+
source.tiles = source.tiles.map(
|
|
3525
|
+
(value) => typeof value === "string" ? absolute(value) : value
|
|
3526
|
+
);
|
|
3527
|
+
if (Array.isArray(source.urls))
|
|
3528
|
+
source.urls = source.urls.map(
|
|
3529
|
+
(value) => typeof value === "string" ? absolute(value) : value
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
const metadata = result.metadata;
|
|
3533
|
+
const fonts = metadata?.["tileflow:fontFaces"];
|
|
3534
|
+
if (Array.isArray(fonts)) {
|
|
3535
|
+
for (const face of fonts) if (face && typeof face === "object") property(face, "source");
|
|
3536
|
+
}
|
|
3537
|
+
for (const key of ["tileflow:buildings", "tileflow:vegetation"]) {
|
|
3538
|
+
const descriptor = metadata?.[key];
|
|
3539
|
+
if (Array.isArray(descriptor?.models))
|
|
3540
|
+
for (const model of descriptor.models) property(model, "url");
|
|
3541
|
+
}
|
|
3542
|
+
return result;
|
|
3543
|
+
}
|
|
3544
|
+
function resolveTileflowFamilyResourceUrls(family, documentUrl) {
|
|
3545
|
+
if (family.updates.appearanceOnly)
|
|
3546
|
+
return withTileflowFamilyTemplate(
|
|
3547
|
+
family,
|
|
3548
|
+
resolveTileflowStyleResourceUrls(
|
|
3549
|
+
expandTileflowFamilyTemplate(family),
|
|
3550
|
+
documentUrl
|
|
3551
|
+
)
|
|
3552
|
+
);
|
|
3553
|
+
return createTileflowStyleFamily(
|
|
3554
|
+
Object.fromEntries(
|
|
3555
|
+
Object.entries(expandTileflowStyleFamily(family)).map(([name, style]) => [
|
|
3556
|
+
name,
|
|
3557
|
+
resolveTileflowStyleResourceUrls(style, documentUrl)
|
|
3558
|
+
])
|
|
3559
|
+
)
|
|
3560
|
+
);
|
|
3561
|
+
}
|
|
3562
|
+
async function loadTileflowRuntimeAppearance(style, options = {}) {
|
|
3563
|
+
if (!style.familyUrl || !style.theme || style.family) return style;
|
|
3564
|
+
const family = prepareTileflowStyleFamilyForRendering(
|
|
3565
|
+
resolveTileflowFamilyResourceUrls(
|
|
3566
|
+
await loadTileflowStyleFamily(style.familyUrl, options),
|
|
3567
|
+
style.familyUrl
|
|
3568
|
+
)
|
|
3569
|
+
);
|
|
3570
|
+
return { ...style, family, style: materializeTileflowStyleFamily(family, style.theme) };
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
// src/appearance.ts
|
|
3574
|
+
init_time_of_day();
|
|
3575
|
+
function resolveTileflowAppearance(map, selection, context) {
|
|
3576
|
+
const theme = (moment) => {
|
|
3577
|
+
if (moment !== void 0 && !isTileflowTimeOfDay(moment))
|
|
3578
|
+
throw new TypeError("Invalid appearance moment.");
|
|
3579
|
+
return resolveTileflowRuntimeTheme(map, selection.palette, context.colorScheme, moment).name;
|
|
3580
|
+
};
|
|
3581
|
+
if (selection.mode === "fixed") return { themes: [theme(selection.moment)], position: 0 };
|
|
3582
|
+
if (selection.mode === "system")
|
|
3583
|
+
return { themes: [theme(selection[context.colorScheme])], position: 0 };
|
|
3584
|
+
if (selection.mode !== "clock")
|
|
3585
|
+
throw new TypeError("Expected fixed, system, or clock appearance.");
|
|
3586
|
+
const schedule = [...selection.schedule].sort((a, b) => a.hour - b.hour);
|
|
3587
|
+
if (schedule.length < 2 || schedule.length > 24 || schedule.some(
|
|
3588
|
+
(stop, index) => !Number.isFinite(stop.hour) || stop.hour < 0 || stop.hour >= 24 || !isTileflowTimeOfDay(stop.moment) || index > 0 && stop.hour === schedule[index - 1].hour
|
|
3589
|
+
))
|
|
3590
|
+
throw new TypeError("A clock schedule needs 2\u201324 distinct hours from 0 to less than 24.");
|
|
3591
|
+
if (!Number.isFinite(context.date.getTime())) throw new TypeError("Invalid appearance date.");
|
|
3592
|
+
const hour = context.date.getHours() + context.date.getMinutes() / 60 + context.date.getSeconds() / 3600;
|
|
3593
|
+
let lower = -1;
|
|
3594
|
+
schedule.forEach((stop, index) => {
|
|
3595
|
+
if (stop.hour <= hour) lower = index;
|
|
3596
|
+
});
|
|
3597
|
+
if (lower < 0) lower = schedule.length - 1;
|
|
3598
|
+
const from = schedule[lower];
|
|
3599
|
+
const to = schedule[(lower + 1) % schedule.length];
|
|
3600
|
+
if (!selection.blend) return { themes: [theme(from.moment)], position: 0 };
|
|
3601
|
+
const span = (to.hour - from.hour + 24) % 24;
|
|
3602
|
+
const themes = [theme(from.moment), theme(to.moment)];
|
|
3603
|
+
return themes[0] === themes[1] ? { themes: [themes[0]], position: 0 } : { themes, position: (hour - from.hour + 24) % 24 / span };
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
// src/runtime.ts
|
|
570
3607
|
init_time_of_day();
|
|
571
3608
|
var defaultTileflowManifestUrl = "/tileflow/manifest.json";
|
|
572
3609
|
var defaultTileflowRuntimeView = Object.freeze({
|
|
@@ -659,6 +3696,7 @@ function resolveTileflowRuntimeStyle(options) {
|
|
|
659
3696
|
styleId: theme.styleId
|
|
660
3697
|
},
|
|
661
3698
|
colorScheme: theme.colorScheme,
|
|
3699
|
+
...options.manifestMap.familyUrl ? { familyUrl: options.manifestMap.familyUrl } : {},
|
|
662
3700
|
...theme.fontFaces === void 0 ? {} : { fontFaces: [...theme.fontFaces] },
|
|
663
3701
|
revision: theme.revision,
|
|
664
3702
|
style: theme.styleUrl,
|
|
@@ -876,7 +3914,7 @@ async function requestSessionGrant(input) {
|
|
|
876
3914
|
else throw error;
|
|
877
3915
|
}
|
|
878
3916
|
throwIfSessionGrantAborted(signal);
|
|
879
|
-
if (response.status === 409 &&
|
|
3917
|
+
if (response.status === 409 && isRecord4(body) && body.code === "COMMERCIAL_SESSION_RESTART_REQUIRED" && body.ok === false && body.retryWithNewSession === true && body.sessionId === input.sessionId) {
|
|
880
3918
|
throw new TileflowSessionRestartError();
|
|
881
3919
|
}
|
|
882
3920
|
if (!response.ok) {
|
|
@@ -949,7 +3987,7 @@ function sessionGrantAbortReason(signal) {
|
|
|
949
3987
|
return error;
|
|
950
3988
|
}
|
|
951
3989
|
function parseSessionGrantResponse(value, expectedSessionId) {
|
|
952
|
-
if (!
|
|
3990
|
+
if (!isRecord4(value) || value.ok !== true || value.sessionId !== expectedSessionId) return null;
|
|
953
3991
|
if (value.usageMode !== void 0 && value.usageMode !== "session") return null;
|
|
954
3992
|
if (value.billingEnabled !== void 0 && value.billingEnabled !== false) return null;
|
|
955
3993
|
if (value.disposition !== void 0 && value.disposition !== "ordinary" && value.disposition !== "unbilled_fail_open") {
|
|
@@ -968,11 +4006,11 @@ function parseSessionGrantResponse(value, expectedSessionId) {
|
|
|
968
4006
|
}
|
|
969
4007
|
return value;
|
|
970
4008
|
}
|
|
971
|
-
function
|
|
4009
|
+
function isRecord4(value) {
|
|
972
4010
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
973
4011
|
}
|
|
974
4012
|
function isPlainRuntimeRecord(value) {
|
|
975
|
-
if (!
|
|
4013
|
+
if (!isRecord4(value)) return false;
|
|
976
4014
|
const prototype = Object.getPrototypeOf(value);
|
|
977
4015
|
return prototype === Object.prototype || prototype === null;
|
|
978
4016
|
}
|
|
@@ -1175,6 +4213,7 @@ function resolveLoadedTileflowManifestUrls(manifest, manifestUrl) {
|
|
|
1175
4213
|
mapName,
|
|
1176
4214
|
{
|
|
1177
4215
|
...entry,
|
|
4216
|
+
...entry.familyUrl ? { familyUrl: resolveTileflowLoadedResourceUrl(entry.familyUrl, manifestUrl) } : {},
|
|
1178
4217
|
themes: Object.fromEntries(
|
|
1179
4218
|
Object.entries(entry.themes).map(([themeName, theme]) => {
|
|
1180
4219
|
const styleUrl = resolveTileflowLoadedResourceUrl(theme.styleUrl, manifestUrl);
|
|
@@ -1224,6 +4263,7 @@ function resolveTileflowManifestMap(manifest, mapName) {
|
|
|
1224
4263
|
mapId: entry.mapId ?? analytics?.mapId,
|
|
1225
4264
|
name: mapName,
|
|
1226
4265
|
defaultTheme: entry.defaultTheme,
|
|
4266
|
+
...entry.familyUrl ? { familyUrl: entry.familyUrl } : {},
|
|
1227
4267
|
...entry.environment ? { environment: entry.environment } : {},
|
|
1228
4268
|
...entry.systemThemes ? { systemThemes: { ...entry.systemThemes } } : {},
|
|
1229
4269
|
...entry.timeOfDay ? { timeOfDay: entry.timeOfDay } : {},
|
|
@@ -1306,14 +4346,14 @@ async function readBoundedUtf8Response(response, maximumBytes, errors) {
|
|
|
1306
4346
|
}
|
|
1307
4347
|
chunks.push(next.value);
|
|
1308
4348
|
}
|
|
1309
|
-
const
|
|
4349
|
+
const bytes2 = new Uint8Array(byteLength);
|
|
1310
4350
|
let offset = 0;
|
|
1311
4351
|
for (const chunk of chunks) {
|
|
1312
|
-
|
|
4352
|
+
bytes2.set(chunk, offset);
|
|
1313
4353
|
offset += chunk.byteLength;
|
|
1314
4354
|
}
|
|
1315
4355
|
try {
|
|
1316
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
4356
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
1317
4357
|
} catch {
|
|
1318
4358
|
throw new Error(errors.invalidUtf8);
|
|
1319
4359
|
}
|
|
@@ -1408,24 +4448,33 @@ function getTileflowLocationBaseUrl() {
|
|
|
1408
4448
|
return `${getTileflowLocationOrigin().replace(/\/+$/u, "")}/`;
|
|
1409
4449
|
}
|
|
1410
4450
|
export {
|
|
4451
|
+
applyTileflowColorOverrides,
|
|
1411
4452
|
assertValidTileflowRuntimeSource,
|
|
1412
4453
|
clearTileflowManifestCache,
|
|
1413
4454
|
createTileflowSessionController,
|
|
1414
4455
|
createTileflowSessionId,
|
|
1415
4456
|
defaultTileflowManifestUrl,
|
|
1416
4457
|
defaultTileflowRuntimeView,
|
|
4458
|
+
expandTileflowFamilyTemplate,
|
|
1417
4459
|
getTileflowAnalyticsApiUrl,
|
|
1418
4460
|
getTileflowStyleFontFaces,
|
|
1419
4461
|
inferTileflowAnalyticsFromStyleUrl,
|
|
1420
4462
|
isTileflowTimeOfDay,
|
|
1421
4463
|
loadTileflowManifest,
|
|
4464
|
+
loadTileflowRuntimeAppearance,
|
|
4465
|
+
loadTileflowStyleFamily,
|
|
4466
|
+
materializeTileflowStyleFamily,
|
|
1422
4467
|
mergeTileflowAnalytics,
|
|
1423
4468
|
nearestTileflowBlendTheme,
|
|
1424
4469
|
normalizeTileflowRuntimeCenter,
|
|
1425
4470
|
normalizeTileflowStaticImageSize,
|
|
1426
4471
|
normalizeTileflowSurfaceId,
|
|
1427
4472
|
normalizeTileflowUrl,
|
|
4473
|
+
parseTileflowStyleFamily,
|
|
4474
|
+
prepareTileflowStyleFamilyForRendering,
|
|
1428
4475
|
resolveTileflowAnalyticsRequestUrl,
|
|
4476
|
+
resolveTileflowAppearance,
|
|
4477
|
+
resolveTileflowFamilyResourceUrls,
|
|
1429
4478
|
resolveTileflowManifestMap,
|
|
1430
4479
|
resolveTileflowMapMode,
|
|
1431
4480
|
resolveTileflowRuntimeStyle,
|
|
@@ -1433,13 +4482,16 @@ export {
|
|
|
1433
4482
|
resolveTileflowRuntimeThemeBlend,
|
|
1434
4483
|
resolveTileflowRuntimeView,
|
|
1435
4484
|
resolveTileflowStaticImageUrl,
|
|
4485
|
+
resolveTileflowStyleResourceUrls,
|
|
1436
4486
|
shouldLoadTileflowManifest,
|
|
1437
4487
|
startTileflowSession,
|
|
4488
|
+
tileflowStyleFamilyLimits,
|
|
1438
4489
|
tileflowStyleFontFaceLimits,
|
|
1439
4490
|
tileflowStyleFontFacesMetadataKey,
|
|
1440
4491
|
tileflowThemeBlendLimits,
|
|
1441
4492
|
tileflowTimesOfDay,
|
|
1442
4493
|
validateTileflowRuntimeSource,
|
|
1443
4494
|
validateTileflowThemeBlendSelection,
|
|
1444
|
-
validateTileflowThemeSelection
|
|
4495
|
+
validateTileflowThemeSelection,
|
|
4496
|
+
withTileflowFamilyTemplate
|
|
1445
4497
|
};
|