@tileflow/react 0.1.0-alpha.19 → 0.1.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,154 +2,501 @@
2
2
 
3
3
  // src/map.tsx
4
4
  import {
5
+ useCallback,
5
6
  useEffect,
6
7
  useLayoutEffect,
7
8
  useMemo,
8
9
  useRef,
9
10
  useState
10
11
  } from "react";
11
- import maplibregl from "maplibre-gl";
12
+ import { createPortal } from "react-dom";
13
+ import {
14
+ attachTileflowFairUseNotice,
15
+ attachTileflowMapLifecycle,
16
+ createTileflowSessionStarter,
17
+ createTileflowThemeController,
18
+ createTileflowTransformRequest,
19
+ getTileflowSystemColorScheme,
20
+ loadTileflowStyleFonts,
21
+ registerTileflowContourProtocol,
22
+ registerTileflowWorldRequestBridge,
23
+ subscribeTileflowSystemColorScheme
24
+ } from "@tileflow/core/browser";
25
+ import { normalizeTileflowCaptureId } from "@tileflow/core/capture";
12
26
  import {
13
27
  createTileflowSessionController,
14
28
  defaultTileflowManifestUrl,
29
+ defaultTileflowRuntimeView,
15
30
  loadTileflowManifest,
16
31
  mergeTileflowAnalytics,
17
- normalizeTileflowCaptureId,
32
+ normalizeTileflowRuntimeCenter,
18
33
  normalizeTileflowStaticImageSize,
19
34
  resolveTileflowManifestMap,
20
35
  resolveTileflowMapMode,
21
36
  resolveTileflowRuntimeStyle,
37
+ resolveTileflowRuntimeTheme,
38
+ resolveTileflowRuntimeView,
22
39
  resolveTileflowStaticImageUrl,
23
40
  shouldLoadTileflowManifest
24
- } from "@tileflow/core";
41
+ } from "@tileflow/core/runtime";
25
42
  import {
26
- attachTileflowMapLifecycle,
27
- createTileflowMarkerController,
28
- createTileflowSessionStarter,
29
- createTileflowTransformRequest
30
- } from "@tileflow/core/browser";
31
- import { jsx } from "react/jsx-runtime";
32
- var defaultCenter = [0, 20];
33
- var defaultMarkers = [];
34
- function Map({
35
- captureId,
36
- map: mapName,
37
- mode = "interactive",
38
- config,
39
- style,
40
- styleUrl,
41
- imageUrl,
42
- styleBaseUrl,
43
- themes,
44
- manifestUrl = defaultTileflowManifestUrl,
45
- preferLocalDev = true,
46
- alt = "",
47
- center,
48
- zoom,
49
- className,
50
- height = 420,
51
- imageLoading = "eager",
52
- interactive,
53
- mapOptions,
54
- markers = defaultMarkers,
55
- analytics,
56
- onLoad
57
- }) {
43
+ createTileflowMapLibreDomRuntime,
44
+ createTileflowMapLibreInteractionCoordinator,
45
+ createTileflowMapLibreSemanticDomRuntime
46
+ } from "@tileflow/interactions/maplibre";
47
+
48
+ // src/annotations.ts
49
+ import {
50
+ validateTileflowAnnotations
51
+ } from "@tileflow/interactions";
52
+ import { normalizeTileflowLegacyMarkers } from "@tileflow/interactions/maplibre";
53
+ function prepareTileflowReactAnnotations(annotations, markers) {
54
+ if (annotations !== void 0 && markers !== void 0) {
55
+ return {
56
+ annotations: [],
57
+ diagnostics: [
58
+ {
59
+ code: "INVALID_DOCUMENT",
60
+ level: "error",
61
+ message: "Tileflow Map accepts annotations or legacy markers, not both.",
62
+ path: ""
63
+ }
64
+ ],
65
+ ok: false,
66
+ titles: /* @__PURE__ */ new Map()
67
+ };
68
+ }
69
+ const normalized = normalizeTileflowLegacyMarkers(markers ?? []);
70
+ const candidates = annotations ?? normalized.annotations;
71
+ const titles = annotations === void 0 ? normalized.titles : new Map(annotations.map((annotation) => [annotation.id, annotation.ariaLabel]));
72
+ const validation = validateTileflowAnnotations(candidates);
73
+ if (!validation.ok) {
74
+ return {
75
+ annotations: [],
76
+ diagnostics: validation.diagnostics,
77
+ ok: false,
78
+ titles
79
+ };
80
+ }
81
+ return {
82
+ // Keep caller-owned identities after validation so generic data and portal reconciliation stay
83
+ // stable instead of replacing application objects with a schema clone.
84
+ annotations: candidates,
85
+ diagnostics: [],
86
+ ok: true,
87
+ titles
88
+ };
89
+ }
90
+ function imageModeAnnotationDiagnostic(annotationCount) {
91
+ return annotationCount > 0 ? {
92
+ code: "UNSUPPORTED_MODE",
93
+ level: "error",
94
+ message: 'Tileflow annotations require mode="interactive"; image mode cannot render interactive overlays.',
95
+ path: ""
96
+ } : void 0;
97
+ }
98
+ function imageModeMapInteractionDiagnostic(annotationCount, bindingCount, hasAdditionalInteractionConfiguration = false) {
99
+ if (bindingCount === 0 && !hasAdditionalInteractionConfiguration) {
100
+ return imageModeAnnotationDiagnostic(annotationCount);
101
+ }
102
+ return {
103
+ code: "UNSUPPORTED_MODE",
104
+ level: "error",
105
+ message: annotationCount > 0 && bindingCount > 0 ? 'Tileflow annotations and interactions require mode="interactive"; image mode cannot render interactive overlays.' : annotationCount > 0 ? 'Tileflow annotations require mode="interactive"; image mode cannot render interactive overlays.' : bindingCount > 0 ? 'Tileflow interactions require mode="interactive"; image mode cannot render interactive overlays.' : 'Tileflow interaction state, callbacks, and renderers require mode="interactive"; image mode cannot render interactive overlays.',
106
+ path: ""
107
+ };
108
+ }
109
+
110
+ // src/interaction-bindings.ts
111
+ import {
112
+ validateTileflowInteractionBindings
113
+ } from "@tileflow/interactions";
114
+ function prepareTileflowReactInteractionBindings(interactions) {
115
+ const validation = validateTileflowInteractionBindings(interactions ?? []);
116
+ return validation.ok ? {
117
+ bindings: validation.bindings,
118
+ diagnostics: [],
119
+ ok: true
120
+ } : {
121
+ diagnostics: validation.diagnostics,
122
+ ok: false
123
+ };
124
+ }
125
+
126
+ // src/interaction-state-input.ts
127
+ import {
128
+ initialTileflowInteractionState,
129
+ tileflowInteractionStateSchema
130
+ } from "@tileflow/interactions";
131
+ function prepareTileflowReactInteractionState(interactionState, defaultInteractionState, controlledOwnership = interactionState !== void 0) {
132
+ const controlled = interactionState !== void 0;
133
+ if (controlled && defaultInteractionState !== void 0) {
134
+ return {
135
+ controlled,
136
+ diagnostics: [
137
+ {
138
+ code: "INVALID_DOCUMENT",
139
+ level: "error",
140
+ message: "Tileflow Map accepts interactionState or defaultInteractionState, not both.",
141
+ path: ""
142
+ }
143
+ ],
144
+ ok: false,
145
+ state: initialTileflowInteractionState
146
+ };
147
+ }
148
+ if (controlled !== controlledOwnership) {
149
+ return {
150
+ controlled,
151
+ diagnostics: [
152
+ {
153
+ code: "INVALID_DOCUMENT",
154
+ level: "error",
155
+ message: "Tileflow interaction state ownership cannot switch between controlled and uncontrolled.",
156
+ path: ""
157
+ }
158
+ ],
159
+ ok: false,
160
+ state: initialTileflowInteractionState
161
+ };
162
+ }
163
+ const statePath = controlled ? "/interactionState" : "/defaultInteractionState";
164
+ const parsed = tileflowInteractionStateSchema.safeParse(
165
+ interactionState ?? defaultInteractionState ?? initialTileflowInteractionState
166
+ );
167
+ if (parsed.success) {
168
+ return { controlled, diagnostics: [], ok: true, state: parsed.data };
169
+ }
170
+ return {
171
+ controlled,
172
+ diagnostics: parsed.error.issues.map((issue) => ({
173
+ code: "INVALID_DOCUMENT",
174
+ level: "error",
175
+ message: issue.message,
176
+ path: `${statePath}${issue.path.map((segment) => String(segment).replaceAll("~", "~0").replaceAll("/", "~1")).map((segment) => `/${segment}`).join("")}`
177
+ })),
178
+ ok: false,
179
+ state: initialTileflowInteractionState
180
+ };
181
+ }
182
+
183
+ // src/map-style-inputs.ts
184
+ import {
185
+ validateTileflowRuntimeSource,
186
+ validateTileflowThemeSelection
187
+ } from "@tileflow/core/runtime";
188
+ function validateTileflowMapStyleInputs(input) {
189
+ const sourceValidation = validateTileflowRuntimeSource(input.source);
190
+ if (!sourceValidation.ok) return sourceValidation;
191
+ if (input.theme !== void 0 && !validateTileflowThemeSelection(input.theme)) {
192
+ return { error: 'theme must be a concrete portable theme name or "system"', ok: false };
193
+ }
194
+ if (input.theme !== void 0 && input.source.kind !== "tileflow") {
195
+ return { error: "theme is only valid with a tileflow source", ok: false };
196
+ }
197
+ return { ok: true };
198
+ }
199
+ function assertTileflowMapStyleInputs(input) {
200
+ const validation = validateTileflowMapStyleInputs(input);
201
+ if (!validation.ok) {
202
+ throw new TypeError(`Invalid Tileflow <Map> source: ${validation.error}`);
203
+ }
204
+ }
205
+
206
+ // src/maplibre.ts
207
+ var mapLibrePromise;
208
+ function loadTileflowMapLibre() {
209
+ mapLibrePromise ??= import("maplibre-gl").then(resolveMapLibreModule).catch((error) => {
210
+ mapLibrePromise = void 0;
211
+ throw error;
212
+ });
213
+ return mapLibrePromise;
214
+ }
215
+ function resolveMapLibreModule(module) {
216
+ const defaultExport = module.default;
217
+ return defaultExport ?? module;
218
+ }
219
+
220
+ // src/map.tsx
221
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
222
+ var defaultInteractions = [];
223
+ function Map2(props) {
224
+ assertTileflowMapStyleInputs(props);
225
+ const {
226
+ annotations,
227
+ captureId,
228
+ defaultInteractionState,
229
+ mode = "interactive",
230
+ source,
231
+ imageUrl,
232
+ alt = "",
233
+ center,
234
+ zoom,
235
+ className,
236
+ height = 420,
237
+ imageLoading = "eager",
238
+ interactive,
239
+ mapOptions,
240
+ markers,
241
+ analytics,
242
+ theme,
243
+ interactionState,
244
+ interactions = defaultInteractions,
245
+ onInteractionDiagnostic,
246
+ onInteractionEvent,
247
+ onInteractionStateChange,
248
+ onLoad,
249
+ renderMarker,
250
+ renderPopup,
251
+ renderTooltip
252
+ } = props;
253
+ const generalRenderPopup = renderPopup;
254
+ const generalRenderTooltip = renderTooltip;
255
+ const sourceKind = source.kind;
256
+ const sourceMap = source.kind === "tileflow" ? source.map : void 0;
257
+ const sourceManifestUrl = source.kind === "tileflow" ? source.manifestUrl : void 0;
258
+ const sourceStyle = source.kind === "maplibre" ? source.style : void 0;
259
+ const runtimeSource = useMemo(
260
+ () => sourceKind === "tileflow" ? { kind: "tileflow", manifestUrl: sourceManifestUrl, map: sourceMap } : { kind: "maplibre", style: sourceStyle },
261
+ [sourceKind, sourceManifestUrl, sourceMap, sourceStyle]
262
+ );
263
+ const mapName = runtimeSource.kind === "tileflow" ? runtimeSource.map : void 0;
264
+ const manifestUrl = runtimeSource.kind === "tileflow" ? runtimeSource.manifestUrl ?? defaultTileflowManifestUrl : defaultTileflowManifestUrl;
265
+ const preparedAnnotations = useMemo(
266
+ () => prepareTileflowReactAnnotations(annotations, markers),
267
+ [annotations, markers]
268
+ );
269
+ const preparedInteractions = useMemo(
270
+ () => prepareTileflowReactInteractionBindings(interactions),
271
+ [interactions]
272
+ );
273
+ const controlledInteractionOwnershipRef = useRef(interactionState !== void 0);
274
+ const preparedInteractionState = useMemo(
275
+ () => prepareTileflowReactInteractionState(
276
+ interactionState,
277
+ defaultInteractionState,
278
+ controlledInteractionOwnershipRef.current
279
+ ),
280
+ [defaultInteractionState, interactionState]
281
+ );
58
282
  const containerRef = useRef(null);
59
283
  const imageRef = useRef(null);
60
284
  const mapRef = useRef(null);
285
+ const themeControllerRef = useRef(void 0);
286
+ const runtimeStyleRef = useRef(null);
287
+ const interactionRuntimeRef = useRef(
288
+ void 0
289
+ );
290
+ const semanticRuntimeRef = useRef(void 0);
291
+ const interactionCoordinatorRef = useRef(
292
+ void 0
293
+ );
294
+ const annotationsRef = useRef(preparedAnnotations.annotations);
295
+ const annotationTitlesRef = useRef(preparedAnnotations.titles);
296
+ const preparedInteractionsRef = useRef(preparedInteractions);
297
+ const coordinatedInteractionStateRef = useRef(preparedInteractionState.state);
298
+ const onInteractionDiagnosticRef = useRef(onInteractionDiagnostic);
299
+ const onInteractionEventRef = useRef(onInteractionEvent);
300
+ const onInteractionStateChangeRef = useRef(onInteractionStateChange);
301
+ const onThemeChangeRef = useRef(props.onThemeChange);
302
+ const renderMarkerRef = useRef(renderMarker);
303
+ const renderPopupRef = useRef(renderPopup);
304
+ const renderTooltipRef = useRef(renderTooltip);
305
+ const reportedDeclarativeDiagnosticKeysRef = useRef(/* @__PURE__ */ new Set());
306
+ const [manifestResolution, setManifestResolution] = useState({
307
+ key: "",
308
+ map: null,
309
+ state: "loading"
310
+ });
311
+ const [systemColorScheme, setSystemColorScheme] = useState("light");
312
+ const [imageSize, setImageSize] = useState(null);
313
+ const [captureState, setCaptureState] = useState("loading");
314
+ const [themeTransitionState, setThemeTransitionState] = useState(
315
+ "idle"
316
+ );
317
+ const [activeRuntimeResource, setActiveRuntimeResource] = useState(void 0);
318
+ const [annotationRenderTargets, setAnnotationRenderTargets] = useState([]);
319
+ const [semanticRenderTargets, setSemanticRenderTargets] = useState([]);
320
+ const [annotationBridgeDiagnostic, setAnnotationBridgeDiagnostic] = useState();
321
+ const [semanticBridgeDiagnostic, setSemanticBridgeDiagnostic] = useState();
322
+ const [stateBridgeDiagnostic, setStateBridgeDiagnostic] = useState();
323
+ const [rendererBridgeDiagnostic, setRendererBridgeDiagnostic] = useState();
324
+ const [annotationDiagnostics, setAnnotationDiagnostics] = useState([]);
325
+ const [semanticDiagnostics, setSemanticDiagnostics] = useState([]);
61
326
  const stableMapOptions = useStableMapOptions(mapOptions);
327
+ const resolvedCaptureId = normalizeTileflowCaptureId(captureId);
328
+ const resolvedMode = resolveTileflowMapMode({ mode });
329
+ const isImageMode = resolvedMode === "image";
330
+ const shouldLoadManifest = shouldLoadTileflowManifest({
331
+ source: runtimeSource
332
+ });
333
+ const manifestRequestKey = shouldLoadManifest ? JSON.stringify([manifestUrl, mapName]) : "not-needed";
334
+ const currentManifestResolution = manifestResolution.key === manifestRequestKey ? manifestResolution : {
335
+ key: manifestRequestKey,
336
+ map: null,
337
+ state: shouldLoadManifest ? "loading" : "not-needed"
338
+ };
339
+ const manifestMap = currentManifestResolution.state === "ready" ? currentManifestResolution.map : null;
340
+ const themeResolution = useMemo(() => {
341
+ if (!manifestMap || runtimeSource.kind !== "tileflow") {
342
+ return {
343
+ error: false,
344
+ name: runtimeSource.kind === "tileflow" && theme && theme !== "system" ? theme : void 0
345
+ };
346
+ }
347
+ try {
348
+ return {
349
+ error: false,
350
+ name: resolveTileflowRuntimeTheme(manifestMap, theme, systemColorScheme).name
351
+ };
352
+ } catch {
353
+ return { error: true, name: void 0 };
354
+ }
355
+ }, [manifestMap, runtimeSource, systemColorScheme, theme]);
356
+ const resolvedThemeName = themeResolution.name;
357
+ const manifestView = resolveTileflowRuntimeView({ manifestMap });
358
+ const manifestCenter = useStableMapOptionValue(
359
+ normalizeTileflowRuntimeCenter(manifestView.center)
360
+ );
62
361
  const resolvedCenter = useStableMapOptionValue(
63
- center ?? stableMapOptions?.center ?? defaultCenter
362
+ center ?? stableMapOptions?.center ?? manifestCenter
64
363
  );
65
- const resolvedZoom = zoom ?? stableMapOptions?.zoom ?? 2;
364
+ const resolvedZoom = zoom ?? stableMapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
365
+ const resolvedBearing = stableMapOptions?.bearing ?? manifestView.bearing;
366
+ const resolvedPitch = stableMapOptions?.pitch ?? manifestView.pitch;
66
367
  const resolvedInteractive = interactive ?? stableMapOptions?.interactive ?? true;
67
- const staticImageCenter = center ?? defaultCenter;
368
+ const staticImageCenter = useStableMapOptionValue(
369
+ normalizeTileflowRuntimeCenter(center ?? stableMapOptions?.center, manifestCenter)
370
+ );
371
+ const staticImageZoom = zoom ?? stableMapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
68
372
  const centerRef = useRef(resolvedCenter);
69
373
  const zoomRef = useRef(resolvedZoom);
374
+ const bearingRef = useRef(resolvedBearing);
375
+ const pitchRef = useRef(resolvedPitch);
70
376
  const onLoadRef = useRef(onLoad);
71
377
  const mapNameRef = useRef(mapName);
72
378
  const resolvedAnalyticsRef = useRef(void 0);
73
379
  const readinessRunRef = useRef(0);
74
- const markerController = useMemo(
75
- () => createTileflowMarkerController({
76
- attach(markerInstance, map, marker) {
77
- markerInstance.setLngLat(marker.coordinates).addTo(map);
78
- markerInstance.getElement().title = marker.label ?? marker.id;
79
- },
80
- create(marker) {
81
- return new maplibregl.Marker({
82
- color: marker.color ?? "#C6A15B"
83
- });
84
- },
85
- remove(markerInstance) {
86
- markerInstance.remove();
87
- }
88
- }),
89
- []
380
+ const hasAdditionalInteractionConfiguration = annotations !== void 0 || defaultInteractionState !== void 0 || interactionState !== void 0 || markers !== void 0 || onInteractionDiagnostic !== void 0 || onInteractionEvent !== void 0 || onInteractionStateChange !== void 0 || renderMarker !== void 0 || renderPopup !== void 0 || renderTooltip !== void 0 || props.interactions !== void 0;
381
+ const modeDiagnostic = useMemo(
382
+ () => isImageMode ? imageModeMapInteractionDiagnostic(
383
+ preparedAnnotations.annotations.length,
384
+ interactions.length,
385
+ hasAdditionalInteractionConfiguration
386
+ ) : void 0,
387
+ [
388
+ hasAdditionalInteractionConfiguration,
389
+ interactions.length,
390
+ isImageMode,
391
+ preparedAnnotations.annotations.length
392
+ ]
90
393
  );
91
- const [manifestMap, setManifestMap] = useState(null);
92
- const [imageSize, setImageSize] = useState(null);
93
- const [captureState, setCaptureState] = useState("loading");
94
- const resolvedCaptureId = normalizeTileflowCaptureId(captureId);
95
- const resolvedMode = resolveTileflowMapMode({ imageUrl, mode, preferLocalDev });
96
- const isImageMode = resolvedMode === "image";
394
+ const declarativeInteractionDiagnostics = useMemo(
395
+ () => [
396
+ modeDiagnostic,
397
+ ...preparedAnnotations.diagnostics,
398
+ ...preparedInteractions.diagnostics,
399
+ ...preparedInteractionState.diagnostics
400
+ ].filter(
401
+ (diagnostic) => diagnostic !== void 0
402
+ ),
403
+ [
404
+ modeDiagnostic,
405
+ preparedAnnotations.diagnostics,
406
+ preparedInteractions.diagnostics,
407
+ preparedInteractionState.diagnostics
408
+ ]
409
+ );
410
+ const interactionDiagnostics = [
411
+ ...declarativeInteractionDiagnostics,
412
+ ...annotationBridgeDiagnostic ? [annotationBridgeDiagnostic] : [],
413
+ ...semanticBridgeDiagnostic ? [semanticBridgeDiagnostic] : [],
414
+ ...stateBridgeDiagnostic ? [stateBridgeDiagnostic] : [],
415
+ ...rendererBridgeDiagnostic ? [rendererBridgeDiagnostic] : [],
416
+ ...annotationDiagnostics,
417
+ ...semanticDiagnostics
418
+ ];
419
+ const interactionDiagnostic = interactionDiagnostics.find((diagnostic) => diagnostic.level === "error") ?? interactionDiagnostics[0];
420
+ const hasInteractionError = interactionDiagnostics.some(
421
+ (diagnostic) => diagnostic.level === "error"
422
+ );
423
+ const portalKeys = useMemo(
424
+ () => [
425
+ ...annotationRenderTargets.map((target) => `annotation:${target.key}`),
426
+ ...semanticRenderTargets.map((target) => `semantic:${target.key}`)
427
+ ],
428
+ [annotationRenderTargets, semanticRenderTargets]
429
+ );
430
+ const portalReadiness = useTileflowPortalReadiness(portalKeys);
431
+ const interactionReadiness = hasInteractionError ? "error" : portalReadiness.idle ? "idle" : "loading";
432
+ if (preparedAnnotations.ok) {
433
+ annotationsRef.current = preparedAnnotations.annotations;
434
+ annotationTitlesRef.current = preparedAnnotations.titles;
435
+ }
436
+ preparedInteractionsRef.current = preparedInteractions;
437
+ onInteractionDiagnosticRef.current = onInteractionDiagnostic;
438
+ onInteractionEventRef.current = onInteractionEvent;
439
+ onInteractionStateChangeRef.current = onInteractionStateChange;
440
+ renderMarkerRef.current = renderMarker;
441
+ renderPopupRef.current = renderPopup;
442
+ renderTooltipRef.current = renderTooltip;
443
+ onThemeChangeRef.current = props.onThemeChange;
444
+ useEffect(() => {
445
+ if (theme !== "system") return;
446
+ setSystemColorScheme(getTileflowSystemColorScheme());
447
+ return subscribeTileflowSystemColorScheme(setSystemColorScheme);
448
+ }, [theme]);
449
+ useEffect(() => {
450
+ const previousKeys = reportedDeclarativeDiagnosticKeysRef.current;
451
+ const nextKeys = /* @__PURE__ */ new Set();
452
+ for (const diagnostic of declarativeInteractionDiagnostics) {
453
+ const key = tileflowInteractionDiagnosticKey(diagnostic);
454
+ nextKeys.add(key);
455
+ if (!previousKeys.has(key)) onInteractionDiagnosticRef.current?.(diagnostic);
456
+ }
457
+ reportedDeclarativeDiagnosticKeysRef.current = nextKeys;
458
+ }, [declarativeInteractionDiagnostics]);
97
459
  useEffect(() => {
98
- const shouldLoadManifest = shouldLoadTileflowManifest({
99
- config,
100
- imageMode: isImageMode,
101
- imageUrl,
102
- map: mapName,
103
- style,
104
- styleBaseUrl,
105
- styleUrl
106
- });
107
460
  if (!shouldLoadManifest) {
108
- setManifestMap(null);
461
+ setManifestResolution({ key: manifestRequestKey, map: null, state: "not-needed" });
109
462
  return;
110
463
  }
111
464
  let cancelled = false;
112
- setManifestMap(null);
465
+ setManifestResolution({ key: manifestRequestKey, map: null, state: "loading" });
113
466
  loadTileflowManifest(manifestUrl).then((manifest) => {
114
467
  if (cancelled) {
115
468
  return;
116
469
  }
117
- setManifestMap(manifest && mapName ? resolveTileflowManifestMap(manifest, mapName) : null);
470
+ const resolvedMap = manifest && mapName ? resolveTileflowManifestMap(manifest, mapName) : null;
471
+ setManifestResolution({
472
+ key: manifestRequestKey,
473
+ map: resolvedMap,
474
+ state: resolvedMap ? "ready" : "error"
475
+ });
118
476
  }).catch((error) => {
119
477
  if (!cancelled) {
120
478
  console.error("Failed to load Tileflow manifest", error);
121
- setManifestMap(null);
479
+ setManifestResolution({ key: manifestRequestKey, map: null, state: "error" });
122
480
  }
123
481
  });
124
482
  return () => {
125
483
  cancelled = true;
126
484
  };
127
- }, [config, imageUrl, isImageMode, manifestUrl, mapName, style, styleUrl, styleBaseUrl]);
485
+ }, [manifestRequestKey, manifestUrl, mapName, shouldLoadManifest]);
128
486
  const runtimeStyle = useMemo(() => {
129
- if (isImageMode) {
487
+ if (isImageMode || themeResolution.error) {
130
488
  return null;
131
489
  }
132
490
  return resolveTileflowRuntimeStyle({
133
- config,
491
+ colorScheme: systemColorScheme,
134
492
  manifestMap,
135
- map: mapName,
136
- preferLocalDev,
137
- style,
138
- styleBaseUrl,
139
- styleUrl,
140
- themes
493
+ source: runtimeSource,
494
+ theme: themeResolution.name
141
495
  });
142
- }, [
143
- config,
144
- isImageMode,
145
- manifestMap,
146
- mapName,
147
- preferLocalDev,
148
- style,
149
- styleBaseUrl,
150
- styleUrl,
151
- themes
152
- ]);
496
+ }, [isImageMode, manifestMap, runtimeSource, systemColorScheme, themeResolution]);
497
+ runtimeStyleRef.current = runtimeStyle;
498
+ const runtimeMapIdentity = runtimeSource.kind === "tileflow" ? `${manifestUrl}\0${runtimeSource.map}` : runtimeSource.style;
499
+ const runtimeStyleReady = runtimeStyle !== null || themeResolution.error && mapRef.current !== null;
153
500
  const resolvedAnalytics = useMemo(
154
501
  () => mergeTileflowAnalytics(analytics, runtimeStyle?.analytics),
155
502
  [analytics, runtimeStyle]
@@ -157,11 +504,15 @@ function Map({
157
504
  useEffect(() => {
158
505
  centerRef.current = resolvedCenter;
159
506
  zoomRef.current = resolvedZoom;
507
+ bearingRef.current = resolvedBearing;
508
+ pitchRef.current = resolvedPitch;
160
509
  mapRef.current?.jumpTo({
510
+ bearing: resolvedBearing,
161
511
  center: resolvedCenter,
512
+ pitch: resolvedPitch,
162
513
  zoom: resolvedZoom
163
514
  });
164
- }, [resolvedCenter, resolvedZoom]);
515
+ }, [resolvedBearing, resolvedCenter, resolvedPitch, resolvedZoom]);
165
516
  useEffect(() => {
166
517
  onLoadRef.current = onLoad;
167
518
  }, [onLoad]);
@@ -172,14 +523,38 @@ function Map({
172
523
  resolvedAnalyticsRef.current = resolvedAnalytics;
173
524
  }, [resolvedAnalytics]);
174
525
  const runtimeImageUrl = useMemo(
175
- () => imageUrl ?? (isImageMode ? resolveTileflowStaticImageUrl({
526
+ () => imageUrl ?? (isImageMode && !themeResolution.error ? resolveTileflowStaticImageUrl({
176
527
  center: staticImageCenter,
528
+ colorScheme: systemColorScheme,
177
529
  imageSize,
178
530
  manifestMap,
179
- zoom: resolvedZoom
531
+ theme: themeResolution.name,
532
+ zoom: staticImageZoom
180
533
  }) : void 0),
181
- [imageSize, imageUrl, isImageMode, manifestMap, resolvedZoom, staticImageCenter]
534
+ [
535
+ imageSize,
536
+ imageUrl,
537
+ isImageMode,
538
+ manifestMap,
539
+ staticImageCenter,
540
+ staticImageZoom,
541
+ systemColorScheme,
542
+ themeResolution
543
+ ]
182
544
  );
545
+ const runtimeResolutionState = (() => {
546
+ if (shouldLoadManifest && currentManifestResolution.state !== "ready") {
547
+ return currentManifestResolution.state === "error" ? "error" : "loading";
548
+ }
549
+ if (themeResolution.error) return "error";
550
+ if (isImageMode) {
551
+ if (runtimeImageUrl) return "idle";
552
+ return imageUrl === void 0 && imageSize === null ? "loading" : "error";
553
+ }
554
+ return runtimeStyle ? "idle" : "error";
555
+ })();
556
+ const currentCaptureState = activeRuntimeResource === (isImageMode ? runtimeImageUrl : runtimeStyle) ? captureState : "loading";
557
+ const effectiveCaptureState = currentCaptureState === "error" || interactionReadiness === "error" || runtimeResolutionState === "error" || themeTransitionState === "error" ? "error" : currentCaptureState === "idle" && interactionReadiness === "idle" && runtimeResolutionState === "idle" && themeTransitionState === "idle" ? "idle" : "loading";
183
558
  useEffect(() => {
184
559
  if (!isImageMode || imageUrl || !containerRef.current) {
185
560
  setImageSize(null);
@@ -203,91 +578,403 @@ function Map({
203
578
  };
204
579
  }, [imageUrl, isImageMode]);
205
580
  useEffect(() => {
206
- if (!containerRef.current || !runtimeStyle) {
581
+ const runtime = interactionRuntimeRef.current;
582
+ if (!runtime || !preparedAnnotations.ok) return;
583
+ try {
584
+ setAnnotationBridgeDiagnostic(void 0);
585
+ runtime.reconcile(preparedAnnotations.annotations);
586
+ } catch (error) {
587
+ console.error("Failed to reconcile Tileflow annotations", error);
588
+ const diagnostic = createOverlayFailureDiagnostic(
589
+ "Tileflow annotations could not be reconciled."
590
+ );
591
+ setAnnotationBridgeDiagnostic(diagnostic);
592
+ onInteractionDiagnosticRef.current?.(diagnostic);
593
+ }
594
+ }, [preparedAnnotations]);
595
+ useEffect(() => {
596
+ const runtime = semanticRuntimeRef.current;
597
+ if (!runtime || !preparedInteractions.ok) return;
598
+ try {
599
+ setSemanticBridgeDiagnostic(void 0);
600
+ runtime.reconcile(preparedInteractions.bindings);
601
+ } catch (error) {
602
+ console.error("Failed to reconcile Tileflow semantic interactions", error);
603
+ const diagnostic = createOverlayFailureDiagnostic(
604
+ "Tileflow semantic interactions could not be reconciled."
605
+ );
606
+ setSemanticBridgeDiagnostic(diagnostic);
607
+ onInteractionDiagnosticRef.current?.(diagnostic);
608
+ }
609
+ }, [preparedInteractions]);
610
+ useEffect(() => {
611
+ if (!preparedInteractionState.controlled || !preparedInteractionState.ok) return;
612
+ try {
613
+ setStateBridgeDiagnostic(void 0);
614
+ interactionCoordinatorRef.current?.setInteractionState(preparedInteractionState.state);
615
+ coordinatedInteractionStateRef.current = preparedInteractionState.state;
616
+ } catch (error) {
617
+ console.error("Failed to update Tileflow interaction state", error);
618
+ const diagnostic = createOverlayFailureDiagnostic(
619
+ "Tileflow interaction state could not be updated."
620
+ );
621
+ setStateBridgeDiagnostic(diagnostic);
622
+ onInteractionDiagnosticRef.current?.(diagnostic);
623
+ }
624
+ }, [preparedInteractionState]);
625
+ useEffect(() => {
626
+ const annotationRuntime = interactionRuntimeRef.current;
627
+ const semanticRuntime = semanticRuntimeRef.current;
628
+ if (!annotationRuntime && !semanticRuntime) return;
629
+ try {
630
+ setRendererBridgeDiagnostic(void 0);
631
+ annotationRuntime?.setCustomRenderers({
632
+ marker: renderMarker !== void 0,
633
+ popup: renderPopup !== void 0,
634
+ tooltip: renderTooltip !== void 0
635
+ });
636
+ semanticRuntime?.setCustomRenderers({
637
+ popup: renderPopup !== void 0,
638
+ tooltip: renderTooltip !== void 0
639
+ });
640
+ } catch (error) {
641
+ console.error("Failed to update Tileflow interaction renderers", error);
642
+ const diagnostic = createOverlayFailureDiagnostic(
643
+ "Tileflow interaction renderers could not be updated."
644
+ );
645
+ setRendererBridgeDiagnostic(diagnostic);
646
+ onInteractionDiagnosticRef.current?.(diagnostic);
647
+ }
648
+ }, [renderMarker, renderPopup, renderTooltip]);
649
+ useEffect(() => {
650
+ setThemeTransitionState("idle");
651
+ const initialRuntimeStyle = runtimeStyleRef.current;
652
+ if (!containerRef.current || !initialRuntimeStyle) {
207
653
  return;
208
654
  }
209
655
  readinessRunRef.current += 1;
656
+ setActiveRuntimeResource(initialRuntimeStyle);
210
657
  setCaptureState("loading");
211
- const session = createTileflowSessionController({ source: "react" });
212
- const sessionStarter = createTileflowSessionStarter({
213
- getSessionId: () => session.sessionId,
214
- sessionId: session.sessionId,
215
- source: "react"
216
- });
217
- const transformRequest = createTileflowTransformRequest({
218
- always: true,
219
- asyncAnalyticsTiming: "resolution",
220
- getAnalytics: () => resolvedAnalyticsRef.current,
221
- sessionController: session,
222
- sessionId: session.sessionId,
223
- transformRequest: stableMapOptions?.transformRequest ?? void 0
224
- });
225
- const map = new maplibregl.Map({
226
- ...stableMapOptions,
227
- container: containerRef.current,
228
- style: runtimeStyle.style,
229
- center: centerRef.current,
230
- zoom: zoomRef.current,
231
- interactive: resolvedInteractive,
232
- attributionControl: stableMapOptions?.attributionControl ?? { compact: true },
233
- transformRequest
234
- });
235
- mapRef.current = map;
236
- const controls = resolvedInteractive ? new maplibregl.NavigationControl() : null;
237
- if (controls) {
238
- map.addControl(controls, "top-right");
239
- }
240
- const resizeObserver = new ResizeObserver(() => {
241
- map.resize();
242
- });
243
- resizeObserver.observe(containerRef.current);
244
- const lifecycle = attachTileflowMapLifecycle({
245
- getSession: () => {
246
- const analyticsForLoad = resolvedAnalyticsRef.current;
247
- return {
248
- analytics: analyticsForLoad,
249
- styleId: analyticsForLoad?.styleId ?? (typeof runtimeStyle.style === "string" ? runtimeStyle.style : mapNameRef.current)
250
- };
251
- },
252
- map,
253
- onLoad: (loadedMap) => {
254
- onLoadRef.current?.(loadedMap);
255
- },
256
- scheduler: {
257
- cancelFrame: (frame) => cancelAnimationFrame(frame),
258
- requestFrame: (callback) => requestAnimationFrame(callback)
259
- },
260
- sessionStarter,
261
- setState: setCaptureState,
262
- subscribe: (subscribedMap, event, listener) => {
263
- const subscription = subscribedMap.on(event, listener);
264
- return () => subscription.unsubscribe();
658
+ const container = containerRef.current;
659
+ let cancelled = false;
660
+ let disposeMap;
661
+ void Promise.all([
662
+ loadTileflowMapLibre(),
663
+ loadTileflowStyleFonts(initialRuntimeStyle.style, {
664
+ fontFaces: initialRuntimeStyle.fontFaces
665
+ })
666
+ ]).then(([maplibregl]) => {
667
+ if (cancelled || containerRef.current !== container) {
668
+ return;
265
669
  }
266
- });
267
- return () => {
268
- readinessRunRef.current += 1;
269
- try {
270
- lifecycle.dispose();
271
- } finally {
670
+ const setupCleanups = [];
671
+ let setupDisposed = false;
672
+ const setupResources = {};
673
+ const registerCleanup = (cleanup) => setupCleanups.push(cleanup);
674
+ disposeMap = () => {
675
+ if (setupDisposed) return;
676
+ setupDisposed = true;
272
677
  try {
273
- resizeObserver.disconnect();
678
+ runTileflowReactCleanups([...setupCleanups].reverse());
274
679
  } finally {
275
- try {
276
- markerController.clear();
277
- } finally {
278
- try {
279
- map.remove();
280
- } finally {
281
- if (mapRef.current === map) mapRef.current = null;
282
- }
680
+ if (mapRef.current === setupResources.map) mapRef.current = null;
681
+ if (interactionRuntimeRef.current === setupResources.annotationRuntime) {
682
+ interactionRuntimeRef.current = void 0;
683
+ }
684
+ if (semanticRuntimeRef.current === setupResources.semanticRuntime) {
685
+ semanticRuntimeRef.current = void 0;
686
+ }
687
+ if (interactionCoordinatorRef.current === setupResources.coordinator) {
688
+ interactionCoordinatorRef.current = void 0;
689
+ }
690
+ setAnnotationDiagnostics([]);
691
+ setAnnotationRenderTargets([]);
692
+ setAnnotationBridgeDiagnostic(void 0);
693
+ setSemanticDiagnostics([]);
694
+ setSemanticRenderTargets([]);
695
+ setSemanticBridgeDiagnostic(void 0);
696
+ setStateBridgeDiagnostic(void 0);
697
+ setRendererBridgeDiagnostic(void 0);
698
+ }
699
+ };
700
+ const session = createTileflowSessionController({ source: "react" });
701
+ const sessionStarter = createTileflowSessionStarter({
702
+ getSessionId: () => session.sessionId,
703
+ sessionId: session.sessionId,
704
+ source: "react"
705
+ });
706
+ const fairUseNotice = attachTileflowFairUseNotice(container);
707
+ registerCleanup(() => fairUseNotice.dispose());
708
+ registerTileflowContourProtocol({ addProtocol: maplibregl.addProtocol });
709
+ const worldRequestBridge = registerTileflowWorldRequestBridge({
710
+ addProtocol: maplibregl.addProtocol,
711
+ onNotice: fairUseNotice.update
712
+ });
713
+ registerCleanup(() => worldRequestBridge.dispose());
714
+ const transformRequest = createTileflowTransformRequest({
715
+ always: true,
716
+ asyncAnalyticsTiming: "resolution",
717
+ getAnalytics: () => resolvedAnalyticsRef.current,
718
+ sessionController: session,
719
+ sessionId: session.sessionId,
720
+ transformRequest: stableMapOptions?.transformRequest ?? void 0,
721
+ worldRequestBridge
722
+ });
723
+ const map = new maplibregl.Map({
724
+ ...stableMapOptions,
725
+ container,
726
+ style: initialRuntimeStyle.style,
727
+ bearing: bearingRef.current,
728
+ center: centerRef.current,
729
+ pitch: pitchRef.current,
730
+ zoom: zoomRef.current,
731
+ interactive: resolvedInteractive,
732
+ attributionControl: stableMapOptions?.attributionControl ?? {
733
+ compact: true
734
+ },
735
+ transformRequest
736
+ });
737
+ mapRef.current = map;
738
+ setupResources.map = map;
739
+ registerCleanup(() => map.remove());
740
+ const themeController = initialRuntimeStyle.theme === void 0 ? void 0 : createTileflowThemeController({
741
+ initial: initialRuntimeStyle,
742
+ map,
743
+ onTransition(transition) {
744
+ onThemeChangeRef.current?.(transition);
745
+ if (transition.phase === "preloading" || transition.phase === "applying") {
746
+ setThemeTransitionState("loading");
747
+ } else if (transition.phase === "error") {
748
+ setThemeTransitionState("error");
749
+ } else {
750
+ setThemeTransitionState("idle");
751
+ }
752
+ }
753
+ });
754
+ if (themeController) {
755
+ themeControllerRef.current = themeController;
756
+ registerCleanup(() => {
757
+ themeController.dispose();
758
+ if (themeControllerRef.current === themeController) {
759
+ themeControllerRef.current = void 0;
760
+ }
761
+ });
762
+ }
763
+ const browserWindow = container.ownerDocument.defaultView;
764
+ if (!browserWindow) {
765
+ throw new Error("Tileflow semantic interactions require a browser document.");
766
+ }
767
+ const interactionCoordinator = createTileflowMapLibreInteractionCoordinator({
768
+ ...controlledInteractionOwnershipRef.current ? { interactionState: coordinatedInteractionStateRef.current } : { defaultInteractionState: coordinatedInteractionStateRef.current },
769
+ onInteractionStateChange(state) {
770
+ if (!controlledInteractionOwnershipRef.current) {
771
+ coordinatedInteractionStateRef.current = state;
283
772
  }
773
+ onInteractionStateChangeRef.current?.(state);
774
+ }
775
+ });
776
+ interactionCoordinatorRef.current = interactionCoordinator;
777
+ setupResources.coordinator = interactionCoordinator;
778
+ registerCleanup(() => interactionCoordinator.dispose());
779
+ const interactionRuntime = createTileflowMapLibreDomRuntime({
780
+ createMarker({ annotation, element }) {
781
+ const marker = new maplibregl.Marker({ element });
782
+ element.title = annotationTitlesRef.current.get(annotation.id) ?? annotation.ariaLabel;
783
+ return wrapMapLibrePositioned(marker);
784
+ },
785
+ createOverlay({ container: overlayContainer, kind }) {
786
+ const popup = new maplibregl.Popup({
787
+ closeButton: false,
788
+ closeOnClick: false,
789
+ closeOnMove: false,
790
+ focusAfterOpen: false,
791
+ offset: kind === "popup" ? 24 : 18
792
+ }).setDOMContent(overlayContainer);
793
+ return wrapMapLibrePositioned(popup);
794
+ },
795
+ customMarker: renderMarkerRef.current !== void 0,
796
+ customPopup: renderPopupRef.current !== void 0,
797
+ customTooltip: renderTooltipRef.current !== void 0,
798
+ document: container.ownerDocument,
799
+ interactionState: interactionCoordinator.getInteractionState(),
800
+ map,
801
+ onInteractionStateChange: interactionCoordinator.requestInteractionState,
802
+ onDiagnostic(diagnostic) {
803
+ onInteractionDiagnosticRef.current?.(diagnostic);
804
+ },
805
+ updateMarker(_marker, { annotation, element }) {
806
+ element.title = annotationTitlesRef.current.get(annotation.id) ?? annotation.ariaLabel;
807
+ }
808
+ });
809
+ interactionRuntimeRef.current = interactionRuntime;
810
+ setupResources.annotationRuntime = interactionRuntime;
811
+ registerCleanup(() => interactionRuntime.dispose());
812
+ const semanticRuntime = createTileflowMapLibreSemanticDomRuntime({
813
+ cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
814
+ createOverlay({ container: overlayContainer, kind }) {
815
+ const popup = new maplibregl.Popup({
816
+ closeButton: false,
817
+ closeOnClick: false,
818
+ closeOnMove: false,
819
+ focusAfterOpen: false,
820
+ offset: kind === "popup" ? 24 : 18
821
+ }).setDOMContent(overlayContainer);
822
+ return wrapMapLibrePositioned(popup);
823
+ },
824
+ customPopup: renderPopupRef.current !== void 0,
825
+ customTooltip: renderTooltipRef.current !== void 0,
826
+ document: container.ownerDocument,
827
+ interactionState: interactionCoordinator.getInteractionState(),
828
+ map,
829
+ onDiagnostic(diagnostic) {
830
+ onInteractionDiagnosticRef.current?.(diagnostic);
831
+ },
832
+ onInteractionStateChange: interactionCoordinator.requestInteractionState,
833
+ poiMap: createTileflowMapLibrePoiMap(map),
834
+ requestFrame: (callback) => browserWindow.requestAnimationFrame(() => callback())
835
+ });
836
+ semanticRuntimeRef.current = semanticRuntime;
837
+ setupResources.semanticRuntime = semanticRuntime;
838
+ registerCleanup(() => semanticRuntime.dispose());
839
+ const detachAnnotationRuntime = interactionCoordinator.attach(
840
+ "annotation",
841
+ interactionRuntime
842
+ );
843
+ registerCleanup(detachAnnotationRuntime);
844
+ const detachSemanticRuntime = interactionCoordinator.attach("semantic", semanticRuntime);
845
+ registerCleanup(detachSemanticRuntime);
846
+ const unsubscribeInteractionEvents = interactionRuntime.subscribeEvents((event) => {
847
+ onInteractionEventRef.current?.(event);
848
+ });
849
+ registerCleanup(unsubscribeInteractionEvents);
850
+ const unsubscribeDiagnostics = interactionRuntime.subscribeDiagnostics((diagnostics) => {
851
+ setAnnotationDiagnostics(diagnostics);
852
+ });
853
+ registerCleanup(unsubscribeDiagnostics);
854
+ const unsubscribeRenderTargets = interactionRuntime.subscribeRenderTargets((targets) => {
855
+ setAnnotationRenderTargets(targets);
856
+ });
857
+ registerCleanup(unsubscribeRenderTargets);
858
+ const unsubscribeSemanticEvents = semanticRuntime.subscribeEvents((event) => {
859
+ if (event.target.kind === "annotation") return;
860
+ onInteractionEventRef.current?.({ ...event, target: event.target });
861
+ });
862
+ registerCleanup(unsubscribeSemanticEvents);
863
+ const unsubscribeSemanticDiagnostics = semanticRuntime.subscribeDiagnostics(
864
+ (diagnostics) => {
865
+ setSemanticDiagnostics(diagnostics);
866
+ }
867
+ );
868
+ registerCleanup(unsubscribeSemanticDiagnostics);
869
+ const unsubscribeSemanticRenderTargets = semanticRuntime.subscribeRenderTargets(
870
+ (targets) => {
871
+ setSemanticRenderTargets(targets);
872
+ }
873
+ );
874
+ registerCleanup(unsubscribeSemanticRenderTargets);
875
+ setAnnotationDiagnostics(interactionRuntime.getDiagnostics());
876
+ setAnnotationRenderTargets(interactionRuntime.getRenderTargets());
877
+ setSemanticDiagnostics(semanticRuntime.getDiagnostics());
878
+ setSemanticRenderTargets(semanticRuntime.getRenderTargets());
879
+ try {
880
+ setAnnotationBridgeDiagnostic(void 0);
881
+ interactionRuntime.reconcile(annotationsRef.current);
882
+ } catch (error) {
883
+ console.error("Failed to reconcile Tileflow annotations", error);
884
+ const diagnostic = createOverlayFailureDiagnostic(
885
+ "Tileflow annotations could not be reconciled."
886
+ );
887
+ setAnnotationBridgeDiagnostic(diagnostic);
888
+ onInteractionDiagnosticRef.current?.(diagnostic);
889
+ }
890
+ const initialInteractions = preparedInteractionsRef.current;
891
+ if (initialInteractions.ok) {
892
+ try {
893
+ setSemanticBridgeDiagnostic(void 0);
894
+ semanticRuntime.reconcile(initialInteractions.bindings);
895
+ } catch (error) {
896
+ console.error("Failed to reconcile Tileflow semantic interactions", error);
897
+ const diagnostic = createOverlayFailureDiagnostic(
898
+ "Tileflow semantic interactions could not be reconciled."
899
+ );
900
+ setSemanticBridgeDiagnostic(diagnostic);
901
+ onInteractionDiagnosticRef.current?.(diagnostic);
284
902
  }
285
903
  }
904
+ const controls = resolvedInteractive ? new maplibregl.NavigationControl() : null;
905
+ if (controls) {
906
+ map.addControl(controls, "top-right");
907
+ }
908
+ const resizeObserver = new ResizeObserver(() => {
909
+ map.resize();
910
+ });
911
+ registerCleanup(() => resizeObserver.disconnect());
912
+ resizeObserver.observe(container);
913
+ const lifecycle = attachTileflowMapLifecycle({
914
+ getSession: () => {
915
+ const analyticsForLoad = resolvedAnalyticsRef.current;
916
+ return {
917
+ analytics: analyticsForLoad,
918
+ styleId: analyticsForLoad?.styleId ?? (typeof initialRuntimeStyle.style === "string" ? initialRuntimeStyle.style : mapNameRef.current)
919
+ };
920
+ },
921
+ map,
922
+ onLoad: (loadedMap) => {
923
+ onLoadRef.current?.(loadedMap);
924
+ },
925
+ scheduler: {
926
+ cancelFrame: (frame) => cancelAnimationFrame(frame),
927
+ requestFrame: (callback) => requestAnimationFrame(callback)
928
+ },
929
+ sessionStarter,
930
+ setState: setCaptureState,
931
+ subscribe: (subscribedMap, event, listener) => {
932
+ const subscription = subscribedMap.on(event, listener);
933
+ return () => subscription.unsubscribe();
934
+ }
935
+ });
936
+ registerCleanup(() => lifecycle.dispose());
937
+ const latestRuntimeStyle = runtimeStyleRef.current;
938
+ if (themeController && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
939
+ setActiveRuntimeResource(latestRuntimeStyle);
940
+ void themeController.setTheme(latestRuntimeStyle);
941
+ }
942
+ }).catch((error) => {
943
+ try {
944
+ disposeMap?.();
945
+ } catch (cleanupError) {
946
+ console.error("Failed to clean up the Tileflow map runtime", cleanupError);
947
+ }
948
+ if (!cancelled) {
949
+ console.error("Failed to load the Tileflow map runtime", error);
950
+ setCaptureState("error");
951
+ }
952
+ });
953
+ return () => {
954
+ cancelled = true;
955
+ readinessRunRef.current += 1;
956
+ disposeMap?.();
286
957
  };
287
- }, [markerController, resolvedInteractive, runtimeStyle, stableMapOptions]);
958
+ }, [resolvedInteractive, runtimeMapIdentity, runtimeStyleReady, stableMapOptions]);
959
+ useEffect(() => {
960
+ const controller = themeControllerRef.current;
961
+ if (!controller) return;
962
+ if (themeResolution.error) {
963
+ void controller.setTheme(controller.getCurrent());
964
+ return;
965
+ }
966
+ if (!runtimeStyle || controller.getCurrent() === runtimeStyle) return;
967
+ setActiveRuntimeResource(runtimeStyle);
968
+ void controller.setTheme(runtimeStyle).then((result) => {
969
+ if (result.status === "failed") {
970
+ console.error("Failed to change the Tileflow map theme", result.error);
971
+ }
972
+ });
973
+ }, [runtimeStyle, themeResolution.error]);
288
974
  useLayoutEffect(() => {
289
975
  if (!isImageMode) return;
290
976
  readinessRunRef.current += 1;
977
+ setActiveRuntimeResource(runtimeImageUrl);
291
978
  setCaptureState("loading");
292
979
  const image = imageRef.current;
293
980
  if (image?.complete) {
@@ -307,17 +994,6 @@ function Map({
307
994
  }
308
995
  if (readinessRunRef.current === run) setCaptureState("idle");
309
996
  }
310
- useEffect(() => {
311
- const map = mapRef.current;
312
- markerController.clear();
313
- if (!map) {
314
- return;
315
- }
316
- markerController.replace(map, markers);
317
- return () => {
318
- markerController.clear();
319
- };
320
- }, [markerController, markers, runtimeStyle]);
321
997
  const frameStyle = {
322
998
  height,
323
999
  minHeight: 240,
@@ -325,51 +1001,279 @@ function Map({
325
1001
  position: "relative",
326
1002
  width: "100%"
327
1003
  };
1004
+ const diagnosticView = interactionDiagnostic ? /* @__PURE__ */ jsx(
1005
+ "div",
1006
+ {
1007
+ "data-tileflow-interaction-diagnostic": interactionDiagnostic.code,
1008
+ role: "alert",
1009
+ style: interactionDiagnosticStyle,
1010
+ children: interactionDiagnostic.message
1011
+ }
1012
+ ) : null;
328
1013
  if (isImageMode) {
329
- return /* @__PURE__ */ jsx(
1014
+ return /* @__PURE__ */ jsxs(
330
1015
  "div",
331
1016
  {
332
1017
  className,
333
1018
  "data-tileflow-capture-id": resolvedCaptureId,
334
1019
  "data-tileflow-map": mapName,
335
- "data-tileflow-state": captureState,
1020
+ "data-tileflow-theme": resolvedThemeName,
1021
+ "data-tileflow-state": effectiveCaptureState,
336
1022
  ref: containerRef,
337
1023
  style: frameStyle,
338
- children: runtimeImageUrl ? /* @__PURE__ */ jsx(
339
- "img",
340
- {
341
- alt,
342
- decoding: "async",
343
- loading: imageLoading,
344
- onError: () => {
345
- readinessRunRef.current += 1;
346
- setCaptureState("error");
347
- },
348
- onLoad: (event) => void markImageReady(event.currentTarget),
349
- ref: imageRef,
350
- src: runtimeImageUrl,
351
- style: {
352
- display: "block",
353
- height: "100%",
354
- objectFit: "cover",
355
- width: "100%"
1024
+ children: [
1025
+ runtimeImageUrl ? /* @__PURE__ */ jsx(
1026
+ "img",
1027
+ {
1028
+ alt,
1029
+ decoding: "async",
1030
+ loading: imageLoading,
1031
+ onError: () => {
1032
+ readinessRunRef.current += 1;
1033
+ setCaptureState("error");
1034
+ },
1035
+ onLoad: (event) => void markImageReady(event.currentTarget),
1036
+ ref: imageRef,
1037
+ src: runtimeImageUrl,
1038
+ style: {
1039
+ display: "block",
1040
+ height: "100%",
1041
+ objectFit: "cover",
1042
+ width: "100%"
1043
+ }
356
1044
  }
357
- }
358
- ) : null
1045
+ ) : null,
1046
+ diagnosticView
1047
+ ]
359
1048
  }
360
1049
  );
361
1050
  }
362
- return /* @__PURE__ */ jsx(
363
- "div",
364
- {
365
- className,
366
- "data-tileflow-capture-id": resolvedCaptureId,
367
- "data-tileflow-map": mapName,
368
- "data-tileflow-state": captureState,
369
- ref: containerRef,
370
- style: frameStyle
1051
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1052
+ /* @__PURE__ */ jsxs(
1053
+ "div",
1054
+ {
1055
+ className,
1056
+ "data-tileflow-capture-id": resolvedCaptureId,
1057
+ "data-tileflow-map": mapName,
1058
+ "data-tileflow-theme": resolvedThemeName,
1059
+ "data-tileflow-state": effectiveCaptureState,
1060
+ style: frameStyle,
1061
+ children: [
1062
+ /* @__PURE__ */ jsx("div", { ref: containerRef, style: mapContainerStyle }),
1063
+ diagnosticView
1064
+ ]
1065
+ }
1066
+ ),
1067
+ annotationRenderTargets.map((target) => {
1068
+ const portalKey = `annotation:${target.key}`;
1069
+ return createPortal(
1070
+ /* @__PURE__ */ jsx(
1071
+ TileflowAnnotationPortal,
1072
+ {
1073
+ onCommit: portalReadiness.markCommitted,
1074
+ portalKey,
1075
+ readinessToken: portalReadiness.token,
1076
+ renderMarker,
1077
+ renderPopup: generalRenderPopup,
1078
+ renderTarget: target,
1079
+ renderTooltip: generalRenderTooltip
1080
+ }
1081
+ ),
1082
+ target.container,
1083
+ portalKey
1084
+ );
1085
+ }),
1086
+ semanticRenderTargets.map((target) => {
1087
+ const portalKey = `semantic:${target.key}`;
1088
+ return createPortal(
1089
+ /* @__PURE__ */ jsx(
1090
+ TileflowSemanticPortal,
1091
+ {
1092
+ onCommit: portalReadiness.markCommitted,
1093
+ portalKey,
1094
+ readinessToken: portalReadiness.token,
1095
+ renderPopup: generalRenderPopup,
1096
+ renderTarget: target,
1097
+ renderTooltip: generalRenderTooltip
1098
+ }
1099
+ ),
1100
+ target.container,
1101
+ portalKey
1102
+ );
1103
+ })
1104
+ ] });
1105
+ }
1106
+ var mapContainerStyle = {
1107
+ inset: 0,
1108
+ position: "absolute"
1109
+ };
1110
+ var interactionDiagnosticStyle = {
1111
+ background: "#7f1d1d",
1112
+ border: "1px solid #fecaca",
1113
+ borderRadius: 4,
1114
+ color: "#ffffff",
1115
+ fontFamily: "system-ui, sans-serif",
1116
+ fontSize: 13,
1117
+ left: 12,
1118
+ maxWidth: "calc(100% - 24px)",
1119
+ padding: "8px 10px",
1120
+ position: "absolute",
1121
+ top: 12,
1122
+ zIndex: 10
1123
+ };
1124
+ function TileflowAnnotationPortal({
1125
+ onCommit,
1126
+ portalKey,
1127
+ readinessToken,
1128
+ renderMarker,
1129
+ renderPopup,
1130
+ renderTarget,
1131
+ renderTooltip
1132
+ }) {
1133
+ useLayoutEffect(() => {
1134
+ onCommit(readinessToken, portalKey);
1135
+ }, [onCommit, portalKey, readinessToken]);
1136
+ const { annotation } = renderTarget;
1137
+ const content = renderTarget.kind === "marker" ? annotation.marker?.content : renderTarget.kind === "tooltip" ? annotation.tooltip?.content : annotation.popup?.content;
1138
+ const context = {
1139
+ annotation,
1140
+ close: renderTarget.close,
1141
+ ...content ? { content } : {},
1142
+ target: {
1143
+ annotation,
1144
+ coordinate: annotation.coordinate,
1145
+ kind: "annotation"
1146
+ },
1147
+ ...content?.kind === "view" ? { viewName: content.name } : {}
1148
+ };
1149
+ if (renderTarget.kind === "marker") return renderMarker?.(context) ?? null;
1150
+ const renderer = renderTarget.kind === "tooltip" ? renderTooltip : renderPopup;
1151
+ return renderer?.(context) ?? null;
1152
+ }
1153
+ function TileflowSemanticPortal({
1154
+ onCommit,
1155
+ portalKey,
1156
+ readinessToken,
1157
+ renderPopup,
1158
+ renderTarget,
1159
+ renderTooltip
1160
+ }) {
1161
+ useLayoutEffect(() => {
1162
+ onCommit(readinessToken, portalKey);
1163
+ }, [onCommit, portalKey, readinessToken]);
1164
+ const context = {
1165
+ close: renderTarget.close,
1166
+ content: renderTarget.content,
1167
+ target: renderTarget.target,
1168
+ ...renderTarget.viewName ? { viewName: renderTarget.viewName } : {}
1169
+ };
1170
+ const renderer = renderTarget.kind === "tooltip" ? renderTooltip : renderPopup;
1171
+ return renderer?.(context) ?? null;
1172
+ }
1173
+ function useTileflowPortalReadiness(portalKeys) {
1174
+ const signature = JSON.stringify([...portalKeys].sort());
1175
+ const versionRef = useRef({ signature, version: 0 });
1176
+ if (versionRef.current.signature !== signature) {
1177
+ versionRef.current = { signature, version: versionRef.current.version + 1 };
1178
+ }
1179
+ const token = `${versionRef.current.version}:${signature}`;
1180
+ const expectedKeysRef = useRef({ keys: [...portalKeys].sort(), token });
1181
+ if (expectedKeysRef.current.token !== token) {
1182
+ expectedKeysRef.current = { keys: [...portalKeys].sort(), token };
1183
+ }
1184
+ const expectedKeys = expectedKeysRef.current.keys;
1185
+ const [committed, setCommitted] = useState(() => ({
1186
+ keys: /* @__PURE__ */ new Set(),
1187
+ token
1188
+ }));
1189
+ const [readyToken, setReadyToken] = useState(token);
1190
+ const readinessRunRef = useRef(0);
1191
+ const markCommitted = useCallback((committedToken, portalKey) => {
1192
+ setCommitted((current) => {
1193
+ if (current.token !== committedToken) {
1194
+ return { keys: /* @__PURE__ */ new Set([portalKey]), token: committedToken };
1195
+ }
1196
+ if (current.keys.has(portalKey)) return current;
1197
+ return { keys: /* @__PURE__ */ new Set([...current.keys, portalKey]), token: committedToken };
1198
+ });
1199
+ }, []);
1200
+ useLayoutEffect(() => {
1201
+ setCommitted((current) => current.token === token ? current : { keys: /* @__PURE__ */ new Set(), token });
1202
+ }, [token]);
1203
+ useLayoutEffect(() => {
1204
+ if (readyToken === token || committed.token !== token || !expectedKeys.every((key) => committed.keys.has(key))) {
1205
+ return;
371
1206
  }
372
- );
1207
+ const browserWindow = globalThis.window;
1208
+ const run = ++readinessRunRef.current;
1209
+ let secondFrame;
1210
+ const firstFrame = browserWindow.requestAnimationFrame(() => {
1211
+ secondFrame = browserWindow.requestAnimationFrame(() => {
1212
+ if (readinessRunRef.current === run) setReadyToken(token);
1213
+ });
1214
+ });
1215
+ return () => {
1216
+ readinessRunRef.current += 1;
1217
+ browserWindow.cancelAnimationFrame(firstFrame);
1218
+ if (secondFrame !== void 0) browserWindow.cancelAnimationFrame(secondFrame);
1219
+ };
1220
+ }, [committed, expectedKeys, readyToken, token]);
1221
+ return { idle: readyToken === token, markCommitted, token };
1222
+ }
1223
+ function wrapMapLibrePositioned(positioned) {
1224
+ return {
1225
+ addTo(map) {
1226
+ positioned.addTo(map);
1227
+ },
1228
+ remove() {
1229
+ positioned.remove();
1230
+ },
1231
+ setLngLat(coordinate) {
1232
+ positioned.setLngLat([coordinate[0], coordinate[1]]);
1233
+ }
1234
+ };
1235
+ }
1236
+ function createTileflowMapLibrePoiMap(map) {
1237
+ return {
1238
+ getStyle: () => map.getStyle(),
1239
+ on(event, listener) {
1240
+ return map.on(event, listener);
1241
+ },
1242
+ queryRenderedFeatures(point, options) {
1243
+ return map.queryRenderedFeatures(point, {
1244
+ layers: [...options.layers]
1245
+ });
1246
+ }
1247
+ };
1248
+ }
1249
+ function createOverlayFailureDiagnostic(message) {
1250
+ return {
1251
+ code: "OVERLAY_FAILURE",
1252
+ level: "error",
1253
+ message,
1254
+ path: ""
1255
+ };
1256
+ }
1257
+ function tileflowInteractionDiagnosticKey(diagnostic) {
1258
+ return [
1259
+ diagnostic.code,
1260
+ diagnostic.level,
1261
+ diagnostic.path ?? "",
1262
+ diagnostic.target ? JSON.stringify(diagnostic.target) : "",
1263
+ diagnostic.message
1264
+ ].join("\0");
1265
+ }
1266
+ function runTileflowReactCleanups(cleanups) {
1267
+ const errors = [];
1268
+ for (const cleanup of cleanups) {
1269
+ try {
1270
+ cleanup();
1271
+ } catch (error) {
1272
+ errors.push(error);
1273
+ }
1274
+ }
1275
+ if (errors.length === 1) throw errors[0];
1276
+ if (errors.length > 1) throw new AggregateError(errors, "Tileflow React cleanup failed.");
373
1277
  }
374
1278
  function useStableMapOptions(options) {
375
1279
  return useStableMapOptionValue(options);
@@ -410,5 +1314,5 @@ function isPlainObject(value) {
410
1314
  return prototype === Object.prototype || prototype === null;
411
1315
  }
412
1316
  export {
413
- Map
1317
+ Map2 as Map
414
1318
  };