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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,8 +5,8 @@ the map, loads its manifest and style, keeps its theme, colours, camera, annotat
5
5
  interactions current, and takes everything down again. It draws nothing of its own around the map:
6
6
  marker, popup and tooltip views, loading and error states belong to your page.
7
7
 
8
- Use it for plain pages and web components. The React, Vue and Svelte packages are the easier way in
9
- those frameworks.
8
+ Use it for plain pages and web components. The React, Vue and Svelte packages run on it and are the
9
+ easier way in those frameworks.
10
10
 
11
11
  > Related packages and guides: [documentation index](https://raw.githubusercontent.com/tileflow/tileflow-sdk/main/llms.txt).
12
12
 
@@ -104,6 +104,46 @@ settles with `null` once the image has loaded.
104
104
 
105
105
  See [REFERENCE.md](REFERENCE.md) for every option and controller method.
106
106
 
107
+ ## Extend the map
108
+
109
+ The map is a real MapLibre map: `ready` and `onLoad` give it, and `getMap()` returns it while it
110
+ exists. Add your own layers, sources, controls, events and MapLibre libraries to it.
111
+
112
+ <!-- docs:check -->
113
+
114
+ ```ts
115
+ import {createTileflowMap} from '@tileflow/dom';
116
+
117
+ const map = createTileflowMap(document.querySelector<HTMLElement>('#map')!, {
118
+ source: {map: 'madrid'},
119
+ onLoad(mapLibreMap) {
120
+ mapLibreMap.on('style.load', () => addRoute(mapLibreMap));
121
+ addRoute(mapLibreMap);
122
+ },
123
+ });
124
+
125
+ function addRoute(mapLibreMap: NonNullable<ReturnType<typeof map.getMap>>) {
126
+ if (mapLibreMap.getSource('route')) return;
127
+ mapLibreMap.addSource('route', {
128
+ type: 'geojson',
129
+ data: {
130
+ type: 'LineString',
131
+ coordinates: [
132
+ [-3.7038, 40.4168],
133
+ [-3.6921, 40.4138],
134
+ ],
135
+ },
136
+ });
137
+ mapLibreMap.addLayer({id: 'route', type: 'line', source: 'route', paint: {'line-width': 4}});
138
+ }
139
+ ```
140
+
141
+ Tileflow owns the style it applied, its protocols, its hosted requests and session, and the
142
+ controls it adds. A theme change may replace the style while keeping the camera, so add your own
143
+ layers and sources again on `style.load`, as above, or pass them as `dataSources` and `dataOverlays`,
144
+ which the map puts back after every theme change. Keep the map's lifetime with the controller:
145
+ `destroy()` takes everything down, and `getMap().remove()` stops the controller too.
146
+
107
147
  ## Next steps
108
148
 
109
149
  - [Show a popup on click](https://tileflow.dev/docs/examples/show-a-popup-on-click)
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
@@ -1,7 +1,18 @@
1
1
  // src/map.ts
2
2
  import {
3
- attachTileflowAtmosphere,
4
3
  attachTileflowBranding,
4
+ createTileflowAppearanceBinding,
5
+ createTileflowContourRequestScope,
6
+ createTileflowNavigationControlBinding,
7
+ getTileflowSystemColorScheme,
8
+ reportTileflowMapError,
9
+ resolveTileflowCameraFit,
10
+ subscribeTileflowSystemColorScheme,
11
+ tileflowMaxPitch
12
+ } from "@tileflow/core/browser";
13
+ import { normalizeTileflowCaptureId } from "@tileflow/core/capture";
14
+ import {
15
+ attachTileflowAtmosphere,
5
16
  attachTileflowFairUseNotice,
6
17
  attachTileflowHorizonQueries,
7
18
  attachTileflowLabelPlacement,
@@ -9,26 +20,17 @@ import {
9
20
  attachTileflowMapLifecycle,
10
21
  attachTileflowNauticalLights,
11
22
  attachTileflowNotices,
12
- createTileflowAppearanceBinding,
13
- createTileflowContourRequestScope,
14
- createTileflowNavigationControlBinding,
15
23
  createTileflowSessionStarter,
16
24
  createTileflowThemeController,
17
25
  createTileflowTransformRequest,
18
- getTileflowSystemColorScheme,
19
26
  loadTileflowRuntimeStyle,
20
27
  loadTileflowStyleFonts,
21
28
  registerTileflowContourProtocol,
22
29
  registerTileflowPathGroundProtocol,
23
30
  registerTileflowPmtilesProtocol,
24
31
  registerTileflowWorldRequestBridge,
25
- reportTileflowMapError,
26
- resolveTileflowCameraFit,
27
- subscribeTileflowSystemColorScheme,
28
- tileflowCanvasContextAttributes,
29
- tileflowMaxPitch
30
- } from "@tileflow/core/browser";
31
- import { normalizeTileflowCaptureId } from "@tileflow/core/capture";
32
+ tileflowCanvasContextAttributes
33
+ } from "@tileflow/core/internal";
32
34
  import {
33
35
  createTileflowSessionController,
34
36
  defaultTileflowManifestUrl,
@@ -181,6 +183,8 @@ function imageModeMapInteractionDiagnostic(annotationCount, bindingCount, hasAdd
181
183
 
182
184
  // src/maplibre.ts
183
185
  var bundlerWorkerUrlKey = /* @__PURE__ */ Symbol.for("tileflow.maplibre.workerUrl");
186
+ var importMapLibre = () => import("maplibre-gl");
187
+ var loadMapLibreModule = importMapLibre;
184
188
  var mapLibrePromise;
185
189
  var mapLibre;
186
190
  var workerUrl;
@@ -202,7 +206,7 @@ function configureTileflowMapLibre(configuration) {
202
206
  if (mapLibre) applyWorkerUrl(mapLibre);
203
207
  }
204
208
  function loadTileflowMapLibre() {
205
- mapLibrePromise ??= import("maplibre-gl").then(resolveMapLibreModule).then(configureTileflowMapLibreModule).catch((error) => {
209
+ mapLibrePromise ??= loadMapLibreModule().then(resolveMapLibreModule).then(configureTileflowMapLibreModule).catch((error) => {
206
210
  mapLibrePromise = void 0;
207
211
  throw error;
208
212
  });
@@ -337,6 +341,32 @@ function throwCollected(errors, message) {
337
341
  if (errors.length > 1) throw new AggregateError(errors, message);
338
342
  }
339
343
 
344
+ // src/scope.ts
345
+ function createTileflowResourceScope() {
346
+ const cleanups = [];
347
+ let disposed = false;
348
+ const scope = {
349
+ add(cleanup) {
350
+ if (disposed) runTileflowCleanups([cleanup]);
351
+ else cleanups.push(cleanup);
352
+ },
353
+ child() {
354
+ const child = createTileflowResourceScope();
355
+ scope.add(() => child.dispose());
356
+ return child;
357
+ },
358
+ dispose() {
359
+ if (disposed) return;
360
+ disposed = true;
361
+ runTileflowCleanups(cleanups.splice(0).reverse());
362
+ },
363
+ get disposed() {
364
+ return disposed;
365
+ }
366
+ };
367
+ return scope;
368
+ }
369
+
340
370
  // src/views.ts
341
371
  function tileflowAnnotationViewContext(target, state) {
342
372
  const { annotation } = target;
@@ -547,13 +577,20 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
547
577
  let stateBridgeDiagnostic;
548
578
  let rendererBridgeDiagnostic;
549
579
  let viewBridgeDiagnostic;
580
+ let dataBridgeDiagnostic;
581
+ let interactionStartDiagnostic;
550
582
  let annotationDiagnostics = [];
551
583
  let semanticDiagnostics = [];
552
584
  let reportedDeclarativeDiagnosticKeys = /* @__PURE__ */ new Set();
553
585
  let imageSize = null;
554
586
  let managedImage = null;
555
587
  let imageReadinessRun = 0;
588
+ let themeMapIdentity;
589
+ let appliedTheme;
590
+ let failedTheme;
591
+ let validThemeSelection;
556
592
  let map = null;
593
+ let mounted;
557
594
  let scene;
558
595
  let themeController;
559
596
  let appearanceBinding;
@@ -644,6 +681,13 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
644
681
  }));
645
682
  const mapName = runtimeSource.map;
646
683
  const manifestUrl = runtimeSource.manifestUrl ?? defaultTileflowManifestUrl;
684
+ const runtimeMapIdentity = `${manifestUrl}\0${mapName}`;
685
+ if (themeMapIdentity !== runtimeMapIdentity) {
686
+ themeMapIdentity = runtimeMapIdentity;
687
+ appliedTheme = void 0;
688
+ failedTheme = void 0;
689
+ validThemeSelection = void 0;
690
+ }
647
691
  const preparedAnnotations = memo(
648
692
  "annotations",
649
693
  [annotations],
@@ -679,37 +723,75 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
679
723
  const manifestMap = currentManifest.state === "ready" ? currentManifest.map : null;
680
724
  const themeBlendKey = themeBlend ? JSON.stringify(themeBlend.themes) : void 0;
681
725
  const blendThemeName = themeBlend ? nearestTileflowBlendTheme(themeBlend) : void 0;
726
+ const resolveSelection = (selection) => {
727
+ if (!manifestMap) {
728
+ return {
729
+ error: false,
730
+ name: selection && selection !== "system" ? selection : void 0
731
+ };
732
+ }
733
+ try {
734
+ if (themeBlendKey) {
735
+ for (const name of JSON.parse(themeBlendKey)) {
736
+ resolveTileflowRuntimeTheme(manifestMap, name);
737
+ }
738
+ }
739
+ return {
740
+ error: false,
741
+ name: resolveTileflowRuntimeTheme(
742
+ manifestMap,
743
+ selection,
744
+ systemColorScheme,
745
+ themeBlendKey ? void 0 : timeOfDay
746
+ ).name
747
+ };
748
+ } catch (cause) {
749
+ return { cause, error: true, name: void 0 };
750
+ }
751
+ };
682
752
  const themeResolution = memo(
683
753
  "themeResolution",
684
- [blendThemeName, manifestMap, systemColorScheme, theme, themeBlendKey, timeOfDay],
754
+ [
755
+ blendThemeName,
756
+ manifestMap,
757
+ (blendThemeName ?? theme) === "system" ? systemColorScheme : void 0,
758
+ theme,
759
+ themeBlendKey,
760
+ timeOfDay
761
+ ],
762
+ () => resolveSelection(blendThemeName ?? theme)
763
+ );
764
+ const themeRequestFailed = failedTheme !== void 0 && failedTheme.selection === theme && failedTheme.theme === themeResolution.name;
765
+ const themeChoice = memo(
766
+ "themeChoice",
767
+ [
768
+ failedTheme,
769
+ manifestMap,
770
+ systemColorScheme,
771
+ themeBlendKey,
772
+ themeRequestFailed,
773
+ themeResolution,
774
+ timeOfDay,
775
+ validThemeSelection
776
+ ],
685
777
  () => {
686
- const selection = blendThemeName ?? theme;
687
- if (!manifestMap) {
688
- return {
689
- error: false,
690
- name: selection && selection !== "system" ? selection : void 0
691
- };
778
+ if (blendThemeName) return { resolution: themeResolution, selection: theme };
779
+ if (themeRequestFailed && failedTheme) {
780
+ const { fallback } = failedTheme;
781
+ const resolution = resolveSelection(fallback.selection);
782
+ return resolution.error || resolution.name === failedTheme.theme ? { resolution: { error: false, name: fallback.theme }, selection: fallback.theme } : { resolution, selection: fallback.selection };
692
783
  }
693
- try {
694
- if (themeBlendKey) {
695
- for (const name of JSON.parse(themeBlendKey)) {
696
- resolveTileflowRuntimeTheme(manifestMap, name);
697
- }
698
- }
784
+ if (themeResolution.error) {
699
785
  return {
700
- error: false,
701
- name: resolveTileflowRuntimeTheme(
702
- manifestMap,
703
- selection,
704
- systemColorScheme,
705
- themeBlendKey ? void 0 : timeOfDay
706
- ).name
786
+ resolution: resolveSelection(validThemeSelection),
787
+ selection: validThemeSelection
707
788
  };
708
- } catch (cause) {
709
- return { cause, error: true, name: void 0 };
710
789
  }
790
+ return { resolution: themeResolution, selection: theme };
711
791
  }
712
792
  );
793
+ const themeSelection = themeChoice.selection;
794
+ const shownThemeResolution = themeChoice.resolution;
713
795
  const manifestView = resolveTileflowRuntimeView({ manifestMap });
714
796
  const manifestCenter = stable(
715
797
  "manifestCenter",
@@ -750,15 +832,18 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
750
832
  preparedInteractionState
751
833
  })
752
834
  );
753
- const runtimeStyle = memo(
835
+ const runtimeStyle = stable(
754
836
  "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
- })
837
+ memo(
838
+ "runtimeStyle",
839
+ [isImageMode, manifestMap, runtimeSource, systemColorScheme, shownThemeResolution],
840
+ () => isImageMode || shownThemeResolution.error ? null : resolveTileflowRuntimeStyle({
841
+ colorScheme: systemColorScheme,
842
+ manifestMap,
843
+ source: runtimeSource,
844
+ theme: shownThemeResolution.name
845
+ })
846
+ )
762
847
  );
763
848
  const blendResolution = memo(
764
849
  "blendResolution",
@@ -808,7 +893,7 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
808
893
  zoom: resolvedZoom
809
894
  }) : void 0)
810
895
  );
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";
896
+ 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
897
  return {
813
898
  annotationClusters,
814
899
  blendResolution,
@@ -834,10 +919,14 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
834
919
  resolvedInteractive,
835
920
  resolvedPitch,
836
921
  resolvedZoom,
837
- runtimeMapIdentity: `${manifestUrl}\0${runtimeSource.map}`,
922
+ runtimeMapIdentity,
838
923
  runtimeResolutionState,
839
924
  runtimeSource,
925
+ manifestReady: manifestMap !== null,
840
926
  runtimeStyle,
927
+ shownThemeResolution,
928
+ themeRequestFailed,
929
+ themeSelection,
841
930
  themeBlendHold: themeBlend?.hold === true,
842
931
  themeBlendKey,
843
932
  themeBlendPosition: themeBlend?.position,
@@ -964,8 +1053,14 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
964
1053
  image.removeEventListener("error", fail);
965
1054
  };
966
1055
  });
