@tileflow/react 0.1.0-alpha.13 → 0.1.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +94 -14
- package/dist/static.d.ts +4 -3
- package/dist/static.js +40 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -55,4 +55,18 @@ the production image behavior locally.
|
|
|
55
55
|
For full static scenes with bounds, overlays, and server-side precache, use
|
|
56
56
|
`StaticMap` from `@tileflow/react/static` or helpers from `@tileflow/static`.
|
|
57
57
|
|
|
58
|
+
## Headless capture readiness
|
|
59
|
+
|
|
60
|
+
Use `captureId` to disambiguate multiple maps with the same configured name:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<Map map="madrid" captureId="checkout-map" />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The root element exposes `data-tileflow-map`, optional `data-tileflow-capture-id`, and
|
|
67
|
+
`data-tileflow-state="loading|idle|error"`. Interactive mode becomes idle after MapLibre is idle
|
|
68
|
+
and two animation frames; style/data work returns it to loading. Image mode waits for decode or a
|
|
69
|
+
successful load fallback, including cached hydration. `tileflow capture` uses these markers for
|
|
70
|
+
application scenes and requires exactly one target.
|
|
71
|
+
|
|
58
72
|
Docs: https://tileflow.dev/docs
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { TileflowConfig, MapLibreStyle, TileflowProjectThemes, TileflowMapMarker
|
|
|
6
6
|
type MapMarker = TileflowMapMarker;
|
|
7
7
|
type TileflowMapOptions = Omit<MapOptions, 'container' | 'style'>;
|
|
8
8
|
type MapProps = {
|
|
9
|
+
captureId?: string;
|
|
9
10
|
map?: string;
|
|
10
11
|
mode?: 'interactive' | 'image';
|
|
11
12
|
config?: TileflowConfig;
|
|
@@ -29,6 +30,6 @@ type MapProps = {
|
|
|
29
30
|
analytics?: TileflowAnalytics;
|
|
30
31
|
onLoad?: (map: Map$1) => void;
|
|
31
32
|
};
|
|
32
|
-
declare function Map({ map: mapName, mode, config, style, styleUrl, imageUrl, styleBaseUrl, tileBaseUrl, themes, manifestUrl, preferLocalDev, alt, center, zoom, className, height, imageLoading, interactive, mapOptions, markers, analytics, onLoad, }: MapProps): react.JSX.Element;
|
|
33
|
+
declare function Map({ captureId, map: mapName, mode, config, style, styleUrl, imageUrl, styleBaseUrl, tileBaseUrl, themes, manifestUrl, preferLocalDev, alt, center, zoom, className, height, imageLoading, interactive, mapOptions, markers, analytics, onLoad, }: MapProps): react.JSX.Element;
|
|
33
34
|
|
|
34
35
|
export { Map, type MapMarker, type MapProps, type TileflowMapOptions };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/map.tsx
|
|
4
4
|
import {
|
|
5
5
|
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
6
7
|
useMemo,
|
|
7
8
|
useRef,
|
|
8
9
|
useState
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
defaultTileflowManifestUrl,
|
|
14
15
|
loadTileflowManifest,
|
|
15
16
|
mergeTileflowAnalytics,
|
|
17
|
+
normalizeTileflowCaptureId,
|
|
16
18
|
normalizeTileflowStaticImageSize,
|
|
17
19
|
resolveTileflowAnalyticsRequestUrl,
|
|
18
20
|
resolveTileflowManifestMap,
|
|
@@ -26,6 +28,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
26
28
|
var defaultCenter = [0, 20];
|
|
27
29
|
var defaultMarkers = [];
|
|
28
30
|
function Map({
|
|
31
|
+
captureId,
|
|
29
32
|
map: mapName,
|
|
30
33
|
mode = "interactive",
|
|
31
34
|
config,
|
|
@@ -50,6 +53,7 @@ function Map({
|
|
|
50
53
|
onLoad
|
|
51
54
|
}) {
|
|
52
55
|
const containerRef = useRef(null);
|
|
56
|
+
const imageRef = useRef(null);
|
|
53
57
|
const mapRef = useRef(null);
|
|
54
58
|
const markerInstancesRef = useRef([]);
|
|
55
59
|
const stableMapOptions = useStableMapOptions(mapOptions);
|
|
@@ -65,9 +69,12 @@ function Map({
|
|
|
65
69
|
const mapNameRef = useRef(mapName);
|
|
66
70
|
const resolvedAnalyticsRef = useRef(void 0);
|
|
67
71
|
const sessionStartsRef = useRef(/* @__PURE__ */ new Set());
|
|
72
|
+
const readinessRunRef = useRef(0);
|
|
68
73
|
const sessionId = useMemo(() => createTileflowSessionId(), []);
|
|
69
74
|
const [manifestMap, setManifestMap] = useState(null);
|
|
70
75
|
const [imageSize, setImageSize] = useState(null);
|
|
76
|
+
const [captureState, setCaptureState] = useState("loading");
|
|
77
|
+
const resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
71
78
|
const resolvedMode = resolveTileflowMapMode({ imageUrl, mode, preferLocalDev });
|
|
72
79
|
const isImageMode = resolvedMode === "image";
|
|
73
80
|
useEffect(() => {
|
|
@@ -192,6 +199,25 @@ function Map({
|
|
|
192
199
|
if (!containerRef.current || !runtimeStyle) {
|
|
193
200
|
return;
|
|
194
201
|
}
|
|
202
|
+
const setLoading = () => {
|
|
203
|
+
readinessRunRef.current += 1;
|
|
204
|
+
setCaptureState("loading");
|
|
205
|
+
};
|
|
206
|
+
const setError = () => {
|
|
207
|
+
readinessRunRef.current += 1;
|
|
208
|
+
setCaptureState("error");
|
|
209
|
+
};
|
|
210
|
+
const setIdleAfterFrames = () => {
|
|
211
|
+
const run = ++readinessRunRef.current;
|
|
212
|
+
requestAnimationFrame(() => {
|
|
213
|
+
requestAnimationFrame(() => {
|
|
214
|
+
if (readinessRunRef.current === run && mapRef.current === map) {
|
|
215
|
+
setCaptureState("idle");
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
setLoading();
|
|
195
221
|
const map = new maplibregl.Map({
|
|
196
222
|
...stableMapOptions,
|
|
197
223
|
container: containerRef.current,
|
|
@@ -228,7 +254,12 @@ function Map({
|
|
|
228
254
|
}
|
|
229
255
|
}
|
|
230
256
|
});
|
|
257
|
+
map.on("dataloading", setLoading);
|
|
258
|
+
map.on("styledataloading", setLoading);
|
|
259
|
+
map.on("idle", setIdleAfterFrames);
|
|
260
|
+
map.on("error", setError);
|
|
231
261
|
return () => {
|
|
262
|
+
readinessRunRef.current += 1;
|
|
232
263
|
resizeObserver.disconnect();
|
|
233
264
|
clearMarkers(markerInstancesRef.current);
|
|
234
265
|
markerInstancesRef.current = [];
|
|
@@ -236,6 +267,28 @@ function Map({
|
|
|
236
267
|
mapRef.current = null;
|
|
237
268
|
};
|
|
238
269
|
}, [resolvedInteractive, runtimeStyle, sessionId, stableMapOptions, transformRequest]);
|
|
270
|
+
useLayoutEffect(() => {
|
|
271
|
+
if (!isImageMode) return;
|
|
272
|
+
readinessRunRef.current += 1;
|
|
273
|
+
setCaptureState("loading");
|
|
274
|
+
const image = imageRef.current;
|
|
275
|
+
if (image?.complete) {
|
|
276
|
+
if (image.naturalWidth === 0) setCaptureState("error");
|
|
277
|
+
else void markImageReady(image);
|
|
278
|
+
}
|
|
279
|
+
}, [isImageMode, runtimeImageUrl]);
|
|
280
|
+
async function markImageReady(image) {
|
|
281
|
+
const run = ++readinessRunRef.current;
|
|
282
|
+
try {
|
|
283
|
+
if (typeof image.decode === "function") await image.decode();
|
|
284
|
+
} catch {
|
|
285
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
286
|
+
if (readinessRunRef.current === run) setCaptureState("error");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (readinessRunRef.current === run) setCaptureState("idle");
|
|
291
|
+
}
|
|
239
292
|
useEffect(() => {
|
|
240
293
|
const map = mapRef.current;
|
|
241
294
|
clearMarkers(markerInstancesRef.current);
|
|
@@ -263,23 +316,50 @@ function Map({
|
|
|
263
316
|
width: "100%"
|
|
264
317
|
};
|
|
265
318
|
if (isImageMode) {
|
|
266
|
-
return /* @__PURE__ */ jsx(
|
|
267
|
-
"
|
|
319
|
+
return /* @__PURE__ */ jsx(
|
|
320
|
+
"div",
|
|
268
321
|
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
322
|
+
className,
|
|
323
|
+
"data-tileflow-capture-id": resolvedCaptureId,
|
|
324
|
+
"data-tileflow-map": mapName,
|
|
325
|
+
"data-tileflow-state": captureState,
|
|
326
|
+
ref: containerRef,
|
|
327
|
+
style: frameStyle,
|
|
328
|
+
children: runtimeImageUrl ? /* @__PURE__ */ jsx(
|
|
329
|
+
"img",
|
|
330
|
+
{
|
|
331
|
+
alt,
|
|
332
|
+
decoding: "async",
|
|
333
|
+
loading: imageLoading,
|
|
334
|
+
onError: () => {
|
|
335
|
+
readinessRunRef.current += 1;
|
|
336
|
+
setCaptureState("error");
|
|
337
|
+
},
|
|
338
|
+
onLoad: (event) => void markImageReady(event.currentTarget),
|
|
339
|
+
ref: imageRef,
|
|
340
|
+
src: runtimeImageUrl,
|
|
341
|
+
style: {
|
|
342
|
+
display: "block",
|
|
343
|
+
height: "100%",
|
|
344
|
+
objectFit: "cover",
|
|
345
|
+
width: "100%"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
) : null
|
|
279
349
|
}
|
|
280
|
-
)
|
|
350
|
+
);
|
|
281
351
|
}
|
|
282
|
-
return /* @__PURE__ */ jsx(
|
|
352
|
+
return /* @__PURE__ */ jsx(
|
|
353
|
+
"div",
|
|
354
|
+
{
|
|
355
|
+
className,
|
|
356
|
+
"data-tileflow-capture-id": resolvedCaptureId,
|
|
357
|
+
"data-tileflow-map": mapName,
|
|
358
|
+
"data-tileflow-state": captureState,
|
|
359
|
+
ref: containerRef,
|
|
360
|
+
style: frameStyle
|
|
361
|
+
}
|
|
362
|
+
);
|
|
283
363
|
}
|
|
284
364
|
function createTransformRequest(getAnalytics, sessionId, userTransformRequest) {
|
|
285
365
|
return (url, resourceType) => {
|
package/dist/static.d.ts
CHANGED
|
@@ -5,11 +5,12 @@ import { StaticSceneInput, StaticMapResult } from '@tileflow/static';
|
|
|
5
5
|
type StaticMapBaseProps = StaticSceneInput & {
|
|
6
6
|
alt?: string;
|
|
7
7
|
className?: string;
|
|
8
|
+
captureId?: string;
|
|
8
9
|
imageStyle?: CSSProperties;
|
|
9
10
|
keepPreviousImage?: boolean;
|
|
10
|
-
loading?: ImgHTMLAttributes<HTMLImageElement>[
|
|
11
|
+
loading?: ImgHTMLAttributes<HTMLImageElement>['loading'];
|
|
11
12
|
onError?: (error: Error) => void;
|
|
12
|
-
onImageLoad?: ImgHTMLAttributes<HTMLImageElement>[
|
|
13
|
+
onImageLoad?: ImgHTMLAttributes<HTMLImageElement>['onLoad'];
|
|
13
14
|
onReady?: (result: StaticMapResult) => void;
|
|
14
15
|
style?: CSSProperties;
|
|
15
16
|
};
|
|
@@ -22,6 +23,6 @@ type StaticMapCreateProps = {
|
|
|
22
23
|
imageUrl?: never;
|
|
23
24
|
};
|
|
24
25
|
type StaticMapProps = StaticMapBaseProps & (StaticMapCreateProps | StaticMapImageProps);
|
|
25
|
-
declare function StaticMap({ alt, camera, className, createUrl, imageStyle, imageUrl, keepPreviousImage, loading, map, onError, onImageLoad, onReady, overlays, size, style, }: StaticMapProps): react.JSX.Element;
|
|
26
|
+
declare function StaticMap({ alt, camera, className, captureId, createUrl, imageStyle, imageUrl, keepPreviousImage, loading, map, onError, onImageLoad, onReady, overlays, size, style, }: StaticMapProps): react.JSX.Element;
|
|
26
27
|
|
|
27
28
|
export { StaticMap, type StaticMapProps };
|
package/dist/static.js
CHANGED
|
@@ -3,16 +3,19 @@
|
|
|
3
3
|
// src/static-map.tsx
|
|
4
4
|
import {
|
|
5
5
|
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
6
7
|
useMemo,
|
|
7
8
|
useRef,
|
|
8
9
|
useState
|
|
9
10
|
} from "react";
|
|
11
|
+
import { normalizeTileflowCaptureId } from "@tileflow/core";
|
|
10
12
|
import { jsx } from "react/jsx-runtime";
|
|
11
13
|
var inFlightRequests = /* @__PURE__ */ new Map();
|
|
12
14
|
function StaticMap({
|
|
13
15
|
alt = "",
|
|
14
16
|
camera,
|
|
15
17
|
className,
|
|
18
|
+
captureId,
|
|
16
19
|
createUrl,
|
|
17
20
|
imageStyle,
|
|
18
21
|
imageUrl,
|
|
@@ -30,6 +33,10 @@ function StaticMap({
|
|
|
30
33
|
imageUrl ? imageResult(imageUrl) : null
|
|
31
34
|
);
|
|
32
35
|
const [error, setError] = useState(null);
|
|
36
|
+
const [captureState, setCaptureState] = useState("loading");
|
|
37
|
+
const imageLoadRunRef = useRef(0);
|
|
38
|
+
const imageRef = useRef(null);
|
|
39
|
+
const resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
33
40
|
const onErrorRef = useRef(onError);
|
|
34
41
|
const onReadyRef = useRef(onReady);
|
|
35
42
|
const sceneKey = useMemo(
|
|
@@ -37,6 +44,27 @@ function StaticMap({
|
|
|
37
44
|
[camera, map, overlays, size]
|
|
38
45
|
);
|
|
39
46
|
const requestKey = imageUrl ? `image:${imageUrl}` : `create:${createUrl}:${sceneKey}`;
|
|
47
|
+
useLayoutEffect(() => {
|
|
48
|
+
imageLoadRunRef.current += 1;
|
|
49
|
+
setCaptureState("loading");
|
|
50
|
+
const image = imageRef.current;
|
|
51
|
+
if (image?.complete) {
|
|
52
|
+
if (image.naturalWidth === 0) setCaptureState("error");
|
|
53
|
+
else void markImageReady(image);
|
|
54
|
+
}
|
|
55
|
+
}, [requestKey]);
|
|
56
|
+
async function markImageReady(image) {
|
|
57
|
+
const runId = ++imageLoadRunRef.current;
|
|
58
|
+
try {
|
|
59
|
+
if (typeof image.decode === "function") await image.decode();
|
|
60
|
+
} catch {
|
|
61
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
62
|
+
if (runId === imageLoadRunRef.current) setCaptureState("error");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (runId === imageLoadRunRef.current) setCaptureState("idle");
|
|
67
|
+
}
|
|
40
68
|
useEffect(() => {
|
|
41
69
|
onErrorRef.current = onError;
|
|
42
70
|
onReadyRef.current = onReady;
|
|
@@ -77,6 +105,8 @@ function StaticMap({
|
|
|
77
105
|
setResult(null);
|
|
78
106
|
}
|
|
79
107
|
setError(normalizedError);
|
|
108
|
+
imageLoadRunRef.current += 1;
|
|
109
|
+
setCaptureState("error");
|
|
80
110
|
onErrorRef.current?.(normalizedError);
|
|
81
111
|
});
|
|
82
112
|
return () => {
|
|
@@ -88,6 +118,9 @@ function StaticMap({
|
|
|
88
118
|
{
|
|
89
119
|
"aria-busy": !result && !error,
|
|
90
120
|
className,
|
|
121
|
+
"data-tileflow-capture-id": resolvedCaptureId,
|
|
122
|
+
"data-tileflow-map": map,
|
|
123
|
+
"data-tileflow-state": captureState,
|
|
91
124
|
style: {
|
|
92
125
|
aspectRatio: `${size.width} / ${size.height}`,
|
|
93
126
|
background: "#f4f4f2",
|
|
@@ -104,9 +137,15 @@ function StaticMap({
|
|
|
104
137
|
onError: () => {
|
|
105
138
|
const imageError = new Error("Static map image failed to load");
|
|
106
139
|
setError(imageError);
|
|
140
|
+
imageLoadRunRef.current += 1;
|
|
141
|
+
setCaptureState("error");
|
|
107
142
|
onErrorRef.current?.(imageError);
|
|
108
143
|
},
|
|
109
|
-
onLoad:
|
|
144
|
+
onLoad: (event) => {
|
|
145
|
+
onImageLoad?.(event);
|
|
146
|
+
void markImageReady(event.currentTarget);
|
|
147
|
+
},
|
|
148
|
+
ref: imageRef,
|
|
110
149
|
src: result.imageUrl,
|
|
111
150
|
style: {
|
|
112
151
|
display: "block",
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tileflow/react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.16",
|
|
4
4
|
"description": "React components for rendering Tileflow maps with MapLibre.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"homepage": "https://tileflow.dev",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/tileflow/tileflow.git",
|
|
10
|
+
"url": "git+https://github.com/tileflow/tileflow-sdk.git",
|
|
11
11
|
"directory": "packages/react"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/tileflow/tileflow/issues"
|
|
14
|
+
"url": "https://github.com/tileflow/tileflow-sdk/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"tileflow",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@tileflow/core": "0.1.0-alpha.
|
|
45
|
-
"@tileflow/static": "0.1.0-alpha.
|
|
44
|
+
"@tileflow/core": "0.1.0-alpha.16",
|
|
45
|
+
"@tileflow/static": "0.1.0-alpha.16"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"maplibre-gl": ">=5",
|