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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/REFERENCE.md +11 -0
  2. package/dist/index.js +564 -381
  3. package/package.json +3 -3
package/REFERENCE.md CHANGED
@@ -73,6 +73,17 @@ with no renderer keeps the default view.
73
73
  | `getInteractionState()`, `getRenderTargets()` | The open popup; the view elements the map handed out. |
74
74
  | `destroy()` | Takes the map down, removes the capture attributes and stops every listener. Later calls do nothing. |
75
75
 
76
+ Use `getMap()` or the map passed to `onLoad` for application-owned MapLibre sources, layers,
77
+ controls and events. Tileflow owns the style; theme changes may replace it, so reinstall your
78
+ own layers on `style.load`, or use `dataSources` and `dataOverlays`. Calling the MapLibre map's
79
+ `remove()` also stops the Tileflow controller. Prefer `destroy()` for normal application cleanup.
80
+ Destroying during loading rejects `ready` and prevents a late response from mounting a map.
81
+
82
+ If optional interactions cannot start, the map remains mounted and reports an interaction
83
+ diagnostic. Changing their inputs retries initialization. A failed theme change keeps the
84
+ applied theme and its system-colour-scheme following; an unknown name does not cancel a valid
85
+ pending change. `getStatus()` reports the failure until the requested selection changes.
86
+
76
87
  ## `getTileflowMapInitialStatus(options, controlledInteractionState?)`
77
88
 
78
89
  The status a map with these options has before it mounts: its declarative diagnostics, `error` or
package/dist/index.js CHANGED
@@ -181,6 +181,8 @@ function imageModeMapInteractionDiagnostic(annotationCount, bindingCount, hasAdd
181
181
 
182
182
  // src/maplibre.ts
183
183
  var bundlerWorkerUrlKey = /* @__PURE__ */ Symbol.for("tileflow.maplibre.workerUrl");
184
+ var importMapLibre = () => import("maplibre-gl");
185
+ var loadMapLibreModule = importMapLibre;
184
186
  var mapLibrePromise;
185
187
  var mapLibre;
186
188
  var workerUrl;
@@ -202,7 +204,7 @@ function configureTileflowMapLibre(configuration) {
202
204
  if (mapLibre) applyWorkerUrl(mapLibre);
203
205
  }
204
206
  function loadTileflowMapLibre() {
205
- mapLibrePromise ??= import("maplibre-gl").then(resolveMapLibreModule).then(configureTileflowMapLibreModule).catch((error) => {
207
+ mapLibrePromise ??= loadMapLibreModule().then(resolveMapLibreModule).then(configureTileflowMapLibreModule).catch((error) => {
206
208
  mapLibrePromise = void 0;
207
209
  throw error;
208
210
  });
@@ -337,6 +339,32 @@ function throwCollected(errors, message) {
337
339
  if (errors.length > 1) throw new AggregateError(errors, message);
338
340
  }
339
341
 
342
+ // src/scope.ts
343
+ function createTileflowResourceScope() {
344
+ const cleanups = [];
345
+ let disposed = false;
346
+ const scope = {
347
+ add(cleanup) {
348
+ if (disposed) runTileflowCleanups([cleanup]);
349
+ else cleanups.push(cleanup);
350
+ },
351
+ child() {
352
+ const child = createTileflowResourceScope();
353
+ scope.add(() => child.dispose());
354
+ return child;
355
+ },
356
+ dispose() {
357
+ if (disposed) return;
358
+ disposed = true;
359
+ runTileflowCleanups(cleanups.splice(0).reverse());
360
+ },
361
+ get disposed() {
362
+ return disposed;
363
+ }
364
+ };
365
+ return scope;
366
+ }
367
+
340
368
  // src/views.ts
341
369
  function tileflowAnnotationViewContext(target, state) {
342
370
  const { annotation } = target;
@@ -547,13 +575,20 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
547
575
  let stateBridgeDiagnostic;
548
576
  let rendererBridgeDiagnostic;
549
577
  let viewBridgeDiagnostic;
578
+ let dataBridgeDiagnostic;
579
+ let interactionStartDiagnostic;
550
580
  let annotationDiagnostics = [];
551
581
  let semanticDiagnostics = [];
552
582
  let reportedDeclarativeDiagnosticKeys = /* @__PURE__ */ new Set();
553
583
  let imageSize = null;
554
584
  let managedImage = null;
555
585
  let imageReadinessRun = 0;
586
+ let themeMapIdentity;
587
+ let appliedTheme;
588
+ let failedTheme;
589
+ let validThemeSelection;
556
590
  let map = null;
591
+ let mounted;
557
592
  let scene;
558
593
  let themeController;
559
594
  let appearanceBinding;
@@ -644,6 +679,13 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
644
679
  }));
645
680
  const mapName = runtimeSource.map;
646
681
  const manifestUrl = runtimeSource.manifestUrl ?? defaultTileflowManifestUrl;
682
+ const runtimeMapIdentity = `${manifestUrl}\0${mapName}`;
683
+ if (themeMapIdentity !== runtimeMapIdentity) {
684
+ themeMapIdentity = runtimeMapIdentity;
685
+ appliedTheme = void 0;
686
+ failedTheme = void 0;
687
+ validThemeSelection = void 0;
688
+ }
647
689
  const preparedAnnotations = memo(
648
690
  "annotations",
649
691
  [annotations],
@@ -679,37 +721,75 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
679
721
  const manifestMap = currentManifest.state === "ready" ? currentManifest.map : null;
680
722
  const themeBlendKey = themeBlend ? JSON.stringify(themeBlend.themes) : void 0;
681
723
  const blendThemeName = themeBlend ? nearestTileflowBlendTheme(themeBlend) : void 0;
724
+ const resolveSelection = (selection) => {
725
+ if (!manifestMap) {
726
+ return {
727
+ error: false,
728
+ name: selection && selection !== "system" ? selection : void 0
729
+ };
730
+ }
731
+ try {
732
+ if (themeBlendKey) {
733
+ for (const name of JSON.parse(themeBlendKey)) {
734
+ resolveTileflowRuntimeTheme(manifestMap, name);
735
+ }
736
+ }
737
+ return {
738
+ error: false,
739
+ name: resolveTileflowRuntimeTheme(
740
+ manifestMap,
741
+ selection,
742
+ systemColorScheme,
743
+ themeBlendKey ? void 0 : timeOfDay
744
+ ).name
745
+ };
746
+ } catch (cause) {
747
+ return { cause, error: true, name: void 0 };
748
+ }
749
+ };
682
750
  const themeResolution = memo(
683
751
  "themeResolution",
684
- [blendThemeName, manifestMap, systemColorScheme, theme, themeBlendKey, timeOfDay],
752
+ [
753
+ blendThemeName,
754
+ manifestMap,
755
+ (blendThemeName ?? theme) === "system" ? systemColorScheme : void 0,
756
+ theme,
757
+ themeBlendKey,
758
+ timeOfDay
759
+ ],
760
+ () => resolveSelection(blendThemeName ?? theme)
761
+ );
762
+ const themeRequestFailed = failedTheme !== void 0 && failedTheme.selection === theme && failedTheme.theme === themeResolution.name;
763
+ const themeChoice = memo(
764
+ "themeChoice",
765
+ [
766
+ failedTheme,
767
+ manifestMap,
768
+ systemColorScheme,
769
+ themeBlendKey,
770
+ themeRequestFailed,
771
+ themeResolution,
772
+ timeOfDay,
773
+ validThemeSelection
774
+ ],
685
775
  () => {
686
- const selection = blendThemeName ?? theme;
687
- if (!manifestMap) {
688
- return {
689
- error: false,
690
- name: selection && selection !== "system" ? selection : void 0
691
- };
776
+ if (blendThemeName) return { resolution: themeResolution, selection: theme };
777
+ if (themeRequestFailed && failedTheme) {
778
+ const { fallback } = failedTheme;
779
+ const resolution = resolveSelection(fallback.selection);
780
+ return resolution.error || resolution.name === failedTheme.theme ? { resolution: { error: false, name: fallback.theme }, selection: fallback.theme } : { resolution, selection: fallback.selection };
692
781
  }
693
- try {
694
- if (themeBlendKey) {
695
- for (const name of JSON.parse(themeBlendKey)) {
696
- resolveTileflowRuntimeTheme(manifestMap, name);
697
- }
698
- }
782
+ if (themeResolution.error) {
699
783
  return {
700
- error: false,
701
- name: resolveTileflowRuntimeTheme(
702
- manifestMap,
703
- selection,
704
- systemColorScheme,
705
- themeBlendKey ? void 0 : timeOfDay
706
- ).name
784
+ resolution: resolveSelection(validThemeSelection),
785
+ selection: validThemeSelection
707
786
  };
708
- } catch (cause) {
709
- return { cause, error: true, name: void 0 };
710
787
  }
788
+ return { resolution: themeResolution, selection: theme };
711
789
  }
712
790
  );
791
+ const themeSelection = themeChoice.selection;
792
+ const shownThemeResolution = themeChoice.resolution;
713
793
  const manifestView = resolveTileflowRuntimeView({ manifestMap });
714
794
  const manifestCenter = stable(
715
795
  "manifestCenter",
@@ -750,15 +830,18 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
750
830
  preparedInteractionState
751
831
  })
752
832
  );
753
- const runtimeStyle = memo(
833
+ const runtimeStyle = stable(
754
834
  "runtimeStyle",
755
- [isImageMode, manifestMap, runtimeSource, systemColorScheme, themeResolution],
756
- () => isImageMode || themeResolution.error ? null : resolveTileflowRuntimeStyle({
757
- colorScheme: systemColorScheme,
758
- manifestMap,
759
- source: runtimeSource,
760
- theme: themeResolution.name
761
- })
835
+ memo(
836
+ "runtimeStyle",
837
+ [isImageMode, manifestMap, runtimeSource, systemColorScheme, shownThemeResolution],
838
+ () => isImageMode || shownThemeResolution.error ? null : resolveTileflowRuntimeStyle({
839
+ colorScheme: systemColorScheme,
840
+ manifestMap,
841
+ source: runtimeSource,
842
+ theme: shownThemeResolution.name
843
+ })
844
+ )
762
845
  );
763
846
  const blendResolution = memo(
764
847
  "blendResolution",
@@ -808,7 +891,7 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
808
891
  zoom: resolvedZoom
809
892
  }) : void 0)
810
893
  );
811
- const runtimeResolutionState = currentManifest.state !== "ready" ? currentManifest.state === "error" ? "error" : "loading" : themeResolution.error ? "error" : isImageMode ? runtimeImageUrl ? "idle" : options.imageUrl === void 0 && imageSize === null ? "loading" : "error" : runtimeStyle ? "idle" : "error";
894
+ const runtimeResolutionState = currentManifest.state !== "ready" ? currentManifest.state === "error" ? "error" : "loading" : themeResolution.error || themeRequestFailed ? "error" : isImageMode ? runtimeImageUrl ? "idle" : options.imageUrl === void 0 && imageSize === null ? "loading" : "error" : runtimeStyle ? "idle" : "error";
812
895
  return {
813
896
  annotationClusters,
814
897
  blendResolution,
@@ -834,10 +917,14 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
834
917
  resolvedInteractive,
835
918
  resolvedPitch,
836
919
  resolvedZoom,
837
- runtimeMapIdentity: `${manifestUrl}\0${runtimeSource.map}`,
920
+ runtimeMapIdentity,
838
921
  runtimeResolutionState,
839
922
  runtimeSource,
923
+ manifestReady: manifestMap !== null,
840
924
  runtimeStyle,
925
+ shownThemeResolution,
926
+ themeRequestFailed,
927
+ themeSelection,
841
928
  themeBlendHold: themeBlend?.hold === true,
842
929
  themeBlendKey,
843
930
  themeBlendPosition: themeBlend?.position,
@@ -964,8 +1051,14 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
964
1051
  image.removeEventListener("error", fail);
965
1052
  };
966
1053
  });
