@tileflow/core 0.1.0-alpha.31 → 0.1.0-alpha.32
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 +8 -1
- package/dist/browser.d.ts +120 -16
- package/dist/browser.js +457 -38
- package/dist/build.d.ts +62 -55
- package/dist/build.js +82 -53
- package/dist/{capture-scene-D6soWztE.d.ts → capture-scene-BaAJjGq7.d.ts} +11 -1
- package/dist/capture.d.ts +1 -1
- package/dist/capture.js +12 -0
- package/dist/config-reference.json +1411 -1367
- package/dist/{icon-composition-BiOTwrvD.d.ts → icon-composition-Befmn-zZ.d.ts} +40 -32
- package/dist/index.d.ts +11 -11
- package/dist/index.js +84 -53
- package/dist/{manifest-types-CTT2eLbp.d.ts → manifest-types-E7m23eXL.d.ts} +18 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/native-profile.d.ts +25 -11
- package/dist/native-profile.js +21 -1
- package/dist/runtime.d.ts +1 -1
- package/docs/authoring-model.md +5 -4
- package/docs/capture-scenes.md +1 -1
- package/docs/compiled-style-performance.md +4 -1
- package/docs/data-is-separate-from-design.md +103 -9
- package/docs/dynamic-style-images.md +100 -0
- package/docs/native-artifact-profile.md +5 -3
- package/docs/shared-visual-primitives.md +20 -0
- package/docs/style-states.md +73 -0
- package/docs/theme-transitions-and-blends.md +41 -2
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -2042,10 +2042,231 @@ var init_dist = __esm({
|
|
|
2042
2042
|
}
|
|
2043
2043
|
});
|
|
2044
2044
|
|
|
2045
|
+
// src/dynamic-style-images.ts
|
|
2046
|
+
var owners = /* @__PURE__ */ new WeakMap();
|
|
2047
|
+
function isRestoredCopy(image, previous) {
|
|
2048
|
+
if (!image || !previous) return false;
|
|
2049
|
+
const a = image, b2 = previous;
|
|
2050
|
+
if (!a.data || !b2.data || a.data.width !== b2.data.width || a.data.height !== b2.data.height || a.pixelRatio !== b2.pixelRatio || a.sdf !== b2.sdf || a.content !== b2.content || a.stretchX !== b2.stretchX || a.stretchY !== b2.stretchY || a.data.data.length !== b2.data.data.length)
|
|
2051
|
+
return false;
|
|
2052
|
+
return a.data.data.every((byte, index) => byte === b2.data.data[index]);
|
|
2053
|
+
}
|
|
2054
|
+
function createTileflowDynamicImageRegistry(input, context, beforeRestore) {
|
|
2055
|
+
for (const method of [
|
|
2056
|
+
"addImage",
|
|
2057
|
+
"getImage",
|
|
2058
|
+
"getStyle",
|
|
2059
|
+
"removeImage",
|
|
2060
|
+
"updateImage"
|
|
2061
|
+
]) {
|
|
2062
|
+
if (typeof input[method] !== "function") {
|
|
2063
|
+
throw new TypeError(`Dynamic images require a map with ${method}().`);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
const map = input;
|
|
2067
|
+
const names = owners.get(map) ?? /* @__PURE__ */ new Set();
|
|
2068
|
+
owners.set(map, names);
|
|
2069
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2070
|
+
const view = typeof map.getCanvas === "function" ? map.getCanvas().ownerDocument?.defaultView : void 0;
|
|
2071
|
+
let cancelFrame;
|
|
2072
|
+
let disposed = false;
|
|
2073
|
+
let suspended = false;
|
|
2074
|
+
const imageContext = (entry, current) => Object.freeze({
|
|
2075
|
+
...current,
|
|
2076
|
+
width: entry.pixels.width,
|
|
2077
|
+
height: entry.pixels.height,
|
|
2078
|
+
pixelRatio: entry.pixelRatio
|
|
2079
|
+
});
|
|
2080
|
+
const keyOf = (entry, current) => JSON.stringify([
|
|
2081
|
+
current.theme,
|
|
2082
|
+
current.blend,
|
|
2083
|
+
(entry.definition.states ?? []).map((name) => [
|
|
2084
|
+
name,
|
|
2085
|
+
typeof current.states[name],
|
|
2086
|
+
current.states[name]
|
|
2087
|
+
])
|
|
2088
|
+
]);
|
|
2089
|
+
const report = (entry, cause) => {
|
|
2090
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
2091
|
+
if (entry.definition.onError) entry.definition.onError(error);
|
|
2092
|
+
else console.error(`Tileflow dynamic image "${entry.name}":`, error);
|
|
2093
|
+
};
|
|
2094
|
+
function render(entry, current) {
|
|
2095
|
+
const output = entry.definition.render(imageContext(entry, current));
|
|
2096
|
+
if (output === void 0) return false;
|
|
2097
|
+
if (!(output instanceof Uint8Array || output instanceof Uint8ClampedArray) || output.byteLength !== entry.pixels.width * entry.pixels.height * 4) {
|
|
2098
|
+
throw new TypeError(
|
|
2099
|
+
`Dynamic image "${entry.name}" must return width \xD7 height \xD7 4 RGBA bytes.`
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
entry.pixels = { ...entry.pixels, data: Uint8Array.from(output) };
|
|
2103
|
+
return true;
|
|
2104
|
+
}
|
|
2105
|
+
function install(entry, changed = false) {
|
|
2106
|
+
if (suspended) return;
|
|
2107
|
+
const existing = map.getImage(entry.name);
|
|
2108
|
+
if (!existing && !map.getStyle()) return;
|
|
2109
|
+
if (existing && existing !== entry.installed && !isRestoredCopy(existing, entry.recoveryImage)) {
|
|
2110
|
+
throw new Error(`Dynamic image "${entry.name}" conflicts with an existing image owner.`);
|
|
2111
|
+
}
|
|
2112
|
+
if (!existing) map.addImage(entry.name, entry.pixels, { pixelRatio: entry.pixelRatio });
|
|
2113
|
+
else if (changed) map.updateImage(entry.name, entry.pixels);
|
|
2114
|
+
entry.installed = map.getImage(entry.name);
|
|
2115
|
+
entry.recoveryImage = void 0;
|
|
2116
|
+
}
|
|
2117
|
+
function flush() {
|
|
2118
|
+
cancelFrame = void 0;
|
|
2119
|
+
if (disposed || suspended) return;
|
|
2120
|
+
const current = context();
|
|
2121
|
+
for (const entry of entries.values()) {
|
|
2122
|
+
if (!entry.dirty) continue;
|
|
2123
|
+
entry.dirty = false;
|
|
2124
|
+
entry.key = keyOf(entry, current);
|
|
2125
|
+
try {
|
|
2126
|
+
const changed = render(entry, current);
|
|
2127
|
+
install(entry, changed);
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
report(entry, error);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
function schedule() {
|
|
2134
|
+
if (cancelFrame || disposed || suspended) return;
|
|
2135
|
+
if (view?.requestAnimationFrame) {
|
|
2136
|
+
const id = view.requestAnimationFrame(flush);
|
|
2137
|
+
cancelFrame = () => view.cancelAnimationFrame(id);
|
|
2138
|
+
} else {
|
|
2139
|
+
let cancelled = false;
|
|
2140
|
+
cancelFrame = () => {
|
|
2141
|
+
cancelled = true;
|
|
2142
|
+
};
|
|
2143
|
+
queueMicrotask(() => {
|
|
2144
|
+
if (!cancelled) flush();
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
function restore() {
|
|
2149
|
+
if (disposed || suspended) return;
|
|
2150
|
+
beforeRestore();
|
|
2151
|
+
for (const entry of entries.values()) {
|
|
2152
|
+
try {
|
|
2153
|
+
install(entry, true);
|
|
2154
|
+
} catch (error) {
|
|
2155
|
+
report(entry, error);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if ([...entries.values()].some((entry) => entry.dirty)) schedule();
|
|
2159
|
+
}
|
|
2160
|
+
function loseContext() {
|
|
2161
|
+
suspended = true;
|
|
2162
|
+
for (const entry of entries.values()) entry.recoveryImage = entry.installed;
|
|
2163
|
+
cancelFrame?.();
|
|
2164
|
+
cancelFrame = void 0;
|
|
2165
|
+
}
|
|
2166
|
+
function restoreContext() {
|
|
2167
|
+
suspended = false;
|
|
2168
|
+
restore();
|
|
2169
|
+
}
|
|
2170
|
+
function release(entry, remove) {
|
|
2171
|
+
if (entries.get(entry.name) !== entry) return;
|
|
2172
|
+
entries.delete(entry.name);
|
|
2173
|
+
names.delete(entry.name);
|
|
2174
|
+
if (remove && entry.installed && map.getImage(entry.name) === entry.installed) {
|
|
2175
|
+
map.removeImage(entry.name);
|
|
2176
|
+
}
|
|
2177
|
+
if (!entries.size) {
|
|
2178
|
+
cancelFrame?.();
|
|
2179
|
+
cancelFrame = void 0;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
function dispose(remove = true) {
|
|
2183
|
+
if (disposed) return;
|
|
2184
|
+
disposed = true;
|
|
2185
|
+
cancelFrame?.();
|
|
2186
|
+
cancelFrame = void 0;
|
|
2187
|
+
for (const entry of [...entries.values()]) release(entry, remove);
|
|
2188
|
+
map.off("style.load", restore);
|
|
2189
|
+
map.off("webglcontextlost", loseContext);
|
|
2190
|
+
map.off("webglcontextrestored", restoreContext);
|
|
2191
|
+
map.off("remove", removed);
|
|
2192
|
+
}
|
|
2193
|
+
const removed = () => dispose(false);
|
|
2194
|
+
map.on("style.load", restore);
|
|
2195
|
+
map.on("webglcontextlost", loseContext);
|
|
2196
|
+
map.on("webglcontextrestored", restoreContext);
|
|
2197
|
+
map.on("remove", removed);
|
|
2198
|
+
return {
|
|
2199
|
+
dispose,
|
|
2200
|
+
assertBlend(images) {
|
|
2201
|
+
for (const image of images) {
|
|
2202
|
+
if (image.names.some((name) => entries.has(name))) {
|
|
2203
|
+
throw new TypeError("Dynamic image names must remain identical across blended themes.");
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2207
|
+
invalidate(state) {
|
|
2208
|
+
if (disposed) return;
|
|
2209
|
+
const current = context();
|
|
2210
|
+
for (const entry of entries.values()) {
|
|
2211
|
+
if (state !== void 0 && !entry.definition.states?.includes(state)) continue;
|
|
2212
|
+
if (keyOf(entry, current) === entry.key) continue;
|
|
2213
|
+
entry.dirty = true;
|
|
2214
|
+
}
|
|
2215
|
+
if ([...entries.values()].some((entry) => entry.dirty)) schedule();
|
|
2216
|
+
},
|
|
2217
|
+
register(name, definition) {
|
|
2218
|
+
if (disposed) throw new Error("Dynamic image registry is disposed.");
|
|
2219
|
+
if (typeof name !== "string" || !name.trim())
|
|
2220
|
+
throw new TypeError("Dynamic image names must be non-empty.");
|
|
2221
|
+
if (names.has(name))
|
|
2222
|
+
throw new Error(`Dynamic image "${name}" already has a registered owner.`);
|
|
2223
|
+
if (map.getImage(name)) throw new Error(`Dynamic image "${name}" already exists.`);
|
|
2224
|
+
const { width, height, pixelRatio = 1 } = definition;
|
|
2225
|
+
if (![width, height].every((value) => Number.isInteger(value) && value > 0 && value <= 2048) || !Number.isFinite(pixelRatio) || pixelRatio <= 0 || typeof definition.render !== "function" || definition.states !== void 0 && (!Array.isArray(definition.states) || definition.states.some(
|
|
2226
|
+
(state) => typeof state !== "string" || !state.trim() || state !== state.trim()
|
|
2227
|
+
))) {
|
|
2228
|
+
throw new TypeError(
|
|
2229
|
+
"Dynamic images require fixed dimensions, a positive pixel ratio, valid state names and a render function."
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
const entry = {
|
|
2233
|
+
name,
|
|
2234
|
+
definition: { ...definition, states: definition.states?.slice() },
|
|
2235
|
+
pixelRatio,
|
|
2236
|
+
pixels: { width, height, data: new Uint8Array(width * height * 4) },
|
|
2237
|
+
dirty: false,
|
|
2238
|
+
key: ""
|
|
2239
|
+
};
|
|
2240
|
+
names.add(name);
|
|
2241
|
+
try {
|
|
2242
|
+
const current = context();
|
|
2243
|
+
entry.key = keyOf(entry, current);
|
|
2244
|
+
render(entry, current);
|
|
2245
|
+
install(entry);
|
|
2246
|
+
entries.set(name, entry);
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
names.delete(name);
|
|
2249
|
+
throw error;
|
|
2250
|
+
}
|
|
2251
|
+
return {
|
|
2252
|
+
refresh() {
|
|
2253
|
+
if (disposed || entries.get(name) !== entry) return;
|
|
2254
|
+
entry.dirty = true;
|
|
2255
|
+
schedule();
|
|
2256
|
+
},
|
|
2257
|
+
dispose() {
|
|
2258
|
+
release(entry, !disposed);
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2045
2265
|
// src/browser.ts
|
|
2046
2266
|
init_portable_identity_rules();
|
|
2047
2267
|
|
|
2048
2268
|
// src/runtime.ts
|
|
2269
|
+
init_portable_identity_rules();
|
|
2049
2270
|
var defaultTileflowRuntimeView = Object.freeze({
|
|
2050
2271
|
bearing: 0,
|
|
2051
2272
|
center: [0, 20],
|
|
@@ -2066,6 +2287,11 @@ var maximumManifestBytes = 1024 * 1024;
|
|
|
2066
2287
|
var maximumSessionGrantBytes = 64 * 1024;
|
|
2067
2288
|
var manifestCache = new globalThis.Map();
|
|
2068
2289
|
var tileflowThemeBlendLimits = Object.freeze({ maximumThemes: 8, minimumThemes: 2 });
|
|
2290
|
+
function validateTileflowThemeBlendSelection(value) {
|
|
2291
|
+
if (!value || typeof value !== "object") return false;
|
|
2292
|
+
const { position, themes } = value;
|
|
2293
|
+
return Array.isArray(themes) && themes.length >= tileflowThemeBlendLimits.minimumThemes && themes.length <= tileflowThemeBlendLimits.maximumThemes && themes.every((theme) => isTileflowThemeName(theme)) && typeof position === "number" && Number.isFinite(position) && position >= 0 && position <= themes.length - 1;
|
|
2294
|
+
}
|
|
2069
2295
|
function getTileflowStyleFontFaces(style) {
|
|
2070
2296
|
const input = style.metadata?.[tileflowStyleFontFacesMetadataKey];
|
|
2071
2297
|
if (input === void 0) return [];
|
|
@@ -3181,6 +3407,8 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
3181
3407
|
const positionPaints = plan.paints.filter((paint) => paint.follows === "position");
|
|
3182
3408
|
const dominantPaints = plan.paints.filter((paint) => paint.follows === "dominant");
|
|
3183
3409
|
const pixels = createImageMixer(map, plan.images);
|
|
3410
|
+
const mixesArtwork = plan.images.some((image) => image.follows === "position");
|
|
3411
|
+
const relayoutAt = relayoutPositions(plan);
|
|
3184
3412
|
const shownValues = /* @__PURE__ */ new Map();
|
|
3185
3413
|
const baseValues = /* @__PURE__ */ new Map();
|
|
3186
3414
|
const valueKey = (layer, property) => `${layer}\0${property}`;
|
|
@@ -3191,6 +3419,8 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
3191
3419
|
let fade;
|
|
3192
3420
|
let frameRequest;
|
|
3193
3421
|
let disposed = false;
|
|
3422
|
+
let holding = false;
|
|
3423
|
+
let artworkWaits = false;
|
|
3194
3424
|
pixels.paint("position", position);
|
|
3195
3425
|
pixels.paint("dominant", label);
|
|
3196
3426
|
const drive = (paints, at) => {
|
|
@@ -3241,9 +3471,13 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
3241
3471
|
get dominant() {
|
|
3242
3472
|
return dominant;
|
|
3243
3473
|
},
|
|
3474
|
+
get held() {
|
|
3475
|
+
return holding && (artworkWaits || Math.round(position) !== dominant);
|
|
3476
|
+
},
|
|
3244
3477
|
get position() {
|
|
3245
3478
|
return position;
|
|
3246
3479
|
},
|
|
3480
|
+
relayoutAt,
|
|
3247
3481
|
refresh(include) {
|
|
3248
3482
|
if (disposed) return;
|
|
3249
3483
|
drive(
|
|
@@ -3274,22 +3508,30 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
3274
3508
|
}
|
|
3275
3509
|
frameRequest = void 0;
|
|
3276
3510
|
},
|
|
3277
|
-
set(next) {
|
|
3511
|
+
set(next, setOptions) {
|
|
3278
3512
|
if (disposed) return;
|
|
3513
|
+
const hold = setOptions?.hold === true;
|
|
3514
|
+
if (hold) holding = true;
|
|
3279
3515
|
const target = clamp(next);
|
|
3280
3516
|
const reachesTheme = target % 1 === 0 && target !== position;
|
|
3281
3517
|
if (Math.abs(target - position) >= 25e-4 || reachesTheme) {
|
|
3282
3518
|
position = target;
|
|
3283
3519
|
drive(positionPaints, position);
|
|
3284
|
-
|
|
3520
|
+
if (holding) artworkWaits ||= mixesArtwork;
|
|
3521
|
+
else pixels.paint("position", position);
|
|
3285
3522
|
if (plan.sky !== void 0 && typeof map.setSky === "function")
|
|
3286
3523
|
map.setSky(fillTileflowThemeBlendTemplate(plan.sky, position), { validate: false });
|
|
3287
3524
|
if (plan.light !== void 0 && typeof map.setLight === "function")
|
|
3288
3525
|
map.setLight(fillTileflowThemeBlendTemplate(plan.light, position), { validate: false });
|
|
3289
3526
|
options.onPosition?.();
|
|
3290
3527
|
}
|
|
3528
|
+
if (!hold && holding) {
|
|
3529
|
+
holding = false;
|
|
3530
|
+
if (artworkWaits) pixels.paint("position", position);
|
|
3531
|
+
artworkWaits = false;
|
|
3532
|
+
}
|
|
3291
3533
|
const nextDominant = Math.round(target);
|
|
3292
|
-
if (nextDominant === dominant) return;
|
|
3534
|
+
if (nextDominant === dominant || holding) return;
|
|
3293
3535
|
dominant = nextDominant;
|
|
3294
3536
|
if (plan.switches.length > 0) {
|
|
3295
3537
|
const change = () => applySwitches(nextDominant);
|
|
@@ -3306,20 +3548,34 @@ function createImageMixer(map, images) {
|
|
|
3306
3548
|
const art = /* @__PURE__ */ new Map();
|
|
3307
3549
|
const shown = /* @__PURE__ */ new Map();
|
|
3308
3550
|
const unusable = /* @__PURE__ */ new Set();
|
|
3551
|
+
const names = /* @__PURE__ */ new Map();
|
|
3552
|
+
const alike = /* @__PURE__ */ new Map();
|
|
3309
3553
|
const artOf = (name) => {
|
|
3310
3554
|
if (!art.has(name)) {
|
|
3311
3555
|
const image = map.getImage(name)?.data;
|
|
3312
3556
|
if (!image) return void 0;
|
|
3313
|
-
|
|
3557
|
+
const copy = { data: Uint8Array.from(image.data), height: image.height, width: image.width };
|
|
3558
|
+
art.set(name, copy);
|
|
3559
|
+
names.set(copy, name);
|
|
3314
3560
|
}
|
|
3315
3561
|
return art.get(name);
|
|
3316
3562
|
};
|
|
3563
|
+
const same2 = (from, to) => {
|
|
3564
|
+
if (from === to) return true;
|
|
3565
|
+
const key = `${names.get(from)}\0${names.get(to)}`;
|
|
3566
|
+
if (!alike.has(key)) {
|
|
3567
|
+
let equal = from.data.length === to.data.length;
|
|
3568
|
+
for (let byte = 0; equal && byte < from.data.length; byte += 1)
|
|
3569
|
+
equal = from.data[byte] === to.data[byte];
|
|
3570
|
+
alike.set(key, equal);
|
|
3571
|
+
}
|
|
3572
|
+
return alike.get(key);
|
|
3573
|
+
};
|
|
3317
3574
|
return {
|
|
3318
3575
|
paint(follows, position) {
|
|
3319
3576
|
const quantized = Math.round(position * 32) / 32;
|
|
3320
3577
|
for (const image of images) {
|
|
3321
|
-
if (image.follows !== follows || unusable.has(image)
|
|
3322
|
-
continue;
|
|
3578
|
+
if (image.follows !== follows || unusable.has(image)) continue;
|
|
3323
3579
|
const sources = image.names.map(artOf);
|
|
3324
3580
|
if (sources.some((source) => !source)) continue;
|
|
3325
3581
|
const [first] = sources;
|
|
@@ -3327,14 +3583,21 @@ function createImageMixer(map, images) {
|
|
|
3327
3583
|
unusable.add(image);
|
|
3328
3584
|
continue;
|
|
3329
3585
|
}
|
|
3330
|
-
shown.set(image,
|
|
3586
|
+
if (!shown.has(image)) shown.set(image, first);
|
|
3331
3587
|
const index = Math.min(sources.length - 2, Math.floor(quantized));
|
|
3332
3588
|
const t = quantized - index;
|
|
3333
3589
|
const from = sources[index];
|
|
3334
3590
|
const to = sources[index + 1];
|
|
3335
|
-
let
|
|
3336
|
-
if (t >= 1)
|
|
3337
|
-
else if (t > 0 && from
|
|
3591
|
+
let content = from;
|
|
3592
|
+
if (t >= 1) content = to;
|
|
3593
|
+
else if (t > 0 && !same2(from, to)) content = `${index}:${t}`;
|
|
3594
|
+
const before = shown.get(image);
|
|
3595
|
+
if (before === content || typeof before !== "string" && typeof content !== "string" && same2(before, content))
|
|
3596
|
+
continue;
|
|
3597
|
+
shown.set(image, content);
|
|
3598
|
+
let data;
|
|
3599
|
+
if (typeof content !== "string") data = content.data;
|
|
3600
|
+
else {
|
|
3338
3601
|
data = new Uint8Array(from.data.length);
|
|
3339
3602
|
for (let byte = 0; byte < data.length; byte += 1)
|
|
3340
3603
|
data[byte] = from.data[byte] + (to.data[byte] - from.data[byte]) * t;
|
|
@@ -3344,6 +3607,20 @@ function createImageMixer(map, images) {
|
|
|
3344
3607
|
}
|
|
3345
3608
|
};
|
|
3346
3609
|
}
|
|
3610
|
+
function relayoutPositions(plan) {
|
|
3611
|
+
const positions = [];
|
|
3612
|
+
const relays = (entry) => entry.group === "layout" || entry.property.endsWith("-pattern") || entry.values.some(readsTileflowFeatureData);
|
|
3613
|
+
for (let index = 0; index + 1 < plan.themes; index += 1) {
|
|
3614
|
+
const switched = plan.switches.some(
|
|
3615
|
+
(entry) => relays(entry) && JSON.stringify(entry.values[index]) !== JSON.stringify(entry.values[index + 1])
|
|
3616
|
+
);
|
|
3617
|
+
const icons = plan.images.some(
|
|
3618
|
+
(image) => image.follows === "dominant" && image.names[index] !== image.names[index + 1]
|
|
3619
|
+
);
|
|
3620
|
+
if (switched || icons) positions.push(index + 0.5);
|
|
3621
|
+
}
|
|
3622
|
+
return positions;
|
|
3623
|
+
}
|
|
3347
3624
|
function isCrossfadeMap(map) {
|
|
3348
3625
|
return typeof map.getCanvas === "function" && typeof map.redraw === "function" && typeof map.project === "function" && typeof map.unproject === "function" && typeof map.isStyleLoaded === "function" && typeof map.areTilesLoaded === "function";
|
|
3349
3626
|
}
|
|
@@ -5046,6 +5323,7 @@ l(H, "PMTiles");
|
|
|
5046
5323
|
var w = H;
|
|
5047
5324
|
|
|
5048
5325
|
// src/pmtiles-browser.ts
|
|
5326
|
+
var missingRasterTile = null;
|
|
5049
5327
|
var registeredPmtilesProtocols = /* @__PURE__ */ new WeakSet();
|
|
5050
5328
|
var maximumSectionBytes = 16 * 1024 * 1024;
|
|
5051
5329
|
var maximumParserBytes = 32 * 1024 * 1024;
|
|
@@ -5073,7 +5351,9 @@ var TileflowPmtilesProtocol = class {
|
|
|
5073
5351
|
const archive2 = this.#archive(parseArchiveTarget(request.url));
|
|
5074
5352
|
const header2 = await archive2.getHeader();
|
|
5075
5353
|
const tileJson = await archive2.getTileJson(request.url);
|
|
5076
|
-
|
|
5354
|
+
if (!isRecord4(tileJson))
|
|
5355
|
+
throw new TypeError("A PMTiles archive described no TileJSON object.");
|
|
5356
|
+
const data = header2.tileType === O.Mlt ? { ...tileJson, encoding: "mlt" } : tileJson;
|
|
5077
5357
|
abortController.signal.throwIfAborted();
|
|
5078
5358
|
return { data };
|
|
5079
5359
|
}
|
|
@@ -5090,7 +5370,7 @@ var TileflowPmtilesProtocol = class {
|
|
|
5090
5370
|
};
|
|
5091
5371
|
}
|
|
5092
5372
|
return {
|
|
5093
|
-
data: header.tileType === O.Mvt || header.tileType === O.Mlt ? new Uint8Array() :
|
|
5373
|
+
data: header.tileType === O.Mvt || header.tileType === O.Mlt ? new Uint8Array() : missingRasterTile
|
|
5094
5374
|
};
|
|
5095
5375
|
};
|
|
5096
5376
|
tile = protocolCompatibility(this.tilev4);
|
|
@@ -5274,10 +5554,22 @@ function handleSystemColorSchemeChange() {
|
|
|
5274
5554
|
systemColorScheme = next;
|
|
5275
5555
|
for (const listener of systemColorSchemeListeners) listener(next);
|
|
5276
5556
|
}
|
|
5557
|
+
function validateTileflowBrowserThemeBlendSelection(value) {
|
|
5558
|
+
if (!validateTileflowThemeBlendSelection(value)) return false;
|
|
5559
|
+
const { hold } = value;
|
|
5560
|
+
return hold === void 0 || typeof hold === "boolean";
|
|
5561
|
+
}
|
|
5277
5562
|
var maximumBlendThemes = 8;
|
|
5278
5563
|
var maximumTransitionMs = 5e3;
|
|
5279
5564
|
var blendLabelFadeMs = 450;
|
|
5280
5565
|
var maximumTileflowBlendStyleBytes = 16 * 1024 * 1024;
|
|
5566
|
+
function validateStyleState(name, value) {
|
|
5567
|
+
if (typeof name !== "string" || !name.trim())
|
|
5568
|
+
return new TypeError("Style state names must be non-empty strings.");
|
|
5569
|
+
if (value === null || typeof value === "boolean" || typeof value === "string" || typeof value === "number" && Number.isFinite(value))
|
|
5570
|
+
return void 0;
|
|
5571
|
+
return new TypeError("Style state values must be booleans, finite numbers, strings, or null.");
|
|
5572
|
+
}
|
|
5281
5573
|
function createTileflowThemeController(options) {
|
|
5282
5574
|
assertConcreteRuntimeTheme(options.initial, "initial");
|
|
5283
5575
|
const loadFonts = options.loadFonts ?? ((runtimeStyle) => loadTileflowStyleFonts(runtimeStyle.style, { fontFaces: runtimeStyle.fontFaces }));
|
|
@@ -5307,26 +5599,72 @@ function createTileflowThemeController(options) {
|
|
|
5307
5599
|
let emphasisRun = 0;
|
|
5308
5600
|
let emphasisFrame;
|
|
5309
5601
|
let emphasisSettle;
|
|
5602
|
+
const states = /* @__PURE__ */ new Map();
|
|
5603
|
+
let images;
|
|
5604
|
+
function imageContext() {
|
|
5605
|
+
return {
|
|
5606
|
+
theme: current.theme,
|
|
5607
|
+
blend: activeBlend ? Object.freeze({
|
|
5608
|
+
position: activeBlend.blender.position,
|
|
5609
|
+
themes: Object.freeze(activeBlend.themes.map((theme) => theme.theme))
|
|
5610
|
+
}) : void 0,
|
|
5611
|
+
states: Object.freeze(
|
|
5612
|
+
Object.fromEntries([...states].map(([name, state]) => [name, state.shown]))
|
|
5613
|
+
)
|
|
5614
|
+
};
|
|
5615
|
+
}
|
|
5310
5616
|
return {
|
|
5311
5617
|
dispose() {
|
|
5312
5618
|
disposed = true;
|
|
5619
|
+
images?.dispose();
|
|
5313
5620
|
requestId += 1;
|
|
5314
5621
|
activeBlend?.blender.dispose();
|
|
5315
5622
|
stopEmphasisMotion({ status: "superseded", theme: current.theme });
|
|
5623
|
+
for (const state of states.values())
|
|
5624
|
+
stopStateMotion(state, { status: "superseded", theme: current.theme });
|
|
5316
5625
|
},
|
|
5317
5626
|
getBlend() {
|
|
5318
5627
|
if (!activeBlend) return void 0;
|
|
5319
5628
|
return {
|
|
5629
|
+
held: activeBlend.blender.held,
|
|
5320
5630
|
position: activeBlend.blender.position,
|
|
5631
|
+
relayoutAt: activeBlend.blender.relayoutAt,
|
|
5321
5632
|
themes: activeBlend.themes.map((theme) => theme.theme)
|
|
5322
5633
|
};
|
|
5323
5634
|
},
|
|
5635
|
+
registerImage(name, definition) {
|
|
5636
|
+
if (disposed) throw new Error("Tileflow theme controller is disposed.");
|
|
5637
|
+
if (activeBlend?.plan.images.some((image) => image.names.includes(name))) {
|
|
5638
|
+
throw new TypeError("Dynamic image names must remain identical across blended themes.");
|
|
5639
|
+
}
|
|
5640
|
+
images ??= createTileflowDynamicImageRegistry(options.map, imageContext, resetStates);
|
|
5641
|
+
return images.register(name, definition);
|
|
5642
|
+
},
|
|
5324
5643
|
getCurrent() {
|
|
5325
5644
|
return current;
|
|
5326
5645
|
},
|
|
5327
5646
|
getEmphasis() {
|
|
5328
5647
|
return emphasis;
|
|
5329
5648
|
},
|
|
5649
|
+
getState(name) {
|
|
5650
|
+
return typeof name === "string" ? states.get(name.trim())?.value : void 0;
|
|
5651
|
+
},
|
|
5652
|
+
setState(name, value, changeOptions) {
|
|
5653
|
+
const invalid = validateStyleState(name, value);
|
|
5654
|
+
if (invalid) return Promise.resolve({ error: invalid, status: "failed", theme: current.theme });
|
|
5655
|
+
if (disposed) return Promise.resolve(disposedResult(current.theme));
|
|
5656
|
+
if (typeof options.map.setGlobalStateProperty !== "function") {
|
|
5657
|
+
return Promise.resolve({
|
|
5658
|
+
error: new TypeError("Style states need a MapLibre map with global state (GL JS 5.6+)."),
|
|
5659
|
+
status: "failed",
|
|
5660
|
+
theme: current.theme
|
|
5661
|
+
});
|
|
5662
|
+
}
|
|
5663
|
+
const transition = transitionOf(changeOptions);
|
|
5664
|
+
if (transition instanceof TypeError)
|
|
5665
|
+
return Promise.resolve({ error: transition, status: "failed", theme: current.theme });
|
|
5666
|
+
return changeState(name.trim(), value, reducedMotion() ? 0 : transition);
|
|
5667
|
+
},
|
|
5330
5668
|
setEmphasis(request, changeOptions) {
|
|
5331
5669
|
if (request !== void 0) {
|
|
5332
5670
|
const invalid = validateTileflowEmphasis(request);
|
|
@@ -5350,8 +5688,9 @@ function createTileflowThemeController(options) {
|
|
|
5350
5688
|
const key = blendKey(request.themes);
|
|
5351
5689
|
if (activeBlend?.key === key) {
|
|
5352
5690
|
requestId += 1;
|
|
5353
|
-
activeBlend.blender.set(request.position);
|
|
5691
|
+
activeBlend.blender.set(request.position, { hold: request.hold === true });
|
|
5354
5692
|
current = activeBlend.themes[activeBlend.blender.dominant];
|
|
5693
|
+
images?.invalidate();
|
|
5355
5694
|
return Promise.resolve({ status: "applied", theme: current.theme });
|
|
5356
5695
|
}
|
|
5357
5696
|
if (pendingBlend?.key === key) {
|
|
@@ -5439,7 +5778,9 @@ function createTileflowThemeController(options) {
|
|
|
5439
5778
|
),
|
|
5440
5779
|
Promise.all(themes.map((theme) => loadFonts(theme)))
|
|
5441
5780
|
]);
|
|
5442
|
-
|
|
5781
|
+
const plan = planTileflowThemeBlend(styles);
|
|
5782
|
+
images?.assertBlend(plan.images);
|
|
5783
|
+
return plan;
|
|
5443
5784
|
}
|
|
5444
5785
|
function enqueue(runId, target) {
|
|
5445
5786
|
const operation = applyQueue.then(
|
|
@@ -5458,6 +5799,8 @@ function createTileflowThemeController(options) {
|
|
|
5458
5799
|
async function applyTarget(runId, target) {
|
|
5459
5800
|
const targetTheme = targetThemeOf(target);
|
|
5460
5801
|
if (disposed || runId !== requestId) return { status: "superseded", theme: targetTheme };
|
|
5802
|
+
await whenContextAvailable(options.map);
|
|
5803
|
+
if (disposed || runId !== requestId) return { status: "superseded", theme: targetTheme };
|
|
5461
5804
|
const previous = { current, shown };
|
|
5462
5805
|
options.onTransition?.({ currentTheme: previous.current.theme, phase: "applying", targetTheme });
|
|
5463
5806
|
const cover = beginTileflowStyleCrossfade(options.map, target.transition);
|
|
@@ -5465,6 +5808,7 @@ function createTileflowThemeController(options) {
|
|
|
5465
5808
|
activeBlend = void 0;
|
|
5466
5809
|
const styleInput = target.kind === "theme" ? target.style.style : target.plan.styleAt(target.position());
|
|
5467
5810
|
try {
|
|
5811
|
+
if (target.kind === "blend") images?.assertBlend(target.plan.images);
|
|
5468
5812
|
await applyMapStyle(options.map, styleInput, timeoutMs);
|
|
5469
5813
|
if (disposed) {
|
|
5470
5814
|
cover?.cancel();
|
|
@@ -5490,6 +5834,8 @@ function createTileflowThemeController(options) {
|
|
|
5490
5834
|
}
|
|
5491
5835
|
shownLayers = typeof styleInput === "string" ? void 0 : styleInput.layers;
|
|
5492
5836
|
resetEmphasis();
|
|
5837
|
+
resetStates();
|
|
5838
|
+
images?.invalidate();
|
|
5493
5839
|
await cover?.finish({ stop: () => disposed || runId !== requestId });
|
|
5494
5840
|
options.onTransition?.({
|
|
5495
5841
|
currentTheme: current.theme,
|
|
@@ -5531,6 +5877,7 @@ function createTileflowThemeController(options) {
|
|
|
5531
5877
|
});
|
|
5532
5878
|
}
|
|
5533
5879
|
async function restore(previous) {
|
|
5880
|
+
await whenContextAvailable(options.map);
|
|
5534
5881
|
const blend = previous.shown.blend;
|
|
5535
5882
|
if (blend) {
|
|
5536
5883
|
const position = blend.blender.position;
|
|
@@ -5546,6 +5893,73 @@ function createTileflowThemeController(options) {
|
|
|
5546
5893
|
}
|
|
5547
5894
|
current = previous.current;
|
|
5548
5895
|
resetEmphasis();
|
|
5896
|
+
resetStates();
|
|
5897
|
+
images?.invalidate();
|
|
5898
|
+
}
|
|
5899
|
+
function writeState(name, value) {
|
|
5900
|
+
const set = options.map.setGlobalStateProperty;
|
|
5901
|
+
try {
|
|
5902
|
+
set?.call(options.map, name, value);
|
|
5903
|
+
} catch {
|
|
5904
|
+
}
|
|
5905
|
+
images?.invalidate(name);
|
|
5906
|
+
}
|
|
5907
|
+
function resetStates() {
|
|
5908
|
+
for (const [name, state] of states) {
|
|
5909
|
+
if (state.shown !== void 0) writeState(name, state.shown);
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
function stopStateMotion(state, result) {
|
|
5913
|
+
state.stop?.();
|
|
5914
|
+
state.stop = void 0;
|
|
5915
|
+
const settle = state.settle;
|
|
5916
|
+
state.settle = void 0;
|
|
5917
|
+
if (result) settle?.(result);
|
|
5918
|
+
}
|
|
5919
|
+
function changeState(name, value, duration) {
|
|
5920
|
+
const state = states.get(name) ?? { run: 0, shown: void 0, value };
|
|
5921
|
+
states.set(name, state);
|
|
5922
|
+
stopStateMotion(state, { status: "superseded", theme: current.theme });
|
|
5923
|
+
const run = ++state.run;
|
|
5924
|
+
const from = state.shown;
|
|
5925
|
+
state.value = value;
|
|
5926
|
+
const view = tileflowThemeMotionView(options.map);
|
|
5927
|
+
if (duration <= 0 || !view || typeof from !== "number" || typeof value !== "number" || from === value) {
|
|
5928
|
+
state.shown = value;
|
|
5929
|
+
writeState(name, value);
|
|
5930
|
+
return Promise.resolve({ status: "applied", theme: current.theme });
|
|
5931
|
+
}
|
|
5932
|
+
return new Promise((resolve) => {
|
|
5933
|
+
state.settle = resolve;
|
|
5934
|
+
const now = () => view.performance?.now?.() ?? Date.now();
|
|
5935
|
+
const start = now();
|
|
5936
|
+
const least = Math.abs(value - from) / 100;
|
|
5937
|
+
let cancelled = false;
|
|
5938
|
+
let frame;
|
|
5939
|
+
const step = () => {
|
|
5940
|
+
frame = void 0;
|
|
5941
|
+
if (cancelled || disposed || run !== state.run) return;
|
|
5942
|
+
const t = Math.min(1, (now() - start) / duration);
|
|
5943
|
+
const eased = t * t * (3 - 2 * t);
|
|
5944
|
+
const shown2 = t >= 1 ? value : from + (value - from) * eased;
|
|
5945
|
+
if (t >= 1 || Math.abs(shown2 - state.shown) >= least) {
|
|
5946
|
+
state.shown = shown2;
|
|
5947
|
+
writeState(name, shown2);
|
|
5948
|
+
}
|
|
5949
|
+
if (t >= 1) {
|
|
5950
|
+
state.stop = void 0;
|
|
5951
|
+
state.settle = void 0;
|
|
5952
|
+
resolve({ status: "applied", theme: current.theme });
|
|
5953
|
+
return;
|
|
5954
|
+
}
|
|
5955
|
+
frame = view.requestAnimationFrame(step);
|
|
5956
|
+
};
|
|
5957
|
+
state.stop = () => {
|
|
5958
|
+
cancelled = true;
|
|
5959
|
+
if (frame !== void 0) view.cancelAnimationFrame(frame);
|
|
5960
|
+
};
|
|
5961
|
+
frame = view.requestAnimationFrame(step);
|
|
5962
|
+
});
|
|
5549
5963
|
}
|
|
5550
5964
|
function valueKey(layer, property) {
|
|
5551
5965
|
return `${layer}\0${property}`;
|
|
@@ -5806,6 +6220,26 @@ function validateConcreteRuntimeTheme(style, role) {
|
|
|
5806
6220
|
`Tileflow theme controller ${role} style requires a concrete portable theme name; received ${JSON.stringify(style.theme)}.`
|
|
5807
6221
|
);
|
|
5808
6222
|
}
|
|
6223
|
+
function whenContextAvailable(map) {
|
|
6224
|
+
if (!isContextLost(map)) return Promise.resolve();
|
|
6225
|
+
return new Promise((resolve) => {
|
|
6226
|
+
const restored = () => {
|
|
6227
|
+
map.off("webglcontextrestored", restored);
|
|
6228
|
+
resolve();
|
|
6229
|
+
};
|
|
6230
|
+
map.on("webglcontextrestored", restored);
|
|
6231
|
+
});
|
|
6232
|
+
}
|
|
6233
|
+
function isContextLost(map) {
|
|
6234
|
+
if (typeof map.getCanvas !== "function") return false;
|
|
6235
|
+
try {
|
|
6236
|
+
const canvas = map.getCanvas.call(map);
|
|
6237
|
+
const context = canvas?.getContext?.("webgl2") ?? canvas?.getContext?.("webgl");
|
|
6238
|
+
return context?.isContextLost?.() === true;
|
|
6239
|
+
} catch {
|
|
6240
|
+
return false;
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
5809
6243
|
function applyMapStyle(map, style, timeoutMs) {
|
|
5810
6244
|
return new Promise((resolve, reject) => {
|
|
5811
6245
|
let settled = false;
|
|
@@ -6115,37 +6549,21 @@ function attachTileflowMapLifecycle(options) {
|
|
|
6115
6549
|
};
|
|
6116
6550
|
}
|
|
6117
6551
|
function createTileflowTransformRequest(options) {
|
|
6118
|
-
if (!options.always && !options.transformRequest && !options.worldRequestBridge) {
|
|
6119
|
-
const analytics = options.getAnalytics();
|
|
6120
|
-
const requiresCommercialSession = Boolean(options.sessionController && analytics?.mapId);
|
|
6121
|
-
if (!requiresCommercialSession && (!analytics || analytics.enabled === false)) {
|
|
6122
|
-
return void 0;
|
|
6123
|
-
}
|
|
6124
|
-
}
|
|
6125
|
-
const asyncAnalyticsTiming = options.asyncAnalyticsTiming ?? "request";
|
|
6126
6552
|
return (url, resourceType) => {
|
|
6127
6553
|
const request = options.transformRequest?.(url, resourceType);
|
|
6128
|
-
const
|
|
6129
|
-
|
|
6130
|
-
return request.then(
|
|
6131
|
-
(resolvedRequest) => applyTileflowRequest(
|
|
6132
|
-
url,
|
|
6133
|
-
resolvedRequest,
|
|
6134
|
-
asyncAnalyticsTiming === "resolution" ? options.getAnalytics() : analyticsAtRequest,
|
|
6135
|
-
options.sessionId,
|
|
6136
|
-
options.sessionController,
|
|
6137
|
-
options.worldRequestBridge
|
|
6138
|
-
) ?? resolvedRequest
|
|
6139
|
-
);
|
|
6140
|
-
}
|
|
6141
|
-
return applyTileflowRequest(
|
|
6554
|
+
const analytics = options.getAnalytics();
|
|
6555
|
+
const apply = (resolvedRequest) => applyTileflowRequest(
|
|
6142
6556
|
url,
|
|
6143
|
-
|
|
6144
|
-
|
|
6557
|
+
resolvedRequest,
|
|
6558
|
+
analytics,
|
|
6145
6559
|
options.sessionId,
|
|
6146
6560
|
options.sessionController,
|
|
6147
6561
|
options.worldRequestBridge
|
|
6148
6562
|
);
|
|
6563
|
+
if (isPromiseLike(request)) {
|
|
6564
|
+
return request.then((resolvedRequest) => apply(resolvedRequest) ?? resolvedRequest);
|
|
6565
|
+
}
|
|
6566
|
+
return apply(request);
|
|
6149
6567
|
};
|
|
6150
6568
|
}
|
|
6151
6569
|
function applyTileflowRequest(url, request, analytics, sessionId, sessionController, worldRequestBridge) {
|
|
@@ -6215,6 +6633,7 @@ export {
|
|
|
6215
6633
|
tileflowMaplibreContourVersion,
|
|
6216
6634
|
tileflowPmtilesMaximumDirectoryDepth,
|
|
6217
6635
|
tileflowPmtilesProtocol,
|
|
6636
|
+
validateTileflowBrowserThemeBlendSelection,
|
|
6218
6637
|
validateTileflowEmphasis
|
|
6219
6638
|
};
|
|
6220
6639
|
/*! Bundled license information:
|