967
- effect("systemColorScheme", [options.theme], () => {
968
- if (options.theme !== "system") return;
1056
+ if (d.manifestReady && !d.themeResolution.error && !d.themeRequestFailed) {
1057
+ validThemeSelection = options.theme;
1058
+ }
1059
+ if (failedTheme && (failedTheme.selection !== options.theme || failedTheme.theme !== d.themeResolution.name)) {
1060
+ failedTheme = void 0;
1061
+ }
1062
+ effect("systemColorScheme", [d.themeSelection], () => {
1063
+ if (d.themeSelection !== "system") return;
969
1064
  systemColorScheme = getTileflowSystemColorScheme();
970
1065
  invalidate();
971
1066
  return subscribeTileflowSystemColorScheme((scheme) => {
@@ -1040,7 +1135,25 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1040
1135
  "reconcileData",
1041
1136
  [d.annotationClusters, d.dataOverlays, d.dataSources, d.preparedAnnotations],
1042
1137
  () => {
1043
- dataRuntime?.reconcile(dataInput);
1138
+ if (dataRuntime) reconcileData(dataRuntime);
1139
+ }
1140
+ );
1141
+ effect(
1142
+ "restartInteractions",
1143
+ [
1144
+ d.annotationClusters,
1145
+ d.dataOverlays,
1146
+ d.dataSources,
1147
+ d.hasMarkerRenderer,
1148
+ d.hasPopupRenderer,
1149
+ d.hasTooltipRenderer,
1150
+ d.preparedAnnotations,
1151
+ d.preparedInteractions
1152
+ ],
1153
+ () => {
1154
+ if (interactionStartDiagnostic && mounted && !mounted.scope.disposed) {
1155
+ attachInteractions(mounted);
1156
+ }
1044
1157
  }
1045
1158
  );
1046
1159
  effect("reconcileInteractions", [d.preparedInteractions], () => {
@@ -1100,23 +1213,13 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1100
1213
  ],
1101
1214
  () => d.isImageMode ? void 0 : mountMap()
1102
1215
  );
1103
- effect("theme", [d.runtimeStyle, d.themeBlendKey, d.themeResolution.error], () => {
1216
+ effect("theme", [d.runtimeStyle, d.themeBlendKey], () => {
1104
1217
  const controller = themeController;
1105
1218
  if (!controller || d.themeBlendKey) return;
1106
- if (d.themeResolution.error) {
1107
- void controller.setTheme(controller.getCurrent());
1108
- return;
1109
- }
1110
1219
  if (!d.runtimeStyle || controller.getCurrent() === d.runtimeStyle && !controller.getBlend()) {
1111
1220
  return;
1112
1221
  }
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
- });
1222
+ requestTheme(controller, d.runtimeStyle, d.themeSelection);
1120
1223
  });
1121
1224
  const themeResolutionCause = d.themeResolution.error ? d.themeResolution.cause : void 0;
1122
1225
  effect("themeError", [d.themeResolution.error, themeResolutionCause], () => {
@@ -1172,6 +1275,19 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1172
1275
  }
1173
1276
  invalidate();
1174
1277
  }
1278
+ function reconcileData(runtime) {
1279
+ try {
1280
+ dataBridgeDiagnostic = void 0;
1281
+ runtime.reconcile(dataInput);
1282
+ } catch (error) {
1283
+ console.error("Failed to reconcile Tileflow data layers", error);
1284
+ dataBridgeDiagnostic = createOverlayFailureDiagnostic(
1285
+ "Tileflow data layers could not be reconciled."
1286
+ );
1287
+ reportDiagnostic(dataBridgeDiagnostic);
1288
+ }
1289
+ invalidate();
1290
+ }
1175
1291
  function reconcileInteractions(runtime, bindings) {
1176
1292
  try {
1177
1293
  semanticBridgeDiagnostic = void 0;
@@ -1203,10 +1319,10 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1203
1319
  if (!initialRuntimeStyle) return void 0;
1204
1320
  activeRuntimeResource = initialRuntimeStyle;
1205
1321
  captureState = "loading";
1206
- let cancelled = false;
1207
- let disposeMap;
1208
- const { mapOptions, resolvedInteractive } = current;
1322
+ const scope = createTileflowResourceScope();
1323
+ const { mapOptions } = current;
1209
1324
  const resourceLifetime = new AbortController();
1325
+ scope.add(() => resourceLifetime.abort());
1210
1326
  const session = createTileflowSessionController({ source: sessionSource });
1211
1327
  const resourceTransform = createTileflowTransformRequest({
1212
1328
  getAnalytics: () => current.resolvedAnalytics,
@@ -1228,337 +1344,400 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1228
1344
  return prepared;
1229
1345
  })
1230
1346
  ]).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
- );
1347
+ if (scope.disposed || destroyed) return;
1348
+ createMap(scope, maplibregl, prepared, { initialRuntimeStyle, session, styleLoadOptions });
1349
+ }).catch((error) => {
1350
+ const cancelled = scope.disposed || destroyed;
1351
+ try {
1352
+ scope.dispose();
1353
+ } catch (cleanupError) {
1354
+ console.error("Failed to release a Tileflow map that could not start", cleanupError);
1345
1355
  }
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);
1356
+ if (cancelled) return;
1357
+ reportError("runtime", error);
1358
+ captureState = "error";
1359
+ invalidate();
1360
+ });
1361
+ return () => scope.dispose();
1362
+ }
1363
+ function createMap(scope, maplibregl, prepared, context) {
1364
+ const { initialRuntimeStyle, session, styleLoadOptions } = context;
1365
+ const { mapOptions, resolvedInteractive } = current;
1366
+ scope.add(() => {
1367
+ map = null;
1368
+ mounted = void 0;
1369
+ appliedTheme = void 0;
1370
+ invalidate();
1371
+ });
1372
+ const sessionStarter = createTileflowSessionStarter({
1373
+ getSessionId: () => session.sessionId,
1374
+ sessionId: session.sessionId,
1375
+ source: sessionSource
1376
+ });
1377
+ const fairUseNotice = attachTileflowFairUseNotice(container);
1378
+ scope.add(() => fairUseNotice.dispose());
1379
+ registerTileflowContourProtocol({ addProtocol: maplibregl.addProtocol });
1380
+ registerTileflowPathGroundProtocol({ addProtocol: maplibregl.addProtocol });
1381
+ registerTileflowPmtilesProtocol({ addProtocol: maplibregl.addProtocol });
1382
+ const worldRequestBridge = registerTileflowWorldRequestBridge({
1383
+ addProtocol: maplibregl.addProtocol,
1384
+ onNotice: fairUseNotice.update
1385
+ });
1386
+ scope.add(() => worldRequestBridge.dispose());
1387
+ const contourRequestScope = createTileflowContourRequestScope({
1388
+ transformRequest: (url, resourceType) => transformRequest(
1389
+ url,
1390
+ resourceType
1391
+ )
1392
+ });
1393
+ scope.add(() => contourRequestScope.dispose());
1394
+ const transformRequest = createTileflowTransformRequest({
1395
+ contourRequestScope,
1396
+ getAnalytics: () => current.resolvedAnalytics,
1397
+ sessionController: session,
1398
+ sessionId: session.sessionId,
1399
+ transformRequest: mapOptions?.transformRequest ?? void 0,
1400
+ worldRequestBridge
1401
+ });
1402
+ const mapLibreMap = new maplibregl.Map({
1403
+ ...mapOptions,
1404
+ canvasContextAttributes: tileflowCanvasContextAttributes(mapOptions?.canvasContextAttributes),
1405
+ container,
1406
+ style: prepared.style,
1407
+ bearing: current.resolvedBearing,
1408
+ center: current.resolvedCenter,
1409
+ pitch: current.resolvedPitch,
1410
+ zoom: current.resolvedZoom,
1411
+ interactive: resolvedInteractive,
1412
+ attributionControl: mapOptions?.attributionControl ?? { compact: true },
1413
+ // Tileflow's compiled styles are validated when they are built.
1414
+ validateStyle: mapOptions?.validateStyle ?? false,
1415
+ maxPitch: mapOptions?.maxPitch ?? tileflowMaxPitch,
1416
+ transformRequest
1417
+ });
1418
+ map = mapLibreMap;
1419
+ let removedByMapLibre = false;
1420
+ scope.add(() => {
1421
+ if (!removedByMapLibre) mapLibreMap.remove();
1422
+ });
1423
+ const handleRemove = () => {
1424
+ removedByMapLibre = true;
1425
+ if (map !== mapLibreMap) return;
1426
+ map = null;
1427
+ queueMicrotask(teardown);
1428
+ };
1429
+ mapLibreMap.on("remove", handleRemove);
1430
+ scope.add(() => mapLibreMap.off("remove", handleRemove));
1431
+ scope.add(attachTileflowAtmosphere(mapLibreMap));
1432
+ scope.add(attachTileflowBranding(mapLibreMap));
1433
+ scope.add(attachTileflowNotices(mapLibreMap));
1434
+ scope.add(attachTileflowNauticalLights(mapLibreMap));
1435
+ scope.add(attachTileflowLabelPlacement(mapLibreMap));
1436
+ scope.add(attachTileflowHorizonQueries(mapLibreMap));
1437
+ let lifecycle;
1438
+ const controller = initialRuntimeStyle.theme === void 0 ? void 0 : createTileflowThemeController({
1439
+ initial: prepared.family ? prepared : initialRuntimeStyle,
1440
+ initialStyle: prepared.style,
1441
+ styleLoadOptions,
1442
+ map: mapLibreMap,
1443
+ onTransition(transition) {
1444
+ if (themeController !== controller) return;
1445
+ if (transition.phase === "ready") {
1446
+ lifecycle?.invalidate("loading");
1447
+ mapLibreMap.triggerRepaint();
1374
1448
  }
1449
+ options.onThemeChange?.(transition);
1450
+ themeTransitionState = transition.phase === "preloading" || transition.phase === "applying" ? "loading" : transition.phase === "error" ? "error" : "idle";
1451
+ invalidate();
1452
+ }
1453
+ });
1454
+ if (controller) {
1455
+ themeController = controller;
1456
+ scope.add(() => {
1457
+ controller.dispose();
1458
+ if (themeController === controller) themeController = void 0;
1375
1459
  });
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(),
1460
+ appliedTheme = { selection: current.themeSelection, theme: initialRuntimeStyle.theme };
1461
+ const appearance = createTileflowAppearanceBinding({
1462
+ controller,
1463
+ // CSS variables in `colors` are read where the map sits.
1464
+ element: container,
1402
1465
  map: mapLibreMap,
1403
- onInteractionStateChange: coordinator.requestInteractionState,
1404
- onDiagnostic: reportDiagnostic,
1405
- updateMarker(_marker, { annotation, element }) {
1406
- element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
1466
+ onError: (error, part) => {
1467
+ if (appearanceBinding === appearance) reportError(part, error);
1407
1468
  }
1408
1469
  });
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
1420
- });
1421
- dataRuntime = mapDataRuntime;
1422
- registerCleanup(() => {
1423
- mapDataRuntime.dispose();
1424
- if (dataRuntime === mapDataRuntime) dataRuntime = void 0;
1470
+ appearanceBinding = appearance;
1471
+ scope.add(() => {
1472
+ appearance.dispose();
1473
+ if (appearanceBinding === appearance) appearanceBinding = void 0;
1425
1474
  });
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
- })
1475
+ appearance.update(
1476
+ { colors: options.colors, emphasis: options.emphasis },
1477
+ { transition: { duration: 0 } }
1475
1478
  );
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 });
1479
+ }
1480
+ const mapScene = attachTileflowMap3D({
1481
+ map: mapLibreMap,
1482
+ ...controller ? { controller } : {},
1483
+ vegetation: options.vegetation,
1484
+ viewMode: options.viewMode ?? "3d",
1485
+ load: () => import("@tileflow/core/3d"),
1486
+ onError: (error) => {
1487
+ if (scene === mapScene) reportError("scene", error);
1488
+ },
1489
+ // Direct tile readers use HTTP, not MapLibre's custom World protocol.
1490
+ transformRequest: createTileflowTransformRequest({
1491
+ getAnalytics: () => current.resolvedAnalytics,
1492
+ sessionController: session,
1493
+ sessionId: session.sessionId,
1494
+ transformRequest: (url) => mapOptions?.transformRequest?.(
1495
+ url,
1496
+ "Tile"
1497
+ )
1498
+ })
1499
+ });
1500
+ scene = mapScene;
1501
+ scope.add(() => {
1502
+ mapScene.dispose();
1503
+ if (scene === mapScene) scene = void 0;
1504
+ });
1505
+ mounted = { map: mapLibreMap, maplibregl, scope };
1506
+ attachInteractions(mounted);
1507
+ const navigation = createTileflowNavigationControlBinding({
1508
+ createControl: (controlOptions) => new maplibregl.NavigationControl(controlOptions),
1509
+ map: mapLibreMap
1510
+ });
1511
+ navigationBinding = navigation;
1512
+ scope.add(() => {
1513
+ navigation.dispose();
1514
+ if (navigationBinding === navigation) navigationBinding = void 0;
1515
+ });
1516
+ navigation.update(current.resolvedInteractive ? options.navigationControl : false);
1517
+ const initialFit = current.cameraFit;
1518
+ appliedCameraFitKey = initialFit.key;
1519
+ if (initialFit.error) reportError("camera", new Error(initialFit.error));
1520
+ if (initialFit.fit) fitCameraTo(mapLibreMap, initialFit.fit, 0);
1521
+ const resizeObserver = new ResizeObserver(() => mapLibreMap.resize());
1522
+ scope.add(() => resizeObserver.disconnect());
1523
+ resizeObserver.observe(container);
1524
+ const mapLifecycle = attachTileflowMapLifecycle({
1525
+ getSession: () => {
1526
+ const analyticsForLoad = current.resolvedAnalytics;
1527
+ return {
1528
+ analytics: analyticsForLoad,
1529
+ styleId: analyticsForLoad?.styleId ?? (typeof initialRuntimeStyle.style === "string" ? initialRuntimeStyle.style : current.mapName)
1530
+ };
1531
+ },
1532
+ map: mapLibreMap,
1533
+ onLoad: (loadedMap) => {
1534
+ settleReady({ map: loadedMap });
1535
+ options.onLoad?.(loadedMap);
1536
+ },
1537
+ scheduler: {
1538
+ cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
1539
+ requestFrame: (callback) => browserWindow.requestAnimationFrame(callback)
1540
+ },
1541
+ sessionStarter,
1542
+ setState: (state) => {
1543
+ if (map !== mapLibreMap) return;
1544
+ captureState = state;
1545
+ invalidate();
1546
+ },
1547
+ subscribe: (subscribedMap, event, listener) => {
1548
+ const subscription = subscribedMap.on(event, listener);
1549
+ return () => subscription.unsubscribe();
1542
1550
  }
1551
+ });
1552
+ lifecycle = mapLifecycle;
1553
+ scope.add(() => {
1554
+ lifecycle = void 0;
1555
+ mapLifecycle.dispose();
1556
+ });
1557
+ const latestRuntimeStyle = current.runtimeStyle;
1558
+ const latestBlend = current.blendResolution.styles && current.themeBlendPosition !== void 0 ? {
1559
+ hold: current.themeBlendHold,
1560
+ position: current.themeBlendPosition,
1561
+ themes: current.blendResolution.styles
1562
+ } : null;
1563
+ if (controller && latestBlend) {
1564
+ if (latestRuntimeStyle) activeRuntimeResource = latestRuntimeStyle;
1565
+ void controller.setBlend(latestBlend, { transition: options.themeTransition });
1566
+ } else if (controller && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
1567
+ requestTheme(controller, latestRuntimeStyle, current.themeSelection);
1568
+ }
1569
+ invalidate();
1570
+ }
1571
+ function attachInteractions(target) {
1572
+ const { map: mapLibreMap, maplibregl } = target;
1573
+ const scope = target.scope.child();
1574
+ scope.add(() => {
1575
+ interactionRuntime = void 0;
1576
+ semanticRuntime = void 0;
1577
+ interactionCoordinator = void 0;
1578
+ dataRuntime = void 0;
1579
+ annotationDiagnostics = [];
1580
+ annotationRenderTargets = [];
1581
+ annotationBridgeDiagnostic = void 0;
1582
+ semanticDiagnostics = [];
1583
+ semanticRenderTargets = [];
1584
+ semanticBridgeDiagnostic = void 0;
1585
+ stateBridgeDiagnostic = void 0;
1586
+ rendererBridgeDiagnostic = void 0;
1587
+ dataBridgeDiagnostic = void 0;
1543
1588
  invalidate();
1544
- }).catch((error) => {
1545
- resourceLifetime.abort();
1589
+ });
1590
+ try {
1591
+ startInteractions(scope, mapLibreMap, maplibregl);
1592
+ interactionStartDiagnostic = void 0;
1593
+ } catch (error) {
1594
+ console.error("Failed to start Tileflow map interactions", error);
1546
1595
  try {
1547
- disposeMap?.();
1596
+ scope.dispose();
1548
1597
  } catch (cleanupError) {
1549
- reportError("runtime", cleanupError);
1598
+ console.error("Failed to release Tileflow map interactions", cleanupError);
1550
1599
  }
1551
- if (!cancelled && !destroyed) {
1552
- reportError("runtime", error);
1553
- captureState = "error";
1600
+ interactionStartDiagnostic = createOverlayFailureDiagnostic(
1601
+ "Tileflow map interactions could not start."
1602
+ );
1603
+ reportDiagnostic(interactionStartDiagnostic);
1604
+ }
1605
+ invalidate();
1606
+ }
1607
+ function startInteractions(scope, mapLibreMap, maplibregl) {
1608
+ const coordinator = createTileflowMapLibreInteractionCoordinator({
1609
+ ...controlledInteractionOwnership ? { interactionState: coordinatedInteractionState } : { defaultInteractionState: coordinatedInteractionState },
1610
+ onInteractionStateChange(state) {
1611
+ if (interactionCoordinator !== coordinator) return;
1612
+ if (!controlledInteractionOwnership) coordinatedInteractionState = state;
1613
+ options.onInteractionStateChange?.(state);
1614
+ }
1615
+ });
1616
+ interactionCoordinator = coordinator;
1617
+ scope.add(() => coordinator.dispose());
1618
+ const createOverlay = ({
1619
+ container: overlayContainer,
1620
+ kind
1621
+ }) => wrapMapLibrePositioned(
1622
+ new maplibregl.Popup({
1623
+ closeButton: false,
1624
+ closeOnClick: false,
1625
+ closeOnMove: false,
1626
+ focusAfterOpen: false,
1627
+ offset: kind === "popup" ? 24 : 18
1628
+ }).setDOMContent(overlayContainer)
1629
+ );
1630
+ const annotationRuntime = createTileflowMapLibreDomRuntime({
1631
+ createMarker({ annotation, element }) {
1632
+ const marker = new maplibregl.Marker({ element });
1633
+ element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
1634
+ return wrapMapLibrePositioned(marker);
1635
+ },
1636
+ createOverlay,
1637
+ customMarker: current.hasMarkerRenderer,
1638
+ customPopup: current.hasPopupRenderer,
1639
+ customTooltip: current.hasTooltipRenderer,
1640
+ document: container.ownerDocument,
1641
+ interactionState: coordinator.getInteractionState(),
1642
+ map: mapLibreMap,
1643
+ onInteractionStateChange: coordinator.requestInteractionState,
1644
+ onDiagnostic: reportDiagnostic,
1645
+ updateMarker(_marker, { annotation, element }) {
1646
+ element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
1647
+ }
1648
+ });
1649
+ interactionRuntime = annotationRuntime;
1650
+ scope.add(() => annotationRuntime.dispose());
1651
+ const mapDataRuntime = createTileflowMapLibreDataRuntime({
1652
+ createMarker: ({ element }) => wrapMapLibrePositioned(new maplibregl.Marker({ element })),
1653
+ document: container.ownerDocument,
1654
+ map: mapLibreMap,
1655
+ onDiagnostic: reportDiagnostic,
1656
+ onInteractionEvent(event) {
1657
+ options.onInteractionEvent?.(event);
1658
+ },
1659
+ setHiddenAnnotations: annotationRuntime.setHiddenAnnotations
1660
+ });
1661
+ dataRuntime = mapDataRuntime;
1662
+ scope.add(() => mapDataRuntime.dispose());
1663
+ const featureRuntime = createTileflowMapLibreSemanticDomRuntime({
1664
+ cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
1665
+ createOverlay,
1666
+ customPopup: current.hasPopupRenderer,
1667
+ customTooltip: current.hasTooltipRenderer,
1668
+ document: container.ownerDocument,
1669
+ interactionState: coordinator.getInteractionState(),
1670
+ map: mapLibreMap,
1671
+ onDiagnostic: reportDiagnostic,
1672
+ onInteractionStateChange: coordinator.requestInteractionState,
1673
+ poiMap: createTileflowMapLibrePoiMap(mapLibreMap),
1674
+ requestFrame: (callback) => browserWindow.requestAnimationFrame(() => callback())
1675
+ });
1676
+ semanticRuntime = featureRuntime;
1677
+ scope.add(() => featureRuntime.dispose());
1678
+ scope.add(coordinator.attach("annotation", annotationRuntime));
1679
+ scope.add(coordinator.attach("semantic", featureRuntime));
1680
+ scope.add(annotationRuntime.subscribeEvents((event) => options.onInteractionEvent?.(event)));
1681
+ scope.add(
1682
+ annotationRuntime.subscribeDiagnostics((diagnostics) => {
1683
+ annotationDiagnostics = diagnostics;
1684
+ invalidate();
1685
+ })
1686
+ );
1687
+ scope.add(
1688
+ annotationRuntime.subscribeRenderTargets((targets) => {
1689
+ annotationRenderTargets = targets;
1690
+ invalidate();
1691
+ })
1692
+ );
1693
+ scope.add(
1694
+ featureRuntime.subscribeEvents((event) => {
1695
+ if (event.target.kind === "annotation") return;
1696
+ options.onInteractionEvent?.({ ...event, target: event.target });
1697
+ })
1698
+ );
1699
+ scope.add(
1700
+ featureRuntime.subscribeDiagnostics((diagnostics) => {
1701
+ semanticDiagnostics = diagnostics;
1554
1702
  invalidate();
1703
+ })
1704
+ );
1705
+ scope.add(
1706
+ featureRuntime.subscribeRenderTargets((targets) => {
1707
+ semanticRenderTargets = targets;
1708
+ invalidate();
1709
+ })
1710
+ );
1711
+ annotationDiagnostics = annotationRuntime.getDiagnostics();
1712
+ annotationRenderTargets = annotationRuntime.getRenderTargets();
1713
+ semanticDiagnostics = featureRuntime.getDiagnostics();
1714
+ semanticRenderTargets = featureRuntime.getRenderTargets();
1715
+ reconcileAnnotations(annotationRuntime, annotationsForRuntime);
1716
+ reconcileData(mapDataRuntime);
1717
+ const initialInteractions = current.preparedInteractions;
1718
+ if (initialInteractions.ok) reconcileInteractions(featureRuntime, initialInteractions.bindings);
1719
+ }
1720
+ function requestTheme(controller, style, selection) {
1721
+ const requested = { selection: options.theme, theme: current.themeResolution.name };
1722
+ const standingIn = current.themeRequestFailed || current.themeResolution.error;
1723
+ activeRuntimeResource = style;
1724
+ invalidate();
1725
+ void controller.setTheme(style, { transition: options.themeTransition }).then((result) => {
1726
+ if (destroyed || themeController !== controller) return;
1727
+ if (result.status === "applied") {
1728
+ appliedTheme = { selection, theme: result.theme };
1729
+ if (!standingIn) failedTheme = void 0;
1730
+ } else if (result.status === "failed") {
1731
+ const shown = controller.getCurrent().theme;
1732
+ const applied = appliedTheme ?? { selection: shown, theme: shown };
1733
+ failedTheme = {
1734
+ ...requested,
1735
+ fallback: standingIn ? { selection: applied.theme, theme: applied.theme } : applied
1736
+ };
1737
+ reportError("theme", result.error ?? new Error("The map theme could not be changed."));
1555
1738
  }
1739
+ invalidate();
1556
1740
  });
1557
- return () => {
1558
- cancelled = true;
1559
- resourceLifetime.abort();
1560
- disposeMap?.();
1561
- };
1562
1741
  }
1563
1742
  function computeStatus() {
1564
1743
  const d = current;
@@ -1569,6 +1748,8 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1569
1748
  ...stateBridgeDiagnostic ? [stateBridgeDiagnostic] : [],
1570
1749
  ...rendererBridgeDiagnostic ? [rendererBridgeDiagnostic] : [],
1571
1750
  ...viewBridgeDiagnostic ? [viewBridgeDiagnostic] : [],
1751
+ ...dataBridgeDiagnostic ? [dataBridgeDiagnostic] : [],
1752
+ ...interactionStartDiagnostic ? [interactionStartDiagnostic] : [],
1572
1753
  ...annotationDiagnostics,
1573
1754
  ...semanticDiagnostics
1574
1755
  ];
@@ -1585,7 +1766,7 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1585
1766
  return {
1586
1767
  diagnostics,
1587
1768
  readiness: parts.includes("error") ? "error" : parts.every((part) => part === "idle") ? "idle" : "loading",
1588
- theme: d.themeResolution.name,
1769
+ theme: d.shownThemeResolution.name,
1589
1770
  imageUrl: d.isImageMode ? d.runtimeImageUrl : void 0
1590
1771
  };
1591
1772
  }
