@tileflow/react 0.1.0-alpha.2 → 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
@@ -1,156 +1,560 @@
1
+ 'use client';
2
+
1
3
  // src/map.tsx
2
4
  import {
5
+ useCallback,
3
6
  useEffect,
7
+ useLayoutEffect,
4
8
  useMemo,
5
9
  useRef,
6
10
  useState
7
11
  } from "react";
8
- import maplibregl from "maplibre-gl";
12
+ import { createPortal } from "react-dom";
9
13
  import {
10
- createStyle
11
- } from "@tileflow/core";
12
- import { jsx } from "react/jsx-runtime";
13
- var defaultCenter = [0, 20];
14
- var defaultMarkers = [];
15
- var defaultManifestUrl = "/tileflow/manifest.json";
16
- var maxStaticImageDimension = 1280;
17
- var maxStaticImagePixels = 1280 * 1280;
18
- var manifestCache = new globalThis.Map();
19
- function Map({
20
- map: mapName,
21
- mode = "interactive",
22
- config,
23
- style,
24
- styleUrl,
25
- imageUrl,
26
- styleBaseUrl,
27
- tileBaseUrl,
28
- themes,
29
- manifestUrl = defaultManifestUrl,
30
- preferLocalDev = true,
31
- alt = "",
32
- center = defaultCenter,
33
- zoom = 2,
34
- className,
35
- height = 420,
36
- imageLoading = "eager",
37
- interactive = true,
38
- markers = defaultMarkers,
39
- analytics,
40
- onLoad
41
- }) {
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";
26
+ import {
27
+ createTileflowSessionController,
28
+ defaultTileflowManifestUrl,
29
+ defaultTileflowRuntimeView,
30
+ loadTileflowManifest,
31
+ mergeTileflowAnalytics,
32
+ normalizeTileflowRuntimeCenter,
33
+ normalizeTileflowStaticImageSize,
34
+ resolveTileflowManifestMap,
35
+ resolveTileflowMapMode,
36
+ resolveTileflowRuntimeStyle,
37
+ resolveTileflowRuntimeTheme,
38
+ resolveTileflowRuntimeView,
39
+ resolveTileflowStaticImageUrl,
40
+ shouldLoadTileflowManifest
41
+ } from "@tileflow/core/runtime";
42
+ import {
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
+ );
42
282
  const containerRef = useRef(null);
283
+ const imageRef = useRef(null);
43
284
  const mapRef = useRef(null);
44
- const sessionId = useMemo(() => createSessionId(), []);
45
- const [manifestMap, setManifestMap] = useState(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");
46
312
  const [imageSize, setImageSize] = useState(null);
47
- const isImageMode = mode === "image";
48
- const useExplicitStyleBaseUrl = useMemo(
49
- () => Boolean(!isImageMode && mapName && !style && !styleUrl && !config && styleBaseUrl),
50
- [config, isImageMode, mapName, style, styleBaseUrl, styleUrl]
313
+ const [captureState, setCaptureState] = useState("loading");
314
+ const [themeTransitionState, setThemeTransitionState] = useState(
315
+ "idle"
51
316
  );
52
- const useLocalDevFallback = useMemo(
53
- () => Boolean(
54
- !isImageMode && mapName && !style && !styleUrl && !config && !styleBaseUrl && preferLocalDev && isLocalDevHost()
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([]);
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
+ );
361
+ const resolvedCenter = useStableMapOptionValue(
362
+ center ?? stableMapOptions?.center ?? manifestCenter
363
+ );
364
+ const resolvedZoom = zoom ?? stableMapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
365
+ const resolvedBearing = stableMapOptions?.bearing ?? manifestView.bearing;
366
+ const resolvedPitch = stableMapOptions?.pitch ?? manifestView.pitch;
367
+ const resolvedInteractive = interactive ?? stableMapOptions?.interactive ?? true;
368
+ const staticImageCenter = useStableMapOptionValue(
369
+ normalizeTileflowRuntimeCenter(center ?? stableMapOptions?.center, manifestCenter)
370
+ );
371
+ const staticImageZoom = zoom ?? stableMapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
372
+ const centerRef = useRef(resolvedCenter);
373
+ const zoomRef = useRef(resolvedZoom);
374
+ const bearingRef = useRef(resolvedBearing);
375
+ const pitchRef = useRef(resolvedPitch);
376
+ const onLoadRef = useRef(onLoad);
377
+ const mapNameRef = useRef(mapName);
378
+ const resolvedAnalyticsRef = useRef(void 0);
379
+ const readinessRunRef = useRef(0);
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
+ ]
393
+ );
394
+ const declarativeInteractionDiagnostics = useMemo(
395
+ () => [
396
+ modeDiagnostic,
397
+ ...preparedAnnotations.diagnostics,
398
+ ...preparedInteractions.diagnostics,
399
+ ...preparedInteractionState.diagnostics
400
+ ].filter(
401
+ (diagnostic) => diagnostic !== void 0
55
402
  ),
56
- [config, isImageMode, mapName, preferLocalDev, style, styleBaseUrl, styleUrl]
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"
57
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]);
58
459
  useEffect(() => {
59
- const shouldLoadManifest = Boolean(
60
- mapName && !config && (isImageMode && !imageUrl || !isImageMode && !style && !styleUrl && !useExplicitStyleBaseUrl)
61
- );
62
460
  if (!shouldLoadManifest) {
63
- setManifestMap(null);
461
+ setManifestResolution({ key: manifestRequestKey, map: null, state: "not-needed" });
64
462
  return;
65
463
  }
66
464
  let cancelled = false;
67
- loadManifest(manifestUrl).then((manifest) => {
465
+ setManifestResolution({ key: manifestRequestKey, map: null, state: "loading" });
466
+ loadTileflowManifest(manifestUrl).then((manifest) => {
68
467
  if (cancelled) {
69
468
  return;
70
469
  }
71
- setManifestMap(manifest ? resolveManifestMap(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
+ });
72
476
  }).catch((error) => {
73
477
  if (!cancelled) {
74
478
  console.error("Failed to load Tileflow manifest", error);
75
- setManifestMap(null);
479
+ setManifestResolution({ key: manifestRequestKey, map: null, state: "error" });
76
480
  }
77
481
  });
78
482
  return () => {
79
483
  cancelled = true;
80
484
  };
81
- }, [
82
- config,
83
- imageUrl,
84
- isImageMode,
85
- manifestUrl,
86
- mapName,
87
- style,
88
- styleUrl,
89
- useExplicitStyleBaseUrl
90
- ]);
485
+ }, [manifestRequestKey, manifestUrl, mapName, shouldLoadManifest]);
91
486
  const runtimeStyle = useMemo(() => {
92
- if (isImageMode) {
487
+ if (isImageMode || themeResolution.error) {
93
488
  return null;
94
489
  }
95
- if (style) {
96
- return { style };
97
- }
98
- if (styleUrl) {
99
- return {
100
- analytics: inferAnalyticsFromStyleUrl(styleUrl),
101
- style: styleUrl
102
- };
103
- }
104
- if (mapName && useExplicitStyleBaseUrl) {
105
- return { style: resolveStyleUrl(mapName, styleBaseUrl) };
106
- }
107
- if (mapName) {
108
- if (manifestMap?.styleUrl) {
109
- return {
110
- analytics: {
111
- apiUrl: manifestMap.apiUrl,
112
- mapId: manifestMap.mapId,
113
- styleId: manifestMap.styleId
114
- },
115
- style: manifestMap.styleUrl
116
- };
117
- }
118
- if (useLocalDevFallback) {
119
- return { style: resolveStyleUrl(mapName) };
120
- }
121
- return null;
122
- }
123
- return { style: createStyle(config, { themes, tileBaseUrl }) };
124
- }, [
125
- config,
126
- isImageMode,
127
- manifestMap,
128
- mapName,
129
- style,
130
- styleBaseUrl,
131
- styleUrl,
132
- themes,
133
- tileBaseUrl,
134
- useExplicitStyleBaseUrl,
135
- useLocalDevFallback
136
- ]);
490
+ return resolveTileflowRuntimeStyle({
491
+ colorScheme: systemColorScheme,
492
+ manifestMap,
493
+ source: runtimeSource,
494
+ theme: themeResolution.name
495
+ });
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;
137
500
  const resolvedAnalytics = useMemo(
138
- () => mergeAnalytics(analytics, runtimeStyle?.analytics),
501
+ () => mergeTileflowAnalytics(analytics, runtimeStyle?.analytics),
139
502
  [analytics, runtimeStyle]
140
503
  );
504
+ useEffect(() => {
505
+ centerRef.current = resolvedCenter;
506
+ zoomRef.current = resolvedZoom;
507
+ bearingRef.current = resolvedBearing;
508
+ pitchRef.current = resolvedPitch;
509
+ mapRef.current?.jumpTo({
510
+ bearing: resolvedBearing,
511
+ center: resolvedCenter,
512
+ pitch: resolvedPitch,
513
+ zoom: resolvedZoom
514
+ });
515
+ }, [resolvedBearing, resolvedCenter, resolvedPitch, resolvedZoom]);
516
+ useEffect(() => {
517
+ onLoadRef.current = onLoad;
518
+ }, [onLoad]);
519
+ useEffect(() => {
520
+ mapNameRef.current = mapName;
521
+ }, [mapName]);
522
+ useEffect(() => {
523
+ resolvedAnalyticsRef.current = resolvedAnalytics;
524
+ }, [resolvedAnalytics]);
141
525
  const runtimeImageUrl = useMemo(
142
- () => imageUrl ?? (isImageMode ? resolveStaticImageUrl({
143
- center,
526
+ () => imageUrl ?? (isImageMode && !themeResolution.error ? resolveTileflowStaticImageUrl({
527
+ center: staticImageCenter,
528
+ colorScheme: systemColorScheme,
144
529
  imageSize,
145
530
  manifestMap,
146
- zoom
531
+ theme: themeResolution.name,
532
+ zoom: staticImageZoom
147
533
  }) : void 0),
148
- [center, imageSize, imageUrl, isImageMode, manifestMap, zoom]
149
- );
150
- const transformRequest = useMemo(
151
- () => createTransformRequest(resolvedAnalytics, sessionId),
152
- [resolvedAnalytics, sessionId]
534
+ [
535
+ imageSize,
536
+ imageUrl,
537
+ isImageMode,
538
+ manifestMap,
539
+ staticImageCenter,
540
+ staticImageZoom,
541
+ systemColorScheme,
542
+ themeResolution
543
+ ]
153
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";
154
558
  useEffect(() => {
155
559
  if (!isImageMode || imageUrl || !containerRef.current) {
156
560
  setImageSize(null);
@@ -158,7 +562,7 @@ function Map({
158
562
  }
159
563
  const element = containerRef.current;
160
564
  const updateImageSize = () => {
161
- const nextSize = normalizeStaticImageSize({
565
+ const nextSize = normalizeTileflowStaticImageSize({
162
566
  height: element.clientHeight,
163
567
  width: element.clientWidth
164
568
  });
@@ -174,60 +578,422 @@ function Map({
174
578
  };
175
579
  }, [imageUrl, isImageMode]);
176
580
  useEffect(() => {
177
- if (!containerRef.current || !runtimeStyle) {
178
- return;
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);
179
593
  }
180
- const map = new maplibregl.Map({
181
- container: containerRef.current,
182
- style: runtimeStyle.style,
183
- center,
184
- zoom,
185
- interactive,
186
- attributionControl: { compact: true },
187
- transformRequest
188
- });
189
- mapRef.current = map;
190
- const controls = interactive ? new maplibregl.NavigationControl() : null;
191
- if (controls) {
192
- map.addControl(controls, "top-right");
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);
193
608
  }
194
- const markerInstances = markers.map((marker) => {
195
- const element = document.createElement("div");
196
- element.className = "tileflow-marker";
197
- element.style.background = marker.color ?? "#C6A15B";
198
- element.title = marker.label ?? marker.id;
199
- return new maplibregl.Marker({ element }).setLngLat(marker.coordinates).addTo(map);
200
- });
201
- const resizeObserver = new ResizeObserver(() => {
202
- map.resize();
203
- });
204
- resizeObserver.observe(containerRef.current);
205
- map.on("load", () => {
206
- onLoad?.(map);
207
- startSession(resolvedAnalytics, {
208
- mapId: resolvedAnalytics?.mapId,
209
- sessionId,
210
- styleId: resolvedAnalytics?.styleId ?? (typeof runtimeStyle.style === "string" ? runtimeStyle.style : mapName)
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
211
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) {
653
+ return;
654
+ }
655
+ readinessRunRef.current += 1;
656
+ setActiveRuntimeResource(initialRuntimeStyle);
657
+ setCaptureState("loading");
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;
669
+ }
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;
677
+ try {
678
+ runTileflowReactCleanups([...setupCleanups].reverse());
679
+ } finally {
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;
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);
902
+ }
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
+ }
212
952
  });
213
953
  return () => {
214
- resizeObserver.disconnect();
215
- markerInstances.forEach((marker) => marker.remove());
216
- map.remove();
217
- mapRef.current = null;
954
+ cancelled = true;
955
+ readinessRunRef.current += 1;
956
+ disposeMap?.();
218
957
  };
219
- }, [
220
- center,
221
- interactive,
222
- mapName,
223
- markers,
224
- onLoad,
225
- resolvedAnalytics,
226
- runtimeStyle,
227
- sessionId,
228
- transformRequest,
229
- zoom
230
- ]);
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]);
974
+ useLayoutEffect(() => {
975
+ if (!isImageMode) return;
976
+ readinessRunRef.current += 1;
977
+ setActiveRuntimeResource(runtimeImageUrl);
978
+ setCaptureState("loading");
979
+ const image = imageRef.current;
980
+ if (image?.complete) {
981
+ if (image.naturalWidth === 0) setCaptureState("error");
982
+ else void markImageReady(image);
983
+ }
984
+ }, [isImageMode, runtimeImageUrl]);
985
+ async function markImageReady(image) {
986
+ const run = ++readinessRunRef.current;
987
+ try {
988
+ if (typeof image.decode === "function") await image.decode();
989
+ } catch {
990
+ if (!image.complete || image.naturalWidth === 0) {
991
+ if (readinessRunRef.current === run) setCaptureState("error");
992
+ return;
993
+ }
994
+ }
995
+ if (readinessRunRef.current === run) setCaptureState("idle");
996
+ }
231
997
  const frameStyle = {
232
998
  height,
233
999
  minHeight: 240,
@@ -235,228 +1001,318 @@ function Map({
235
1001
  position: "relative",
236
1002
  width: "100%"
237
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;
238
1013
  if (isImageMode) {
239
- return /* @__PURE__ */ jsx("div", { className, ref: containerRef, style: frameStyle, children: runtimeImageUrl ? /* @__PURE__ */ jsx(
240
- "img",
1014
+ return /* @__PURE__ */ jsxs(
1015
+ "div",
241
1016
  {
242
- alt,
243
- decoding: "async",
244
- loading: imageLoading,
245
- src: runtimeImageUrl,
246
- style: {
247
- display: "block",
248
- height: "100%",
249
- objectFit: "cover",
250
- width: "100%"
251
- }
1017
+ className,
1018
+ "data-tileflow-capture-id": resolvedCaptureId,
1019
+ "data-tileflow-map": mapName,
1020
+ "data-tileflow-theme": resolvedThemeName,
1021
+ "data-tileflow-state": effectiveCaptureState,
1022
+ ref: containerRef,
1023
+ style: frameStyle,
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
+ }
1044
+ }
1045
+ ) : null,
1046
+ diagnosticView
1047
+ ]
252
1048
  }
253
- ) : null });
1049
+ );
254
1050
  }
255
- return /* @__PURE__ */ jsx("div", { className, ref: containerRef, style: frameStyle });
256
- }
257
- function resolveStyleUrl(mapName, styleBaseUrl) {
258
- const baseUrl = normalizeUrl(styleBaseUrl ?? getDefaultStyleBaseUrl());
259
- return `${baseUrl}/styles/${mapName}.json`;
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
+ ] });
260
1105
  }
261
- function getDefaultStyleBaseUrl() {
262
- const globalValue = globalThis.__TILEFLOW_STYLE_BASE_URL__;
263
- return globalValue ?? "http://localhost:3333";
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;
264
1152
  }
265
- function normalizeUrl(value) {
266
- return value.replace(/\/+$/, "");
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;
267
1172
  }
268
- function createSessionId() {
269
- if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
270
- return `ses_${crypto.randomUUID()}`;
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 };
271
1178
  }