967
- effect("systemColorScheme", [options.theme], () => {
968
- if (options.theme !== "system") return;
1054
+ if (d.manifestReady && !d.themeResolution.error && !d.themeRequestFailed) {
1055
+ validThemeSelection = options.theme;
1056
+ }
1057
+ if (failedTheme && (failedTheme.selection !== options.theme || failedTheme.theme !== d.themeResolution.name)) {
1058
+ failedTheme = void 0;
1059
+ }
1060
+ effect("systemColorScheme", [d.themeSelection], () => {
1061
+ if (d.themeSelection !== "system") return;
969
1062
  systemColorScheme = getTileflowSystemColorScheme();
970
1063
  invalidate();
971
1064
  return subscribeTileflowSystemColorScheme((scheme) => {
@@ -1040,7 +1133,25 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1040
1133
  "reconcileData",
1041
1134
  [d.annotationClusters, d.dataOverlays, d.dataSources, d.preparedAnnotations],
1042
1135
  () => {
1043
- dataRuntime?.reconcile(dataInput);
1136
+ if (dataRuntime) reconcileData(dataRuntime);
1137
+ }
1138
+ );
1139
+ effect(
1140
+ "restartInteractions",
1141
+ [
1142
+ d.annotationClusters,
1143
+ d.dataOverlays,
1144
+ d.dataSources,
1145
+ d.hasMarkerRenderer,
1146
+ d.hasPopupRenderer,
1147
+ d.hasTooltipRenderer,
1148
+ d.preparedAnnotations,
1149
+ d.preparedInteractions
1150
+ ],
1151
+ () => {
1152
+ if (interactionStartDiagnostic && mounted && !mounted.scope.disposed) {
1153
+ attachInteractions(mounted);
1154
+ }
1044
1155
  }
1045
1156
  );
1046
1157
  effect("reconcileInteractions", [d.preparedInteractions], () => {
@@ -1100,23 +1211,13 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1100
1211
  ],
1101
1212
  () => d.isImageMode ? void 0 : mountMap()
1102
1213
  );
1103
- effect("theme", [d.runtimeStyle, d.themeBlendKey, d.themeResolution.error], () => {
1214
+ effect("theme", [d.runtimeStyle, d.themeBlendKey], () => {
1104
1215
  const controller = themeController;
1105
1216
  if (!controller || d.themeBlendKey) return;
1106
- if (d.themeResolution.error) {
1107
- void controller.setTheme(controller.getCurrent());
1108
- return;
1109
- }
1110
1217
  if (!d.runtimeStyle || controller.getCurrent() === d.runtimeStyle && !controller.getBlend()) {
1111
1218
  return;
1112
1219
  }
1113
- activeRuntimeResource = d.runtimeStyle;
1114
- invalidate();
1115
- void controller.setTheme(d.runtimeStyle, { transition: options.themeTransition }).then((result) => {
1116
- if (result.status === "failed") {
1117
- reportError("theme", result.error ?? new Error("The map theme could not be changed."));
1118
- }
1119
- });
1220
+ requestTheme(controller, d.runtimeStyle, d.themeSelection);
1120
1221
  });
1121
1222
  const themeResolutionCause = d.themeResolution.error ? d.themeResolution.cause : void 0;
1122
1223
  effect("themeError", [d.themeResolution.error, themeResolutionCause], () => {
@@ -1172,6 +1273,19 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1172
1273
  }
1173
1274
  invalidate();
1174
1275
  }
1276
+ function reconcileData(runtime) {
1277
+ try {
1278
+ dataBridgeDiagnostic = void 0;
1279
+ runtime.reconcile(dataInput);
1280
+ } catch (error) {
1281
+ console.error("Failed to reconcile Tileflow data layers", error);
1282
+ dataBridgeDiagnostic = createOverlayFailureDiagnostic(
1283
+ "Tileflow data layers could not be reconciled."
1284
+ );
1285
+ reportDiagnostic(dataBridgeDiagnostic);
1286
+ }
1287
+ invalidate();
1288
+ }
1175
1289
  function reconcileInteractions(runtime, bindings) {
1176
1290
  try {
1177
1291
  semanticBridgeDiagnostic = void 0;
@@ -1203,10 +1317,10 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1203
1317
  if (!initialRuntimeStyle) return void 0;
1204
1318
  activeRuntimeResource = initialRuntimeStyle;
1205
1319
  captureState = "loading";
1206
- let cancelled = false;
1207
- let disposeMap;
1208
- const { mapOptions, resolvedInteractive } = current;
1320
+ const scope = createTileflowResourceScope();
1321
+ const { mapOptions } = current;
1209
1322
  const resourceLifetime = new AbortController();
1323
+ scope.add(() => resourceLifetime.abort());
1210
1324
  const session = createTileflowSessionController({ source: sessionSource });
1211
1325
  const resourceTransform = createTileflowTransformRequest({
1212
1326
  getAnalytics: () => current.resolvedAnalytics,
@@ -1228,337 +1342,400 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1228
1342
  return prepared;
1229
1343
  })
1230
1344
  ]).then(([maplibregl, prepared]) => {
1231
- if (cancelled || destroyed) return;
1232
- const setupCleanups = [];
1233
- let setupDisposed = false;
1234
- const registerCleanup = (cleanup) => setupCleanups.push(cleanup);
1235
- disposeMap = () => {
1236
- if (setupDisposed) return;
1237
- setupDisposed = true;
1238
- try {
1239
- runTileflowCleanups([...setupCleanups].reverse());
1240
- } finally {
1241
- map = null;
1242
- interactionRuntime = void 0;
1243
- semanticRuntime = void 0;
1244
- interactionCoordinator = void 0;
1245
- annotationDiagnostics = [];
1246
- annotationRenderTargets = [];
1247
- annotationBridgeDiagnostic = void 0;
1248
- semanticDiagnostics = [];
1249
- semanticRenderTargets = [];
1250
- semanticBridgeDiagnostic = void 0;
1251
- stateBridgeDiagnostic = void 0;
1252
- rendererBridgeDiagnostic = void 0;
1253
- invalidate();
1254
- }
1255
- };
1256
- const sessionStarter = createTileflowSessionStarter({
1257
- getSessionId: () => session.sessionId,
1258
- sessionId: session.sessionId,
1259
- source: sessionSource
1260
- });
1261
- const fairUseNotice = attachTileflowFairUseNotice(container);
1262
- registerCleanup(() => fairUseNotice.dispose());
1263
- registerTileflowContourProtocol({ addProtocol: maplibregl.addProtocol });
1264
- registerTileflowPathGroundProtocol({ addProtocol: maplibregl.addProtocol });
1265
- registerTileflowPmtilesProtocol({ addProtocol: maplibregl.addProtocol });
1266
- const worldRequestBridge = registerTileflowWorldRequestBridge({
1267
- addProtocol: maplibregl.addProtocol,
1268
- onNotice: fairUseNotice.update
1269
- });
1270
- registerCleanup(() => worldRequestBridge.dispose());
1271
- const contourRequestScope = createTileflowContourRequestScope({
1272
- transformRequest: (url, resourceType) => transformRequest(
1273
- url,
1274
- resourceType
1275
- )
1276
- });
1277
- registerCleanup(() => contourRequestScope.dispose());
1278
- const transformRequest = createTileflowTransformRequest({
1279
- contourRequestScope,
1280
- getAnalytics: () => current.resolvedAnalytics,
1281
- sessionController: session,
1282
- sessionId: session.sessionId,
1283
- transformRequest: mapOptions?.transformRequest ?? void 0,
1284
- worldRequestBridge
1285
- });
1286
- const mapLibreMap = new maplibregl.Map({
1287
- ...mapOptions,
1288
- canvasContextAttributes: tileflowCanvasContextAttributes(
1289
- mapOptions?.canvasContextAttributes
1290
- ),
1291
- container,
1292
- style: prepared.style,
1293
- bearing: current.resolvedBearing,
1294
- center: current.resolvedCenter,
1295
- pitch: current.resolvedPitch,
1296
- zoom: current.resolvedZoom,
1297
- interactive: resolvedInteractive,
1298
- attributionControl: mapOptions?.attributionControl ?? { compact: true },
1299
- // Tileflow's compiled styles are validated when they are built.
1300
- validateStyle: mapOptions?.validateStyle ?? false,
1301
- maxPitch: mapOptions?.maxPitch ?? tileflowMaxPitch,
1302
- transformRequest
1303
- });
1304
- map = mapLibreMap;
1305
- registerCleanup(() => mapLibreMap.remove());
1306
- registerCleanup(attachTileflowAtmosphere(mapLibreMap));
1307
- registerCleanup(attachTileflowBranding(mapLibreMap));
1308
- registerCleanup(attachTileflowNotices(mapLibreMap));
1309
- registerCleanup(attachTileflowNauticalLights(mapLibreMap));
1310
- registerCleanup(attachTileflowLabelPlacement(mapLibreMap));
1311
- registerCleanup(attachTileflowHorizonQueries(mapLibreMap));
1312
- const controller = initialRuntimeStyle.theme === void 0 ? void 0 : createTileflowThemeController({
1313
- initial: prepared.family ? prepared : initialRuntimeStyle,
1314
- initialStyle: prepared.style,
1315
- styleLoadOptions,
1316
- map: mapLibreMap,
1317
- onTransition(transition) {
1318
- options.onThemeChange?.(transition);
1319
- themeTransitionState = transition.phase === "preloading" || transition.phase === "applying" ? "loading" : transition.phase === "error" ? "error" : "idle";
1320
- invalidate();
1321
- }
1322
- });
1323
- if (controller) {
1324
- themeController = controller;
1325
- registerCleanup(() => {
1326
- controller.dispose();
1327
- if (themeController === controller) themeController = void 0;
1328
- });
1329
- const appearance = createTileflowAppearanceBinding({
1330
- controller,
1331
- // CSS variables in `colors` are read where the map sits.
1332
- element: container,
1333
- map: mapLibreMap,
1334
- onError: (error, part) => reportError(part, error)
1335
- });
1336
- appearanceBinding = appearance;
1337
- registerCleanup(() => {
1338
- appearance.dispose();
1339
- if (appearanceBinding === appearance) appearanceBinding = void 0;
1340
- });
1341
- appearance.update(
1342
- { colors: options.colors, emphasis: options.emphasis },
1343
- { transition: { duration: 0 } }
1344
- );
1345
+ if (scope.disposed || destroyed) return;
1346
+ createMap(scope, maplibregl, prepared, { initialRuntimeStyle, session, styleLoadOptions });
1347
+ }).catch((error) => {
1348
+ const cancelled = scope.disposed || destroyed;
1349
+ try {
1350
+ scope.dispose();
1351
+ } catch (cleanupError) {
1352
+ console.error("Failed to release a Tileflow map that could not start", cleanupError);
1345
1353
  }
1346
- const mapScene = attachTileflowMap3D({
1347
- map: mapLibreMap,
1348
- ...controller ? { controller } : {},
1349
- vegetation: options.vegetation,
1350
- viewMode: options.viewMode ?? "3d",
1351
- load: () => import("@tileflow/core/3d"),
1352
- onError: (error) => reportError("scene", error),
1353
- // Direct tile readers use HTTP, not MapLibre's custom World protocol.
1354
- transformRequest: createTileflowTransformRequest({
1355
- getAnalytics: () => current.resolvedAnalytics,
1356
- sessionController: session,
1357
- sessionId: session.sessionId,
1358
- transformRequest: (url) => mapOptions?.transformRequest?.(
1359
- url,
1360
- "Tile"
1361
- )
1362
- })
1363
- });
1364
- scene = mapScene;
1365
- registerCleanup(() => {
1366
- mapScene.dispose();
1367
- if (scene === mapScene) scene = void 0;
1368
- });
1369
- const coordinator = createTileflowMapLibreInteractionCoordinator({
1370
- ...controlledInteractionOwnership ? { interactionState: coordinatedInteractionState } : { defaultInteractionState: coordinatedInteractionState },
1371
- onInteractionStateChange(state) {
1372
- if (!controlledInteractionOwnership) coordinatedInteractionState = state;
1373
- options.onInteractionStateChange?.(state);
1354
+ if (cancelled) return;
1355
+ reportError("runtime", error);
1356
+ captureState = "error";
1357
+ invalidate();
1358
+ });
1359
+ return () => scope.dispose();
1360
+ }
1361
+ function createMap(scope, maplibregl, prepared, context) {
1362
+ const { initialRuntimeStyle, session, styleLoadOptions } = context;
1363
+ const { mapOptions, resolvedInteractive } = current;
1364
+ scope.add(() => {
1365
+ map = null;
1366
+ mounted = void 0;
1367
+ appliedTheme = void 0;
1368
+ invalidate();
1369
+ });
1370
+ const sessionStarter = createTileflowSessionStarter({
1371
+ getSessionId: () => session.sessionId,
1372
+ sessionId: session.sessionId,
1373
+ source: sessionSource
1374
+ });
1375
+ const fairUseNotice = attachTileflowFairUseNotice(container);
1376
+ scope.add(() => fairUseNotice.dispose());
1377
+ registerTileflowContourProtocol({ addProtocol: maplibregl.addProtocol });
1378
+ registerTileflowPathGroundProtocol({ addProtocol: maplibregl.addProtocol });
1379
+ registerTileflowPmtilesProtocol({ addProtocol: maplibregl.addProtocol });
1380
+ const worldRequestBridge = registerTileflowWorldRequestBridge({
1381
+ addProtocol: maplibregl.addProtocol,
1382
+ onNotice: fairUseNotice.update
1383
+ });
1384
+ scope.add(() => worldRequestBridge.dispose());
1385
+ const contourRequestScope = createTileflowContourRequestScope({
1386
+ transformRequest: (url, resourceType) => transformRequest(
1387
+ url,
1388
+ resourceType
1389
+ )
1390
+ });
1391
+ scope.add(() => contourRequestScope.dispose());
1392
+ const transformRequest = createTileflowTransformRequest({
1393
+ contourRequestScope,
1394
+ getAnalytics: () => current.resolvedAnalytics,
1395
+ sessionController: session,
1396
+ sessionId: session.sessionId,
1397
+ transformRequest: mapOptions?.transformRequest ?? void 0,
1398
+ worldRequestBridge
1399
+ });
1400
+ const mapLibreMap = new maplibregl.Map({
1401
+ ...mapOptions,
1402
+ canvasContextAttributes: tileflowCanvasContextAttributes(mapOptions?.canvasContextAttributes),
1403
+ container,
1404
+ style: prepared.style,
1405
+ bearing: current.resolvedBearing,
1406
+ center: current.resolvedCenter,
1407
+ pitch: current.resolvedPitch,
1408
+ zoom: current.resolvedZoom,
1409
+ interactive: resolvedInteractive,
1410
+ attributionControl: mapOptions?.attributionControl ?? { compact: true },
1411
+ // Tileflow's compiled styles are validated when they are built.
1412
+ validateStyle: mapOptions?.validateStyle ?? false,
1413
+ maxPitch: mapOptions?.maxPitch ?? tileflowMaxPitch,
1414
+ transformRequest
1415
+ });
1416
+ map = mapLibreMap;
1417
+ let removedByMapLibre = false;
1418
+ scope.add(() => {
1419
+ if (!removedByMapLibre) mapLibreMap.remove();
1420
+ });
1421
+ const handleRemove = () => {
1422
+ removedByMapLibre = true;
1423
+ if (map !== mapLibreMap) return;
1424
+ map = null;
1425
+ queueMicrotask(teardown);
1426
+ };
1427
+ mapLibreMap.on("remove", handleRemove);
1428
+ scope.add(() => mapLibreMap.off("remove", handleRemove));
1429
+ scope.add(attachTileflowAtmosphere(mapLibreMap));
1430
+ scope.add(attachTileflowBranding(mapLibreMap));
1431
+ scope.add(attachTileflowNotices(mapLibreMap));
1432
+ scope.add(attachTileflowNauticalLights(mapLibreMap));
1433
+ scope.add(attachTileflowLabelPlacement(mapLibreMap));
1434
+ scope.add(attachTileflowHorizonQueries(mapLibreMap));
1435
+ let lifecycle;
1436
+ const controller = initialRuntimeStyle.theme === void 0 ? void 0 : createTileflowThemeController({
1437
+ initial: prepared.family ? prepared : initialRuntimeStyle,
1438
+ initialStyle: prepared.style,
1439
+ styleLoadOptions,
1440
+ map: mapLibreMap,
1441
+ onTransition(transition) {
1442
+ if (themeController !== controller) return;
1443
+ if (transition.phase === "ready") {
1444
+ lifecycle?.invalidate("loading");
1445
+ mapLibreMap.triggerRepaint();
1374
1446
  }
1447
+ options.onThemeChange?.(transition);
1448
+ themeTransitionState = transition.phase === "preloading" || transition.phase === "applying" ? "loading" : transition.phase === "error" ? "error" : "idle";
1449
+ invalidate();
1450
+ }
1451
+ });
1452
+ if (controller) {
1453
+ themeController = controller;
1454
+ scope.add(() => {
1455
+ controller.dispose();
1456
+ if (themeController === controller) themeController = void 0;
1375
1457
  });
1376
- interactionCoordinator = coordinator;
1377
- registerCleanup(() => coordinator.dispose());
1378
- const createOverlay = ({
1379
- container: overlayContainer,
1380
- kind
1381
- }) => wrapMapLibrePositioned(
1382
- new maplibregl.Popup({
1383
- closeButton: false,
1384
- closeOnClick: false,
1385
- closeOnMove: false,
1386
- focusAfterOpen: false,
1387
- offset: kind === "popup" ? 24 : 18
1388
- }).setDOMContent(overlayContainer)
1389
- );
1390
- const annotationRuntime = createTileflowMapLibreDomRuntime({
1391
- createMarker({ annotation, element }) {
1392
- const marker = new maplibregl.Marker({ element });
1393
- element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
1394
- return wrapMapLibrePositioned(marker);
1395
- },
1396
- createOverlay,
1397
- customMarker: current.hasMarkerRenderer,
1398
- customPopup: current.hasPopupRenderer,
1399
- customTooltip: current.hasTooltipRenderer,
1400
- document: container.ownerDocument,
1401
- interactionState: coordinator.getInteractionState(),
1458
+ appliedTheme = { selection: current.themeSelection, theme: initialRuntimeStyle.theme };
1459
+ const appearance = createTileflowAppearanceBinding({
1460
+ controller,
1461
+ // CSS variables in `colors` are read where the map sits.
1462
+ element: container,
1402
1463
  map: mapLibreMap,
1403
- onInteractionStateChange: coordinator.requestInteractionState,
1404
- onDiagnostic: reportDiagnostic,
1405
- updateMarker(_marker, { annotation, element }) {
1406
- element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
1464
+ onError: (error, part) => {
1465
+ if (appearanceBinding === appearance) reportError(part, error);
1407
1466
  }
1408
1467
  });
1409
- interactionRuntime = annotationRuntime;
1410
- registerCleanup(() => annotationRuntime.dispose());
1411
- const mapDataRuntime = createTileflowMapLibreDataRuntime({
1412
- createMarker: ({ element }) => wrapMapLibrePositioned(new maplibregl.Marker({ element })),
1413
- document: container.ownerDocument,
1414
- map: mapLibreMap,
1415
- onDiagnostic: reportDiagnostic,
1416
- onInteractionEvent(event) {
1417
- options.onInteractionEvent?.(event);
1418
- },
1419
- setHiddenAnnotations: annotationRuntime.setHiddenAnnotations
1468
+ appearanceBinding = appearance;
1469
+ scope.add(() => {
1470
+ appearance.dispose();
1471
+ if (appearanceBinding === appearance) appearanceBinding = void 0;
1420
1472
  });
1421
- dataRuntime = mapDataRuntime;
1422
- registerCleanup(() => {
1423
- mapDataRuntime.dispose();
1424
- if (dataRuntime === mapDataRuntime) dataRuntime = void 0;
1425
- });
1426
- const featureRuntime = createTileflowMapLibreSemanticDomRuntime({
1427
- cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
1428
- createOverlay,
1429
- customPopup: current.hasPopupRenderer,
1430
- customTooltip: current.hasTooltipRenderer,
1431
- document: container.ownerDocument,
1432
- interactionState: coordinator.getInteractionState(),
1433
- map: mapLibreMap,
1434
- onDiagnostic: reportDiagnostic,
1435
- onInteractionStateChange: coordinator.requestInteractionState,
1436
- poiMap: createTileflowMapLibrePoiMap(mapLibreMap),
1437
- requestFrame: (callback) => browserWindow.requestAnimationFrame(() => callback())
1438
- });
1439
- semanticRuntime = featureRuntime;
1440
- registerCleanup(() => featureRuntime.dispose());
1441
- registerCleanup(coordinator.attach("annotation", annotationRuntime));
1442
- registerCleanup(coordinator.attach("semantic", featureRuntime));
1443
- registerCleanup(
1444
- annotationRuntime.subscribeEvents((event) => options.onInteractionEvent?.(event))
1445
- );
1446
- registerCleanup(
1447
- annotationRuntime.subscribeDiagnostics((diagnostics) => {
1448
- annotationDiagnostics = diagnostics;
1449
- invalidate();
1450
- })
1451
- );
1452
- registerCleanup(
1453
- annotationRuntime.subscribeRenderTargets((targets) => {
1454
- annotationRenderTargets = targets;
1455
- invalidate();
1456
- })
1457
- );
1458
- registerCleanup(
1459
- featureRuntime.subscribeEvents((event) => {
1460
- if (event.target.kind === "annotation") return;
1461
- options.onInteractionEvent?.({ ...event, target: event.target });
1462
- })
1463
- );
1464
- registerCleanup(
1465
- featureRuntime.subscribeDiagnostics((diagnostics) => {
1466
- semanticDiagnostics = diagnostics;
1467
- invalidate();
1468
- })
1469
- );
1470
- registerCleanup(
1471
- featureRuntime.subscribeRenderTargets((targets) => {
1472
- semanticRenderTargets = targets;
1473
- invalidate();
1474
- })
1473
+ appearance.update(
1474
+ { colors: options.colors, emphasis: options.emphasis },
1475
+ { transition: { duration: 0 } }
1475
1476
  );
1476
- annotationDiagnostics = annotationRuntime.getDiagnostics();
1477
- annotationRenderTargets = annotationRuntime.getRenderTargets();
1478
- semanticDiagnostics = featureRuntime.getDiagnostics();
1479
- semanticRenderTargets = featureRuntime.getRenderTargets();
1480
- reconcileAnnotations(annotationRuntime, annotationsForRuntime);
1481
- mapDataRuntime.reconcile(dataInput);
1482
- const initialInteractions = current.preparedInteractions;
1483
- if (initialInteractions.ok)
1484
- reconcileInteractions(featureRuntime, initialInteractions.bindings);
1485
- const navigation = createTileflowNavigationControlBinding({
1486
- createControl: (controlOptions) => new maplibregl.NavigationControl(controlOptions),
1487
- map: mapLibreMap
1488
- });
1489
- navigationBinding = navigation;
1490
- registerCleanup(() => {
1491
- navigation.dispose();
1492
- if (navigationBinding === navigation) navigationBinding = void 0;
1493
- });
1494
- navigation.update(current.resolvedInteractive ? options.navigationControl : false);
1495
- const initialFit = current.cameraFit;
1496
- appliedCameraFitKey = initialFit.key;
1497
- if (initialFit.error) reportError("camera", new Error(initialFit.error));
1498
- if (initialFit.fit) fitCameraTo(mapLibreMap, initialFit.fit, 0);
1499
- const resizeObserver = new ResizeObserver(() => mapLibreMap.resize());
1500
- registerCleanup(() => resizeObserver.disconnect());
1501
- resizeObserver.observe(container);
1502
- const lifecycle = attachTileflowMapLifecycle({
1503
- getSession: () => {
1504
- const analyticsForLoad = current.resolvedAnalytics;
1505
- return {
1506
- analytics: analyticsForLoad,
1507
- styleId: analyticsForLoad?.styleId ?? (typeof initialRuntimeStyle.style === "string" ? initialRuntimeStyle.style : current.mapName)
1508
- };
1509
- },
1510
- map: mapLibreMap,
1511
- onLoad: (loadedMap) => {
1512
- settleReady({ map: loadedMap });
1513
- options.onLoad?.(loadedMap);
1514
- },
1515
- scheduler: {
1516
- cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
1517
- requestFrame: (callback) => browserWindow.requestAnimationFrame(callback)
1518
- },
1519
- sessionStarter,
1520
- setState: (state) => {
1521
- captureState = state;
1522
- invalidate();
1523
- },
1524
- subscribe: (subscribedMap, event, listener) => {
1525
- const subscription = subscribedMap.on(event, listener);
1526
- return () => subscription.unsubscribe();
1527
- }
1528
- });
1529
- registerCleanup(() => lifecycle.dispose());
1530
- const latestRuntimeStyle = current.runtimeStyle;
1531
- const latestBlend = current.blendResolution.styles && current.themeBlendPosition !== void 0 ? {
1532
- hold: current.themeBlendHold,
1533
- position: current.themeBlendPosition,
1534
- themes: current.blendResolution.styles
1535
- } : null;
1536
- if (controller && latestBlend) {
1537
- if (latestRuntimeStyle) activeRuntimeResource = latestRuntimeStyle;
1538
- void controller.setBlend(latestBlend, { transition: options.themeTransition });
1539
- } else if (controller && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
1540
- activeRuntimeResource = latestRuntimeStyle;
1541
- void controller.setTheme(latestRuntimeStyle, { transition: options.themeTransition });
1477
+ }
1478
+ const mapScene = attachTileflowMap3D({
1479
+ map: mapLibreMap,
1480
+ ...controller ? { controller } : {},
1481
+ vegetation: options.vegetation,
1482
+ viewMode: options.viewMode ?? "3d",
1483
+ load: () => import("@tileflow/core/3d"),
1484
+ onError: (error) => {
1485
+ if (scene === mapScene) reportError("scene", error);
1486
+ },
1487
+ // Direct tile readers use HTTP, not MapLibre's custom World protocol.
1488
+ transformRequest: createTileflowTransformRequest({
1489
+ getAnalytics: () => current.resolvedAnalytics,
1490
+ sessionController: session,
1491
+ sessionId: session.sessionId,
1492
+ transformRequest: (url) => mapOptions?.transformRequest?.(
1493
+ url,
1494
+ "Tile"
1495
+ )
1496
+ })
1497
+ });
1498
+ scene = mapScene;
1499
+ scope.add(() => {
1500
+ mapScene.dispose();
1501
+ if (scene === mapScene) scene = void 0;
1502
+ });
1503
+ mounted = { map: mapLibreMap, maplibregl, scope };
1504
+ attachInteractions(mounted);
1505
+ const navigation = createTileflowNavigationControlBinding({
1506
+ createControl: (controlOptions) => new maplibregl.NavigationControl(controlOptions),
1507
+ map: mapLibreMap
1508
+ });
1509
+ navigationBinding = navigation;
1510
+ scope.add(() => {
1511
+ navigation.dispose();
1512
+ if (navigationBinding === navigation) navigationBinding = void 0;
1513
+ });
1514
+ navigation.update(current.resolvedInteractive ? options.navigationControl : false);
1515
+ const initialFit = current.cameraFit;
1516
+ appliedCameraFitKey = initialFit.key;
1517
+ if (initialFit.error) reportError("camera", new Error(initialFit.error));
1518
+ if (initialFit.fit) fitCameraTo(mapLibreMap, initialFit.fit, 0);
1519
+ const resizeObserver = new ResizeObserver(() => mapLibreMap.resize());
1520
+ scope.add(() => resizeObserver.disconnect());
1521
+ resizeObserver.observe(container);
1522
+ const mapLifecycle = attachTileflowMapLifecycle({
1523
+ getSession: () => {
1524
+ const analyticsForLoad = current.resolvedAnalytics;
1525
+ return {
1526
+ analytics: analyticsForLoad,
1527
+ styleId: analyticsForLoad?.styleId ?? (typeof initialRuntimeStyle.style === "string" ? initialRuntimeStyle.style : current.mapName)
1528
+ };
1529
+ },
1530
+ map: mapLibreMap,
1531
+ onLoad: (loadedMap) => {
1532
+ settleReady({ map: loadedMap });
1533
+ options.onLoad?.(loadedMap);
1534
+ },
1535
+ scheduler: {
1536
+ cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
1537
+ requestFrame: (callback) => browserWindow.requestAnimationFrame(callback)
1538
+ },
1539
+ sessionStarter,
1540
+ setState: (state) => {
1541
+ if (map !== mapLibreMap) return;
1542
+ captureState = state;
1543
+ invalidate();
1544
+ },
1545
+ subscribe: (subscribedMap, event, listener) => {
1546
+ const subscription = subscribedMap.on(event, listener);
1547
+ return () => subscription.unsubscribe();
1542
1548
  }
1549
+ });
1550
+ lifecycle = mapLifecycle;
1551
+ scope.add(() => {
1552
+ lifecycle = void 0;
1553
+ mapLifecycle.dispose();
1554
+ });
1555
+ const latestRuntimeStyle = current.runtimeStyle;
1556
+ const latestBlend = current.blendResolution.styles && current.themeBlendPosition !== void 0 ? {
1557
+ hold: current.themeBlendHold,
1558
+ position: current.themeBlendPosition,
1559
+ themes: current.blendResolution.styles
1560
+ } : null;
1561
+ if (controller && latestBlend) {
1562
+ if (latestRuntimeStyle) activeRuntimeResource = latestRuntimeStyle;
1563
+ void controller.setBlend(latestBlend, { transition: options.themeTransition });
1564
+ } else if (controller && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
1565
+ requestTheme(controller, latestRuntimeStyle, current.themeSelection);
1566
+ }
1567
+ invalidate();
1568
+ }
1569
+ function attachInteractions(target) {
1570
+ const { map: mapLibreMap, maplibregl } = target;
1571
+ const scope = target.scope.child();
1572
+ scope.add(() => {
1573
+ interactionRuntime = void 0;
1574
+ semanticRuntime = void 0;
1575
+ interactionCoordinator = void 0;
1576
+ dataRuntime = void 0;
1577
+ annotationDiagnostics = [];
1578
+ annotationRenderTargets = [];
1579
+ annotationBridgeDiagnostic = void 0;
1580
+ semanticDiagnostics = [];
1581
+ semanticRenderTargets = [];
1582
+ semanticBridgeDiagnostic = void 0;
1583
+ stateBridgeDiagnostic = void 0;
1584
+ rendererBridgeDiagnostic = void 0;
1585
+ dataBridgeDiagnostic = void 0;
1543
1586
  invalidate();
1544
- }).catch((error) => {
1545
- resourceLifetime.abort();
1587
+ });
1588
+ try {
1589
+ startInteractions(scope, mapLibreMap, maplibregl);
1590
+ interactionStartDiagnostic = void 0;
1591
+ } catch (error) {
1592
+ console.error("Failed to start Tileflow map interactions", error);
1546
1593
  try {
1547
- disposeMap?.();
1594
+ scope.dispose();
1548
1595
  } catch (cleanupError) {
1549
- reportError("runtime", cleanupError);
1596
+ console.error("Failed to release Tileflow map interactions", cleanupError);
1550
1597
  }
1551
- if (!cancelled && !destroyed) {
1552
- reportError("runtime", error);
1553
- captureState = "error";
1598
+ interactionStartDiagnostic = createOverlayFailureDiagnostic(
1599
+ "Tileflow map interactions could not start."
1600
+ );
1601
+ reportDiagnostic(interactionStartDiagnostic);
1602
+ }
1603
+ invalidate();
1604
+ }
1605
+ function startInteractions(scope, mapLibreMap, maplibregl) {
1606
+ const coordinator = createTileflowMapLibreInteractionCoordinator({
1607
+ ...controlledInteractionOwnership ? { interactionState: coordinatedInteractionState } : { defaultInteractionState: coordinatedInteractionState },
1608
+ onInteractionStateChange(state) {
1609
+ if (interactionCoordinator !== coordinator) return;
1610
+ if (!controlledInteractionOwnership) coordinatedInteractionState = state;
1611
+ options.onInteractionStateChange?.(state);
1612
+ }
1613
+ });
1614
+ interactionCoordinator = coordinator;
1615
+ scope.add(() => coordinator.dispose());
1616
+ const createOverlay = ({
1617
+ container: overlayContainer,
1618
+ kind
1619
+ }) => wrapMapLibrePositioned(
1620
+ new maplibregl.Popup({
1621
+ closeButton: false,
1622
+ closeOnClick: false,
1623
+ closeOnMove: false,
1624
+ focusAfterOpen: false,
1625
+ offset: kind === "popup" ? 24 : 18
1626
+ }).setDOMContent(overlayContainer)
1627
+ );
1628
+ const annotationRuntime = createTileflowMapLibreDomRuntime({
1629
+ createMarker({ annotation, element }) {
1630
+ const marker = new maplibregl.Marker({ element });
1631
+ element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
1632
+ return wrapMapLibrePositioned(marker);
1633
+ },
1634
+ createOverlay,
1635
+ customMarker: current.hasMarkerRenderer,
1636
+ customPopup: current.hasPopupRenderer,
1637
+ customTooltip: current.hasTooltipRenderer,
1638
+ document: container.ownerDocument,
1639
+ interactionState: coordinator.getInteractionState(),
1640
+ map: mapLibreMap,
1641
+ onInteractionStateChange: coordinator.requestInteractionState,
1642
+ onDiagnostic: reportDiagnostic,
1643
+ updateMarker(_marker, { annotation, element }) {
1644
+ element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
1645
+ }
1646
+ });
1647
+ interactionRuntime = annotationRuntime;
1648
+ scope.add(() => annotationRuntime.dispose());
1649
+ const mapDataRuntime = createTileflowMapLibreDataRuntime({
1650
+ createMarker: ({ element }) => wrapMapLibrePositioned(new maplibregl.Marker({ element })),
1651
+ document: container.ownerDocument,
1652
+ map: mapLibreMap,
1653
+ onDiagnostic: reportDiagnostic,
1654
+ onInteractionEvent(event) {
1655
+ options.onInteractionEvent?.(event);
1656
+ },
1657
+ setHiddenAnnotations: annotationRuntime.setHiddenAnnotations
1658
+ });
1659
+ dataRuntime = mapDataRuntime;
1660
+ scope.add(() => mapDataRuntime.dispose());
1661
+ const featureRuntime = createTileflowMapLibreSemanticDomRuntime({
1662
+ cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
1663
+ createOverlay,
1664
+ customPopup: current.hasPopupRenderer,
1665
+ customTooltip: current.hasTooltipRenderer,
1666
+ document: container.ownerDocument,
1667
+ interactionState: coordinator.getInteractionState(),
1668
+ map: mapLibreMap,
1669
+ onDiagnostic: reportDiagnostic,
1670
+ onInteractionStateChange: coordinator.requestInteractionState,
1671
+ poiMap: createTileflowMapLibrePoiMap(mapLibreMap),
1672
+ requestFrame: (callback) => browserWindow.requestAnimationFrame(() => callback())
1673
+ });
1674
+ semanticRuntime = featureRuntime;
1675
+ scope.add(() => featureRuntime.dispose());
1676
+ scope.add(coordinator.attach("annotation", annotationRuntime));
1677
+ scope.add(coordinator.attach("semantic", featureRuntime));
1678
+ scope.add(annotationRuntime.subscribeEvents((event) => options.onInteractionEvent?.(event)));
1679
+ scope.add(
1680
+ annotationRuntime.subscribeDiagnostics((diagnostics) => {
1681
+ annotationDiagnostics = diagnostics;
1682
+ invalidate();
1683
+ })
1684
+ );
1685
+ scope.add(
1686
+ annotationRuntime.subscribeRenderTargets((targets) => {
1687
+ annotationRenderTargets = targets;
1554
1688
  invalidate();
1689
+ })
1690
+ );
1691
+ scope.add(
1692
+ featureRuntime.subscribeEvents((event) => {
1693
+ if (event.target.kind === "annotation") return;
1694
+ options.onInteractionEvent?.({ ...event, target: event.target });
1695
+ })
1696
+ );
1697
+ scope.add(
1698
+ featureRuntime.subscribeDiagnostics((diagnostics) => {
1699
+ semanticDiagnostics = diagnostics;
1700
+ invalidate();
1701
+ })
1702
+ );
1703
+ scope.add(
1704
+ featureRuntime.subscribeRenderTargets((targets) => {
1705
+ semanticRenderTargets = targets;
1706
+ invalidate();
1707
+ })
1708
+ );
1709
+ annotationDiagnostics = annotationRuntime.getDiagnostics();
1710
+ annotationRenderTargets = annotationRuntime.getRenderTargets();
1711
+ semanticDiagnostics = featureRuntime.getDiagnostics();
1712
+ semanticRenderTargets = featureRuntime.getRenderTargets();
1713
+ reconcileAnnotations(annotationRuntime, annotationsForRuntime);
1714
+ reconcileData(mapDataRuntime);
1715
+ const initialInteractions = current.preparedInteractions;
1716
+ if (initialInteractions.ok) reconcileInteractions(featureRuntime, initialInteractions.bindings);
1717
+ }
1718
+ function requestTheme(controller, style, selection) {
1719
+ const requested = { selection: options.theme, theme: current.themeResolution.name };
1720
+ const standingIn = current.themeRequestFailed || current.themeResolution.error;
1721
+ activeRuntimeResource = style;
1722
+ invalidate();
1723
+ void controller.setTheme(style, { transition: options.themeTransition }).then((result) => {
1724
+ if (destroyed || themeController !== controller) return;
1725
+ if (result.status === "applied") {
1726
+ appliedTheme = { selection, theme: result.theme };
1727
+ if (!standingIn) failedTheme = void 0;
1728
+ } else if (result.status === "failed") {
1729
+ const shown = controller.getCurrent().theme;
1730
+ const applied = appliedTheme ?? { selection: shown, theme: shown };
1731
+ failedTheme = {
1732
+ ...requested,
1733
+ fallback: standingIn ? { selection: applied.theme, theme: applied.theme } : applied
1734
+ };
1735
+ reportError("theme", result.error ?? new Error("The map theme could not be changed."));
1555
1736
  }
1737
+ invalidate();
1556
1738
  });
1557
- return () => {
1558
- cancelled = true;
1559
- resourceLifetime.abort();
1560
- disposeMap?.();
1561
- };
1562
1739
  }
1563
1740
  function computeStatus() {
1564
1741
  const d = current;
@@ -1569,6 +1746,8 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1569
1746
  ...stateBridgeDiagnostic ? [stateBridgeDiagnostic] : [],
1570
1747
  ...rendererBridgeDiagnostic ? [rendererBridgeDiagnostic] : [],
1571
1748
  ...viewBridgeDiagnostic ? [viewBridgeDiagnostic] : [],
1749
+ ...dataBridgeDiagnostic ? [dataBridgeDiagnostic] : [],
1750
+ ...interactionStartDiagnostic ? [interactionStartDiagnostic] : [],
1572
1751
  ...annotationDiagnostics,
1573
1752
  ...semanticDiagnostics
1574
1753
  ];
@@ -1585,7 +1764,7 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1585
1764
  return {
1586
1765
  diagnostics,
1587
1766
  readiness: parts.includes("error") ? "error" : parts.every((part) => part === "idle") ? "idle" : "loading",
1588
- theme: d.themeResolution.name,
1767
+ theme: d.shownThemeResolution.name,
1589
1768
  imageUrl: d.isImageMode ? d.runtimeImageUrl : void 0
1590
1769
  };
1591
1770
  }
@@ -1619,27 +1798,31 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1619
1798
  }
1620
1799
  reconcile();
1621
1800
  }
1801
+ function teardown() {
1802
+ if (destroyed) return;
1803
+ destroyed = true;
1804
+ settleReady({ error: new DOMException("The Tileflow map was destroyed.", "AbortError") });
1805
+ try {
1806
+ runTileflowCleanups([() => viewHost.dispose(), () => reconciler.dispose()]);
1807
+ } catch (error) {
1808
+ console.error("Failed to release a Tileflow map", error);
1809
+ } finally {
1810
+ map = null;
1811
+ mounted = void 0;
1812
+ if (statusElement) {
1813
+ for (const attribute of statusAttributes) statusElement.removeAttribute(attribute);
1814
+ }
1815
+ }
1816
+ }
1622
1817
  reconcile();
1623
1818
  return {
1624
1819
  container,
1625
- destroy() {
1626
- if (destroyed) return;
1627
- destroyed = true;
1628
- settleReady({ error: new DOMException("The Tileflow map was destroyed.", "AbortError") });
1629
- try {
1630
- viewHost.dispose();
1631
- reconciler.dispose();
1632
- } finally {
1633
- if (statusElement) {
1634
- for (const attribute of statusAttributes) statusElement.removeAttribute(attribute);
1635
- }
1636
- }
1637
- },
1820
+ destroy: teardown,
1638
1821
  getInteractionState: () => interactionCoordinator?.getInteractionState() ?? coordinatedInteractionState,
1639
1822
  getMap: () => map,
1640
1823
  getRenderTargets: () => lastRenderTargets,
1641
1824
  getStatus: () => lastStatus ?? computeStatus(),
1642
- getThemeController: () => themeController ?? null,
1825
+ getThemeController: () => map ? themeController ?? null : null,
1643
1826
  ready,
1644
1827
  setAnnotations: (annotations) => update({ annotations }),
1645
1828
  setBounds: (bounds, fitBoundsOptions) => update(fitBoundsOptions === void 0 ? { bounds } : { bounds, fitBoundsOptions }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/dom",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Mount a Tileflow map in any DOM element, without a framework.",
6
6
  "type": "module",
@@ -40,8 +40,8 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "@tileflow/core": ">=0.1.0-alpha.50 <0.1.0-beta.0",
44
- "@tileflow/interactions": ">=0.1.0-alpha.9 <0.1.0-beta.0"
43
+ "@tileflow/core": ">=0.1.0-alpha.51 <0.1.0-beta.0",
44
+ "@tileflow/interactions": ">=0.1.0-alpha.10 <0.1.0-beta.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "maplibre-gl": ">=6.4.1 <7"