@@ -1619,27 +1800,31 @@ function createTileflowMap(container, initialOptions, createOptions = {}) {
1619
1800
  }
1620
1801
  reconcile();
1621
1802
  }
1803
+ function teardown() {
1804
+ if (destroyed) return;
1805
+ destroyed = true;
1806
+ settleReady({ error: new DOMException("The Tileflow map was destroyed.", "AbortError") });
1807
+ try {
1808
+ runTileflowCleanups([() => viewHost.dispose(), () => reconciler.dispose()]);
1809
+ } catch (error) {
1810
+ console.error("Failed to release a Tileflow map", error);
1811
+ } finally {
1812
+ map = null;
1813
+ mounted = void 0;
1814
+ if (statusElement) {
1815
+ for (const attribute of statusAttributes) statusElement.removeAttribute(attribute);
1816
+ }
1817
+ }
1818
+ }
1622
1819
  reconcile();
1623
1820
  return {
1624
1821
  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
- },
1822
+ destroy: teardown,
1638
1823
  getInteractionState: () => interactionCoordinator?.getInteractionState() ?? coordinatedInteractionState,
1639
1824
  getMap: () => map,
1640
1825
  getRenderTargets: () => lastRenderTargets,
1641
1826
  getStatus: () => lastStatus ?? computeStatus(),
1642
- getThemeController: () => themeController ?? null,
1827
+ getThemeController: () => map ? themeController ?? null : null,
1643
1828
  ready,
1644
1829
  setAnnotations: (annotations) => update({ annotations }),
1645
1830
  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.2",
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.52 <0.1.0-beta.0",
44
+ "@tileflow/interactions": ">=0.1.0-alpha.11 <0.1.0-beta.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "maplibre-gl": ">=6.4.1 <7"