272
- return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
273
- }
274
- function createTransformRequest(analytics, sessionId) {
275
- if (!analytics || analytics.enabled === false) {
276
- return void 0;
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 };
277
1183
  }
278
- return (url) => {
279
- const apiUrl = getAnalyticsApiUrl(analytics);
280
- if (!apiUrl || !isTileflowTileUrl(url, apiUrl)) {
281
- return void 0;
282
- }
283
- const nextUrl = new URL(url);
284
- nextUrl.searchParams.set("session", sessionId);
285
- if (analytics.mapId) {
286
- nextUrl.searchParams.set("map", analytics.mapId);
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;
287
1206
  }
288
- if (analytics.styleId) {
289
- nextUrl.searchParams.set("styleId", analytics.styleId);
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]]);
290
1233
  }
291
- return { url: nextUrl.toString() };
292
1234
  };
293
1235
  }
294
- function startSession(analytics, input) {
295
- if (!analytics || analytics.enabled === false || !input.mapId || typeof navigator === "undefined") {
296
- return;
297
- }
298
- const endpoint = analytics.endpoint ?? `${getAnalyticsApiUrl(analytics)}/v1/sessions/start`;
299
- const payload = JSON.stringify({
300
- mapId: input.mapId,
301
- metadata: analytics.metadata,
302
- referrer: typeof document === "undefined" ? void 0 : document.referrer || "",
303
- sdkVersion: analytics.sdkVersion,
304
- sessionId: input.sessionId,
305
- source: analytics.source ?? "react",
306
- styleId: input.styleId,
307
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
308
- });
309
- const blob = new Blob([payload], { type: "application/json" });
310
- if (navigator.sendBeacon?.(endpoint, blob)) {
311
- return;
312
- }
313
- void fetch(endpoint, {
314
- body: payload,
315
- credentials: "omit",
316
- headers: {
317
- "Content-Type": "application/json"
1236
+ function createTileflowMapLibrePoiMap(map) {
1237
+ return {
1238
+ getStyle: () => map.getStyle(),
1239
+ on(event, listener) {
1240
+ return map.on(event, listener);
318
1241
  },
319
- keepalive: true,
320
- method: "POST"
321
- });
322
- }
323
- function getAnalyticsApiUrl(analytics) {
324
- if (analytics.apiUrl) {
325
- return normalizeUrl(analytics.apiUrl);
326
- }
327
- if (analytics.endpoint) {
328
- const url = new URL(analytics.endpoint);
329
- return url.origin;
330
- }
331
- return "https://api.tileflow.dev";
1242
+ queryRenderedFeatures(point, options) {
1243
+ return map.queryRenderedFeatures(point, {
1244
+ layers: [...options.layers]
1245
+ });
1246
+ }
1247
+ };
332
1248
  }
333
- function mergeAnalytics(userAnalytics, runtimeAnalytics) {
334
- if (userAnalytics?.enabled === false) {
335
- return userAnalytics;
336
- }
337
- if (!userAnalytics && !runtimeAnalytics?.mapId) {
338
- return void 0;
339
- }
1249
+ function createOverlayFailureDiagnostic(message) {
340
1250
  return {
341
- ...runtimeAnalytics,
342
- ...userAnalytics,
343
- apiUrl: userAnalytics?.apiUrl ?? runtimeAnalytics?.apiUrl,
344
- mapId: userAnalytics?.mapId ?? runtimeAnalytics?.mapId,
345
- styleId: userAnalytics?.styleId ?? runtimeAnalytics?.styleId
1251
+ code: "OVERLAY_FAILURE",
1252
+ level: "error",
1253
+ message,
1254
+ path: ""
346
1255
  };
347
1256
  }
348
- function isTileflowTileUrl(url, apiUrl) {
349
- try {
350
- const parsedUrl = new URL(url);
351
- const parsedApiUrl = new URL(apiUrl);
352
- return parsedUrl.origin === parsedApiUrl.origin && isTilePath(parsedUrl);
353
- } catch {
354
- return false;
355
- }
356
- }
357
- function isTilePath(url) {
358
- return url.pathname.startsWith("/tiles/") || url.pathname.startsWith("/v1/tiles/");
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");
359
1265
  }
360
- function isLocalDevHost() {
361
- if (typeof window === "undefined") {
362
- return false;
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
+ }
363
1274
  }
364
- return ["localhost", "127.0.0.1", "::1"].includes(window.location.hostname);
1275
+ if (errors.length === 1) throw errors[0];
1276
+ if (errors.length > 1) throw new AggregateError(errors, "Tileflow React cleanup failed.");
365
1277
  }
366
- function loadManifest(url) {
367
- if (!manifestCache.has(url)) {
368
- manifestCache.set(
369
- url,
370
- fetch(url, { cache: "no-store" }).then(async (response) => {
371
- if (response.status === 404) {
372
- return null;
373
- }
374
- if (!response.ok) {
375
- throw new Error(`Tileflow manifest failed: ${response.status}`);
376
- }
377
- return await response.json();
378
- })
379
- );
380
- }
381
- return manifestCache.get(url);
1278
+ function useStableMapOptions(options) {
1279
+ return useStableMapOptionValue(options);
382
1280
  }
383
- function resolveManifestMap(manifest, mapName) {
384
- const entry = manifest.maps?.[mapName] ?? manifest.styles?.[mapName];
385
- if (!entry) {
386
- return null;
387
- }
388
- if (typeof entry === "string") {
389
- const analytics2 = inferAnalyticsFromStyleUrl(entry);
390
- return {
391
- apiUrl: analytics2?.apiUrl ?? manifest.apiUrl,
392
- mapId: analytics2?.mapId,
393
- styleId: analytics2?.styleId,
394
- styleUrl: entry
395
- };
396
- }
397
- const styleUrl = entry.styleUrl ?? entry.url;
398
- if (!styleUrl && !entry.mapId) {
399
- return null;
1281
+ function useStableMapOptionValue(value) {
1282
+ const valueRef = useRef(value);
1283
+ if (!areEquivalentMapOptionValues(valueRef.current, value)) {
1284
+ valueRef.current = value;
400
1285
  }
401
- const analytics = styleUrl ? inferAnalyticsFromStyleUrl(styleUrl) : void 0;
402
- return {
403
- apiUrl: entry.apiUrl ?? manifest.apiUrl ?? analytics?.apiUrl,
404
- mapId: entry.mapId ?? analytics?.mapId,
405
- styleId: entry.styleId ?? analytics?.styleId,
406
- styleUrl
407
- };
1286
+ return valueRef.current;
408
1287
  }
409
- function resolveStaticImageUrl(input) {
410
- if (!input.manifestMap?.mapId || !input.imageSize) {
411
- return void 0;
1288
+ function areEquivalentMapOptionValues(left, right, seen = /* @__PURE__ */ new WeakMap()) {
1289
+ if (Object.is(left, right)) {
1290
+ return true;
412
1291
  }
413
- const apiUrl = normalizeUrl(input.manifestMap.apiUrl ?? "https://api.tileflow.dev");
414
- const url = new URL(`/maps/${input.manifestMap.mapId}/static.png`, apiUrl);
415
- url.searchParams.set("center", input.center.join(","));
416
- url.searchParams.set("zoom", String(input.zoom));
417
- url.searchParams.set("width", String(input.imageSize.width));
418
- url.searchParams.set("height", String(input.imageSize.height));
419
- return url.toString();
420
- }
421
- function normalizeStaticImageSize(input) {
422
- let width = clampDimension(input.width);
423
- let height = clampDimension(input.height);
424
- const pixelCount = width * height;
425
- if (pixelCount > maxStaticImagePixels) {
426
- const scale = Math.sqrt(maxStaticImagePixels / pixelCount);
427
- width = clampDimension(width * scale);
428
- height = clampDimension(height * scale);
1292
+ if (!left || !right || typeof left !== "object" || typeof right !== "object") {
1293
+ return false;
429
1294
  }
430
- return { height, width };
431
- }
432
- function clampDimension(value) {
433
- if (!Number.isFinite(value) || value <= 0) {
434
- return 640;
1295
+ const seenRight = seen.get(left);
1296
+ if (seenRight) {
1297
+ return seenRight === right;
435
1298
  }
436
- return Math.max(64, Math.min(maxStaticImageDimension, Math.round(value)));
437
- }
438
- function inferAnalyticsFromStyleUrl(styleUrl) {
439
- try {
440
- const url = new URL(styleUrl, getLocationOrigin());
441
- const match = url.pathname.match(/\/(?:v1\/)?maps\/([^/]+)\/style\.json$/);
442
- if (!match?.[1]) {
443
- return void 0;
444
- }
445
- return {
446
- apiUrl: url.origin,
447
- mapId: match[1],
448
- styleId: url.toString()
449
- };
450
- } catch {
451
- return void 0;
1299
+ seen.set(left, right);
1300
+ if (Array.isArray(left) || Array.isArray(right)) {
1301
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areEquivalentMapOptionValues(value, right[index], seen));
452
1302
  }
453
- }
454
- function getLocationOrigin() {
455
- if (typeof window === "undefined") {
456
- return "http://localhost";
1303
+ if (!isPlainObject(left) || !isPlainObject(right)) {
1304
+ return false;
457
1305
  }
458
- return window.location.origin;
1306
+ const leftKeys = Object.keys(left);
1307
+ const rightKeys = Object.keys(right);
1308
+ return leftKeys.length === rightKeys.length && leftKeys.every(
1309
+ (key) => Object.hasOwn(right, key) && areEquivalentMapOptionValues(left[key], right[key], seen)
1310
+ );
1311
+ }
1312
+ function isPlainObject(value) {
1313
+ const prototype = Object.getPrototypeOf(value);
1314
+ return prototype === Object.prototype || prototype === null;
459
1315
  }
460
1316
  export {
461
- Map
1317
+ Map2 as Map
462
1318
  };