@tileflow/core 0.1.0-alpha.32 → 0.1.0-alpha.34

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/dist/native.js CHANGED
@@ -3493,2046 +3493,2055 @@ var init_runtime_view_schema = __esm({
3493
3493
  }
3494
3494
  });
3495
3495
 
3496
- // src/themes/model.ts
3497
- var tileflowThemeLimits, themeTypographyStyleKeys, themeTypographyKeys;
3498
- var init_model = __esm({
3499
- "src/themes/model.ts"() {
3500
- "use strict";
3501
- tileflowThemeLimits = Object.freeze({ maxThemes: 64 });
3502
- themeTypographyStyleKeys = ["fallbacks", "font", "letterSpacing", "transform"];
3503
- themeTypographyKeys = [
3504
- ...themeTypographyStyleKeys,
3505
- "places",
3506
- "poi",
3507
- "roads",
3508
- "water"
3509
- ];
3496
+ // src/theme-blend.ts
3497
+ function planTileflowThemeBlend(styles, options = {}) {
3498
+ if (styles.length < 2)
3499
+ throw new TileflowThemeBlendError("A theme blend needs two or more themes.");
3500
+ const [base2] = styles;
3501
+ assertSameStructure(styles, base2);
3502
+ const mixedImages = new Set(imageProperties);
3503
+ if (options.iconImages === "switch") mixedImages.delete("icon-image");
3504
+ const paints = [];
3505
+ const switches = [];
3506
+ const imageGroups = collectImageNames(styles, base2, mixedImages);
3507
+ const symbolLayers = /* @__PURE__ */ new Set();
3508
+ const images = /* @__PURE__ */ new Map();
3509
+ let splitLayers = 0;
3510
+ let copiedLayers = 0;
3511
+ let switchedLayers = 0;
3512
+ const layers = [];
3513
+ for (const [index, layer] of base2.layers.entries()) {
3514
+ const variants = styles.map((style) => style.layers[index]);
3515
+ layers.push(...planLayer(variants, String(layer.type)));
3510
3516
  }
3511
- });
3512
-
3513
- // src/manifest-schema.ts
3514
- function createTileflowRuntimeManifestSchema(primitives, operations, identities = createPortableIdentitySchemas(operations)) {
3515
- const { tileflowPortableIdSchema, tileflowThemeNameSchema } = identities;
3516
- const safeTextSchema = operations.refine(
3517
- operations.refine(
3518
- operations.maxLength(operations.minLength(operations.string(), 1), 2048),
3519
- (value) => value === value.trim(),
3520
- "Expected text without surrounding whitespace"
3521
- ),
3522
- (value) => !/[\p{Cc}\\]/u.test(value),
3523
- "Expected safe text"
3524
- );
3525
- const publicResourceUrlSchema = operations.refine(
3526
- safeTextSchema,
3527
- isPublicResourceUrl,
3528
- "Expected an HTTP(S), root-relative, or path-relative public URL"
3529
- );
3530
- const apiOriginSchema = operations.refine(
3531
- safeTextSchema,
3532
- isApiOrigin,
3533
- "Expected an absolute HTTP(S) origin without path, credentials, query, or fragment"
3534
- );
3535
- const viewSchema2 = createTileflowRuntimeViewSchema(operations);
3536
- const fontFaceSchema = operations.object({
3537
- family: operations.maxLength(safeTextSchema, 100),
3538
- source: publicResourceUrlSchema,
3539
- style: operations.optional(operations.enum(["italic", "normal", "oblique"])),
3540
- weight: operations.optional(
3541
- operations.enum(["100", "200", "300", "400", "500", "600", "700", "800", "900"])
3542
- )
3543
- });
3544
- const fontFacesSchema = operations.superRefine(
3545
- operations.maxLength(operations.array(fontFaceSchema), 16),
3546
- validateUniqueFontFaces
3547
- );
3548
- const manifestThemeSchema = operations.object({
3549
- colorScheme: operations.enum(["dark", "light"]),
3550
- fontFaces: operations.optional(fontFacesSchema),
3551
- revision: operations.optional(operations.maxLength(safeTextSchema, 128)),
3552
- styleId: operations.optional(operations.maxLength(safeTextSchema, 128)),
3553
- styleUrl: publicResourceUrlSchema
3554
- });
3555
- const manifestMapSchema = operations.superRefine(
3556
- operations.object({
3557
- apiUrl: operations.optional(apiOriginSchema),
3558
- defaultTheme: tileflowThemeNameSchema,
3559
- environment: operations.optional(operations.maxLength(safeTextSchema, 128)),
3560
- mapId: operations.optional(operations.maxLength(safeTextSchema, 128)),
3561
- systemThemes: operations.optional(
3562
- operations.object({
3563
- dark: tileflowThemeNameSchema,
3564
- light: tileflowThemeNameSchema
3565
- })
3566
- ),
3567
- themes: operations.record(tileflowThemeNameSchema, manifestThemeSchema),
3568
- usageMode: operations.optional(operations.literal("session")),
3569
- view: operations.optional(viewSchema2),
3570
- worldGeneration: operations.optional(operations.literal("v1"))
3571
- }),
3572
- (entry, context) => {
3573
- const names = Object.keys(entry.themes);
3574
- if (names.length === 0) {
3575
- context.addIssue({
3576
- code: "custom",
3577
- message: "Expected at least one theme",
3578
- path: ["themes"]
3579
- });
3580
- }
3581
- if (names.length > tileflowThemeLimits.maxThemes) {
3582
- context.addIssue({
3583
- code: "too_big",
3584
- maximum: tileflowThemeLimits.maxThemes,
3585
- origin: "object",
3586
- path: ["themes"]
3587
- });
3588
- }
3589
- if (!Object.hasOwn(entry.themes, entry.defaultTheme)) {
3590
- context.addIssue({
3591
- code: "custom",
3592
- message: "defaultTheme must name a declared theme",
3593
- path: ["defaultTheme"]
3594
- });
3595
- }
3596
- if (entry.systemThemes) {
3597
- for (const colorScheme of ["light", "dark"]) {
3598
- const name = entry.systemThemes[colorScheme];
3599
- const theme = entry.themes[name];
3600
- if (!theme) {
3601
- context.addIssue({
3602
- code: "custom",
3603
- message: `systemThemes.${colorScheme} must name a declared theme`,
3604
- path: ["systemThemes", colorScheme]
3605
- });
3606
- } else if (theme.colorScheme !== colorScheme) {
3607
- context.addIssue({
3608
- code: "custom",
3609
- message: `systemThemes.${colorScheme} must reference a ${colorScheme} theme`,
3610
- path: ["systemThemes", colorScheme]
3611
- });
3517
+ const switchesByLayer = /* @__PURE__ */ new Map();
3518
+ for (const entry of switches) {
3519
+ const list = switchesByLayer.get(entry.layer) ?? [];
3520
+ list.push(entry);
3521
+ switchesByLayer.set(entry.layer, list);
3522
+ }
3523
+ const sky = differingRoot(styles, "sky");
3524
+ const light = differingRoot(styles, "light");
3525
+ const metadata = alignInteractionManifest(base2.metadata, layers);
3526
+ return {
3527
+ themes: styles.length,
3528
+ paints,
3529
+ images: [...images.values()],
3530
+ switches,
3531
+ ...sky === void 0 ? {} : { sky },
3532
+ ...light === void 0 ? {} : { light },
3533
+ summary: { splitLayers, copiedLayers, switchedLayers, switchedValues: switches.length },
3534
+ styleAt(position) {
3535
+ const at = clampPosition(position, styles.length);
3536
+ const dominant = Math.round(at);
3537
+ const style = {
3538
+ ...base2,
3539
+ ...metadata === void 0 ? {} : { metadata },
3540
+ // Symbols show the nearest theme, as the label cross-fade leaves them between changes.
3541
+ layers: layers.map(
3542
+ (layer) => fillLayer(
3543
+ layer,
3544
+ symbolLayers.has(String(layer.id)) ? dominant : at,
3545
+ dominant,
3546
+ switchesByLayer.get(String(layer.id))
3547
+ )
3548
+ )
3549
+ };
3550
+ if (sky !== void 0) style.sky = fillTemplate(sky, at);
3551
+ if (light !== void 0) style.light = fillTemplate(light, at);
3552
+ return style;
3553
+ }
3554
+ };
3555
+ function planLayer(variants, type) {
3556
+ const [layer] = variants;
3557
+ const id = String(layer.id);
3558
+ const paint = { ...layer.paint };
3559
+ const layout = { ...layer.layout };
3560
+ const follows = type === "symbol" ? "dominant" : "position";
3561
+ const ownPaints = [];
3562
+ const ownSwitches = [];
3563
+ const driven = [];
3564
+ const paintOf = (variant) => variant.paint ?? {};
3565
+ for (const group of ["layout", "paint"]) {
3566
+ const target = group === "paint" ? paint : layout;
3567
+ for (const property of propertyNames(variants, group)) {
3568
+ const values = variants.map(
3569
+ (variant) => variant[group]?.[property]
3570
+ );
3571
+ if (values.every((value) => same(value, values[0]))) continue;
3572
+ if (mixedImages.has(property)) {
3573
+ const tuples = imageTuples(values);
3574
+ if (tuples && tuples.every((names) => imageGroups.get(names[0])?.size === 1)) {
3575
+ for (const names of tuples) {
3576
+ if (names.every((name) => name === names[0])) continue;
3577
+ images.set(JSON.stringify(names), {
3578
+ names,
3579
+ follows: property === "icon-image" ? "dominant" : "position"
3580
+ });
3581
+ }
3582
+ continue;
3612
3583
  }
3613
3584
  }
3585
+ if (group === "layout" || values.includes(void 0) || imageProperties.has(property)) {
3586
+ ownSwitches.push({ group, property, values });
3587
+ continue;
3588
+ }
3589
+ if (values.some(isDataDriven)) {
3590
+ driven.push(property);
3591
+ continue;
3592
+ }
3593
+ const template = tryMixTemplate(values);
3594
+ if (template === void 0) {
3595
+ ownSwitches.push({ group, property, values });
3596
+ continue;
3597
+ }
3598
+ ownPaints.push({ property, template });
3599
+ target[property] = template;
3614
3600
  }
3615
- if (entry.usageMode === void 0 !== (entry.worldGeneration === void 0)) {
3616
- context.addIssue({
3617
- code: "custom",
3618
- message: "usageMode and worldGeneration must be declared together"
3601
+ }
3602
+ const planned = { ...layer, paint, layout };
3603
+ let results = [{ layer: planned, extra: [] }];
3604
+ if (driven.length > 0) {
3605
+ const split = type === "symbol" ? void 0 : splitLayer();
3606
+ const copies = split ?? (type === "symbol" ? void 0 : copyLayer());
3607
+ if (copies) results = copies;
3608
+ else
3609
+ for (const property of driven)
3610
+ ownSwitches.push({
3611
+ group: "paint",
3612
+ property,
3613
+ values: variants.map((variant) => paintOf(variant)[property])
3614
+ });
3615
+ }
3616
+ if (options.featureSwitches === "layers" && results.length === 1 && ownSwitches.some((entry) => entry.values.some(isDataDriven)))
3617
+ results = switchLayer();
3618
+ for (const result of results) {
3619
+ const resultId = String(result.layer.id);
3620
+ if (type === "symbol") symbolLayers.add(resultId);
3621
+ const overridden = new Set(result.extra.map((entry) => entry.property));
3622
+ for (const entry of [
3623
+ ...ownPaints.filter((own) => !overridden.has(own.property)),
3624
+ ...result.extra
3625
+ ])
3626
+ paints.push({ layer: resultId, property: entry.property, template: entry.template, follows });
3627
+ if (result.visibility)
3628
+ switches.push({
3629
+ layer: resultId,
3630
+ group: "layout",
3631
+ property: "visibility",
3632
+ values: result.visibility
3619
3633
  });
3620
- }
3634
+ else for (const entry of ownSwitches) switches.push({ layer: resultId, ...entry });
3621
3635
  }
3622
- );
3623
- const canonicalManifestSchema = operations.superRefine(
3624
- operations.object({
3625
- apiUrl: operations.optional(apiOriginSchema),
3626
- maps: operations.record(tileflowPortableIdSchema, manifestMapSchema),
3627
- version: operations.literal(tileflowRuntimeManifestVersion)
3628
- }),
3629
- (manifest, context) => {
3630
- const mapCount = Object.keys(manifest.maps).length;
3631
- if (mapCount === 0) {
3632
- context.addIssue({ code: "custom", message: "Expected at least one map", path: ["maps"] });
3633
- }
3634
- if (mapCount > 1e3) {
3635
- context.addIssue({ code: "too_big", maximum: 1e3, origin: "object", path: ["maps"] });
3636
- }
3637
- if (primitives.utf8ByteLength(JSON.stringify(manifest)) > tileflowRuntimeManifestLimits.maximumBytes) {
3638
- context.addIssue({
3639
- code: "too_big",
3640
- maximum: tileflowRuntimeManifestLimits.maximumBytes,
3641
- origin: "string",
3642
- message: "Manifest JSON exceeds the 1 MiB limit"
3643
- });
3644
- }
3645
- }
3646
- );
3647
- const safeManifestInputSchema = operations.superRefine(operations.unknown(), (input, context) => {
3648
- const unsafe = findUnsafeManifestStructure(input);
3649
- if (unsafe) {
3650
- context.addIssue({
3651
- code: "custom",
3652
- message: unsafe.reason === "prototype" ? "Expected plain manifest objects" : "Expected no prototype-mutating keys",
3653
- path: unsafe.path
3636
+ return results.map((result) => result.layer);
3637
+ function switchLayer() {
3638
+ switchedLayers += variants.length;
3639
+ return variants.map((_, theme) => {
3640
+ const copyPaint = { ...paint };
3641
+ const copyLayout = { ...layout };
3642
+ for (const entry of ownSwitches) {
3643
+ const target = entry.group === "paint" ? copyPaint : copyLayout;
3644
+ const value = entry.values[theme];
3645
+ if (value === void 0) delete target[entry.property];
3646
+ else target[entry.property] = value;
3647
+ }
3648
+ const shown = copyLayout.visibility === "none" ? "none" : "visible";
3649
+ return {
3650
+ layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint, layout: copyLayout },
3651
+ extra: [],
3652
+ visibility: variants.map((__, nearest) => nearest === theme ? shown : "none")
3653
+ };
3654
3654
  });
3655
3655
  }
3656
- });
3657
- function validateUniqueFontFaces(fontFaces, context) {
3658
- const seen = /* @__PURE__ */ new Map();
3659
- for (const [index, fontFace] of fontFaces.entries()) {
3660
- const identity = `${fontFace.family}\0${fontFace.style ?? "normal"}\0${fontFace.weight ?? "400"}`;
3661
- const first = seen.get(identity);
3662
- if (first !== void 0) {
3663
- context.addIssue({
3664
- code: "custom",
3665
- message: `Duplicate font face identity; first declared at index ${first}`,
3666
- path: [index]
3667
- });
3668
- continue;
3656
+ function splitLayer() {
3657
+ if (isLegacyFilter(layer.filter)) return void 0;
3658
+ const branches = splitBranches(
3659
+ Object.fromEntries(
3660
+ driven.map((property) => [
3661
+ property,
3662
+ variants.map((variant) => paintOf(variant)[property])
3663
+ ])
3664
+ )
3665
+ );
3666
+ if (!branches) return void 0;
3667
+ const groups = /* @__PURE__ */ new Map();
3668
+ for (const branch of branches) {
3669
+ const key = JSON.stringify(branch.valuesByProperty);
3670
+ let group = groups.get(key);
3671
+ if (!group) {
3672
+ const templates = {};
3673
+ for (const [property, values] of Object.entries(branch.valuesByProperty)) {
3674
+ const template = tryMixTemplate(values);
3675
+ if (template === void 0) return void 0;
3676
+ templates[property] = template;
3677
+ }
3678
+ group = { templates, conditions: [] };
3679
+ groups.set(key, group);
3680
+ }
3681
+ group.conditions.push(
3682
+ branch.conditions.length === 1 ? branch.conditions[0] : ["all", ...branch.conditions]
3683
+ );
3669
3684
  }
3670
- seen.set(identity, index);
3671
- }
3672
- }
3673
- function isPublicResourceUrl(value) {
3674
- if (value.startsWith("//")) return false;
3675
- if (!value.startsWith("/") && !value.startsWith("./") && !value.startsWith("../") && !isAbsolutePublicUrl(value)) {
3676
- return false;
3685
+ splitLayers += groups.size;
3686
+ return [...groups.values()].map((group, number3) => {
3687
+ const chosen = group.conditions.length === 1 ? group.conditions[0] : ["any", ...group.conditions];
3688
+ return {
3689
+ layer: {
3690
+ ...planned,
3691
+ id: `${id}::branch-${number3}`,
3692
+ filter: layer.filter === void 0 ? chosen : ["all", layer.filter, chosen],
3693
+ paint: { ...paint, ...group.templates }
3694
+ },
3695
+ extra: Object.entries(group.templates).filter(([, template]) => containsLeaf(template)).map(([property, template]) => ({ property, template }))
3696
+ };
3697
+ });
3677
3698
  }
3678
- return isSafeResolvedPublicUrl(value);
3679
- }
3680
- function isAbsolutePublicUrl(value) {
3681
- try {
3682
- const url = primitives.parseUrl(value);
3683
- return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && !url.hash && !hasEncodedPathSeparator(value);
3684
- } catch {
3685
- return false;
3699
+ function copyLayer() {
3700
+ const opacity = opacityProperties[type];
3701
+ if (!opacity || variants.some((variant) => isDataDriven(paintOf(variant)[opacity])))
3702
+ return void 0;
3703
+ copiedLayers += variants.length;
3704
+ return variants.map((variant, theme) => {
3705
+ const weight = new BlendLeaf(variants.map((_, other) => other === theme ? 1 : 0));
3706
+ const template = scaleByWeight(paint[opacity] ?? 1, weight);
3707
+ const copyPaint = { ...paint, [opacity]: template };
3708
+ for (const property of driven) copyPaint[property] = paintOf(variant)[property];
3709
+ return {
3710
+ layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint },
3711
+ extra: [{ property: opacity, template }]
3712
+ };
3713
+ });
3686
3714
  }
3687
3715
  }
3688
- function isSafeResolvedPublicUrl(value) {
3689
- try {
3690
- const url = primitives.parseUrl(value, "https://manifest.invalid/owner/manifest.json");
3691
- return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && !url.hash && !hasEncodedPathSeparator(value);
3692
- } catch {
3693
- return false;
3716
+ }
3717
+ function fillTileflowThemeBlendTemplate(template, position) {
3718
+ return fillTemplate(template, position);
3719
+ }
3720
+ function fillLayer(layer, position, dominant, layerSwitches) {
3721
+ const filled = { ...layer };
3722
+ for (const group of ["layout", "paint"]) {
3723
+ const values = layer[group];
3724
+ if (!values) continue;
3725
+ const out = {};
3726
+ for (const [key, value] of Object.entries(values)) out[key] = fillTemplate(value, position);
3727
+ for (const entry of layerSwitches ?? []) {
3728
+ if (entry.group !== group) continue;
3729
+ const value = entry.values[dominant];
3730
+ if (value === void 0) delete out[entry.property];
3731
+ else out[entry.property] = value;
3694
3732
  }
3733
+ if (Object.keys(out).length > 0) filled[group] = out;
3734
+ else delete filled[group];
3695
3735
  }
3696
- function hasEncodedPathSeparator(value) {
3697
- return /%(?:2f|5c)/iu.test(value);
3736
+ return filled;
3737
+ }
3738
+ function fillTemplate(node, position) {
3739
+ if (node instanceof BlendLeaf) return mixLeaf(node.values, position);
3740
+ if (Array.isArray(node)) return node.map((item) => fillTemplate(item, position));
3741
+ if (node && typeof node === "object" && containsLeaf(node)) {
3742
+ return Object.fromEntries(
3743
+ Object.entries(node).map(([key, value]) => [key, fillTemplate(value, position)])
3744
+ );
3698
3745
  }
3699
- function isApiOrigin(value) {
3700
- try {
3701
- const url = primitives.parseUrl(value);
3702
- return isAbsolutePublicUrl(value) && url.pathname === "/" && !url.search && value.replace(/\/$/u, "") === url.origin;
3703
- } catch {
3704
- return false;
3746
+ return node;
3747
+ }
3748
+ function containsLeaf(node) {
3749
+ if (node instanceof BlendLeaf) return true;
3750
+ if (Array.isArray(node)) return node.some(containsLeaf);
3751
+ if (node && typeof node === "object") return Object.values(node).some(containsLeaf);
3752
+ return false;
3753
+ }
3754
+ function mixLeaf(values, position) {
3755
+ const at = clampPosition(position, values.length);
3756
+ const index = Math.min(values.length - 2, Math.floor(at));
3757
+ const t = at - index;
3758
+ const from = values[index];
3759
+ const to = values[index + 1];
3760
+ if (t <= 0 || from === to) return from;
3761
+ if (t >= 1) return to;
3762
+ if (typeof from === "number" && typeof to === "number") return from + (to - from) * t;
3763
+ const a = oklab(String(from));
3764
+ const b = oklab(String(to));
3765
+ if (!a || !b) return t < 0.5 ? from : to;
3766
+ return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
3767
+ }
3768
+ function clampPosition(position, themes) {
3769
+ if (!Number.isFinite(position)) return 0;
3770
+ return Math.min(themes - 1, Math.max(0, position));
3771
+ }
3772
+ function assertSameStructure(styles, base2) {
3773
+ const ids = (style) => style.layers.map((layer) => layer.id);
3774
+ for (const style of styles) {
3775
+ if (!same(ids(style), ids(base2)))
3776
+ throw new TileflowThemeBlendError(
3777
+ "Blended themes must have the same layers in the same order."
3778
+ );
3779
+ if (!same(style.sources, base2.sources))
3780
+ throw new TileflowThemeBlendError("Blended themes must use the same sources.");
3781
+ if (!same(style.sprite, base2.sprite) || !same(style.glyphs, base2.glyphs))
3782
+ throw new TileflowThemeBlendError("Blended themes must use the same sprite and glyphs.");
3783
+ for (const [index, layer] of style.layers.entries()) {
3784
+ const other = base2.layers[index];
3785
+ for (const key of ["type", "source", "source-layer", "filter", "minzoom", "maxzoom"])
3786
+ if (!same(layer[key], other[key]))
3787
+ throw new TileflowThemeBlendError(
3788
+ `Blended themes differ in ${key} of layer ${JSON.stringify(layer.id)}.`
3789
+ );
3705
3790
  }
3706
3791
  }
3707
- function findUnsafeManifestStructure(input) {
3708
- if (!input || typeof input !== "object") return void 0;
3709
- const pending = [
3710
- { path: [], value: input }
3711
- ];
3712
- const visited = /* @__PURE__ */ new WeakSet();
3713
- while (pending.length > 0) {
3714
- const current = pending.pop();
3715
- if (visited.has(current.value)) continue;
3716
- visited.add(current.value);
3717
- if (!Array.isArray(current.value)) {
3718
- const prototype = Object.getPrototypeOf(current.value);
3719
- if (prototype !== Object.prototype && prototype !== null) {
3720
- return { path: current.path, reason: "prototype" };
3792
+ }
3793
+ function propertyNames(variants, group) {
3794
+ const names = /* @__PURE__ */ new Set();
3795
+ for (const variant of variants)
3796
+ for (const name of Object.keys(variant[group] ?? {})) names.add(name);
3797
+ return [...names];
3798
+ }
3799
+ function differingRoot(styles, key) {
3800
+ const values = styles.map((style) => style[key]);
3801
+ if (values.every((value) => same(value, values[0]))) return void 0;
3802
+ return tryMixTemplate(values);
3803
+ }
3804
+ function collectImageNames(styles, base2, properties) {
3805
+ const uses = /* @__PURE__ */ new Map();
3806
+ for (const [index] of base2.layers.entries()) {
3807
+ for (const group of ["layout", "paint"]) {
3808
+ for (const property of properties) {
3809
+ const values = styles.map(
3810
+ (style) => style.layers[index]?.[group]?.[property]
3811
+ );
3812
+ if (values[0] === void 0) continue;
3813
+ for (const names of imageTuples(values) ?? []) {
3814
+ const set = uses.get(names[0]) ?? /* @__PURE__ */ new Set();
3815
+ set.add(JSON.stringify(names));
3816
+ uses.set(names[0], set);
3721
3817
  }
3722
3818
  }
3723
- for (const key of Object.keys(current.value)) {
3724
- const path = [...current.path, Array.isArray(current.value) ? Number(key) : key];
3725
- if (key === "__proto__") return { path, reason: "key" };
3726
- const descriptor = Object.getOwnPropertyDescriptor(current.value, key);
3727
- if (descriptor && "value" in descriptor && descriptor.value && typeof descriptor.value === "object") {
3728
- pending.push({ path, value: descriptor.value });
3819
+ }
3820
+ }
3821
+ return uses;
3822
+ }
3823
+ function alignInteractionManifest(metadata, layers) {
3824
+ if (!isRecord(metadata)) return void 0;
3825
+ const planned = /* @__PURE__ */ new Map();
3826
+ for (const [index, layer] of layers.entries()) {
3827
+ const id = String(layer.id);
3828
+ const original = /^(.+)::(?:branch|theme)-\d+$/u.exec(id)?.[1] ?? id;
3829
+ planned.set(original, [...planned.get(original) ?? [], { id, index }]);
3830
+ }
3831
+ let aligned;
3832
+ const interactions = metadata["tileflow:interaction-manifest"];
3833
+ const domains = isRecord(interactions) ? interactions.domains : void 0;
3834
+ if (isRecord(interactions) && isRecord(domains) && isRecord(domains.poi)) {
3835
+ const poi = domains.poi;
3836
+ if (Array.isArray(poi.layers)) {
3837
+ const poiLayers = poi.layers.flatMap((entry) => {
3838
+ if (!isRecord(entry) || typeof entry.layerId !== "string") return [entry];
3839
+ const copies = planned.get(entry.layerId);
3840
+ if (!copies) return [entry];
3841
+ return copies.map(({ id, index }) => ({ ...entry, layerId: id, priority: index }));
3842
+ });
3843
+ aligned = {
3844
+ ...metadata,
3845
+ "tileflow:interaction-manifest": {
3846
+ ...interactions,
3847
+ domains: { ...domains, poi: { ...poi, layers: poiLayers } }
3729
3848
  }
3730
- }
3849
+ };
3731
3850
  }
3732
- return void 0;
3733
3851
  }
3734
- return operations.pipe(safeManifestInputSchema, canonicalManifestSchema);
3852
+ const overlays = metadata["tileflow:overlay-placement-manifest"];
3853
+ if (isRecord(overlays) && isRecord(overlays.anchors)) {
3854
+ const anchors = Object.fromEntries(
3855
+ Object.entries(overlays.anchors).map(([placement, anchor]) => [
3856
+ placement,
3857
+ typeof anchor === "string" ? planned.get(anchor)?.[0]?.id ?? anchor : anchor
3858
+ ])
3859
+ );
3860
+ aligned = {
3861
+ ...aligned ?? metadata,
3862
+ "tileflow:overlay-placement-manifest": { ...overlays, anchors }
3863
+ };
3864
+ }
3865
+ return aligned;
3735
3866
  }
3736
- var init_manifest_schema = __esm({
3737
- "src/manifest-schema.ts"() {
3738
- "use strict";
3739
- init_manifest_types();
3740
- init_portable_identity_schema();
3741
- init_runtime_view_schema();
3742
- init_model();
3867
+ function imageTuples(values, out = []) {
3868
+ const [first] = values;
3869
+ if (values.every((value) => typeof value === "string")) {
3870
+ out.push(values);
3871
+ return out;
3743
3872
  }
3744
- });
3745
-
3746
- // src/native-url-policy.ts
3747
- var tileflowNativeUrlLimits = Object.freeze({ maximumLength: 2048 });
3748
- var errorMessages = {
3749
- NATIVE_URL_INVALID: "Expected a bounded HTTP(S) URL without credentials, fragments or backslashes.",
3750
- NATIVE_URL_ABSOLUTE_REQUIRED: "An explicit absolute HTTP(S) document URL is required.",
3751
- NATIVE_URL_HTTPS_REQUIRED: "HTTP is allowed only at the explicitly configured development origin.",
3752
- NATIVE_URL_DEVELOPMENT_ORIGIN_INVALID: "Expected one HTTP origin without a path, credentials, query or fragment.",
3753
- NATIVE_URL_TEMPLATE_INVALID: "Expected supported URL placeholders in the resource path or query only."
3754
- };
3755
- var TileflowNativeUrlError = class extends TypeError {
3756
- constructor(code, field) {
3757
- super(errorMessages[code]);
3758
- __publicField(this, "code");
3759
- __publicField(this, "field");
3760
- this.name = "TileflowNativeUrlError";
3761
- this.code = code;
3762
- this.field = field;
3873
+ if (!Array.isArray(first) || typeof first[0] !== "string") {
3874
+ return values.every((value) => same(value, first)) ? out : void 0;
3763
3875
  }
3764
- };
3765
- function createNativeUrlPolicy(parse2) {
3766
- const tilePlaceholders = /* @__PURE__ */ new Set(["z", "x", "y", "ratio", "quadkey", "bbox-epsg-3857", "prefix"]);
3767
- const glyphPlaceholders = /* @__PURE__ */ new Set(["fontstack", "range"]);
3768
- const absoluteHttpPattern = /^https?:\/\/[^/?#]+/iu;
3769
- const schemePattern = /^[a-z][a-z\d+.-]*:/iu;
3770
- function resolveTileflowNativeManifestUrl2(value, options = {}) {
3771
- assertOptionsObject(options, "developmentOrigin");
3772
- const developmentOrigin = resolveDevelopmentOrigin(options.developmentOrigin);
3773
- return resolveDocumentUrl(value, "manifestUrl", developmentOrigin).href;
3876
+ const operator = first[0];
3877
+ if (!values.every(
3878
+ (value) => Array.isArray(value) && value[0] === operator && value.length === first.length
3879
+ )) {
3880
+ return void 0;
3774
3881
  }
3775
- function resolveTileflowNativeResourceUrl2(value, options) {
3776
- assertOptionsObject(options, "documentUrl");
3777
- const developmentOrigin = resolveDevelopmentOrigin(options.developmentOrigin);
3778
- const document2 = resolveDocumentUrl(options.documentUrl, "documentUrl", developmentOrigin);
3779
- assertUrlText(value, "resourceUrl");
3780
- if (options.template !== void 0 && options.template !== "tile" && options.template !== "glyphs") {
3781
- throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", "resourceUrl");
3782
- }
3783
- assertReferenceSyntax(value, "resourceUrl", false);
3784
- const authority = absoluteHttpPattern.exec(value)?.[0];
3785
- if (authority && /[{}]/u.test(authority)) {
3786
- throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", "resourceUrl");
3882
+ const at = (index) => values.map((value) => value[index]);
3883
+ const sameAt = (index) => at(index).every((value) => same(value, first[index]));
3884
+ const outputs = [];
3885
+ if (operator === "literal" || operator === "image") outputs.push(1);
3886
+ else if (operator === "coalesce")
3887
+ for (let index = 1; index < first.length; index += 1) outputs.push(index);
3888
+ else if (operator === "match") {
3889
+ if (!sameAt(1)) return void 0;
3890
+ for (let index = 2; index < first.length - 1; index += 2) {
3891
+ if (!sameAt(index)) return void 0;
3892
+ outputs.push(index + 1);
3787
3893
  }
3788
- let prefix = "__tileflow_native_template_";
3789
- let attempt = 0;
3790
- while (value.includes(prefix) || document2.href.includes(prefix)) {
3791
- prefix = `__tileflow_native_template_${attempt++}_`;
3894
+ outputs.push(first.length - 1);
3895
+ } else if (operator === "case") {
3896
+ for (let index = 1; index < first.length - 1; index += 2) {
3897
+ if (!sameAt(index)) return void 0;
3898
+ outputs.push(index + 1);
3792
3899
  }
3793
- const placeholders = [];
3794
- const allowed = options.template === "tile" ? tilePlaceholders : glyphPlaceholders;
3795
- const protectedValue = value.replace(/\{([^{}]*)\}/gu, (placeholder, name) => {
3796
- if (!options.template || !allowed.has(name)) {
3797
- throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", "resourceUrl");
3798
- }
3799
- placeholders.push(placeholder);
3800
- return `${prefix}${placeholders.length - 1}__`;
3801
- });
3802
- if (/[{}]/u.test(protectedValue)) {
3803
- throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", "resourceUrl");
3900
+ outputs.push(first.length - 1);
3901
+ } else if (operator === "step") {
3902
+ if (!sameAt(1)) return void 0;
3903
+ outputs.push(2);
3904
+ for (let index = 3; index < first.length; index += 2) {
3905
+ if (!sameAt(index)) return void 0;
3906
+ outputs.push(index + 1);
3804
3907
  }
3805
- const resolved = parseUrl(protectedValue, "resourceUrl", document2.href);
3806
- assertTransport(resolved, "resourceUrl", developmentOrigin);
3807
- const resourcePath = resolved.href.slice(resolved.origin.length);
3808
- const suffix = resourcePath.replace(
3809
- new RegExp(`${prefix}(\\d+)__`, "gu"),
3810
- (match, index) => placeholders[Number(index)] ?? match
3811
- );
3812
- const result = resolved.origin + suffix;
3813
- assertUrlText(result, "resourceUrl");
3814
- return result;
3908
+ } else {
3909
+ return values.every((value) => same(value, first)) ? out : void 0;
3815
3910
  }
3816
- function assertOptionsObject(value, field) {
3817
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
3818
- throw new TileflowNativeUrlError(
3819
- field === "documentUrl" ? "NATIVE_URL_INVALID" : "NATIVE_URL_DEVELOPMENT_ORIGIN_INVALID",
3820
- field
3821
- );
3911
+ for (const index of outputs) if (!imageTuples(at(index), out)) return void 0;
3912
+ return out;
3913
+ }
3914
+ function tryMixTemplate(values) {
3915
+ const [first] = values;
3916
+ if (values.every((value) => same(value, first))) return first;
3917
+ if (Array.isArray(first) && first[0] !== "literal" && values.every((value) => Array.isArray(value) && value.length === first.length)) {
3918
+ const items = [];
3919
+ for (let index = 0; index < first.length; index += 1) {
3920
+ const item = tryMixTemplate(values.map((value) => value[index]));
3921
+ if (item === void 0 && first[index] !== void 0) return void 0;
3922
+ items.push(item);
3822
3923
  }
3924
+ return items;
3823
3925
  }
3824
- function resolveDevelopmentOrigin(value) {
3825
- if (value === void 0) return void 0;
3826
- try {
3827
- assertUrlText(value, "developmentOrigin");
3828
- const match = /^(http:\/\/[^/?#]+)\/?$/iu.exec(value);
3829
- if (!match || /[{}@*]/u.test(value)) throw new Error();
3830
- const url = parseUrl(value, "developmentOrigin");
3831
- if (url.protocol !== "http:" || url.pathname !== "/" || url.search || url.hash || url.hostname.includes("*")) {
3832
- throw new Error();
3833
- }
3834
- return url.origin;
3835
- } catch {
3836
- throw new TileflowNativeUrlError(
3837
- "NATIVE_URL_DEVELOPMENT_ORIGIN_INVALID",
3838
- "developmentOrigin"
3839
- );
3926
+ if (first && typeof first === "object" && !Array.isArray(first)) {
3927
+ const keys = Object.keys(first);
3928
+ if (!values.every((value) => value && typeof value === "object" && same(Object.keys(value), keys)))
3929
+ return void 0;
3930
+ const out = {};
3931
+ for (const key of keys) {
3932
+ const item = tryMixTemplate(values.map((value) => value[key]));
3933
+ if (item === void 0) return void 0;
3934
+ out[key] = item;
3840
3935
  }
3936
+ return out;
3841
3937
  }
3842
- function resolveDocumentUrl(value, field, developmentOrigin) {
3843
- assertUrlText(value, field);
3844
- assertReferenceSyntax(value, field, true);
3845
- if (/[{}]/u.test(value)) throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", field);
3846
- const url = parseUrl(value, field);
3847
- assertTransport(url, field, developmentOrigin);
3848
- assertUrlText(url.href, field);
3849
- return url;
3850
- }
3851
- function assertUrlText(value, field) {
3852
- if (typeof value !== "string" || value.length === 0 || value.length > tileflowNativeUrlLimits.maximumLength || value !== value.trim() || /[\p{Cc}\\#]/u.test(value) || /%(?![\da-f]{2})/iu.test(value)) {
3853
- throw new TileflowNativeUrlError("NATIVE_URL_INVALID", field);
3938
+ if (values.every((value) => typeof value === "number")) return new BlendLeaf(values);
3939
+ if (values.every((value) => typeof value === "string" && oklab(value)))
3940
+ return new BlendLeaf(values);
3941
+ return void 0;
3942
+ }
3943
+ function isDataDriven(value) {
3944
+ if (!Array.isArray(value)) return false;
3945
+ if (typeof value[0] === "string" && featureOperators.has(value[0])) return true;
3946
+ return value.some(isDataDriven);
3947
+ }
3948
+ function decisionOf(node) {
3949
+ if (!Array.isArray(node) || typeof node[0] !== "string") return void 0;
3950
+ const [operator] = node;
3951
+ if (operator === "match" && isDataDriven(node[1])) {
3952
+ const labels = [];
3953
+ const outputs = [];
3954
+ for (let index = 2; index < node.length - 1; index += 2) {
3955
+ labels.push(node[index]);
3956
+ outputs.push(node[index + 1]);
3854
3957
  }
3958
+ outputs.push(node.at(-1));
3959
+ return {
3960
+ index: ["match", node[1], ...labels.flatMap((label, index) => [label, index]), labels.length],
3961
+ outputs
3962
+ };
3855
3963
  }
3856
- function assertReferenceSyntax(value, field, absolute) {
3857
- if (value.startsWith("//")) {
3858
- throw new TileflowNativeUrlError(
3859
- absolute ? "NATIVE_URL_ABSOLUTE_REQUIRED" : "NATIVE_URL_INVALID",
3860
- field
3861
- );
3964
+ if (operator === "case") {
3965
+ const conditions = [];
3966
+ const outputs = [];
3967
+ for (let index = 1; index < node.length - 1; index += 2) {
3968
+ conditions.push(node[index]);
3969
+ outputs.push(node[index + 1]);
3862
3970
  }
3863
- const authority = absoluteHttpPattern.exec(value)?.[0];
3864
- if (schemePattern.test(value) && !authority) {
3865
- throw new TileflowNativeUrlError("NATIVE_URL_INVALID", field);
3866
- }
3867
- if (absolute && !authority) {
3868
- throw new TileflowNativeUrlError("NATIVE_URL_ABSOLUTE_REQUIRED", field);
3869
- }
3870
- if (authority?.includes("@")) throw new TileflowNativeUrlError("NATIVE_URL_INVALID", field);
3971
+ outputs.push(node.at(-1));
3972
+ if (!conditions.some(isDataDriven)) return void 0;
3973
+ return {
3974
+ index: [
3975
+ "case",
3976
+ ...conditions.flatMap((condition, index) => [condition, index]),
3977
+ conditions.length
3978
+ ],
3979
+ outputs
3980
+ };
3871
3981
  }
3872
- function parseUrl(value, field, base2) {
3873
- let url;
3874
- try {
3875
- url = parse2(value, base2);
3876
- if (!url.hostname || url.username || url.password || url.hash) throw new Error();
3877
- } catch {
3878
- throw new TileflowNativeUrlError("NATIVE_URL_INVALID", field);
3879
- }
3880
- if (/[{}]/u.test(url.hostname)) {
3881
- throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", field);
3982
+ if (operator === "step" && isDataDriven(node[1])) {
3983
+ const outputs = [node[2]];
3984
+ const stops = [];
3985
+ for (let index = 3; index < node.length; index += 2) {
3986
+ stops.push(node[index]);
3987
+ outputs.push(node[index + 1]);
3882
3988
  }
3883
- return url;
3884
- }
3885
- function assertTransport(url, field, developmentOrigin) {
3886
- if (url.protocol === "https:") return;
3887
- if (url.protocol === "http:" && url.origin === developmentOrigin) return;
3888
- throw new TileflowNativeUrlError("NATIVE_URL_HTTPS_REQUIRED", field);
3989
+ return {
3990
+ index: ["step", node[1], 0, ...stops.flatMap((stop, index) => [stop, index + 1])],
3991
+ outputs
3992
+ };
3889
3993
  }
3890
- return { resolveTileflowNativeManifestUrl: resolveTileflowNativeManifestUrl2, resolveTileflowNativeResourceUrl: resolveTileflowNativeResourceUrl2 };
3891
- }
3892
-
3893
- // src/native-url-provider.ts
3894
- var import_url_state_machine = __toESM(require_url_state_machine(), 1);
3895
- function parseNativeUrl(value, base2) {
3896
- const baseURL = base2 === void 0 ? void 0 : (0, import_url_state_machine.basicURLParse)(base2);
3897
- if (baseURL === null) throw new TypeError("Invalid native URL.");
3898
- const parsed = (0, import_url_state_machine.basicURLParse)(value, baseURL === void 0 ? void 0 : { baseURL });
3899
- if (parsed === null) throw new TypeError("Invalid native URL.");
3900
- return {
3901
- href: (0, import_url_state_machine.serializeURL)(parsed),
3902
- origin: (0, import_url_state_machine.serializeURLOrigin)(parsed),
3903
- protocol: `${parsed.scheme}:`,
3904
- hostname: parsed.host === null ? "" : (0, import_url_state_machine.serializeHost)(parsed.host),
3905
- pathname: (0, import_url_state_machine.serializePath)(parsed),
3906
- search: parsed.query ? `?${parsed.query}` : "",
3907
- hash: parsed.fragment ? `#${parsed.fragment}` : "",
3908
- username: parsed.username,
3909
- password: parsed.password
3910
- };
3911
- }
3912
-
3913
- // src/native-urls.ts
3914
- var policy = createNativeUrlPolicy(parseNativeUrl);
3915
- function resolveTileflowNativeManifestUrl(value, options = {}) {
3916
- return policy.resolveTileflowNativeManifestUrl(value, options);
3994
+ return void 0;
3917
3995
  }
3918
- function resolveTileflowNativeResourceUrl(value, options) {
3919
- return policy.resolveTileflowNativeResourceUrl(value, options);
3996
+ function firstDecision(value) {
3997
+ const decision = decisionOf(value);
3998
+ if (decision) return decision;
3999
+ if (!Array.isArray(value) || value[0] === "literal") return void 0;
4000
+ for (const item of value) {
4001
+ const found = firstDecision(item);
4002
+ if (found) return found;
4003
+ }
4004
+ return void 0;
3920
4005
  }
3921
-
3922
- // src/native-manifest.ts
3923
- init_manifest_types();
3924
-
3925
- // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/parse.js
3926
- init_core2();
3927
-
3928
- // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/schemas.js
3929
- init_core2();
3930
- init_util();
3931
- var ZodMiniType = /* @__PURE__ */ $constructor("ZodMiniType", (inst, def) => {
3932
- if (!inst._zod)
3933
- throw new Error("Uninitialized schema in ZodMiniType.");
3934
- $ZodType.init(inst, def);
3935
- inst.def = def;
3936
- inst.type = def.type;
3937
- inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });
3938
- inst.safeParse = (data, params) => safeParse(inst, data, params);
3939
- inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
3940
- inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);
3941
- inst.check = (...checks) => {
3942
- return inst.clone({
3943
- ...def,
3944
- checks: [
3945
- ...def.checks ?? [],
3946
- ...checks.map((ch) => typeof ch === "function" ? {
3947
- _zod: { check: ch, def: { check: "custom" }, onattach: [] }
3948
- } : ch)
3949
- ]
3950
- }, { parent: true });
3951
- };
3952
- inst.with = inst.check;
3953
- inst.clone = (_def, params) => clone(inst, _def, params);
3954
- inst.brand = () => inst;
3955
- inst.register = ((reg, meta2) => {
3956
- reg.add(inst, meta2);
3957
- return inst;
3958
- });
3959
- inst.apply = (fn) => fn(inst);
3960
- });
3961
- var ZodMiniString = /* @__PURE__ */ $constructor("ZodMiniString", (inst, def) => {
3962
- $ZodString.init(inst, def);
3963
- ZodMiniType.init(inst, def);
3964
- });
3965
- // @__NO_SIDE_EFFECTS__
3966
- function string2(params) {
3967
- return _string(ZodMiniString, params);
4006
+ function choose(value, key, branch) {
4007
+ const decision = decisionOf(value);
4008
+ if (decision && JSON.stringify(decision.index) === key)
4009
+ return choose(decision.outputs[branch], key, branch);
4010
+ if (!Array.isArray(value) || value[0] === "literal") return value;
4011
+ return value.map((item) => choose(item, key, branch));
3968
4012
  }
3969
- var ZodMiniNumber = /* @__PURE__ */ $constructor("ZodMiniNumber", (inst, def) => {
3970
- $ZodNumber.init(inst, def);
3971
- ZodMiniType.init(inst, def);
3972
- });
3973
- // @__NO_SIDE_EFFECTS__
3974
- function number2(params) {
3975
- return _number(ZodMiniNumber, params);
4013
+ function splitBranches(valuesByProperty, conditions = []) {
4014
+ let decision;
4015
+ for (const values of Object.values(valuesByProperty)) {
4016
+ decision = firstDecision(values[0]);
4017
+ if (decision) break;
4018
+ }
4019
+ if (!decision) {
4020
+ const residual = Object.values(valuesByProperty).some((values) => values.some(isDataDriven));
4021
+ return residual ? void 0 : [{ conditions, valuesByProperty }];
4022
+ }
4023
+ if (reads(decision.index, "zoom") || reads(decision.index, "feature-state")) return void 0;
4024
+ const key = JSON.stringify(decision.index);
4025
+ const branches = [];
4026
+ for (let branch = 0; branch < decision.outputs.length; branch += 1) {
4027
+ const chosen = Object.fromEntries(
4028
+ Object.entries(valuesByProperty).map(([property, values]) => [
4029
+ property,
4030
+ values.map((value) => choose(value, key, branch))
4031
+ ])
4032
+ );
4033
+ const nested = splitBranches(chosen, [...conditions, ["==", decision.index, branch]]);
4034
+ if (!nested) return void 0;
4035
+ branches.push(...nested);
4036
+ if (branches.length > maximumBranches) return void 0;
4037
+ }
4038
+ return branches;
3976
4039
  }
3977
- var ZodMiniUnknown = /* @__PURE__ */ $constructor("ZodMiniUnknown", (inst, def) => {
3978
- $ZodUnknown.init(inst, def);
3979
- ZodMiniType.init(inst, def);
3980
- });
3981
- // @__NO_SIDE_EFFECTS__
3982
- function unknown() {
3983
- return _unknown(ZodMiniUnknown);
4040
+ function scaleByWeight(opacity, weight) {
4041
+ const input = Array.isArray(opacity) ? opacity[0] === "interpolate" ? opacity[2] : opacity[1] : void 0;
4042
+ if (Array.isArray(opacity) && (opacity[0] === "interpolate" || opacity[0] === "step") && reads(input, "zoom")) {
4043
+ const head = opacity[0] === "interpolate" ? 3 : 2;
4044
+ return opacity.map(
4045
+ (item, index) => index >= head && (index - head) % 2 === (opacity[0] === "interpolate" ? 1 : 0) ? scaleByWeight(item, weight) : item
4046
+ );
4047
+ }
4048
+ if (typeof opacity === "number")
4049
+ return new BlendLeaf(weight.values.map((value) => opacity * Number(value)));
4050
+ return ["*", opacity, weight];
3984
4051
  }
3985
- var ZodMiniNever = /* @__PURE__ */ $constructor("ZodMiniNever", (inst, def) => {
3986
- $ZodNever.init(inst, def);
3987
- ZodMiniType.init(inst, def);
3988
- });
3989
- // @__NO_SIDE_EFFECTS__
3990
- function never(params) {
3991
- return _never(ZodMiniNever, params);
4052
+ function same(a, b) {
4053
+ return JSON.stringify(a) === JSON.stringify(b);
3992
4054
  }
3993
- var ZodMiniArray = /* @__PURE__ */ $constructor("ZodMiniArray", (inst, def) => {
3994
- $ZodArray.init(inst, def);
3995
- ZodMiniType.init(inst, def);
3996
- });
3997
- // @__NO_SIDE_EFFECTS__
3998
- function array(element, params) {
3999
- return new ZodMiniArray({
4000
- type: "array",
4001
- element,
4002
- ...normalizeParams(params)
4003
- });
4055
+ function oklab(colour) {
4056
+ const cached2 = oklabCache.get(colour);
4057
+ if (cached2 !== void 0) return cached2;
4058
+ const rgba = parseColour(colour);
4059
+ let result = null;
4060
+ if (rgba) {
4061
+ const [r, g, b] = rgba.slice(0, 3).map((channel) => {
4062
+ const value = channel / 255;
4063
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
4064
+ });
4065
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
4066
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
4067
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
4068
+ result = [
4069
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
4070
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
4071
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
4072
+ rgba[3]
4073
+ ];
4074
+ }
4075
+ if (oklabCache.size > 4096) oklabCache.clear();
4076
+ oklabCache.set(colour, result);
4077
+ return result;
4004
4078
  }
4005
- var ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
4006
- $ZodObject.init(inst, def);
4007
- ZodMiniType.init(inst, def);
4008
- defineLazy(inst, "shape", () => def.shape);
4009
- });
4010
- // @__NO_SIDE_EFFECTS__
4011
- function strictObject(shape, params) {
4012
- return new ZodMiniObject({
4013
- type: "object",
4014
- shape,
4015
- catchall: /* @__PURE__ */ never(),
4016
- ...normalizeParams(params)
4079
+ function colourFromOklab([lightness, a, b, alpha]) {
4080
+ const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
4081
+ const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
4082
+ const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
4083
+ const [red, green, blue] = [
4084
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
4085
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
4086
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
4087
+ ].map((value) => {
4088
+ const clamped = Math.min(1, Math.max(0, value));
4089
+ const encoded = clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
4090
+ return Math.round(encoded * 255);
4017
4091
  });
4092
+ return `rgba(${red}, ${green}, ${blue}, ${Number(alpha.toFixed(3))})`;
4018
4093
  }
4019
- var ZodMiniTuple = /* @__PURE__ */ $constructor("ZodMiniTuple", (inst, def) => {
4020
- $ZodTuple.init(inst, def);
4021
- ZodMiniType.init(inst, def);
4022
- });
4023
- // @__NO_SIDE_EFFECTS__
4024
- function tuple(items, _paramsOrRest, _params) {
4025
- const hasRest = _paramsOrRest instanceof $ZodType;
4026
- const params = hasRest ? _params : _paramsOrRest;
4027
- const rest = hasRest ? _paramsOrRest : null;
4028
- return new ZodMiniTuple({
4029
- type: "tuple",
4030
- items,
4031
- rest,
4032
- ...normalizeParams(params)
4033
- });
4034
- }
4035
- var ZodMiniRecord = /* @__PURE__ */ $constructor("ZodMiniRecord", (inst, def) => {
4036
- $ZodRecord.init(inst, def);
4037
- ZodMiniType.init(inst, def);
4038
- });
4039
- // @__NO_SIDE_EFFECTS__
4040
- function record(keyType, valueType, params) {
4041
- if (!valueType || !valueType._zod) {
4042
- return new ZodMiniRecord({
4043
- type: "record",
4044
- keyType: /* @__PURE__ */ string2(),
4045
- valueType: keyType,
4046
- ...normalizeParams(valueType)
4047
- });
4094
+ function parseColour(colour) {
4095
+ const text = colour.trim().toLowerCase();
4096
+ const hex = /^#([\da-f]{3,8})$/u.exec(text)?.[1];
4097
+ if (hex && [3, 4, 6, 8].includes(hex.length)) {
4098
+ const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
4099
+ const channels = full.match(/../gu).map((pair) => Number.parseInt(pair, 16));
4100
+ return [channels[0], channels[1], channels[2], channels.length > 3 ? channels[3] / 255 : 1];
4048
4101
  }
4049
- return new ZodMiniRecord({
4050
- type: "record",
4051
- keyType,
4052
- valueType,
4053
- ...normalizeParams(params)
4054
- });
4055
- }
4056
- var ZodMiniEnum = /* @__PURE__ */ $constructor("ZodMiniEnum", (inst, def) => {
4057
- $ZodEnum.init(inst, def);
4058
- ZodMiniType.init(inst, def);
4059
- inst.options = Object.values(def.entries);
4060
- });
4061
- // @__NO_SIDE_EFFECTS__
4062
- function _enum(values, params) {
4063
- const entries = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
4064
- return new ZodMiniEnum({
4065
- type: "enum",
4066
- entries,
4067
- ...normalizeParams(params)
4068
- });
4069
- }
4070
- var ZodMiniLiteral = /* @__PURE__ */ $constructor("ZodMiniLiteral", (inst, def) => {
4071
- $ZodLiteral.init(inst, def);
4072
- ZodMiniType.init(inst, def);
4073
- });
4074
- // @__NO_SIDE_EFFECTS__
4075
- function literal(value, params) {
4076
- return new ZodMiniLiteral({
4077
- type: "literal",
4078
- values: Array.isArray(value) ? value : [value],
4079
- ...normalizeParams(params)
4080
- });
4081
- }
4082
- var ZodMiniOptional = /* @__PURE__ */ $constructor("ZodMiniOptional", (inst, def) => {
4083
- $ZodOptional.init(inst, def);
4084
- ZodMiniType.init(inst, def);
4085
- });
4086
- // @__NO_SIDE_EFFECTS__
4087
- function optional(innerType) {
4088
- return new ZodMiniOptional({
4089
- type: "optional",
4090
- innerType
4091
- });
4102
+ const functional = /^(rgba?|hsla?)\(([^)]+)\)$/u.exec(text);
4103
+ if (!functional) return null;
4104
+ const parts = functional[2].split(/[\s,/]+/u).filter(Boolean);
4105
+ if (parts.length < 3 || parts.length > 4) return null;
4106
+ const alpha = parts[3] === void 0 ? 1 : parseUnit(parts[3], 1);
4107
+ if (functional[1].startsWith("rgb")) {
4108
+ const [r, g, b] = parts.slice(0, 3).map((part) => parseUnit(part, 255));
4109
+ if ([r, g, b, alpha].some((value) => value === null)) return null;
4110
+ return [r, g, b, alpha];
4111
+ }
4112
+ const hue = Number.parseFloat(parts[0]);
4113
+ const saturation = parseUnit(parts[1], 1);
4114
+ const light = parseUnit(parts[2], 1);
4115
+ if (!Number.isFinite(hue) || saturation === null || light === null || alpha === null) return null;
4116
+ const chroma = (1 - Math.abs(2 * light - 1)) * saturation;
4117
+ const sector = (hue % 360 + 360) % 360 / 60;
4118
+ const x = chroma * (1 - Math.abs(sector % 2 - 1));
4119
+ 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];
4120
+ const offset = light - chroma / 2;
4121
+ return [(r1 + offset) * 255, (g1 + offset) * 255, (b1 + offset) * 255, alpha];
4092
4122
  }
4093
- var ZodMiniPipe = /* @__PURE__ */ $constructor("ZodMiniPipe", (inst, def) => {
4094
- $ZodPipe.init(inst, def);
4095
- ZodMiniType.init(inst, def);
4096
- });
4097
- // @__NO_SIDE_EFFECTS__
4098
- function pipe(in_, out) {
4099
- return new ZodMiniPipe({
4100
- type: "pipe",
4101
- in: in_,
4102
- out
4103
- });
4123
+ function parseUnit(part, scale) {
4124
+ const percentage = part.endsWith("%");
4125
+ const value = Number.parseFloat(part);
4126
+ if (!Number.isFinite(value)) return null;
4127
+ return percentage ? value / 100 * scale : value;
4104
4128
  }
4105
- var ZodMiniCustom = /* @__PURE__ */ $constructor("ZodMiniCustom", (inst, def) => {
4106
- $ZodCustom.init(inst, def);
4107
- ZodMiniType.init(inst, def);
4129
+ var TileflowThemeBlendError, BlendLeaf, imageProperties, opacityProperties, featureOperators, maximumBranches, isRecord, reads, isLegacyFilter, oklabCache;
4130
+ var init_theme_blend = __esm({
4131
+ "src/theme-blend.ts"() {
4132
+ "use strict";
4133
+ TileflowThemeBlendError = class extends Error {
4134
+ constructor() {
4135
+ super(...arguments);
4136
+ __publicField(this, "name", "TileflowThemeBlendError");
4137
+ }
4138
+ };
4139
+ BlendLeaf = class {
4140
+ constructor(values) {
4141
+ __publicField(this, "values", values);
4142
+ }
4143
+ };
4144
+ imageProperties = /* @__PURE__ */ new Set([
4145
+ "icon-image",
4146
+ "fill-pattern",
4147
+ "line-pattern",
4148
+ "background-pattern",
4149
+ "fill-extrusion-pattern"
4150
+ ]);
4151
+ opacityProperties = {
4152
+ background: "background-opacity",
4153
+ circle: "circle-opacity",
4154
+ fill: "fill-opacity",
4155
+ "fill-extrusion": "fill-extrusion-opacity",
4156
+ heatmap: "heatmap-opacity",
4157
+ line: "line-opacity",
4158
+ raster: "raster-opacity"
4159
+ };
4160
+ featureOperators = /* @__PURE__ */ new Set([
4161
+ "feature-state",
4162
+ "geometry-type",
4163
+ "get",
4164
+ "has",
4165
+ "id",
4166
+ "in",
4167
+ "properties"
4168
+ ]);
4169
+ maximumBranches = 24;
4170
+ isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
4171
+ reads = (value, operator) => Array.isArray(value) && (value[0] === operator || value.some((item) => reads(item, operator)));
4172
+ isLegacyFilter = (filter) => Array.isArray(filter) && typeof filter[1] === "string" && !Array.isArray(filter[1]);
4173
+ oklabCache = /* @__PURE__ */ new Map();
4174
+ }
4108
4175
  });
4109
- // @__NO_SIDE_EFFECTS__
4110
- function refine(fn, _params = {}) {
4111
- return _refine(ZodMiniCustom, fn, _params);
4112
- }
4113
- // @__NO_SIDE_EFFECTS__
4114
- function superRefine(fn, params) {
4115
- return _superRefine(fn, params);
4116
- }
4117
-
4118
- // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/checks.js
4119
- init_core2();
4120
-
4121
- // src/native-manifest-schema.ts
4122
- init_manifest_schema();
4123
-
4124
- // src/manifest-schema-mini.ts
4125
- function mini(schema2) {
4126
- return schema2;
4127
- }
4128
- var miniManifestSchemaOperations = {
4129
- string: () => string2(),
4130
- minLength: (schema2, minimum) => mini(schema2).check(_minLength(minimum)),
4131
- maxLength: (schema2, maximum) => mini(schema2).check(_maxLength(maximum)),
4132
- regex: (schema2, pattern, message) => mini(schema2).check(_regex(pattern, { message })),
4133
- refine: (schema2, check, message) => mini(schema2).check(refine(check, message)),
4134
- // Zod 4's number parser rejects NaN and infinities; Classic finite() is a no-op.
4135
- number: (minimum, maximum) => number2().check(_gte(minimum), _lte(maximum)),
4136
- unknown: () => unknown(),
4137
- optional: (schema2) => optional(schema2),
4138
- array: (schema2) => array(schema2),
4139
- enum(values) {
4140
- return _enum(values);
4141
- },
4142
- literal: (value) => literal(value),
4143
- tuplePair(left, right) {
4144
- return tuple([left, right]);
4145
- },
4146
- // ZodMiniObject uses the interpreted $ZodObject, not the Classic object JIT constructor.
4147
- object(shape) {
4148
- return strictObject(shape);
4149
- },
4150
- record(key, value) {
4151
- return record(key, value);
4152
- },
4153
- superRefine: (schema2, check) => mini(schema2).check(superRefine(check)),
4154
- pipe: (input, output) => pipe(input, output)
4155
- };
4156
4176
 
4157
- // src/native-manifest-utf8.ts
4158
- init_native_url_utf8();
4159
- function nativeManifestUtf8ByteLength(input) {
4160
- let bytes = 0;
4161
- for (let index = 0; index < input.length; index++) {
4162
- const value = input.charCodeAt(index);
4163
- if (value <= 127) bytes++;
4164
- else if (value <= 2047) bytes += 2;
4165
- else if (value >= 55296 && value <= 56319 && input.charCodeAt(index + 1) >= 56320 && input.charCodeAt(index + 1) <= 57343) {
4166
- bytes += 4;
4167
- index++;
4168
- } else bytes += 3;
4169
- }
4170
- return bytes;
4171
- }
4172
- function decodeNativeManifestUtf8(input) {
4173
- let remaining = 0;
4174
- let lower = 128;
4175
- let upper = 191;
4176
- for (const byte of input) {
4177
- if (remaining === 0) {
4178
- if (byte <= 127) continue;
4179
- if (byte >= 194 && byte <= 223) remaining = 1;
4180
- else if (byte >= 224 && byte <= 239) {
4181
- remaining = 2;
4182
- if (byte === 224) lower = 160;
4183
- if (byte === 237) upper = 159;
4184
- } else if (byte >= 240 && byte <= 244) {
4185
- remaining = 3;
4186
- if (byte === 240) lower = 144;
4187
- if (byte === 244) upper = 143;
4188
- } else throw new TypeError("Invalid manifest UTF-8.");
4189
- } else {
4190
- if (byte < lower || byte > upper) throw new TypeError("Invalid manifest UTF-8.");
4191
- remaining--;
4192
- lower = 128;
4193
- upper = 191;
4194
- }
4195
- }
4196
- if (remaining) throw new TypeError("Invalid manifest UTF-8.");
4197
- const parts = [];
4198
- let offset = 0;
4199
- while (offset < input.byteLength) {
4200
- let end = Math.min(offset + nativeUrlCodecMaximumLength, input.byteLength);
4201
- while (end < input.byteLength && (input[end] & 192) === 128) end--;
4202
- parts.push(utf8DecodeWithoutBOM(input.subarray(offset, end)));
4203
- offset = end;
4204
- }
4205
- const text = parts.join("");
4206
- return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
4207
- }
4208
- function hasBoundedNativeManifestDepth(text, maximumDepth) {
4209
- let depth = 0;
4210
- let quoted = false;
4211
- let escaped = false;
4212
- for (let index = 0; index < text.length; index++) {
4213
- const character = text[index];
4214
- if (quoted) {
4215
- if (escaped) escaped = false;
4216
- else if (character === "\\") escaped = true;
4217
- else if (character === '"') quoted = false;
4218
- } else if (character === '"') quoted = true;
4219
- else if (character === "[" || character === "{") {
4220
- if (++depth > maximumDepth) return false;
4221
- } else if (character === "]" || character === "}") depth--;
4177
+ // src/themes/model.ts
4178
+ var tileflowThemeLimits, themeTypographyStyleKeys, themeTypographyKeys;
4179
+ var init_model = __esm({
4180
+ "src/themes/model.ts"() {
4181
+ "use strict";
4182
+ tileflowThemeLimits = Object.freeze({ maxThemes: 64 });
4183
+ themeTypographyStyleKeys = ["fallbacks", "font", "letterSpacing", "transform"];
4184
+ themeTypographyKeys = [
4185
+ ...themeTypographyStyleKeys,
4186
+ "places",
4187
+ "poi",
4188
+ "roads",
4189
+ "water"
4190
+ ];
4222
4191
  }
4223
- return true;
4224
- }
4192
+ });
4225
4193
 
4226
- // src/native-manifest-schema.ts
4227
- var schema = createTileflowRuntimeManifestSchema(
4228
- {
4229
- parseUrl: parseNativeUrl,
4230
- utf8ByteLength: nativeManifestUtf8ByteLength
4231
- },
4232
- miniManifestSchemaOperations
4233
- );
4234
- function parseTileflowRuntimeManifest(input) {
4235
- return parse(schema, input);
4236
- }
4237
-
4238
- // src/native-source-types.ts
4239
- var messages = {
4240
- NATIVE_SOURCE_INVALID: "Expected a valid native source or renderer-neutral view.",
4241
- NATIVE_SOURCE_ABORTED: "The source operation was cancelled.",
4242
- NATIVE_SOURCE_DISPOSED: "The source controller has been disposed.",
4243
- NATIVE_MANIFEST_URL_INVALID: "Expected a safe absolute manifest URL and an exact development origin.",
4244
- NATIVE_MANIFEST_REQUEST_FAILED: "Manifest acquisition failed.",
4245
- NATIVE_MANIFEST_RESPONSE_INVALID: "Expected a bounded byte reader and an explicit final response URL.",
4246
- NATIVE_MANIFEST_ACCESS_DENIED: "Manifest access was denied.",
4247
- NATIVE_MANIFEST_NOT_FOUND: "The manifest was not found.",
4248
- NATIVE_MANIFEST_TOO_LARGE: "The manifest exceeds the 1 MiB limit.",
4249
- NATIVE_MANIFEST_UTF8_INVALID: "The manifest body is not valid UTF-8.",
4250
- NATIVE_MANIFEST_JSON_INVALID: "The manifest body is not valid JSON.",
4251
- NATIVE_MANIFEST_INVALID: "The manifest does not satisfy the version-1 runtime contract.",
4252
- NATIVE_MANIFEST_RESOURCE_INVALID: "A manifest resource URL is outside the native URL policy.",
4253
- NATIVE_MAP_NOT_FOUND: "The requested map is not declared by the manifest.",
4254
- NATIVE_THEME_INVALID: "Expected a declared concrete theme or an explicitly supplied system color scheme."
4255
- };
4256
- var TileflowNativeSourceError = class extends Error {
4257
- constructor(code, field) {
4258
- super(messages[code]);
4259
- __publicField(this, "code");
4260
- __publicField(this, "field");
4261
- __publicField(this, "kind");
4262
- this.name = "TileflowNativeSourceError";
4263
- this.code = code;
4264
- this.field = field;
4265
- this.kind = code === "NATIVE_SOURCE_ABORTED" || code === "NATIVE_SOURCE_DISPOSED" ? "cancelled" : "terminal";
4266
- }
4267
- };
4268
- function normalizeNativeSourceError(error2) {
4269
- try {
4270
- if (error2 instanceof TileflowNativeSourceError) {
4271
- const code = Object.getOwnPropertyDescriptor(error2, "code")?.value;
4272
- const field = Object.getOwnPropertyDescriptor(error2, "field")?.value;
4273
- if (typeof code === "string" && Object.hasOwn(messages, code) && ["source", "signal", "manifestUrl", "response", "body", "map", "theme", "view"].includes(
4274
- field
4275
- )) {
4276
- return new TileflowNativeSourceError(code, field);
4194
+ // src/manifest-schema.ts
4195
+ function createTileflowRuntimeManifestSchema(primitives, operations, identities = createPortableIdentitySchemas(operations)) {
4196
+ const { tileflowPortableIdSchema, tileflowThemeNameSchema } = identities;
4197
+ const safeTextSchema = operations.refine(
4198
+ operations.refine(
4199
+ operations.maxLength(operations.minLength(operations.string(), 1), 2048),
4200
+ (value) => value === value.trim(),
4201
+ "Expected text without surrounding whitespace"
4202
+ ),
4203
+ (value) => !/[\p{Cc}\\]/u.test(value),
4204
+ "Expected safe text"
4205
+ );
4206
+ const publicResourceUrlSchema = operations.refine(
4207
+ safeTextSchema,
4208
+ isPublicResourceUrl,
4209
+ "Expected an HTTP(S), root-relative, or path-relative public URL"
4210
+ );
4211
+ const apiOriginSchema = operations.refine(
4212
+ safeTextSchema,
4213
+ isApiOrigin,
4214
+ "Expected an absolute HTTP(S) origin without path, credentials, query, or fragment"
4215
+ );
4216
+ const viewSchema2 = createTileflowRuntimeViewSchema(operations);
4217
+ const fontFaceSchema = operations.object({
4218
+ family: operations.maxLength(safeTextSchema, 100),
4219
+ source: publicResourceUrlSchema,
4220
+ style: operations.optional(operations.enum(["italic", "normal", "oblique"])),
4221
+ weight: operations.optional(
4222
+ operations.enum(["100", "200", "300", "400", "500", "600", "700", "800", "900"])
4223
+ )
4224
+ });
4225
+ const fontFacesSchema = operations.superRefine(
4226
+ operations.maxLength(operations.array(fontFaceSchema), 16),
4227
+ validateUniqueFontFaces
4228
+ );
4229
+ const manifestThemeSchema = operations.object({
4230
+ colorScheme: operations.enum(["dark", "light"]),
4231
+ fontFaces: operations.optional(fontFacesSchema),
4232
+ revision: operations.optional(operations.maxLength(safeTextSchema, 128)),
4233
+ styleId: operations.optional(operations.maxLength(safeTextSchema, 128)),
4234
+ styleUrl: publicResourceUrlSchema
4235
+ });
4236
+ const manifestMapSchema = operations.superRefine(
4237
+ operations.object({
4238
+ apiUrl: operations.optional(apiOriginSchema),
4239
+ defaultTheme: tileflowThemeNameSchema,
4240
+ environment: operations.optional(operations.maxLength(safeTextSchema, 128)),
4241
+ mapId: operations.optional(operations.maxLength(safeTextSchema, 128)),
4242
+ systemThemes: operations.optional(
4243
+ operations.object({
4244
+ dark: tileflowThemeNameSchema,
4245
+ light: tileflowThemeNameSchema
4246
+ })
4247
+ ),
4248
+ themes: operations.record(tileflowThemeNameSchema, manifestThemeSchema),
4249
+ usageMode: operations.optional(operations.literal("session")),
4250
+ view: operations.optional(viewSchema2),
4251
+ worldGeneration: operations.optional(operations.literal("v1"))
4252
+ }),
4253
+ (entry, context) => {
4254
+ const names = Object.keys(entry.themes);
4255
+ if (names.length === 0) {
4256
+ context.addIssue({
4257
+ code: "custom",
4258
+ message: "Expected at least one theme",
4259
+ path: ["themes"]
4260
+ });
4261
+ }
4262
+ if (names.length > tileflowThemeLimits.maxThemes) {
4263
+ context.addIssue({
4264
+ code: "too_big",
4265
+ maximum: tileflowThemeLimits.maxThemes,
4266
+ origin: "object",
4267
+ path: ["themes"]
4268
+ });
4269
+ }
4270
+ if (!Object.hasOwn(entry.themes, entry.defaultTheme)) {
4271
+ context.addIssue({
4272
+ code: "custom",
4273
+ message: "defaultTheme must name a declared theme",
4274
+ path: ["defaultTheme"]
4275
+ });
4276
+ }
4277
+ if (entry.systemThemes) {
4278
+ for (const colorScheme of ["light", "dark"]) {
4279
+ const name = entry.systemThemes[colorScheme];
4280
+ const theme = entry.themes[name];
4281
+ if (!theme) {
4282
+ context.addIssue({
4283
+ code: "custom",
4284
+ message: `systemThemes.${colorScheme} must name a declared theme`,
4285
+ path: ["systemThemes", colorScheme]
4286
+ });
4287
+ } else if (theme.colorScheme !== colorScheme) {
4288
+ context.addIssue({
4289
+ code: "custom",
4290
+ message: `systemThemes.${colorScheme} must reference a ${colorScheme} theme`,
4291
+ path: ["systemThemes", colorScheme]
4292
+ });
4293
+ }
4294
+ }
4295
+ }
4296
+ if (entry.usageMode === void 0 !== (entry.worldGeneration === void 0)) {
4297
+ context.addIssue({
4298
+ code: "custom",
4299
+ message: "usageMode and worldGeneration must be declared together"
4300
+ });
4277
4301
  }
4278
4302
  }
4279
- } catch {
4280
- }
4281
- return new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
4282
- }
4283
-
4284
- // src/native-source-cancellation.ts
4285
- var NativeSourceCancellation = class {
4286
- constructor() {
4287
- __publicField(this, "cancelled", false);
4288
- __publicField(this, "listeners", /* @__PURE__ */ new Set());
4289
- }
4290
- get aborted() {
4291
- return this.cancelled;
4292
- }
4293
- cancel() {
4294
- if (this.cancelled) return;
4295
- this.cancelled = true;
4296
- const listeners = [...this.listeners];
4297
- this.listeners.clear();
4298
- for (const listener of listeners) {
4299
- try {
4300
- listener();
4301
- } catch {
4303
+ );
4304
+ const canonicalManifestSchema = operations.superRefine(
4305
+ operations.object({
4306
+ apiUrl: operations.optional(apiOriginSchema),
4307
+ maps: operations.record(tileflowPortableIdSchema, manifestMapSchema),
4308
+ version: operations.literal(tileflowRuntimeManifestVersion)
4309
+ }),
4310
+ (manifest, context) => {
4311
+ const mapCount = Object.keys(manifest.maps).length;
4312
+ if (mapCount === 0) {
4313
+ context.addIssue({ code: "custom", message: "Expected at least one map", path: ["maps"] });
4314
+ }
4315
+ if (mapCount > 1e3) {
4316
+ context.addIssue({ code: "too_big", maximum: 1e3, origin: "object", path: ["maps"] });
4317
+ }
4318
+ if (primitives.utf8ByteLength(JSON.stringify(manifest)) > tileflowRuntimeManifestLimits.maximumBytes) {
4319
+ context.addIssue({
4320
+ code: "too_big",
4321
+ maximum: tileflowRuntimeManifestLimits.maximumBytes,
4322
+ origin: "string",
4323
+ message: "Manifest JSON exceeds the 1 MiB limit"
4324
+ });
4325
+ }
4326
+ }
4327
+ );
4328
+ const safeManifestInputSchema = operations.superRefine(operations.unknown(), (input, context) => {
4329
+ const unsafe = findUnsafeManifestStructure(input);
4330
+ if (unsafe) {
4331
+ context.addIssue({
4332
+ code: "custom",
4333
+ message: unsafe.reason === "prototype" ? "Expected plain manifest objects" : "Expected no prototype-mutating keys",
4334
+ path: unsafe.path
4335
+ });
4336
+ }
4337
+ });
4338
+ function validateUniqueFontFaces(fontFaces, context) {
4339
+ const seen = /* @__PURE__ */ new Map();
4340
+ for (const [index, fontFace] of fontFaces.entries()) {
4341
+ const identity = `${fontFace.family}\0${fontFace.style ?? "normal"}\0${fontFace.weight ?? "400"}`;
4342
+ const first = seen.get(identity);
4343
+ if (first !== void 0) {
4344
+ context.addIssue({
4345
+ code: "custom",
4346
+ message: `Duplicate font face identity; first declared at index ${first}`,
4347
+ path: [index]
4348
+ });
4349
+ continue;
4302
4350
  }
4351
+ seen.set(identity, index);
4303
4352
  }
4304
4353
  }
4305
- check() {
4306
- if (this.cancelled) throw new TileflowNativeSourceError("NATIVE_SOURCE_ABORTED", "signal");
4354
+ function isPublicResourceUrl(value) {
4355
+ if (value.startsWith("//")) return false;
4356
+ if (!value.startsWith("/") && !value.startsWith("./") && !value.startsWith("../") && !isAbsolutePublicUrl(value)) {
4357
+ return false;
4358
+ }
4359
+ return isSafeResolvedPublicUrl(value);
4307
4360
  }
4308
- onCancel(listener) {
4309
- if (this.cancelled) {
4310
- listener();
4311
- return () => void 0;
4361
+ function isAbsolutePublicUrl(value) {
4362
+ try {
4363
+ const url = primitives.parseUrl(value);
4364
+ return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && !url.hash && !hasEncodedPathSeparator(value);
4365
+ } catch {
4366
+ return false;
4312
4367
  }
4313
- this.listeners.add(listener);
4314
- return () => {
4315
- this.listeners.delete(listener);
4316
- };
4317
4368
  }
4318
- race(promise) {
4319
- return new Promise((resolve, reject) => {
4320
- const off = this.onCancel(
4321
- () => reject(new TileflowNativeSourceError("NATIVE_SOURCE_ABORTED", "signal"))
4322
- );
4323
- promise.then(
4324
- (value) => {
4325
- off();
4326
- resolve(value);
4327
- },
4328
- (error2) => {
4329
- off();
4330
- reject(error2);
4331
- }
4332
- );
4333
- });
4369
+ function isSafeResolvedPublicUrl(value) {
4370
+ try {
4371
+ const url = primitives.parseUrl(value, "https://manifest.invalid/owner/manifest.json");
4372
+ return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && !url.hash && !hasEncodedPathSeparator(value);
4373
+ } catch {
4374
+ return false;
4375
+ }
4334
4376
  }
4335
- link(signal) {
4336
- if (signal === void 0) return () => void 0;
4337
- let onAbort;
4377
+ function hasEncodedPathSeparator(value) {
4378
+ return /%(?:2f|5c)/iu.test(value);
4379
+ }
4380
+ function isApiOrigin(value) {
4338
4381
  try {
4339
- if (!signal || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function" || typeof signal.removeEventListener !== "function")
4340
- throw new Error();
4341
- onAbort = () => this.cancel();
4342
- if (signal.aborted) {
4343
- this.cancel();
4344
- return () => void 0;
4345
- }
4346
- signal.addEventListener("abort", onAbort, { once: true });
4347
- if (signal.aborted) this.cancel();
4382
+ const url = primitives.parseUrl(value);
4383
+ return isAbsolutePublicUrl(value) && url.pathname === "/" && !url.search && value.replace(/\/$/u, "") === url.origin;
4348
4384
  } catch {
4349
- if (onAbort) {
4350
- try {
4351
- signal.removeEventListener("abort", onAbort);
4352
- } catch {
4385
+ return false;
4386
+ }
4387
+ }
4388
+ function findUnsafeManifestStructure(input) {
4389
+ if (!input || typeof input !== "object") return void 0;
4390
+ const pending = [
4391
+ { path: [], value: input }
4392
+ ];
4393
+ const visited = /* @__PURE__ */ new WeakSet();
4394
+ while (pending.length > 0) {
4395
+ const current = pending.pop();
4396
+ if (visited.has(current.value)) continue;
4397
+ visited.add(current.value);
4398
+ if (!Array.isArray(current.value)) {
4399
+ const prototype = Object.getPrototypeOf(current.value);
4400
+ if (prototype !== Object.prototype && prototype !== null) {
4401
+ return { path: current.path, reason: "prototype" };
4353
4402
  }
4354
4403
  }
4355
- throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "signal");
4356
- }
4357
- return () => {
4358
- try {
4359
- signal.removeEventListener("abort", onAbort);
4360
- } catch {
4404
+ for (const key of Object.keys(current.value)) {
4405
+ const path = [...current.path, Array.isArray(current.value) ? Number(key) : key];
4406
+ if (key === "__proto__") return { path, reason: "key" };
4407
+ const descriptor = Object.getOwnPropertyDescriptor(current.value, key);
4408
+ if (descriptor && "value" in descriptor && descriptor.value && typeof descriptor.value === "object") {
4409
+ pending.push({ path, value: descriptor.value });
4410
+ }
4361
4411
  }
4362
- };
4363
- }
4364
- };
4365
- function cancelNativeOperation(cancel) {
4366
- try {
4367
- void Promise.resolve(cancel()).catch(() => void 0);
4368
- } catch {
4369
- }
4370
- }
4371
-
4372
- // src/native-source-utils.ts
4373
- function nativeOwnRecord(value) {
4374
- try {
4375
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
4376
- const prototype = Object.getPrototypeOf(value);
4377
- if (prototype !== null && prototype !== Object.prototype) return void 0;
4378
- const result = /* @__PURE__ */ Object.create(null);
4379
- for (const key of Object.keys(value)) {
4380
- const property = Object.getOwnPropertyDescriptor(value, key);
4381
- if (!property || !("value" in property)) return void 0;
4382
- result[key] = property.value;
4383
4412
  }
4384
- return result;
4385
- } catch {
4386
4413
  return void 0;
4387
4414
  }
4415
+ return operations.pipe(safeManifestInputSchema, canonicalManifestSchema);
4388
4416
  }
4389
- function freezeNativeSnapshot(value) {
4390
- if (value && typeof value === "object" && !Object.isFrozen(value)) {
4391
- for (const child of Object.values(value)) freezeNativeSnapshot(child);
4392
- Object.freeze(value);
4417
+ var init_manifest_schema = __esm({
4418
+ "src/manifest-schema.ts"() {
4419
+ "use strict";
4420
+ init_manifest_types();
4421
+ init_portable_identity_schema();
4422
+ init_runtime_view_schema();
4423
+ init_model();
4393
4424
  }
4394
- return value;
4395
- }
4396
-
4397
- // src/native-manifest.ts
4398
- var tileflowNativeManifestLimits = Object.freeze({
4399
- maximumBytes: tileflowRuntimeManifestLimits.maximumBytes,
4400
- maximumChunkBytes: 64 * 1024,
4401
- maximumDepth: 64
4402
4425
  });
4403
- async function loadTileflowNativeManifest(manifestUrl, options) {
4404
- const input = nativeOwnRecord(options);
4405
- if (!input || typeof input.acquire !== "function")
4406
- throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
4407
- const cancellation = new NativeSourceCancellation();
4408
- const detach = cancellation.link(input.signal);
4409
- try {
4410
- return await loadNativeManifest(
4411
- manifestUrl,
4412
- input.acquire,
4413
- {
4414
- developmentOrigin: input.developmentOrigin
4415
- },
4416
- cancellation
4417
- );
4418
- } finally {
4419
- detach();
4426
+
4427
+ // src/native-url-policy.ts
4428
+ var tileflowNativeUrlLimits = Object.freeze({ maximumLength: 2048 });
4429
+ var errorMessages = {
4430
+ NATIVE_URL_INVALID: "Expected a bounded HTTP(S) URL without credentials, fragments or backslashes.",
4431
+ NATIVE_URL_ABSOLUTE_REQUIRED: "An explicit absolute HTTP(S) document URL is required.",
4432
+ NATIVE_URL_HTTPS_REQUIRED: "HTTP is allowed only at the explicitly configured development origin.",
4433
+ NATIVE_URL_DEVELOPMENT_ORIGIN_INVALID: "Expected one HTTP origin without a path, credentials, query or fragment.",
4434
+ NATIVE_URL_TEMPLATE_INVALID: "Expected supported URL placeholders in the resource path or query only."
4435
+ };
4436
+ var TileflowNativeUrlError = class extends TypeError {
4437
+ constructor(code, field) {
4438
+ super(errorMessages[code]);
4439
+ __publicField(this, "code");
4440
+ __publicField(this, "field");
4441
+ this.name = "TileflowNativeUrlError";
4442
+ this.code = code;
4443
+ this.field = field;
4420
4444
  }
4421
- }
4422
- async function loadNativeManifest(manifestUrl, acquire, network, cancellation) {
4423
- cancellation.check();
4424
- let requestUrl;
4425
- try {
4426
- requestUrl = resolveTileflowNativeManifestUrl(manifestUrl, network);
4427
- } catch {
4428
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_URL_INVALID", "manifestUrl");
4445
+ };
4446
+ function createNativeUrlPolicy(parse2) {
4447
+ const tilePlaceholders = /* @__PURE__ */ new Set(["z", "x", "y", "ratio", "quadkey", "bbox-epsg-3857", "prefix"]);
4448
+ const glyphPlaceholders = /* @__PURE__ */ new Set(["fontstack", "range"]);
4449
+ const absoluteHttpPattern = /^https?:\/\/[^/?#]+/iu;
4450
+ const schemePattern = /^[a-z][a-z\d+.-]*:/iu;
4451
+ function resolveTileflowNativeManifestUrl2(value, options = {}) {
4452
+ assertOptionsObject(options, "developmentOrigin");
4453
+ const developmentOrigin = resolveDevelopmentOrigin(options.developmentOrigin);
4454
+ return resolveDocumentUrl(value, "manifestUrl", developmentOrigin).href;
4429
4455
  }
4430
- let operation;
4431
- try {
4432
- operation = acquire(
4433
- requestUrl,
4434
- Object.freeze({ maximumBytes: tileflowNativeManifestLimits.maximumBytes })
4435
- );
4436
- if (!operation || typeof operation.cancel !== "function" || !operation.response || typeof operation.response.then !== "function") {
4437
- throw new Error();
4456
+ function resolveTileflowNativeResourceUrl2(value, options) {
4457
+ assertOptionsObject(options, "documentUrl");
4458
+ const developmentOrigin = resolveDevelopmentOrigin(options.developmentOrigin);
4459
+ const document2 = resolveDocumentUrl(options.documentUrl, "documentUrl", developmentOrigin);
4460
+ assertUrlText(value, "resourceUrl");
4461
+ if (options.template !== void 0 && options.template !== "tile" && options.template !== "glyphs") {
4462
+ throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", "resourceUrl");
4438
4463
  }
4439
- } catch {
4440
- if (operation) cancelNativeOperation(() => operation.cancel());
4441
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
4442
- }
4443
- let reader;
4444
- let readerCancelled = false;
4445
- let operationCancelled = false;
4446
- const cancelReader = () => {
4447
- if (!reader || readerCancelled) return;
4448
- readerCancelled = true;
4449
- cancelNativeOperation(() => reader.cancel());
4450
- };
4451
- const cleanup = () => {
4452
- if (!operationCancelled) {
4453
- operationCancelled = true;
4454
- cancelNativeOperation(() => operation.cancel());
4464
+ assertReferenceSyntax(value, "resourceUrl", false);
4465
+ const authority = absoluteHttpPattern.exec(value)?.[0];
4466
+ if (authority && /[{}]/u.test(authority)) {
4467
+ throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", "resourceUrl");
4455
4468
  }
4456
- cancelReader();
4457
- };
4458
- const unlisten = cancellation.onCancel(cleanup);
4459
- try {
4460
- const headers = Promise.resolve(operation.response).then(
4461
- (response2) => {
4462
- try {
4463
- if (!response2 || !response2.reader || typeof response2.reader.read !== "function" || typeof response2.reader.cancel !== "function") {
4464
- throw new Error();
4465
- }
4466
- reader = response2.reader;
4467
- } catch {
4468
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
4469
- }
4470
- if (cancellation.aborted) cancelReader();
4471
- cancellation.check();
4472
- return response2;
4473
- },
4474
- () => {
4475
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
4476
- }
4477
- );
4478
- const response = await cancellation.race(headers);
4479
- cancellation.check();
4480
- let finalUrl;
4481
- try {
4482
- finalUrl = resolveTileflowNativeManifestUrl(response.url, network);
4483
- } catch {
4484
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_URL_INVALID", "manifestUrl");
4469
+ let prefix = "__tileflow_native_template_";
4470
+ let attempt = 0;
4471
+ while (value.includes(prefix) || document2.href.includes(prefix)) {
4472
+ prefix = `__tileflow_native_template_${attempt++}_`;
4485
4473
  }
4486
- if (!Number.isInteger(response.status) || response.status < 100 || response.status > 599) {
4487
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
4474
+ const placeholders = [];
4475
+ const allowed = options.template === "tile" ? tilePlaceholders : glyphPlaceholders;
4476
+ const protectedValue = value.replace(/\{([^{}]*)\}/gu, (placeholder, name) => {
4477
+ if (!options.template || !allowed.has(name)) {
4478
+ throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", "resourceUrl");
4479
+ }
4480
+ placeholders.push(placeholder);
4481
+ return `${prefix}${placeholders.length - 1}__`;
4482
+ });
4483
+ if (/[{}]/u.test(protectedValue)) {
4484
+ throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", "resourceUrl");
4488
4485
  }
4489
- if (response.status === 401 || response.status === 403)
4490
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_ACCESS_DENIED", "response");
4491
- if (response.status === 404)
4492
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_NOT_FOUND", "response");
4493
- if (response.status < 200 || response.status >= 300)
4494
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
4495
- const buffer = new Uint8Array(tileflowNativeManifestLimits.maximumBytes);
4496
- let length = 0;
4497
- while (true) {
4498
- cancellation.check();
4499
- const allowance = Math.min(
4500
- tileflowNativeManifestLimits.maximumChunkBytes,
4501
- buffer.byteLength - length + 1
4486
+ const resolved = parseUrl(protectedValue, "resourceUrl", document2.href);
4487
+ assertTransport(resolved, "resourceUrl", developmentOrigin);
4488
+ const resourcePath = resolved.href.slice(resolved.origin.length);
4489
+ const suffix = resourcePath.replace(
4490
+ new RegExp(`${prefix}(\\d+)__`, "gu"),
4491
+ (match, index) => placeholders[Number(index)] ?? match
4492
+ );
4493
+ const result = resolved.origin + suffix;
4494
+ assertUrlText(result, "resourceUrl");
4495
+ return result;
4496
+ }
4497
+ function assertOptionsObject(value, field) {
4498
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
4499
+ throw new TileflowNativeUrlError(
4500
+ field === "documentUrl" ? "NATIVE_URL_INVALID" : "NATIVE_URL_DEVELOPMENT_ORIGIN_INVALID",
4501
+ field
4502
4502
  );
4503
- let next;
4504
- try {
4505
- const pending = Promise.resolve(reader.read(allowance)).catch(() => {
4506
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
4507
- });
4508
- next = await cancellation.race(pending);
4509
- } catch {
4510
- cancellation.check();
4511
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
4512
- }
4513
- cancellation.check();
4514
- if (!next || typeof next.done !== "boolean")
4515
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
4516
- if (next.done) {
4517
- if (next.value !== void 0)
4518
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
4519
- break;
4520
- }
4521
- if (!(next.value instanceof Uint8Array) || next.value.byteLength === 0)
4522
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
4523
- if (next.value.byteLength > buffer.byteLength - length)
4524
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_TOO_LARGE", "body");
4525
- if (next.value.byteLength > allowance)
4526
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
4527
- buffer.set(next.value, length);
4528
- length += next.value.byteLength;
4529
4503
  }
4530
- let text;
4504
+ }
4505
+ function resolveDevelopmentOrigin(value) {
4506
+ if (value === void 0) return void 0;
4531
4507
  try {
4532
- text = decodeNativeManifestUtf8(buffer.subarray(0, length));
4508
+ assertUrlText(value, "developmentOrigin");
4509
+ const match = /^(http:\/\/[^/?#]+)\/?$/iu.exec(value);
4510
+ if (!match || /[{}@*]/u.test(value)) throw new Error();
4511
+ const url = parseUrl(value, "developmentOrigin");
4512
+ if (url.protocol !== "http:" || url.pathname !== "/" || url.search || url.hash || url.hostname.includes("*")) {
4513
+ throw new Error();
4514
+ }
4515
+ return url.origin;
4533
4516
  } catch {
4534
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_UTF8_INVALID", "body");
4517
+ throw new TileflowNativeUrlError(
4518
+ "NATIVE_URL_DEVELOPMENT_ORIGIN_INVALID",
4519
+ "developmentOrigin"
4520
+ );
4535
4521
  }
4536
- if (!hasBoundedNativeManifestDepth(text, tileflowNativeManifestLimits.maximumDepth)) {
4537
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_INVALID", "body");
4522
+ }
4523
+ function resolveDocumentUrl(value, field, developmentOrigin) {
4524
+ assertUrlText(value, field);
4525
+ assertReferenceSyntax(value, field, true);
4526
+ if (/[{}]/u.test(value)) throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", field);
4527
+ const url = parseUrl(value, field);
4528
+ assertTransport(url, field, developmentOrigin);
4529
+ assertUrlText(url.href, field);
4530
+ return url;
4531
+ }
4532
+ function assertUrlText(value, field) {
4533
+ if (typeof value !== "string" || value.length === 0 || value.length > tileflowNativeUrlLimits.maximumLength || value !== value.trim() || /[\p{Cc}\\#]/u.test(value) || /%(?![\da-f]{2})/iu.test(value)) {
4534
+ throw new TileflowNativeUrlError("NATIVE_URL_INVALID", field);
4538
4535
  }
4539
- let json;
4540
- try {
4541
- json = JSON.parse(text);
4542
- } catch {
4543
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_JSON_INVALID", "body");
4536
+ }
4537
+ function assertReferenceSyntax(value, field, absolute) {
4538
+ if (value.startsWith("//")) {
4539
+ throw new TileflowNativeUrlError(
4540
+ absolute ? "NATIVE_URL_ABSOLUTE_REQUIRED" : "NATIVE_URL_INVALID",
4541
+ field
4542
+ );
4544
4543
  }
4545
- let manifest;
4546
- try {
4547
- manifest = parseTileflowRuntimeManifest(json);
4548
- } catch {
4549
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_INVALID", "body");
4544
+ const authority = absoluteHttpPattern.exec(value)?.[0];
4545
+ if (schemePattern.test(value) && !authority) {
4546
+ throw new TileflowNativeUrlError("NATIVE_URL_INVALID", field);
4550
4547
  }
4551
- let resolvedBytes = nativeManifestUtf8ByteLength(JSON.stringify(manifest));
4552
- const resolveResource = (value, owner) => {
4553
- const resolved = resolveTileflowNativeResourceUrl(value, { documentUrl: owner, ...network });
4554
- resolvedBytes += nativeManifestUtf8ByteLength(JSON.stringify(resolved)) - nativeManifestUtf8ByteLength(JSON.stringify(value));
4555
- if (resolvedBytes > tileflowNativeManifestLimits.maximumBytes) {
4556
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_TOO_LARGE", "body");
4557
- }
4558
- return resolved;
4559
- };
4560
- try {
4561
- for (const name of Object.keys(manifest.maps).sort()) {
4562
- const map2 = manifest.maps[name];
4563
- for (const themeName of Object.keys(map2.themes).sort()) {
4564
- const theme = map2.themes[themeName];
4565
- theme.styleUrl = resolveResource(theme.styleUrl, finalUrl);
4566
- for (const face of theme.fontFaces ?? []) {
4567
- face.source = resolveResource(face.source, theme.styleUrl);
4568
- }
4569
- }
4570
- }
4571
- } catch (error2) {
4572
- if (error2 instanceof TileflowNativeSourceError) throw normalizeNativeSourceError(error2);
4573
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESOURCE_INVALID", "body");
4548
+ if (absolute && !authority) {
4549
+ throw new TileflowNativeUrlError("NATIVE_URL_ABSOLUTE_REQUIRED", field);
4574
4550
  }
4575
- cancellation.check();
4576
- return freezeNativeSnapshot({ manifestUrl: finalUrl, manifest });
4577
- } catch (error2) {
4578
- cleanup();
4579
- cancellation.check();
4580
- throw normalizeNativeSourceError(error2);
4581
- } finally {
4582
- unlisten();
4551
+ if (authority?.includes("@")) throw new TileflowNativeUrlError("NATIVE_URL_INVALID", field);
4583
4552
  }
4584
- }
4585
-
4586
- // src/native-source-controller.ts
4587
- init_portable_identity_rules();
4588
-
4589
- // src/runtime.ts
4590
- init_portable_identity_rules();
4591
- var defaultTileflowRuntimeView = Object.freeze({
4592
- bearing: 0,
4593
- center: [0, 20],
4594
- pitch: 0,
4595
- zoom: 2
4596
- });
4597
- var tileflowStyleFontFaceLimits = Object.freeze({
4598
- maximumCount: 16,
4599
- maximumSourceLength: 2048
4600
- });
4601
- var maxStaticImagePixels = 1280 * 1280;
4602
- var maximumManifestBytes = 1024 * 1024;
4603
- var maximumSessionGrantBytes = 64 * 1024;
4604
- var manifestCache = new globalThis.Map();
4605
- function validateTileflowThemeSelection(value) {
4606
- return value === "system" || isTileflowThemeName(value);
4607
- }
4608
- var tileflowThemeBlendLimits = Object.freeze({ maximumThemes: 8, minimumThemes: 2 });
4609
- function validateTileflowThemeBlendSelection(value) {
4610
- if (!value || typeof value !== "object") return false;
4611
- const { position, themes } = value;
4612
- 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;
4613
- }
4614
- function nearestTileflowBlendTheme(blend) {
4615
- const index = Math.min(blend.themes.length - 1, Math.max(0, Math.round(blend.position)));
4616
- return blend.themes[index];
4617
- }
4618
- function resolveTileflowRuntimeTheme(map2, selection, colorScheme) {
4619
- let name;
4620
- if (selection === void 0) {
4621
- name = map2.defaultTheme;
4622
- } else if (selection === "system") {
4623
- if (!map2.systemThemes) {
4624
- throw new Error(
4625
- `Tileflow map "${map2.name}" does not declare systemThemes; select a concrete theme.`
4626
- );
4553
+ function parseUrl(value, field, base2) {
4554
+ let url;
4555
+ try {
4556
+ url = parse2(value, base2);
4557
+ if (!url.hostname || url.username || url.password || url.hash) throw new Error();
4558
+ } catch {
4559
+ throw new TileflowNativeUrlError("NATIVE_URL_INVALID", field);
4627
4560
  }
4628
- if (!colorScheme) {
4629
- throw new Error('Resolving theme="system" requires an explicit browser color scheme.');
4561
+ if (/[{}]/u.test(url.hostname)) {
4562
+ throw new TileflowNativeUrlError("NATIVE_URL_TEMPLATE_INVALID", field);
4630
4563
  }
4631
- name = map2.systemThemes[colorScheme];
4632
- } else {
4633
- name = selection;
4634
- }
4635
- if (!isTileflowThemeName(name)) {
4636
- throw new Error(
4637
- `Tileflow resolved invalid theme ${JSON.stringify(name)} for map "${map2.name}"; expected a concrete portable theme name.`
4638
- );
4564
+ return url;
4639
4565
  }
4640
- const theme = Object.hasOwn(map2.themes, name) ? map2.themes[name] : void 0;
4641
- if (!theme) {
4642
- const available = Object.keys(map2.themes).sort().join(", ");
4643
- throw new Error(
4644
- `Unknown Tileflow theme "${name}" for map "${map2.name}". Available themes: ${available}.`
4645
- );
4566
+ function assertTransport(url, field, developmentOrigin) {
4567
+ if (url.protocol === "https:") return;
4568
+ if (url.protocol === "http:" && url.origin === developmentOrigin) return;
4569
+ throw new TileflowNativeUrlError("NATIVE_URL_HTTPS_REQUIRED", field);
4646
4570
  }
4647
- return { ...theme, name };
4571
+ return { resolveTileflowNativeManifestUrl: resolveTileflowNativeManifestUrl2, resolveTileflowNativeResourceUrl: resolveTileflowNativeResourceUrl2 };
4648
4572
  }
4649
- function resolveTileflowRuntimeView(options) {
4650
- const manifestView = options.manifestMap?.view;
4573
+
4574
+ // src/native-url-provider.ts
4575
+ var import_url_state_machine = __toESM(require_url_state_machine(), 1);
4576
+ function parseNativeUrl(value, base2) {
4577
+ const baseURL = base2 === void 0 ? void 0 : (0, import_url_state_machine.basicURLParse)(base2);
4578
+ if (baseURL === null) throw new TypeError("Invalid native URL.");
4579
+ const parsed = (0, import_url_state_machine.basicURLParse)(value, baseURL === void 0 ? void 0 : { baseURL });
4580
+ if (parsed === null) throw new TypeError("Invalid native URL.");
4651
4581
  return {
4652
- ...options.fallback ?? defaultTileflowRuntimeView,
4653
- ...manifestView ?? {},
4654
- ...options.bearing !== void 0 ? { bearing: options.bearing } : {},
4655
- ...options.center !== void 0 ? { center: options.center } : {},
4656
- ...options.pitch !== void 0 ? { pitch: options.pitch } : {},
4657
- ...options.zoom !== void 0 ? { zoom: options.zoom } : {}
4582
+ href: (0, import_url_state_machine.serializeURL)(parsed),
4583
+ origin: (0, import_url_state_machine.serializeURLOrigin)(parsed),
4584
+ protocol: `${parsed.scheme}:`,
4585
+ hostname: parsed.host === null ? "" : (0, import_url_state_machine.serializeHost)(parsed.host),
4586
+ pathname: (0, import_url_state_machine.serializePath)(parsed),
4587
+ search: parsed.query ? `?${parsed.query}` : "",
4588
+ hash: parsed.fragment ? `#${parsed.fragment}` : "",
4589
+ username: parsed.username,
4590
+ password: parsed.password
4658
4591
  };
4659
4592
  }
4660
4593
 
4661
- // src/native-source-controller.ts
4662
- function createTileflowNativeSourceController(options) {
4663
- const input = nativeOwnRecord(options);
4664
- if (!input || typeof input.acquire !== "function")
4665
- throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
4666
- const acquire = input.acquire;
4667
- const listeners = /* @__PURE__ */ new Set();
4668
- let state;
4669
- let generation = 0;
4670
- let disposed = false;
4671
- let active;
4672
- const notify = (listener, value) => {
4673
- try {
4674
- void Promise.resolve(listener(value)).catch(() => void 0);
4675
- } catch {
4676
- }
4677
- };
4678
- const publish = (next) => {
4679
- if (disposed || next.generation !== generation) return;
4680
- state = freezeNativeSnapshot(next);
4681
- for (const listener of [...listeners]) {
4682
- if (disposed || state !== next || generation !== next.generation) break;
4683
- if (listeners.has(listener)) notify(listener, next);
4684
- }
4685
- };
4686
- return {
4687
- get state() {
4688
- return state;
4689
- },
4690
- subscribe(listener) {
4691
- if (typeof listener !== "function")
4692
- throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
4693
- if (disposed) return () => void 0;
4694
- listeners.add(listener);
4695
- if (state) notify(listener, state);
4696
- return () => {
4697
- listeners.delete(listener);
4698
- };
4699
- },
4700
- async replace(source, options2 = {}) {
4701
- if (disposed) throw new TileflowNativeSourceError("NATIVE_SOURCE_DISPOSED", "source");
4702
- if (generation >= Number.MAX_SAFE_INTEGER)
4703
- throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
4704
- const own = { generation: ++generation, cancellation: new NativeSourceCancellation() };
4705
- const previous = active;
4706
- active = own;
4707
- const current = () => !disposed && generation === own.generation;
4708
- let sourceInput;
4709
- let selection;
4710
- let snapshotError;
4711
- try {
4712
- sourceInput = nativeOwnRecord(source);
4713
- selection = nativeOwnRecord(options2);
4714
- } catch (error2) {
4715
- snapshotError = normalizeNativeSourceError(error2);
4716
- }
4717
- previous?.cancellation.cancel();
4718
- let detach = () => void 0;
4719
- try {
4720
- if (!current()) return;
4721
- publish({ status: "loading", generation: own.generation });
4722
- if (!current()) return;
4723
- if (snapshotError) throw snapshotError;
4724
- if (!sourceInput || !selection || Object.hasOwn(sourceInput, "kind") || Object.hasOwn(sourceInput, "style") || !isTileflowPortableId(sourceInput.map))
4725
- throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
4726
- if (selection.theme !== void 0 && !validateTileflowThemeSelection(selection.theme) || selection.colorScheme !== void 0 && selection.colorScheme !== "light" && selection.colorScheme !== "dark") {
4727
- throw new TileflowNativeSourceError("NATIVE_THEME_INVALID", "theme");
4728
- }
4729
- const selectedSource = {
4730
- map: sourceInput.map,
4731
- manifestUrl: sourceInput.manifestUrl
4732
- };
4733
- if (sourceInput.developmentOrigin !== void 0) {
4734
- try {
4735
- const developmentOrigin = sourceInput.developmentOrigin;
4736
- selectedSource.developmentOrigin = resolveTileflowNativeManifestUrl(developmentOrigin, {
4737
- developmentOrigin
4738
- }).slice(0, -1);
4739
- } catch {
4740
- throw new TileflowNativeSourceError("NATIVE_MANIFEST_URL_INVALID", "manifestUrl");
4741
- }
4742
- }
4743
- detach = own.cancellation.link(selection.signal);
4744
- const result = await loadNativeManifest(
4745
- selectedSource.manifestUrl,
4746
- acquire,
4747
- {
4748
- developmentOrigin: selectedSource.developmentOrigin
4749
- },
4750
- own.cancellation
4751
- );
4752
- if (!current()) return;
4753
- if (!Object.hasOwn(result.manifest.maps, selectedSource.map)) {
4754
- throw new TileflowNativeSourceError("NATIVE_MAP_NOT_FOUND", "map");
4755
- }
4756
- const entry = result.manifest.maps[selectedSource.map];
4757
- const apiUrl = entry.apiUrl ?? result.manifest.apiUrl;
4758
- const map2 = {
4759
- ...entry,
4760
- name: selectedSource.map,
4761
- ...apiUrl === void 0 ? {} : { apiUrl }
4762
- };
4763
- let theme;
4764
- try {
4765
- theme = resolveTileflowRuntimeTheme(
4766
- map2,
4767
- selection.theme,
4768
- selection.colorScheme
4769
- );
4770
- } catch {
4771
- throw new TileflowNativeSourceError("NATIVE_THEME_INVALID", "theme");
4772
- }
4773
- publish({
4774
- status: "ready",
4775
- generation: own.generation,
4776
- source: selectedSource,
4777
- ...result,
4778
- map: map2,
4779
- theme
4780
- });
4781
- } catch (error2) {
4782
- if (current())
4783
- publish({
4784
- status: "error",
4785
- generation: own.generation,
4786
- error: normalizeNativeSourceError(error2)
4787
- });
4788
- } finally {
4789
- detach();
4790
- if (active === own) active = void 0;
4791
- }
4792
- },
4793
- dispose() {
4794
- if (disposed) return;
4795
- disposed = true;
4796
- listeners.clear();
4797
- const previous = active;
4798
- active = void 0;
4799
- previous?.cancellation.cancel();
4800
- }
4801
- };
4594
+ // src/native-urls.ts
4595
+ var policy = createNativeUrlPolicy(parseNativeUrl);
4596
+ function resolveTileflowNativeManifestUrl(value, options = {}) {
4597
+ return policy.resolveTileflowNativeManifestUrl(value, options);
4598
+ }
4599
+ function resolveTileflowNativeResourceUrl(value, options) {
4600
+ return policy.resolveTileflowNativeResourceUrl(value, options);
4802
4601
  }
4803
4602
 
4804
- // src/native-initial-view.ts
4805
- init_runtime_view_schema();
4806
- var viewSchema = createTileflowRuntimeViewSchema(miniManifestSchemaOperations);
4807
- var viewKeys = ["bearing", "center", "pitch", "zoom"];
4808
- var inputKeys = ["view", "mapOptionsView", "manifestView"];
4809
- var invalid = () => {
4810
- throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "view");
4811
- };
4812
- function resolveTileflowNativeInitialView(options = {}) {
4813
- try {
4814
- const input = snapshotRecord(options, inputKeys);
4815
- const manifestView = snapshotView(input.manifestView);
4816
- const mapOptionsView = snapshotView(input.mapOptionsView);
4817
- const view = snapshotView(input.view);
4818
- const resolved = resolveTileflowRuntimeView({
4819
- ...view,
4820
- fallback: { ...defaultTileflowRuntimeView, ...manifestView, ...mapOptionsView }
4821
- });
4822
- return freezeNativeSnapshot({
4823
- center: [resolved.center[0], resolved.center[1]],
4824
- zoom: resolved.zoom,
4825
- bearing: resolved.bearing,
4826
- pitch: resolved.pitch
4603
+ // src/native-manifest.ts
4604
+ init_manifest_types();
4605
+
4606
+ // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/parse.js
4607
+ init_core2();
4608
+
4609
+ // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/schemas.js
4610
+ init_core2();
4611
+ init_util();
4612
+ var ZodMiniType = /* @__PURE__ */ $constructor("ZodMiniType", (inst, def) => {
4613
+ if (!inst._zod)
4614
+ throw new Error("Uninitialized schema in ZodMiniType.");
4615
+ $ZodType.init(inst, def);
4616
+ inst.def = def;
4617
+ inst.type = def.type;
4618
+ inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });
4619
+ inst.safeParse = (data, params) => safeParse(inst, data, params);
4620
+ inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
4621
+ inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);
4622
+ inst.check = (...checks) => {
4623
+ return inst.clone({
4624
+ ...def,
4625
+ checks: [
4626
+ ...def.checks ?? [],
4627
+ ...checks.map((ch) => typeof ch === "function" ? {
4628
+ _zod: { check: ch, def: { check: "custom" }, onattach: [] }
4629
+ } : ch)
4630
+ ]
4631
+ }, { parent: true });
4632
+ };
4633
+ inst.with = inst.check;
4634
+ inst.clone = (_def, params) => clone(inst, _def, params);
4635
+ inst.brand = () => inst;
4636
+ inst.register = ((reg, meta2) => {
4637
+ reg.add(inst, meta2);
4638
+ return inst;
4639
+ });
4640
+ inst.apply = (fn) => fn(inst);
4641
+ });
4642
+ var ZodMiniString = /* @__PURE__ */ $constructor("ZodMiniString", (inst, def) => {
4643
+ $ZodString.init(inst, def);
4644
+ ZodMiniType.init(inst, def);
4645
+ });
4646
+ // @__NO_SIDE_EFFECTS__
4647
+ function string2(params) {
4648
+ return _string(ZodMiniString, params);
4649
+ }
4650
+ var ZodMiniNumber = /* @__PURE__ */ $constructor("ZodMiniNumber", (inst, def) => {
4651
+ $ZodNumber.init(inst, def);
4652
+ ZodMiniType.init(inst, def);
4653
+ });
4654
+ // @__NO_SIDE_EFFECTS__
4655
+ function number2(params) {
4656
+ return _number(ZodMiniNumber, params);
4657
+ }
4658
+ var ZodMiniUnknown = /* @__PURE__ */ $constructor("ZodMiniUnknown", (inst, def) => {
4659
+ $ZodUnknown.init(inst, def);
4660
+ ZodMiniType.init(inst, def);
4661
+ });
4662
+ // @__NO_SIDE_EFFECTS__
4663
+ function unknown() {
4664
+ return _unknown(ZodMiniUnknown);
4665
+ }
4666
+ var ZodMiniNever = /* @__PURE__ */ $constructor("ZodMiniNever", (inst, def) => {
4667
+ $ZodNever.init(inst, def);
4668
+ ZodMiniType.init(inst, def);
4669
+ });
4670
+ // @__NO_SIDE_EFFECTS__
4671
+ function never(params) {
4672
+ return _never(ZodMiniNever, params);
4673
+ }
4674
+ var ZodMiniArray = /* @__PURE__ */ $constructor("ZodMiniArray", (inst, def) => {
4675
+ $ZodArray.init(inst, def);
4676
+ ZodMiniType.init(inst, def);
4677
+ });
4678
+ // @__NO_SIDE_EFFECTS__
4679
+ function array(element, params) {
4680
+ return new ZodMiniArray({
4681
+ type: "array",
4682
+ element,
4683
+ ...normalizeParams(params)
4684
+ });
4685
+ }
4686
+ var ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
4687
+ $ZodObject.init(inst, def);
4688
+ ZodMiniType.init(inst, def);
4689
+ defineLazy(inst, "shape", () => def.shape);
4690
+ });
4691
+ // @__NO_SIDE_EFFECTS__
4692
+ function strictObject(shape, params) {
4693
+ return new ZodMiniObject({
4694
+ type: "object",
4695
+ shape,
4696
+ catchall: /* @__PURE__ */ never(),
4697
+ ...normalizeParams(params)
4698
+ });
4699
+ }
4700
+ var ZodMiniTuple = /* @__PURE__ */ $constructor("ZodMiniTuple", (inst, def) => {
4701
+ $ZodTuple.init(inst, def);
4702
+ ZodMiniType.init(inst, def);
4703
+ });
4704
+ // @__NO_SIDE_EFFECTS__
4705
+ function tuple(items, _paramsOrRest, _params) {
4706
+ const hasRest = _paramsOrRest instanceof $ZodType;
4707
+ const params = hasRest ? _params : _paramsOrRest;
4708
+ const rest = hasRest ? _paramsOrRest : null;
4709
+ return new ZodMiniTuple({
4710
+ type: "tuple",
4711
+ items,
4712
+ rest,
4713
+ ...normalizeParams(params)
4714
+ });
4715
+ }
4716
+ var ZodMiniRecord = /* @__PURE__ */ $constructor("ZodMiniRecord", (inst, def) => {
4717
+ $ZodRecord.init(inst, def);
4718
+ ZodMiniType.init(inst, def);
4719
+ });
4720
+ // @__NO_SIDE_EFFECTS__
4721
+ function record(keyType, valueType, params) {
4722
+ if (!valueType || !valueType._zod) {
4723
+ return new ZodMiniRecord({
4724
+ type: "record",
4725
+ keyType: /* @__PURE__ */ string2(),
4726
+ valueType: keyType,
4727
+ ...normalizeParams(valueType)
4827
4728
  });
4828
- } catch {
4829
- return invalid();
4830
4729
  }
4730
+ return new ZodMiniRecord({
4731
+ type: "record",
4732
+ keyType,
4733
+ valueType,
4734
+ ...normalizeParams(params)
4735
+ });
4736
+ }
4737
+ var ZodMiniEnum = /* @__PURE__ */ $constructor("ZodMiniEnum", (inst, def) => {
4738
+ $ZodEnum.init(inst, def);
4739
+ ZodMiniType.init(inst, def);
4740
+ inst.options = Object.values(def.entries);
4741
+ });
4742
+ // @__NO_SIDE_EFFECTS__
4743
+ function _enum(values, params) {
4744
+ const entries = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
4745
+ return new ZodMiniEnum({
4746
+ type: "enum",
4747
+ entries,
4748
+ ...normalizeParams(params)
4749
+ });
4750
+ }
4751
+ var ZodMiniLiteral = /* @__PURE__ */ $constructor("ZodMiniLiteral", (inst, def) => {
4752
+ $ZodLiteral.init(inst, def);
4753
+ ZodMiniType.init(inst, def);
4754
+ });
4755
+ // @__NO_SIDE_EFFECTS__
4756
+ function literal(value, params) {
4757
+ return new ZodMiniLiteral({
4758
+ type: "literal",
4759
+ values: Array.isArray(value) ? value : [value],
4760
+ ...normalizeParams(params)
4761
+ });
4762
+ }
4763
+ var ZodMiniOptional = /* @__PURE__ */ $constructor("ZodMiniOptional", (inst, def) => {
4764
+ $ZodOptional.init(inst, def);
4765
+ ZodMiniType.init(inst, def);
4766
+ });
4767
+ // @__NO_SIDE_EFFECTS__
4768
+ function optional(innerType) {
4769
+ return new ZodMiniOptional({
4770
+ type: "optional",
4771
+ innerType
4772
+ });
4773
+ }
4774
+ var ZodMiniPipe = /* @__PURE__ */ $constructor("ZodMiniPipe", (inst, def) => {
4775
+ $ZodPipe.init(inst, def);
4776
+ ZodMiniType.init(inst, def);
4777
+ });
4778
+ // @__NO_SIDE_EFFECTS__
4779
+ function pipe(in_, out) {
4780
+ return new ZodMiniPipe({
4781
+ type: "pipe",
4782
+ in: in_,
4783
+ out
4784
+ });
4785
+ }
4786
+ var ZodMiniCustom = /* @__PURE__ */ $constructor("ZodMiniCustom", (inst, def) => {
4787
+ $ZodCustom.init(inst, def);
4788
+ ZodMiniType.init(inst, def);
4789
+ });
4790
+ // @__NO_SIDE_EFFECTS__
4791
+ function refine(fn, _params = {}) {
4792
+ return _refine(ZodMiniCustom, fn, _params);
4793
+ }
4794
+ // @__NO_SIDE_EFFECTS__
4795
+ function superRefine(fn, params) {
4796
+ return _superRefine(fn, params);
4797
+ }
4798
+
4799
+ // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/checks.js
4800
+ init_core2();
4801
+
4802
+ // src/native-manifest-schema.ts
4803
+ init_manifest_schema();
4804
+
4805
+ // src/manifest-schema-mini.ts
4806
+ function mini(schema2) {
4807
+ return schema2;
4808
+ }
4809
+ var miniManifestSchemaOperations = {
4810
+ string: () => string2(),
4811
+ minLength: (schema2, minimum) => mini(schema2).check(_minLength(minimum)),
4812
+ maxLength: (schema2, maximum) => mini(schema2).check(_maxLength(maximum)),
4813
+ regex: (schema2, pattern, message) => mini(schema2).check(_regex(pattern, { message })),
4814
+ refine: (schema2, check, message) => mini(schema2).check(refine(check, message)),
4815
+ // Zod 4's number parser rejects NaN and infinities; Classic finite() is a no-op.
4816
+ number: (minimum, maximum) => number2().check(_gte(minimum), _lte(maximum)),
4817
+ unknown: () => unknown(),
4818
+ optional: (schema2) => optional(schema2),
4819
+ array: (schema2) => array(schema2),
4820
+ enum(values) {
4821
+ return _enum(values);
4822
+ },
4823
+ literal: (value) => literal(value),
4824
+ tuplePair(left, right) {
4825
+ return tuple([left, right]);
4826
+ },
4827
+ // ZodMiniObject uses the interpreted $ZodObject, not the Classic object JIT constructor.
4828
+ object(shape) {
4829
+ return strictObject(shape);
4830
+ },
4831
+ record(key, value) {
4832
+ return record(key, value);
4833
+ },
4834
+ superRefine: (schema2, check) => mini(schema2).check(superRefine(check)),
4835
+ pipe: (input, output) => pipe(input, output)
4836
+ };
4837
+
4838
+ // src/native-manifest-utf8.ts
4839
+ init_native_url_utf8();
4840
+ function nativeManifestUtf8ByteLength(input) {
4841
+ let bytes = 0;
4842
+ for (let index = 0; index < input.length; index++) {
4843
+ const value = input.charCodeAt(index);
4844
+ if (value <= 127) bytes++;
4845
+ else if (value <= 2047) bytes += 2;
4846
+ else if (value >= 55296 && value <= 56319 && input.charCodeAt(index + 1) >= 56320 && input.charCodeAt(index + 1) <= 57343) {
4847
+ bytes += 4;
4848
+ index++;
4849
+ } else bytes += 3;
4850
+ }
4851
+ return bytes;
4831
4852
  }
4832
- function snapshotRecord(input, keys) {
4833
- const snapshot = nativeOwnRecord(input);
4834
- if (!snapshot) return invalid();
4835
- const ownKeys = Reflect.ownKeys(input);
4836
- if (ownKeys.length !== Object.keys(snapshot).length || ownKeys.some((key) => typeof key !== "string" || !keys.includes(key)))
4837
- return invalid();
4838
- return snapshot;
4853
+ function decodeNativeManifestUtf8(input) {
4854
+ let remaining = 0;
4855
+ let lower = 128;
4856
+ let upper = 191;
4857
+ for (const byte of input) {
4858
+ if (remaining === 0) {
4859
+ if (byte <= 127) continue;
4860
+ if (byte >= 194 && byte <= 223) remaining = 1;
4861
+ else if (byte >= 224 && byte <= 239) {
4862
+ remaining = 2;
4863
+ if (byte === 224) lower = 160;
4864
+ if (byte === 237) upper = 159;
4865
+ } else if (byte >= 240 && byte <= 244) {
4866
+ remaining = 3;
4867
+ if (byte === 240) lower = 144;
4868
+ if (byte === 244) upper = 143;
4869
+ } else throw new TypeError("Invalid manifest UTF-8.");
4870
+ } else {
4871
+ if (byte < lower || byte > upper) throw new TypeError("Invalid manifest UTF-8.");
4872
+ remaining--;
4873
+ lower = 128;
4874
+ upper = 191;
4875
+ }
4876
+ }
4877
+ if (remaining) throw new TypeError("Invalid manifest UTF-8.");
4878
+ const parts = [];
4879
+ let offset = 0;
4880
+ while (offset < input.byteLength) {
4881
+ let end = Math.min(offset + nativeUrlCodecMaximumLength, input.byteLength);
4882
+ while (end < input.byteLength && (input[end] & 192) === 128) end--;
4883
+ parts.push(utf8DecodeWithoutBOM(input.subarray(offset, end)));
4884
+ offset = end;
4885
+ }
4886
+ const text = parts.join("");
4887
+ return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
4839
4888
  }
4840
- function snapshotView(input) {
4841
- if (input === void 0) return {};
4842
- const snapshot = snapshotRecord(input, viewKeys);
4843
- if (snapshot.center !== void 0) {
4844
- const center = snapshot.center;
4845
- if (!Array.isArray(center) || Object.getPrototypeOf(center) !== Array.prototype || Reflect.ownKeys(center).length !== 3 || Object.getOwnPropertyDescriptor(center, "length")?.value !== 2)
4846
- return invalid();
4847
- const longitude = Object.getOwnPropertyDescriptor(center, "0");
4848
- const latitude = Object.getOwnPropertyDescriptor(center, "1");
4849
- if (!longitude?.enumerable || !latitude?.enumerable || !("value" in longitude) || !("value" in latitude))
4850
- return invalid();
4851
- snapshot.center = [longitude.value, latitude.value];
4889
+ function hasBoundedNativeManifestDepth(text, maximumDepth) {
4890
+ let depth = 0;
4891
+ let quoted = false;
4892
+ let escaped = false;
4893
+ for (let index = 0; index < text.length; index++) {
4894
+ const character = text[index];
4895
+ if (quoted) {
4896
+ if (escaped) escaped = false;
4897
+ else if (character === "\\") escaped = true;
4898
+ else if (character === '"') quoted = false;
4899
+ } else if (character === '"') quoted = true;
4900
+ else if (character === "[" || character === "{") {
4901
+ if (++depth > maximumDepth) return false;
4902
+ } else if (character === "]" || character === "}") depth--;
4852
4903
  }
4853
- const parsed = parse(viewSchema, snapshot);
4854
- return {
4855
- ...parsed.bearing === void 0 ? {} : { bearing: parsed.bearing },
4856
- ...parsed.center === void 0 ? {} : { center: parsed.center },
4857
- ...parsed.pitch === void 0 ? {} : { pitch: parsed.pitch },
4858
- ...parsed.zoom === void 0 ? {} : { zoom: parsed.zoom }
4859
- };
4904
+ return true;
4860
4905
  }
4861
4906
 
4862
- // src/theme-blend.ts
4863
- var TileflowThemeBlendError = class extends Error {
4864
- constructor() {
4865
- super(...arguments);
4866
- __publicField(this, "name", "TileflowThemeBlendError");
4867
- }
4907
+ // src/native-manifest-schema.ts
4908
+ var schema = createTileflowRuntimeManifestSchema(
4909
+ {
4910
+ parseUrl: parseNativeUrl,
4911
+ utf8ByteLength: nativeManifestUtf8ByteLength
4912
+ },
4913
+ miniManifestSchemaOperations
4914
+ );
4915
+ function parseTileflowRuntimeManifest(input) {
4916
+ return parse(schema, input);
4917
+ }
4918
+
4919
+ // src/native-source-types.ts
4920
+ var messages = {
4921
+ NATIVE_SOURCE_INVALID: "Expected a valid native source or renderer-neutral view.",
4922
+ NATIVE_SOURCE_ABORTED: "The source operation was cancelled.",
4923
+ NATIVE_SOURCE_DISPOSED: "The source controller has been disposed.",
4924
+ NATIVE_MANIFEST_URL_INVALID: "Expected a safe absolute manifest URL and an exact development origin.",
4925
+ NATIVE_MANIFEST_REQUEST_FAILED: "Manifest acquisition failed.",
4926
+ NATIVE_MANIFEST_RESPONSE_INVALID: "Expected a bounded byte reader and an explicit final response URL.",
4927
+ NATIVE_MANIFEST_ACCESS_DENIED: "Manifest access was denied.",
4928
+ NATIVE_MANIFEST_NOT_FOUND: "The manifest was not found.",
4929
+ NATIVE_MANIFEST_TOO_LARGE: "The manifest exceeds the 1 MiB limit.",
4930
+ NATIVE_MANIFEST_UTF8_INVALID: "The manifest body is not valid UTF-8.",
4931
+ NATIVE_MANIFEST_JSON_INVALID: "The manifest body is not valid JSON.",
4932
+ NATIVE_MANIFEST_INVALID: "The manifest does not satisfy the version-1 runtime contract.",
4933
+ NATIVE_MANIFEST_RESOURCE_INVALID: "A manifest resource URL is outside the native URL policy.",
4934
+ NATIVE_MAP_NOT_FOUND: "The requested map is not declared by the manifest.",
4935
+ NATIVE_THEME_INVALID: "Expected a declared concrete theme or an explicitly supplied system color scheme."
4868
4936
  };
4869
- var BlendLeaf = class {
4870
- constructor(values) {
4871
- __publicField(this, "values", values);
4937
+ var TileflowNativeSourceError = class extends Error {
4938
+ constructor(code, field) {
4939
+ super(messages[code]);
4940
+ __publicField(this, "code");
4941
+ __publicField(this, "field");
4942
+ __publicField(this, "kind");
4943
+ this.name = "TileflowNativeSourceError";
4944
+ this.code = code;
4945
+ this.field = field;
4946
+ this.kind = code === "NATIVE_SOURCE_ABORTED" || code === "NATIVE_SOURCE_DISPOSED" ? "cancelled" : "terminal";
4872
4947
  }
4873
4948
  };
4874
- var imageProperties = /* @__PURE__ */ new Set([
4875
- "icon-image",
4876
- "fill-pattern",
4877
- "line-pattern",
4878
- "background-pattern",
4879
- "fill-extrusion-pattern"
4880
- ]);
4881
- var opacityProperties = {
4882
- background: "background-opacity",
4883
- circle: "circle-opacity",
4884
- fill: "fill-opacity",
4885
- "fill-extrusion": "fill-extrusion-opacity",
4886
- heatmap: "heatmap-opacity",
4887
- line: "line-opacity",
4888
- raster: "raster-opacity"
4889
- };
4890
- var featureOperators = /* @__PURE__ */ new Set([
4891
- "feature-state",
4892
- "geometry-type",
4893
- "get",
4894
- "has",
4895
- "id",
4896
- "in",
4897
- "properties"
4898
- ]);
4899
- var maximumBranches = 24;
4900
- function planTileflowThemeBlend(styles, options = {}) {
4901
- if (styles.length < 2)
4902
- throw new TileflowThemeBlendError("A theme blend needs two or more themes.");
4903
- const [base2] = styles;
4904
- assertSameStructure(styles, base2);
4905
- const mixedImages = new Set(imageProperties);
4906
- if (options.iconImages === "switch") mixedImages.delete("icon-image");
4907
- const paints = [];
4908
- const switches = [];
4909
- const imageGroups = collectImageNames(styles, base2, mixedImages);
4910
- const symbolLayers = /* @__PURE__ */ new Set();
4911
- const images = /* @__PURE__ */ new Map();
4912
- let splitLayers = 0;
4913
- let copiedLayers = 0;
4914
- let switchedLayers = 0;
4915
- const layers = [];
4916
- for (const [index, layer] of base2.layers.entries()) {
4917
- const variants = styles.map((style) => style.layers[index]);
4918
- layers.push(...planLayer(variants, String(layer.type)));
4919
- }
4920
- const switchesByLayer = /* @__PURE__ */ new Map();
4921
- for (const entry of switches) {
4922
- const list = switchesByLayer.get(entry.layer) ?? [];
4923
- list.push(entry);
4924
- switchesByLayer.set(entry.layer, list);
4925
- }
4926
- const sky = differingRoot(styles, "sky");
4927
- const light = differingRoot(styles, "light");
4928
- const metadata = alignInteractionManifest(base2.metadata, layers);
4929
- return {
4930
- themes: styles.length,
4931
- paints,
4932
- images: [...images.values()],
4933
- switches,
4934
- ...sky === void 0 ? {} : { sky },
4935
- ...light === void 0 ? {} : { light },
4936
- summary: { splitLayers, copiedLayers, switchedLayers, switchedValues: switches.length },
4937
- styleAt(position) {
4938
- const at = clampPosition(position, styles.length);
4939
- const dominant = Math.round(at);
4940
- const style = {
4941
- ...base2,
4942
- ...metadata === void 0 ? {} : { metadata },
4943
- // Symbols show the nearest theme, as the label cross-fade leaves them between changes.
4944
- layers: layers.map(
4945
- (layer) => fillLayer(
4946
- layer,
4947
- symbolLayers.has(String(layer.id)) ? dominant : at,
4948
- dominant,
4949
- switchesByLayer.get(String(layer.id))
4950
- )
4951
- )
4952
- };
4953
- if (sky !== void 0) style.sky = fillTemplate(sky, at);
4954
- if (light !== void 0) style.light = fillTemplate(light, at);
4955
- return style;
4949
+ function normalizeNativeSourceError(error2) {
4950
+ try {
4951
+ if (error2 instanceof TileflowNativeSourceError) {
4952
+ const code = Object.getOwnPropertyDescriptor(error2, "code")?.value;
4953
+ const field = Object.getOwnPropertyDescriptor(error2, "field")?.value;
4954
+ if (typeof code === "string" && Object.hasOwn(messages, code) && ["source", "signal", "manifestUrl", "response", "body", "map", "theme", "view"].includes(
4955
+ field
4956
+ )) {
4957
+ return new TileflowNativeSourceError(code, field);
4958
+ }
4956
4959
  }
4957
- };
4958
- function planLayer(variants, type) {
4959
- const [layer] = variants;
4960
- const id = String(layer.id);
4961
- const paint = { ...layer.paint };
4962
- const layout = { ...layer.layout };
4963
- const follows = type === "symbol" ? "dominant" : "position";
4964
- const ownPaints = [];
4965
- const ownSwitches = [];
4966
- const driven = [];
4967
- const paintOf = (variant) => variant.paint ?? {};
4968
- for (const group of ["layout", "paint"]) {
4969
- const target = group === "paint" ? paint : layout;
4970
- for (const property of propertyNames(variants, group)) {
4971
- const values = variants.map(
4972
- (variant) => variant[group]?.[property]
4973
- );
4974
- if (values.every((value) => same(value, values[0]))) continue;
4975
- if (mixedImages.has(property)) {
4976
- const tuples = imageTuples(values);
4977
- if (tuples && tuples.every((names) => imageGroups.get(names[0])?.size === 1)) {
4978
- for (const names of tuples) {
4979
- if (names.every((name) => name === names[0])) continue;
4980
- images.set(JSON.stringify(names), {
4981
- names,
4982
- follows: property === "icon-image" ? "dominant" : "position"
4983
- });
4984
- }
4985
- continue;
4986
- }
4987
- }
4988
- if (group === "layout" || values.includes(void 0) || imageProperties.has(property)) {
4989
- ownSwitches.push({ group, property, values });
4990
- continue;
4991
- }
4992
- if (values.some(isDataDriven)) {
4993
- driven.push(property);
4994
- continue;
4995
- }
4996
- const template = tryMixTemplate(values);
4997
- if (template === void 0) {
4998
- ownSwitches.push({ group, property, values });
4999
- continue;
5000
- }
5001
- ownPaints.push({ property, template });
5002
- target[property] = template;
4960
+ } catch {
4961
+ }
4962
+ return new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
4963
+ }
4964
+
4965
+ // src/native-source-cancellation.ts
4966
+ var NativeSourceCancellation = class {
4967
+ constructor() {
4968
+ __publicField(this, "cancelled", false);
4969
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
4970
+ }
4971
+ get aborted() {
4972
+ return this.cancelled;
4973
+ }
4974
+ cancel() {
4975
+ if (this.cancelled) return;
4976
+ this.cancelled = true;
4977
+ const listeners = [...this.listeners];
4978
+ this.listeners.clear();
4979
+ for (const listener of listeners) {
4980
+ try {
4981
+ listener();
4982
+ } catch {
5003
4983
  }
5004
4984
  }
5005
- const planned = { ...layer, paint, layout };
5006
- let results = [{ layer: planned, extra: [] }];
5007
- if (driven.length > 0) {
5008
- const split = type === "symbol" ? void 0 : splitLayer();
5009
- const copies = split ?? (type === "symbol" ? void 0 : copyLayer());
5010
- if (copies) results = copies;
5011
- else
5012
- for (const property of driven)
5013
- ownSwitches.push({
5014
- group: "paint",
5015
- property,
5016
- values: variants.map((variant) => paintOf(variant)[property])
5017
- });
5018
- }
5019
- if (options.featureSwitches === "layers" && results.length === 1 && ownSwitches.some((entry) => entry.values.some(isDataDriven)))
5020
- results = switchLayer();
5021
- for (const result of results) {
5022
- const resultId = String(result.layer.id);
5023
- if (type === "symbol") symbolLayers.add(resultId);
5024
- const overridden = new Set(result.extra.map((entry) => entry.property));
5025
- for (const entry of [
5026
- ...ownPaints.filter((own) => !overridden.has(own.property)),
5027
- ...result.extra
5028
- ])
5029
- paints.push({ layer: resultId, property: entry.property, template: entry.template, follows });
5030
- if (result.visibility)
5031
- switches.push({
5032
- layer: resultId,
5033
- group: "layout",
5034
- property: "visibility",
5035
- values: result.visibility
5036
- });
5037
- else for (const entry of ownSwitches) switches.push({ layer: resultId, ...entry });
4985
+ }
4986
+ check() {
4987
+ if (this.cancelled) throw new TileflowNativeSourceError("NATIVE_SOURCE_ABORTED", "signal");
4988
+ }
4989
+ onCancel(listener) {
4990
+ if (this.cancelled) {
4991
+ listener();
4992
+ return () => void 0;
5038
4993
  }
5039
- return results.map((result) => result.layer);
5040
- function switchLayer() {
5041
- switchedLayers += variants.length;
5042
- return variants.map((_, theme) => {
5043
- const copyPaint = { ...paint };
5044
- const copyLayout = { ...layout };
5045
- for (const entry of ownSwitches) {
5046
- const target = entry.group === "paint" ? copyPaint : copyLayout;
5047
- const value = entry.values[theme];
5048
- if (value === void 0) delete target[entry.property];
5049
- else target[entry.property] = value;
4994
+ this.listeners.add(listener);
4995
+ return () => {
4996
+ this.listeners.delete(listener);
4997
+ };
4998
+ }
4999
+ race(promise) {
5000
+ return new Promise((resolve, reject) => {
5001
+ const off = this.onCancel(
5002
+ () => reject(new TileflowNativeSourceError("NATIVE_SOURCE_ABORTED", "signal"))
5003
+ );
5004
+ promise.then(
5005
+ (value) => {
5006
+ off();
5007
+ resolve(value);
5008
+ },
5009
+ (error2) => {
5010
+ off();
5011
+ reject(error2);
5050
5012
  }
5051
- const shown = copyLayout.visibility === "none" ? "none" : "visible";
5052
- return {
5053
- layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint, layout: copyLayout },
5054
- extra: [],
5055
- visibility: variants.map((__, nearest) => nearest === theme ? shown : "none")
5056
- };
5057
- });
5058
- }
5059
- function splitLayer() {
5060
- if (isLegacyFilter(layer.filter)) return void 0;
5061
- const branches = splitBranches(
5062
- Object.fromEntries(
5063
- driven.map((property) => [
5064
- property,
5065
- variants.map((variant) => paintOf(variant)[property])
5066
- ])
5067
- )
5068
5013
  );
5069
- if (!branches) return void 0;
5070
- const groups = /* @__PURE__ */ new Map();
5071
- for (const branch of branches) {
5072
- const key = JSON.stringify(branch.valuesByProperty);
5073
- let group = groups.get(key);
5074
- if (!group) {
5075
- const templates = {};
5076
- for (const [property, values] of Object.entries(branch.valuesByProperty)) {
5077
- const template = tryMixTemplate(values);
5078
- if (template === void 0) return void 0;
5079
- templates[property] = template;
5080
- }
5081
- group = { templates, conditions: [] };
5082
- groups.set(key, group);
5014
+ });
5015
+ }
5016
+ link(signal) {
5017
+ if (signal === void 0) return () => void 0;
5018
+ let onAbort;
5019
+ try {
5020
+ if (!signal || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function" || typeof signal.removeEventListener !== "function")
5021
+ throw new Error();
5022
+ onAbort = () => this.cancel();
5023
+ if (signal.aborted) {
5024
+ this.cancel();
5025
+ return () => void 0;
5026
+ }
5027
+ signal.addEventListener("abort", onAbort, { once: true });
5028
+ if (signal.aborted) this.cancel();
5029
+ } catch {
5030
+ if (onAbort) {
5031
+ try {
5032
+ signal.removeEventListener("abort", onAbort);
5033
+ } catch {
5083
5034
  }
5084
- group.conditions.push(
5085
- branch.conditions.length === 1 ? branch.conditions[0] : ["all", ...branch.conditions]
5086
- );
5087
5035
  }
5088
- splitLayers += groups.size;
5089
- return [...groups.values()].map((group, number3) => {
5090
- const chosen = group.conditions.length === 1 ? group.conditions[0] : ["any", ...group.conditions];
5091
- return {
5092
- layer: {
5093
- ...planned,
5094
- id: `${id}::branch-${number3}`,
5095
- filter: layer.filter === void 0 ? chosen : ["all", layer.filter, chosen],
5096
- paint: { ...paint, ...group.templates }
5097
- },
5098
- extra: Object.entries(group.templates).filter(([, template]) => containsLeaf(template)).map(([property, template]) => ({ property, template }))
5099
- };
5100
- });
5101
- }
5102
- function copyLayer() {
5103
- const opacity = opacityProperties[type];
5104
- if (!opacity || variants.some((variant) => isDataDriven(paintOf(variant)[opacity])))
5105
- return void 0;
5106
- copiedLayers += variants.length;
5107
- return variants.map((variant, theme) => {
5108
- const weight = new BlendLeaf(variants.map((_, other) => other === theme ? 1 : 0));
5109
- const template = scaleByWeight(paint[opacity] ?? 1, weight);
5110
- const copyPaint = { ...paint, [opacity]: template };
5111
- for (const property of driven) copyPaint[property] = paintOf(variant)[property];
5112
- return {
5113
- layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint },
5114
- extra: [{ property: opacity, template }]
5115
- };
5116
- });
5117
- }
5118
- }
5119
- }
5120
- function fillTileflowThemeBlendTemplate(template, position) {
5121
- return fillTemplate(template, position);
5122
- }
5123
- function fillLayer(layer, position, dominant, layerSwitches) {
5124
- const filled = { ...layer };
5125
- for (const group of ["layout", "paint"]) {
5126
- const values = layer[group];
5127
- if (!values) continue;
5128
- const out = {};
5129
- for (const [key, value] of Object.entries(values)) out[key] = fillTemplate(value, position);
5130
- for (const entry of layerSwitches ?? []) {
5131
- if (entry.group !== group) continue;
5132
- const value = entry.values[dominant];
5133
- if (value === void 0) delete out[entry.property];
5134
- else out[entry.property] = value;
5036
+ throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "signal");
5135
5037
  }
5136
- if (Object.keys(out).length > 0) filled[group] = out;
5137
- else delete filled[group];
5038
+ return () => {
5039
+ try {
5040
+ signal.removeEventListener("abort", onAbort);
5041
+ } catch {
5042
+ }
5043
+ };
5138
5044
  }
5139
- return filled;
5140
- }
5141
- function fillTemplate(node, position) {
5142
- if (node instanceof BlendLeaf) return mixLeaf(node.values, position);
5143
- if (Array.isArray(node)) return node.map((item) => fillTemplate(item, position));
5144
- if (node && typeof node === "object" && containsLeaf(node)) {
5145
- return Object.fromEntries(
5146
- Object.entries(node).map(([key, value]) => [key, fillTemplate(value, position)])
5147
- );
5045
+ };
5046
+ function cancelNativeOperation(cancel) {
5047
+ try {
5048
+ void Promise.resolve(cancel()).catch(() => void 0);
5049
+ } catch {
5148
5050
  }
5149
- return node;
5150
- }
5151
- function containsLeaf(node) {
5152
- if (node instanceof BlendLeaf) return true;
5153
- if (Array.isArray(node)) return node.some(containsLeaf);
5154
- if (node && typeof node === "object") return Object.values(node).some(containsLeaf);
5155
- return false;
5156
- }
5157
- function mixLeaf(values, position) {
5158
- const at = clampPosition(position, values.length);
5159
- const index = Math.min(values.length - 2, Math.floor(at));
5160
- const t = at - index;
5161
- const from = values[index];
5162
- const to = values[index + 1];
5163
- if (t <= 0 || from === to) return from;
5164
- if (t >= 1) return to;
5165
- if (typeof from === "number" && typeof to === "number") return from + (to - from) * t;
5166
- const a = oklab(String(from));
5167
- const b = oklab(String(to));
5168
- if (!a || !b) return t < 0.5 ? from : to;
5169
- return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
5170
- }
5171
- function clampPosition(position, themes) {
5172
- if (!Number.isFinite(position)) return 0;
5173
- return Math.min(themes - 1, Math.max(0, position));
5174
5051
  }
5175
- function assertSameStructure(styles, base2) {
5176
- const ids = (style) => style.layers.map((layer) => layer.id);
5177
- for (const style of styles) {
5178
- if (!same(ids(style), ids(base2)))
5179
- throw new TileflowThemeBlendError(
5180
- "Blended themes must have the same layers in the same order."
5181
- );
5182
- if (!same(style.sources, base2.sources))
5183
- throw new TileflowThemeBlendError("Blended themes must use the same sources.");
5184
- if (!same(style.sprite, base2.sprite) || !same(style.glyphs, base2.glyphs))
5185
- throw new TileflowThemeBlendError("Blended themes must use the same sprite and glyphs.");
5186
- for (const [index, layer] of style.layers.entries()) {
5187
- const other = base2.layers[index];
5188
- for (const key of ["type", "source", "source-layer", "filter", "minzoom", "maxzoom"])
5189
- if (!same(layer[key], other[key]))
5190
- throw new TileflowThemeBlendError(
5191
- `Blended themes differ in ${key} of layer ${JSON.stringify(layer.id)}.`
5192
- );
5052
+
5053
+ // src/native-source-utils.ts
5054
+ function nativeOwnRecord(value) {
5055
+ try {
5056
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
5057
+ const prototype = Object.getPrototypeOf(value);
5058
+ if (prototype !== null && prototype !== Object.prototype) return void 0;
5059
+ const result = /* @__PURE__ */ Object.create(null);
5060
+ for (const key of Object.keys(value)) {
5061
+ const property = Object.getOwnPropertyDescriptor(value, key);
5062
+ if (!property || !("value" in property)) return void 0;
5063
+ result[key] = property.value;
5193
5064
  }
5065
+ return result;
5066
+ } catch {
5067
+ return void 0;
5194
5068
  }
5195
5069
  }
5196
- function propertyNames(variants, group) {
5197
- const names = /* @__PURE__ */ new Set();
5198
- for (const variant of variants)
5199
- for (const name of Object.keys(variant[group] ?? {})) names.add(name);
5200
- return [...names];
5201
- }
5202
- function differingRoot(styles, key) {
5203
- const values = styles.map((style) => style[key]);
5204
- if (values.every((value) => same(value, values[0]))) return void 0;
5205
- return tryMixTemplate(values);
5070
+ function freezeNativeSnapshot(value) {
5071
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
5072
+ for (const child of Object.values(value)) freezeNativeSnapshot(child);
5073
+ Object.freeze(value);
5074
+ }
5075
+ return value;
5206
5076
  }
5207
- function collectImageNames(styles, base2, properties) {
5208
- const uses = /* @__PURE__ */ new Map();
5209
- for (const [index] of base2.layers.entries()) {
5210
- for (const group of ["layout", "paint"]) {
5211
- for (const property of properties) {
5212
- const values = styles.map(
5213
- (style) => style.layers[index]?.[group]?.[property]
5214
- );
5215
- if (values[0] === void 0) continue;
5216
- for (const names of imageTuples(values) ?? []) {
5217
- const set = uses.get(names[0]) ?? /* @__PURE__ */ new Set();
5218
- set.add(JSON.stringify(names));
5219
- uses.set(names[0], set);
5220
- }
5221
- }
5222
- }
5077
+
5078
+ // src/native-manifest.ts
5079
+ var tileflowNativeManifestLimits = Object.freeze({
5080
+ maximumBytes: tileflowRuntimeManifestLimits.maximumBytes,
5081
+ maximumChunkBytes: 64 * 1024,
5082
+ maximumDepth: 64
5083
+ });
5084
+ async function loadTileflowNativeManifest(manifestUrl, options) {
5085
+ const input = nativeOwnRecord(options);
5086
+ if (!input || typeof input.acquire !== "function")
5087
+ throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
5088
+ const cancellation = new NativeSourceCancellation();
5089
+ const detach = cancellation.link(input.signal);
5090
+ try {
5091
+ return await loadNativeManifest(
5092
+ manifestUrl,
5093
+ input.acquire,
5094
+ {
5095
+ developmentOrigin: input.developmentOrigin
5096
+ },
5097
+ cancellation
5098
+ );
5099
+ } finally {
5100
+ detach();
5223
5101
  }
5224
- return uses;
5225
5102
  }
5226
- function alignInteractionManifest(metadata, layers) {
5227
- if (!isRecord(metadata)) return void 0;
5228
- const planned = /* @__PURE__ */ new Map();
5229
- for (const [index, layer] of layers.entries()) {
5230
- const id = String(layer.id);
5231
- const original = /^(.+)::(?:branch|theme)-\d+$/u.exec(id)?.[1] ?? id;
5232
- planned.set(original, [...planned.get(original) ?? [], { id, index }]);
5103
+ async function loadNativeManifest(manifestUrl, acquire, network, cancellation) {
5104
+ cancellation.check();
5105
+ let requestUrl;
5106
+ try {
5107
+ requestUrl = resolveTileflowNativeManifestUrl(manifestUrl, network);
5108
+ } catch {
5109
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_URL_INVALID", "manifestUrl");
5233
5110
  }
5234
- let aligned;
5235
- const interactions = metadata["tileflow:interaction-manifest"];
5236
- const domains = isRecord(interactions) ? interactions.domains : void 0;
5237
- if (isRecord(interactions) && isRecord(domains) && isRecord(domains.poi)) {
5238
- const poi = domains.poi;
5239
- if (Array.isArray(poi.layers)) {
5240
- const poiLayers = poi.layers.flatMap((entry) => {
5241
- if (!isRecord(entry) || typeof entry.layerId !== "string") return [entry];
5242
- const copies = planned.get(entry.layerId);
5243
- if (!copies) return [entry];
5244
- return copies.map(({ id, index }) => ({ ...entry, layerId: id, priority: index }));
5245
- });
5246
- aligned = {
5247
- ...metadata,
5248
- "tileflow:interaction-manifest": {
5249
- ...interactions,
5250
- domains: { ...domains, poi: { ...poi, layers: poiLayers } }
5251
- }
5252
- };
5111
+ let operation;
5112
+ try {
5113
+ operation = acquire(
5114
+ requestUrl,
5115
+ Object.freeze({ maximumBytes: tileflowNativeManifestLimits.maximumBytes })
5116
+ );
5117
+ if (!operation || typeof operation.cancel !== "function" || !operation.response || typeof operation.response.then !== "function") {
5118
+ throw new Error();
5253
5119
  }
5120
+ } catch {
5121
+ if (operation) cancelNativeOperation(() => operation.cancel());
5122
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
5254
5123
  }
5255
- const overlays = metadata["tileflow:overlay-placement-manifest"];
5256
- if (isRecord(overlays) && isRecord(overlays.anchors)) {
5257
- const anchors = Object.fromEntries(
5258
- Object.entries(overlays.anchors).map(([placement, anchor]) => [
5259
- placement,
5260
- typeof anchor === "string" ? planned.get(anchor)?.[0]?.id ?? anchor : anchor
5261
- ])
5124
+ let reader;
5125
+ let readerCancelled = false;
5126
+ let operationCancelled = false;
5127
+ const cancelReader = () => {
5128
+ if (!reader || readerCancelled) return;
5129
+ readerCancelled = true;
5130
+ cancelNativeOperation(() => reader.cancel());
5131
+ };
5132
+ const cleanup = () => {
5133
+ if (!operationCancelled) {
5134
+ operationCancelled = true;
5135
+ cancelNativeOperation(() => operation.cancel());
5136
+ }
5137
+ cancelReader();
5138
+ };
5139
+ const unlisten = cancellation.onCancel(cleanup);
5140
+ try {
5141
+ const headers = Promise.resolve(operation.response).then(
5142
+ (response2) => {
5143
+ try {
5144
+ if (!response2 || !response2.reader || typeof response2.reader.read !== "function" || typeof response2.reader.cancel !== "function") {
5145
+ throw new Error();
5146
+ }
5147
+ reader = response2.reader;
5148
+ } catch {
5149
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
5150
+ }
5151
+ if (cancellation.aborted) cancelReader();
5152
+ cancellation.check();
5153
+ return response2;
5154
+ },
5155
+ () => {
5156
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
5157
+ }
5262
5158
  );
5263
- aligned = {
5264
- ...aligned ?? metadata,
5265
- "tileflow:overlay-placement-manifest": { ...overlays, anchors }
5159
+ const response = await cancellation.race(headers);
5160
+ cancellation.check();
5161
+ let finalUrl;
5162
+ try {
5163
+ finalUrl = resolveTileflowNativeManifestUrl(response.url, network);
5164
+ } catch {
5165
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_URL_INVALID", "manifestUrl");
5166
+ }
5167
+ if (!Number.isInteger(response.status) || response.status < 100 || response.status > 599) {
5168
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
5169
+ }
5170
+ if (response.status === 401 || response.status === 403)
5171
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_ACCESS_DENIED", "response");
5172
+ if (response.status === 404)
5173
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_NOT_FOUND", "response");
5174
+ if (response.status < 200 || response.status >= 300)
5175
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
5176
+ const buffer = new Uint8Array(tileflowNativeManifestLimits.maximumBytes);
5177
+ let length = 0;
5178
+ while (true) {
5179
+ cancellation.check();
5180
+ const allowance = Math.min(
5181
+ tileflowNativeManifestLimits.maximumChunkBytes,
5182
+ buffer.byteLength - length + 1
5183
+ );
5184
+ let next;
5185
+ try {
5186
+ const pending = Promise.resolve(reader.read(allowance)).catch(() => {
5187
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
5188
+ });
5189
+ next = await cancellation.race(pending);
5190
+ } catch {
5191
+ cancellation.check();
5192
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_REQUEST_FAILED", "response");
5193
+ }
5194
+ cancellation.check();
5195
+ if (!next || typeof next.done !== "boolean")
5196
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
5197
+ if (next.done) {
5198
+ if (next.value !== void 0)
5199
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
5200
+ break;
5201
+ }
5202
+ if (!(next.value instanceof Uint8Array) || next.value.byteLength === 0)
5203
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
5204
+ if (next.value.byteLength > buffer.byteLength - length)
5205
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_TOO_LARGE", "body");
5206
+ if (next.value.byteLength > allowance)
5207
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESPONSE_INVALID", "response");
5208
+ buffer.set(next.value, length);
5209
+ length += next.value.byteLength;
5210
+ }
5211
+ let text;
5212
+ try {
5213
+ text = decodeNativeManifestUtf8(buffer.subarray(0, length));
5214
+ } catch {
5215
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_UTF8_INVALID", "body");
5216
+ }
5217
+ if (!hasBoundedNativeManifestDepth(text, tileflowNativeManifestLimits.maximumDepth)) {
5218
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_INVALID", "body");
5219
+ }
5220
+ let json;
5221
+ try {
5222
+ json = JSON.parse(text);
5223
+ } catch {
5224
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_JSON_INVALID", "body");
5225
+ }
5226
+ let manifest;
5227
+ try {
5228
+ manifest = parseTileflowRuntimeManifest(json);
5229
+ } catch {
5230
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_INVALID", "body");
5231
+ }
5232
+ let resolvedBytes = nativeManifestUtf8ByteLength(JSON.stringify(manifest));
5233
+ const resolveResource = (value, owner) => {
5234
+ const resolved = resolveTileflowNativeResourceUrl(value, { documentUrl: owner, ...network });
5235
+ resolvedBytes += nativeManifestUtf8ByteLength(JSON.stringify(resolved)) - nativeManifestUtf8ByteLength(JSON.stringify(value));
5236
+ if (resolvedBytes > tileflowNativeManifestLimits.maximumBytes) {
5237
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_TOO_LARGE", "body");
5238
+ }
5239
+ return resolved;
5266
5240
  };
5241
+ try {
5242
+ for (const name of Object.keys(manifest.maps).sort()) {
5243
+ const map2 = manifest.maps[name];
5244
+ for (const themeName of Object.keys(map2.themes).sort()) {
5245
+ const theme = map2.themes[themeName];
5246
+ theme.styleUrl = resolveResource(theme.styleUrl, finalUrl);
5247
+ for (const face of theme.fontFaces ?? []) {
5248
+ face.source = resolveResource(face.source, theme.styleUrl);
5249
+ }
5250
+ }
5251
+ }
5252
+ } catch (error2) {
5253
+ if (error2 instanceof TileflowNativeSourceError) throw normalizeNativeSourceError(error2);
5254
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_RESOURCE_INVALID", "body");
5255
+ }
5256
+ cancellation.check();
5257
+ return freezeNativeSnapshot({ manifestUrl: finalUrl, manifest });
5258
+ } catch (error2) {
5259
+ cleanup();
5260
+ cancellation.check();
5261
+ throw normalizeNativeSourceError(error2);
5262
+ } finally {
5263
+ unlisten();
5267
5264
  }
5268
- return aligned;
5269
5265
  }
5270
- var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
5271
- function imageTuples(values, out = []) {
5272
- const [first] = values;
5273
- if (values.every((value) => typeof value === "string")) {
5274
- out.push(values);
5275
- return out;
5276
- }
5277
- if (!Array.isArray(first) || typeof first[0] !== "string") {
5278
- return values.every((value) => same(value, first)) ? out : void 0;
5279
- }
5280
- const operator = first[0];
5281
- if (!values.every(
5282
- (value) => Array.isArray(value) && value[0] === operator && value.length === first.length
5283
- )) {
5284
- return void 0;
5285
- }
5286
- const at = (index) => values.map((value) => value[index]);
5287
- const sameAt = (index) => at(index).every((value) => same(value, first[index]));
5288
- const outputs = [];
5289
- if (operator === "literal" || operator === "image") outputs.push(1);
5290
- else if (operator === "coalesce")
5291
- for (let index = 1; index < first.length; index += 1) outputs.push(index);
5292
- else if (operator === "match") {
5293
- if (!sameAt(1)) return void 0;
5294
- for (let index = 2; index < first.length - 1; index += 2) {
5295
- if (!sameAt(index)) return void 0;
5296
- outputs.push(index + 1);
5297
- }
5298
- outputs.push(first.length - 1);
5299
- } else if (operator === "case") {
5300
- for (let index = 1; index < first.length - 1; index += 2) {
5301
- if (!sameAt(index)) return void 0;
5302
- outputs.push(index + 1);
5266
+
5267
+ // src/native-source-controller.ts
5268
+ init_portable_identity_rules();
5269
+
5270
+ // src/runtime.ts
5271
+ init_portable_identity_rules();
5272
+ var defaultTileflowRuntimeView = Object.freeze({
5273
+ bearing: 0,
5274
+ center: [0, 20],
5275
+ pitch: 0,
5276
+ zoom: 2
5277
+ });
5278
+ var tileflowStyleFontFaceLimits = Object.freeze({
5279
+ maximumCount: 16,
5280
+ maximumSourceLength: 2048
5281
+ });
5282
+ var maxStaticImagePixels = 1280 * 1280;
5283
+ var maximumManifestBytes = 1024 * 1024;
5284
+ var maximumSessionGrantBytes = 64 * 1024;
5285
+ var manifestCache = new globalThis.Map();
5286
+ function validateTileflowThemeSelection(value) {
5287
+ return value === "system" || isTileflowThemeName(value);
5288
+ }
5289
+ var tileflowThemeBlendLimits = Object.freeze({ maximumThemes: 8, minimumThemes: 2 });
5290
+ function validateTileflowThemeBlendSelection(value) {
5291
+ if (!value || typeof value !== "object") return false;
5292
+ const { position, themes } = value;
5293
+ 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;
5294
+ }
5295
+ function nearestTileflowBlendTheme(blend) {
5296
+ const index = Math.min(blend.themes.length - 1, Math.max(0, Math.round(blend.position)));
5297
+ return blend.themes[index];
5298
+ }
5299
+ function resolveTileflowRuntimeTheme(map2, selection, colorScheme) {
5300
+ let name;
5301
+ if (selection === void 0) {
5302
+ name = map2.defaultTheme;
5303
+ } else if (selection === "system") {
5304
+ if (!map2.systemThemes) {
5305
+ throw new Error(
5306
+ `Tileflow map "${map2.name}" does not declare systemThemes; select a concrete theme.`
5307
+ );
5303
5308
  }
5304
- outputs.push(first.length - 1);
5305
- } else if (operator === "step") {
5306
- if (!sameAt(1)) return void 0;
5307
- outputs.push(2);
5308
- for (let index = 3; index < first.length; index += 2) {
5309
- if (!sameAt(index)) return void 0;
5310
- outputs.push(index + 1);
5309
+ if (!colorScheme) {
5310
+ throw new Error('Resolving theme="system" requires an explicit browser color scheme.');
5311
5311
  }
5312
+ name = map2.systemThemes[colorScheme];
5312
5313
  } else {
5313
- return values.every((value) => same(value, first)) ? out : void 0;
5314
+ name = selection;
5314
5315
  }
5315
- for (const index of outputs) if (!imageTuples(at(index), out)) return void 0;
5316
- return out;
5317
- }
5318
- function tryMixTemplate(values) {
5319
- const [first] = values;
5320
- if (values.every((value) => same(value, first))) return first;
5321
- if (Array.isArray(first) && first[0] !== "literal" && values.every((value) => Array.isArray(value) && value.length === first.length)) {
5322
- const items = [];
5323
- for (let index = 0; index < first.length; index += 1) {
5324
- const item = tryMixTemplate(values.map((value) => value[index]));
5325
- if (item === void 0 && first[index] !== void 0) return void 0;
5326
- items.push(item);
5327
- }
5328
- return items;
5316
+ if (!isTileflowThemeName(name)) {
5317
+ throw new Error(
5318
+ `Tileflow resolved invalid theme ${JSON.stringify(name)} for map "${map2.name}"; expected a concrete portable theme name.`
5319
+ );
5329
5320
  }
5330
- if (first && typeof first === "object" && !Array.isArray(first)) {
5331
- const keys = Object.keys(first);
5332
- if (!values.every((value) => value && typeof value === "object" && same(Object.keys(value), keys)))
5333
- return void 0;
5334
- const out = {};
5335
- for (const key of keys) {
5336
- const item = tryMixTemplate(values.map((value) => value[key]));
5337
- if (item === void 0) return void 0;
5338
- out[key] = item;
5339
- }
5340
- return out;
5321
+ const theme = Object.hasOwn(map2.themes, name) ? map2.themes[name] : void 0;
5322
+ if (!theme) {
5323
+ const available = Object.keys(map2.themes).sort().join(", ");
5324
+ throw new Error(
5325
+ `Unknown Tileflow theme "${name}" for map "${map2.name}". Available themes: ${available}.`
5326
+ );
5341
5327
  }
5342
- if (values.every((value) => typeof value === "number")) return new BlendLeaf(values);
5343
- if (values.every((value) => typeof value === "string" && oklab(value)))
5344
- return new BlendLeaf(values);
5345
- return void 0;
5328
+ return { ...theme, name };
5346
5329
  }
5347
- function isDataDriven(value) {
5348
- if (!Array.isArray(value)) return false;
5349
- if (typeof value[0] === "string" && featureOperators.has(value[0])) return true;
5350
- return value.some(isDataDriven);
5330
+ function resolveTileflowRuntimeView(options) {
5331
+ const manifestView = options.manifestMap?.view;
5332
+ return {
5333
+ ...options.fallback ?? defaultTileflowRuntimeView,
5334
+ ...manifestView ?? {},
5335
+ ...options.bearing !== void 0 ? { bearing: options.bearing } : {},
5336
+ ...options.center !== void 0 ? { center: options.center } : {},
5337
+ ...options.pitch !== void 0 ? { pitch: options.pitch } : {},
5338
+ ...options.zoom !== void 0 ? { zoom: options.zoom } : {}
5339
+ };
5351
5340
  }
5352
- var reads = (value, operator) => Array.isArray(value) && (value[0] === operator || value.some((item) => reads(item, operator)));
5353
- function decisionOf(node) {
5354
- if (!Array.isArray(node) || typeof node[0] !== "string") return void 0;
5355
- const [operator] = node;
5356
- if (operator === "match" && isDataDriven(node[1])) {
5357
- const labels = [];
5358
- const outputs = [];
5359
- for (let index = 2; index < node.length - 1; index += 2) {
5360
- labels.push(node[index]);
5361
- outputs.push(node[index + 1]);
5341
+
5342
+ // src/native-source-controller.ts
5343
+ function createTileflowNativeSourceController(options) {
5344
+ const input = nativeOwnRecord(options);
5345
+ if (!input || typeof input.acquire !== "function")
5346
+ throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
5347
+ const acquire = input.acquire;
5348
+ const listeners = /* @__PURE__ */ new Set();
5349
+ let state;
5350
+ let generation = 0;
5351
+ let disposed = false;
5352
+ let active;
5353
+ const notify = (listener, value) => {
5354
+ try {
5355
+ void Promise.resolve(listener(value)).catch(() => void 0);
5356
+ } catch {
5362
5357
  }
5363
- outputs.push(node.at(-1));
5364
- return {
5365
- index: ["match", node[1], ...labels.flatMap((label, index) => [label, index]), labels.length],
5366
- outputs
5367
- };
5368
- }
5369
- if (operator === "case") {
5370
- const conditions = [];
5371
- const outputs = [];
5372
- for (let index = 1; index < node.length - 1; index += 2) {
5373
- conditions.push(node[index]);
5374
- outputs.push(node[index + 1]);
5358
+ };
5359
+ const publish = (next) => {
5360
+ if (disposed || next.generation !== generation) return;
5361
+ state = freezeNativeSnapshot(next);
5362
+ for (const listener of [...listeners]) {
5363
+ if (disposed || state !== next || generation !== next.generation) break;
5364
+ if (listeners.has(listener)) notify(listener, next);
5375
5365
  }
5376
- outputs.push(node.at(-1));
5377
- if (!conditions.some(isDataDriven)) return void 0;
5378
- return {
5379
- index: [
5380
- "case",
5381
- ...conditions.flatMap((condition, index) => [condition, index]),
5382
- conditions.length
5383
- ],
5384
- outputs
5385
- };
5386
- }
5387
- if (operator === "step" && isDataDriven(node[1])) {
5388
- const outputs = [node[2]];
5389
- const stops = [];
5390
- for (let index = 3; index < node.length; index += 2) {
5391
- stops.push(node[index]);
5392
- outputs.push(node[index + 1]);
5366
+ };
5367
+ return {
5368
+ get state() {
5369
+ return state;
5370
+ },
5371
+ subscribe(listener) {
5372
+ if (typeof listener !== "function")
5373
+ throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
5374
+ if (disposed) return () => void 0;
5375
+ listeners.add(listener);
5376
+ if (state) notify(listener, state);
5377
+ return () => {
5378
+ listeners.delete(listener);
5379
+ };
5380
+ },
5381
+ async replace(source, options2 = {}) {
5382
+ if (disposed) throw new TileflowNativeSourceError("NATIVE_SOURCE_DISPOSED", "source");
5383
+ if (generation >= Number.MAX_SAFE_INTEGER)
5384
+ throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
5385
+ const own = { generation: ++generation, cancellation: new NativeSourceCancellation() };
5386
+ const previous = active;
5387
+ active = own;
5388
+ const current = () => !disposed && generation === own.generation;
5389
+ let sourceInput;
5390
+ let selection;
5391
+ let snapshotError;
5392
+ try {
5393
+ sourceInput = nativeOwnRecord(source);
5394
+ selection = nativeOwnRecord(options2);
5395
+ } catch (error2) {
5396
+ snapshotError = normalizeNativeSourceError(error2);
5397
+ }
5398
+ previous?.cancellation.cancel();
5399
+ let detach = () => void 0;
5400
+ try {
5401
+ if (!current()) return;
5402
+ publish({ status: "loading", generation: own.generation });
5403
+ if (!current()) return;
5404
+ if (snapshotError) throw snapshotError;
5405
+ if (!sourceInput || !selection || Object.hasOwn(sourceInput, "kind") || Object.hasOwn(sourceInput, "style") || !isTileflowPortableId(sourceInput.map))
5406
+ throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "source");
5407
+ if (selection.theme !== void 0 && !validateTileflowThemeSelection(selection.theme) || selection.colorScheme !== void 0 && selection.colorScheme !== "light" && selection.colorScheme !== "dark") {
5408
+ throw new TileflowNativeSourceError("NATIVE_THEME_INVALID", "theme");
5409
+ }
5410
+ const selectedSource = {
5411
+ map: sourceInput.map,
5412
+ manifestUrl: sourceInput.manifestUrl
5413
+ };
5414
+ if (sourceInput.developmentOrigin !== void 0) {
5415
+ try {
5416
+ const developmentOrigin = sourceInput.developmentOrigin;
5417
+ selectedSource.developmentOrigin = resolveTileflowNativeManifestUrl(developmentOrigin, {
5418
+ developmentOrigin
5419
+ }).slice(0, -1);
5420
+ } catch {
5421
+ throw new TileflowNativeSourceError("NATIVE_MANIFEST_URL_INVALID", "manifestUrl");
5422
+ }
5423
+ }
5424
+ detach = own.cancellation.link(selection.signal);
5425
+ const result = await loadNativeManifest(
5426
+ selectedSource.manifestUrl,
5427
+ acquire,
5428
+ {
5429
+ developmentOrigin: selectedSource.developmentOrigin
5430
+ },
5431
+ own.cancellation
5432
+ );
5433
+ if (!current()) return;
5434
+ if (!Object.hasOwn(result.manifest.maps, selectedSource.map)) {
5435
+ throw new TileflowNativeSourceError("NATIVE_MAP_NOT_FOUND", "map");
5436
+ }
5437
+ const entry = result.manifest.maps[selectedSource.map];
5438
+ const apiUrl = entry.apiUrl ?? result.manifest.apiUrl;
5439
+ const map2 = {
5440
+ ...entry,
5441
+ name: selectedSource.map,
5442
+ ...apiUrl === void 0 ? {} : { apiUrl }
5443
+ };
5444
+ let theme;
5445
+ try {
5446
+ theme = resolveTileflowRuntimeTheme(
5447
+ map2,
5448
+ selection.theme,
5449
+ selection.colorScheme
5450
+ );
5451
+ } catch {
5452
+ throw new TileflowNativeSourceError("NATIVE_THEME_INVALID", "theme");
5453
+ }
5454
+ publish({
5455
+ status: "ready",
5456
+ generation: own.generation,
5457
+ source: selectedSource,
5458
+ ...result,
5459
+ map: map2,
5460
+ theme
5461
+ });
5462
+ } catch (error2) {
5463
+ if (current())
5464
+ publish({
5465
+ status: "error",
5466
+ generation: own.generation,
5467
+ error: normalizeNativeSourceError(error2)
5468
+ });
5469
+ } finally {
5470
+ detach();
5471
+ if (active === own) active = void 0;
5472
+ }
5473
+ },
5474
+ dispose() {
5475
+ if (disposed) return;
5476
+ disposed = true;
5477
+ listeners.clear();
5478
+ const previous = active;
5479
+ active = void 0;
5480
+ previous?.cancellation.cancel();
5393
5481
  }
5394
- return {
5395
- index: ["step", node[1], 0, ...stops.flatMap((stop, index) => [stop, index + 1])],
5396
- outputs
5397
- };
5398
- }
5399
- return void 0;
5400
- }
5401
- function firstDecision(value) {
5402
- const decision = decisionOf(value);
5403
- if (decision) return decision;
5404
- if (!Array.isArray(value) || value[0] === "literal") return void 0;
5405
- for (const item of value) {
5406
- const found = firstDecision(item);
5407
- if (found) return found;
5408
- }
5409
- return void 0;
5410
- }
5411
- function choose(value, key, branch) {
5412
- const decision = decisionOf(value);
5413
- if (decision && JSON.stringify(decision.index) === key)
5414
- return choose(decision.outputs[branch], key, branch);
5415
- if (!Array.isArray(value) || value[0] === "literal") return value;
5416
- return value.map((item) => choose(item, key, branch));
5417
- }
5418
- function splitBranches(valuesByProperty, conditions = []) {
5419
- let decision;
5420
- for (const values of Object.values(valuesByProperty)) {
5421
- decision = firstDecision(values[0]);
5422
- if (decision) break;
5423
- }
5424
- if (!decision) {
5425
- const residual = Object.values(valuesByProperty).some((values) => values.some(isDataDriven));
5426
- return residual ? void 0 : [{ conditions, valuesByProperty }];
5427
- }
5428
- if (reads(decision.index, "zoom") || reads(decision.index, "feature-state")) return void 0;
5429
- const key = JSON.stringify(decision.index);
5430
- const branches = [];
5431
- for (let branch = 0; branch < decision.outputs.length; branch += 1) {
5432
- const chosen = Object.fromEntries(
5433
- Object.entries(valuesByProperty).map(([property, values]) => [
5434
- property,
5435
- values.map((value) => choose(value, key, branch))
5436
- ])
5437
- );
5438
- const nested = splitBranches(chosen, [...conditions, ["==", decision.index, branch]]);
5439
- if (!nested) return void 0;
5440
- branches.push(...nested);
5441
- if (branches.length > maximumBranches) return void 0;
5442
- }
5443
- return branches;
5444
- }
5445
- var isLegacyFilter = (filter) => Array.isArray(filter) && typeof filter[1] === "string" && !Array.isArray(filter[1]);
5446
- function scaleByWeight(opacity, weight) {
5447
- const input = Array.isArray(opacity) ? opacity[0] === "interpolate" ? opacity[2] : opacity[1] : void 0;
5448
- if (Array.isArray(opacity) && (opacity[0] === "interpolate" || opacity[0] === "step") && reads(input, "zoom")) {
5449
- const head = opacity[0] === "interpolate" ? 3 : 2;
5450
- return opacity.map(
5451
- (item, index) => index >= head && (index - head) % 2 === (opacity[0] === "interpolate" ? 1 : 0) ? scaleByWeight(item, weight) : item
5452
- );
5453
- }
5454
- if (typeof opacity === "number")
5455
- return new BlendLeaf(weight.values.map((value) => opacity * Number(value)));
5456
- return ["*", opacity, weight];
5457
- }
5458
- function same(a, b) {
5459
- return JSON.stringify(a) === JSON.stringify(b);
5482
+ };
5460
5483
  }
5461
- var oklabCache = /* @__PURE__ */ new Map();
5462
- function oklab(colour) {
5463
- const cached2 = oklabCache.get(colour);
5464
- if (cached2 !== void 0) return cached2;
5465
- const rgba = parseColour(colour);
5466
- let result = null;
5467
- if (rgba) {
5468
- const [r, g, b] = rgba.slice(0, 3).map((channel) => {
5469
- const value = channel / 255;
5470
- return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
5484
+
5485
+ // src/native-initial-view.ts
5486
+ init_runtime_view_schema();
5487
+ var viewSchema = createTileflowRuntimeViewSchema(miniManifestSchemaOperations);
5488
+ var viewKeys = ["bearing", "center", "pitch", "zoom"];
5489
+ var inputKeys = ["view", "mapOptionsView", "manifestView"];
5490
+ var invalid = () => {
5491
+ throw new TileflowNativeSourceError("NATIVE_SOURCE_INVALID", "view");
5492
+ };
5493
+ function resolveTileflowNativeInitialView(options = {}) {
5494
+ try {
5495
+ const input = snapshotRecord(options, inputKeys);
5496
+ const manifestView = snapshotView(input.manifestView);
5497
+ const mapOptionsView = snapshotView(input.mapOptionsView);
5498
+ const view = snapshotView(input.view);
5499
+ const resolved = resolveTileflowRuntimeView({
5500
+ ...view,
5501
+ fallback: { ...defaultTileflowRuntimeView, ...manifestView, ...mapOptionsView }
5471
5502
  });
5472
- const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
5473
- const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
5474
- const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
5475
- result = [
5476
- 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
5477
- 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
5478
- 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
5479
- rgba[3]
5480
- ];
5503
+ return freezeNativeSnapshot({
5504
+ center: [resolved.center[0], resolved.center[1]],
5505
+ zoom: resolved.zoom,
5506
+ bearing: resolved.bearing,
5507
+ pitch: resolved.pitch
5508
+ });
5509
+ } catch {
5510
+ return invalid();
5481
5511
  }
5482
- if (oklabCache.size > 4096) oklabCache.clear();
5483
- oklabCache.set(colour, result);
5484
- return result;
5485
5512
  }
5486
- function colourFromOklab([lightness, a, b, alpha]) {
5487
- const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
5488
- const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
5489
- const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
5490
- const [red, green, blue] = [
5491
- 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
5492
- -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
5493
- -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
5494
- ].map((value) => {
5495
- const clamped = Math.min(1, Math.max(0, value));
5496
- const encoded = clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
5497
- return Math.round(encoded * 255);
5498
- });
5499
- return `rgba(${red}, ${green}, ${blue}, ${Number(alpha.toFixed(3))})`;
5513
+ function snapshotRecord(input, keys) {
5514
+ const snapshot = nativeOwnRecord(input);
5515
+ if (!snapshot) return invalid();
5516
+ const ownKeys = Reflect.ownKeys(input);
5517
+ if (ownKeys.length !== Object.keys(snapshot).length || ownKeys.some((key) => typeof key !== "string" || !keys.includes(key)))
5518
+ return invalid();
5519
+ return snapshot;
5500
5520
  }
5501
- function parseColour(colour) {
5502
- const text = colour.trim().toLowerCase();
5503
- const hex = /^#([\da-f]{3,8})$/u.exec(text)?.[1];
5504
- if (hex && [3, 4, 6, 8].includes(hex.length)) {
5505
- const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
5506
- const channels = full.match(/../gu).map((pair) => Number.parseInt(pair, 16));
5507
- return [channels[0], channels[1], channels[2], channels.length > 3 ? channels[3] / 255 : 1];
5508
- }
5509
- const functional = /^(rgba?|hsla?)\(([^)]+)\)$/u.exec(text);
5510
- if (!functional) return null;
5511
- const parts = functional[2].split(/[\s,/]+/u).filter(Boolean);
5512
- if (parts.length < 3 || parts.length > 4) return null;
5513
- const alpha = parts[3] === void 0 ? 1 : parseUnit(parts[3], 1);
5514
- if (functional[1].startsWith("rgb")) {
5515
- const [r, g, b] = parts.slice(0, 3).map((part) => parseUnit(part, 255));
5516
- if ([r, g, b, alpha].some((value) => value === null)) return null;
5517
- return [r, g, b, alpha];
5521
+ function snapshotView(input) {
5522
+ if (input === void 0) return {};
5523
+ const snapshot = snapshotRecord(input, viewKeys);
5524
+ if (snapshot.center !== void 0) {
5525
+ const center = snapshot.center;
5526
+ if (!Array.isArray(center) || Object.getPrototypeOf(center) !== Array.prototype || Reflect.ownKeys(center).length !== 3 || Object.getOwnPropertyDescriptor(center, "length")?.value !== 2)
5527
+ return invalid();
5528
+ const longitude = Object.getOwnPropertyDescriptor(center, "0");
5529
+ const latitude = Object.getOwnPropertyDescriptor(center, "1");
5530
+ if (!longitude?.enumerable || !latitude?.enumerable || !("value" in longitude) || !("value" in latitude))
5531
+ return invalid();
5532
+ snapshot.center = [longitude.value, latitude.value];
5518
5533
  }
5519
- const hue = Number.parseFloat(parts[0]);
5520
- const saturation = parseUnit(parts[1], 1);
5521
- const light = parseUnit(parts[2], 1);
5522
- if (!Number.isFinite(hue) || saturation === null || light === null || alpha === null) return null;
5523
- const chroma = (1 - Math.abs(2 * light - 1)) * saturation;
5524
- const sector = (hue % 360 + 360) % 360 / 60;
5525
- const x = chroma * (1 - Math.abs(sector % 2 - 1));
5526
- 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];
5527
- const offset = light - chroma / 2;
5528
- return [(r1 + offset) * 255, (g1 + offset) * 255, (b1 + offset) * 255, alpha];
5529
- }
5530
- function parseUnit(part, scale) {
5531
- const percentage = part.endsWith("%");
5532
- const value = Number.parseFloat(part);
5533
- if (!Number.isFinite(value)) return null;
5534
- return percentage ? value / 100 * scale : value;
5534
+ const parsed = parse(viewSchema, snapshot);
5535
+ return {
5536
+ ...parsed.bearing === void 0 ? {} : { bearing: parsed.bearing },
5537
+ ...parsed.center === void 0 ? {} : { center: parsed.center },
5538
+ ...parsed.pitch === void 0 ? {} : { pitch: parsed.pitch },
5539
+ ...parsed.zoom === void 0 ? {} : { zoom: parsed.zoom }
5540
+ };
5535
5541
  }
5542
+
5543
+ // src/native.ts
5544
+ init_theme_blend();
5536
5545
  export {
5537
5546
  TileflowNativeSourceError,
5538
5547
  TileflowNativeUrlError,