@tileflow/react 0.1.0-alpha.0 → 0.1.0-alpha.10
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 +47 -2
- package/dist/index.d.ts +11 -24
- package/dist/index.js +182 -319
- package/dist/static.js +2 -0
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -2,12 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
React components for rendering Tileflow maps with MapLibre.
|
|
4
4
|
|
|
5
|
+
Install `maplibre-gl` alongside the package and import its CSS once in your app.
|
|
6
|
+
|
|
5
7
|
```tsx
|
|
6
|
-
import {
|
|
8
|
+
import {Map} from '@tileflow/react';
|
|
9
|
+
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
7
10
|
|
|
8
11
|
export function App() {
|
|
9
|
-
return
|
|
12
|
+
return (
|
|
13
|
+
<Map
|
|
14
|
+
map="madrid"
|
|
15
|
+
center={[-3.7038, 40.4168]}
|
|
16
|
+
zoom={12}
|
|
17
|
+
mapOptions={{
|
|
18
|
+
cooperativeGestures: true,
|
|
19
|
+
maxZoom: 18,
|
|
20
|
+
}}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
10
23
|
}
|
|
11
24
|
```
|
|
12
25
|
|
|
26
|
+
`mapOptions` accepts native MapLibre options except `container` and `style`, which
|
|
27
|
+
Tileflow resolves from the map name, manifest, or explicit style props. Direct
|
|
28
|
+
Tileflow props such as `center`, `zoom`, and `interactive` take priority when
|
|
29
|
+
provided.
|
|
30
|
+
|
|
31
|
+
To reuse one published map across multiple repos, point every app at the same
|
|
32
|
+
manifest:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<Map map="madrid" manifestUrl="https://cdn.example.com/tileflow/manifest.json" />
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or bypass the manifest and load a hosted style directly:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<Map styleUrl="https://api.tileflow.dev/v1/styles/acme/madrid.json" />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For simple hosted static images, use image mode:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<Map map="madrid" mode="image" center={[-3.7038, 40.4168]} zoom={12} />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
When `mode="image"` does not include an explicit `imageUrl`, local development
|
|
51
|
+
hosts render the interactive map while `preferLocalDev` is enabled. Production
|
|
52
|
+
hosts render the deployed static image. Set `preferLocalDev={false}` to preview
|
|
53
|
+
the production image behavior locally.
|
|
54
|
+
|
|
55
|
+
For full static scenes with bounds, overlays, and server-side precache, use
|
|
56
|
+
`StaticMap` from `@tileflow/react/static` or helpers from `@tileflow/static`.
|
|
57
|
+
|
|
13
58
|
Docs: https://tileflow.dev/docs
|
package/dist/index.d.ts
CHANGED
|
@@ -1,47 +1,34 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties, ImgHTMLAttributes } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { MapOptions, Map as Map$1 } from 'maplibre-gl';
|
|
4
|
+
import { TileflowConfig, MapLibreStyle, TileflowProjectThemes, TileflowMapMarker, TileflowAnalytics } from '@tileflow/core';
|
|
5
5
|
|
|
6
|
-
type MapMarker =
|
|
7
|
-
|
|
8
|
-
coordinates: [number, number];
|
|
9
|
-
color?: string;
|
|
10
|
-
label?: string;
|
|
11
|
-
};
|
|
12
|
-
type TileflowAnalytics = {
|
|
13
|
-
apiUrl?: string;
|
|
14
|
-
endpoint?: string;
|
|
15
|
-
enabled?: boolean;
|
|
16
|
-
mapId?: string;
|
|
17
|
-
metadata?: Record<string, unknown>;
|
|
18
|
-
projectId?: string;
|
|
19
|
-
sdkVersion?: string;
|
|
20
|
-
source?: string;
|
|
21
|
-
styleId?: string;
|
|
22
|
-
};
|
|
6
|
+
type MapMarker = TileflowMapMarker;
|
|
7
|
+
type TileflowMapOptions = Omit<MapOptions, 'container' | 'style'>;
|
|
23
8
|
type MapProps = {
|
|
24
9
|
map?: string;
|
|
25
|
-
mode?:
|
|
10
|
+
mode?: 'interactive' | 'image';
|
|
26
11
|
config?: TileflowConfig;
|
|
27
12
|
style?: MapLibreStyle;
|
|
28
13
|
styleUrl?: string;
|
|
29
14
|
imageUrl?: string;
|
|
30
15
|
styleBaseUrl?: string;
|
|
31
16
|
tileBaseUrl?: string;
|
|
17
|
+
themes?: TileflowProjectThemes;
|
|
32
18
|
manifestUrl?: string;
|
|
33
19
|
preferLocalDev?: boolean;
|
|
34
20
|
alt?: string;
|
|
35
21
|
center?: [number, number];
|
|
36
22
|
zoom?: number;
|
|
37
23
|
className?: string;
|
|
38
|
-
height?: CSSProperties[
|
|
39
|
-
imageLoading?: ImgHTMLAttributes<HTMLImageElement>[
|
|
24
|
+
height?: CSSProperties['height'];
|
|
25
|
+
imageLoading?: ImgHTMLAttributes<HTMLImageElement>['loading'];
|
|
40
26
|
interactive?: boolean;
|
|
27
|
+
mapOptions?: TileflowMapOptions;
|
|
41
28
|
markers?: MapMarker[];
|
|
42
29
|
analytics?: TileflowAnalytics;
|
|
43
30
|
onLoad?: (map: Map$1) => void;
|
|
44
31
|
};
|
|
45
|
-
declare function Map({ map: mapName, mode, config, style, styleUrl, imageUrl, styleBaseUrl, tileBaseUrl, manifestUrl, preferLocalDev, alt, center, zoom, className, height, imageLoading, interactive, markers, analytics, onLoad, }: MapProps): react.JSX.Element;
|
|
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;
|
|
46
33
|
|
|
47
|
-
export { Map, type MapMarker, type MapProps };
|
|
34
|
+
export { Map, type MapMarker, type MapProps, type TileflowMapOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
// src/map.tsx
|
|
2
|
-
import {
|
|
3
|
-
createStyle
|
|
4
|
-
} from "@tileflow/core";
|
|
5
|
-
import maplibregl from "maplibre-gl";
|
|
6
4
|
import {
|
|
7
5
|
useEffect,
|
|
8
6
|
useMemo,
|
|
9
7
|
useRef,
|
|
10
8
|
useState
|
|
11
9
|
} from "react";
|
|
10
|
+
import maplibregl from "maplibre-gl";
|
|
11
|
+
import {
|
|
12
|
+
createTileflowSessionId,
|
|
13
|
+
defaultTileflowManifestUrl,
|
|
14
|
+
loadTileflowManifest,
|
|
15
|
+
mergeTileflowAnalytics,
|
|
16
|
+
normalizeTileflowStaticImageSize,
|
|
17
|
+
resolveTileflowAnalyticsRequestUrl,
|
|
18
|
+
resolveTileflowManifestMap,
|
|
19
|
+
resolveTileflowMapMode,
|
|
20
|
+
resolveTileflowRuntimeStyle,
|
|
21
|
+
resolveTileflowStaticImageUrl,
|
|
22
|
+
shouldLoadTileflowManifest,
|
|
23
|
+
startTileflowSession
|
|
24
|
+
} from "@tileflow/core";
|
|
12
25
|
import { jsx } from "react/jsx-runtime";
|
|
13
26
|
var defaultCenter = [0, 20];
|
|
14
27
|
var defaultMarkers = [];
|
|
15
|
-
var defaultManifestUrl = "/tileflow/manifest.json";
|
|
16
|
-
var maxStaticImageDimension = 1280;
|
|
17
|
-
var maxStaticImagePixels = 1280 * 1280;
|
|
18
|
-
var manifestCache = new globalThis.Map();
|
|
19
28
|
function Map({
|
|
20
29
|
map: mapName,
|
|
21
30
|
mode = "interactive",
|
|
@@ -25,63 +34,63 @@ function Map({
|
|
|
25
34
|
imageUrl,
|
|
26
35
|
styleBaseUrl,
|
|
27
36
|
tileBaseUrl,
|
|
28
|
-
|
|
37
|
+
themes,
|
|
38
|
+
manifestUrl = defaultTileflowManifestUrl,
|
|
29
39
|
preferLocalDev = true,
|
|
30
40
|
alt = "",
|
|
31
|
-
center
|
|
32
|
-
zoom
|
|
41
|
+
center,
|
|
42
|
+
zoom,
|
|
33
43
|
className,
|
|
34
44
|
height = 420,
|
|
35
45
|
imageLoading = "eager",
|
|
36
|
-
interactive
|
|
46
|
+
interactive,
|
|
47
|
+
mapOptions,
|
|
37
48
|
markers = defaultMarkers,
|
|
38
49
|
analytics,
|
|
39
50
|
onLoad
|
|
40
51
|
}) {
|
|
41
52
|
const containerRef = useRef(null);
|
|
42
53
|
const mapRef = useRef(null);
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
54
|
+
const markerInstancesRef = useRef([]);
|
|
55
|
+
const stableMapOptions = useStableMapOptions(mapOptions);
|
|
56
|
+
const resolvedCenter = useStableMapOptionValue(
|
|
57
|
+
center ?? stableMapOptions?.center ?? defaultCenter
|
|
46
58
|
);
|
|
59
|
+
const resolvedZoom = zoom ?? stableMapOptions?.zoom ?? 2;
|
|
60
|
+
const resolvedInteractive = interactive ?? stableMapOptions?.interactive ?? true;
|
|
61
|
+
const staticImageCenter = center ?? defaultCenter;
|
|
62
|
+
const centerRef = useRef(resolvedCenter);
|
|
63
|
+
const zoomRef = useRef(resolvedZoom);
|
|
64
|
+
const onLoadRef = useRef(onLoad);
|
|
65
|
+
const mapNameRef = useRef(mapName);
|
|
66
|
+
const resolvedAnalyticsRef = useRef(void 0);
|
|
67
|
+
const sessionStartsRef = useRef(/* @__PURE__ */ new Set());
|
|
68
|
+
const sessionId = useMemo(() => createTileflowSessionId(), []);
|
|
69
|
+
const [manifestMap, setManifestMap] = useState(null);
|
|
47
70
|
const [imageSize, setImageSize] = useState(null);
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
),
|
|
53
|
-
[config, isImageMode, mapName, style, styleBaseUrl, styleUrl]
|
|
54
|
-
);
|
|
55
|
-
const useLocalDevFallback = useMemo(
|
|
56
|
-
() => Boolean(
|
|
57
|
-
!isImageMode && mapName && !style && !styleUrl && !config && !styleBaseUrl && preferLocalDev && isLocalDevHost()
|
|
58
|
-
),
|
|
59
|
-
[
|
|
71
|
+
const resolvedMode = resolveTileflowMapMode({ imageUrl, mode, preferLocalDev });
|
|
72
|
+
const isImageMode = resolvedMode === "image";
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const shouldLoadManifest = shouldLoadTileflowManifest({
|
|
60
75
|
config,
|
|
61
|
-
isImageMode,
|
|
62
|
-
|
|
63
|
-
|
|
76
|
+
imageMode: isImageMode,
|
|
77
|
+
imageUrl,
|
|
78
|
+
map: mapName,
|
|
64
79
|
style,
|
|
65
80
|
styleBaseUrl,
|
|
66
81
|
styleUrl
|
|
67
|
-
|
|
68
|
-
);
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
const shouldLoadManifest = Boolean(
|
|
71
|
-
mapName && !config && (isImageMode && !imageUrl || !isImageMode && !style && !styleUrl && !useExplicitStyleBaseUrl)
|
|
72
|
-
);
|
|
82
|
+
});
|
|
73
83
|
if (!shouldLoadManifest) {
|
|
74
84
|
setManifestMap(null);
|
|
75
85
|
return;
|
|
76
86
|
}
|
|
77
87
|
let cancelled = false;
|
|
78
|
-
|
|
88
|
+
setManifestMap(null);
|
|
89
|
+
loadTileflowManifest(manifestUrl).then((manifest) => {
|
|
79
90
|
if (cancelled) {
|
|
80
91
|
return;
|
|
81
92
|
}
|
|
82
|
-
setManifestMap(
|
|
83
|
-
manifest ? resolveManifestMap(manifest, mapName) : null
|
|
84
|
-
);
|
|
93
|
+
setManifestMap(manifest && mapName ? resolveTileflowManifestMap(manifest, mapName) : null);
|
|
85
94
|
}).catch((error) => {
|
|
86
95
|
if (!cancelled) {
|
|
87
96
|
console.error("Failed to load Tileflow manifest", error);
|
|
@@ -91,77 +100,71 @@ function Map({
|
|
|
91
100
|
return () => {
|
|
92
101
|
cancelled = true;
|
|
93
102
|
};
|
|
94
|
-
}, [
|
|
95
|
-
config,
|
|
96
|
-
imageUrl,
|
|
97
|
-
isImageMode,
|
|
98
|
-
manifestUrl,
|
|
99
|
-
mapName,
|
|
100
|
-
style,
|
|
101
|
-
styleUrl,
|
|
102
|
-
useExplicitStyleBaseUrl
|
|
103
|
-
]);
|
|
103
|
+
}, [config, imageUrl, isImageMode, manifestUrl, mapName, style, styleUrl, styleBaseUrl]);
|
|
104
104
|
const runtimeStyle = useMemo(() => {
|
|
105
105
|
if (isImageMode) {
|
|
106
106
|
return null;
|
|
107
107
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
if (mapName) {
|
|
121
|
-
if (manifestMap?.styleUrl) {
|
|
122
|
-
return {
|
|
123
|
-
analytics: {
|
|
124
|
-
apiUrl: manifestMap.apiUrl,
|
|
125
|
-
mapId: manifestMap.mapId,
|
|
126
|
-
styleId: manifestMap.styleId
|
|
127
|
-
},
|
|
128
|
-
style: manifestMap.styleUrl
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
if (useLocalDevFallback) {
|
|
132
|
-
return { style: resolveStyleUrl(mapName) };
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
return { style: createStyle(config, { tileBaseUrl }) };
|
|
108
|
+
return resolveTileflowRuntimeStyle({
|
|
109
|
+
config,
|
|
110
|
+
manifestMap,
|
|
111
|
+
map: mapName,
|
|
112
|
+
preferLocalDev,
|
|
113
|
+
style,
|
|
114
|
+
styleBaseUrl,
|
|
115
|
+
styleUrl,
|
|
116
|
+
themes,
|
|
117
|
+
tileBaseUrl
|
|
118
|
+
});
|
|
137
119
|
}, [
|
|
138
120
|
config,
|
|
139
121
|
isImageMode,
|
|
140
122
|
manifestMap,
|
|
141
123
|
mapName,
|
|
124
|
+
preferLocalDev,
|
|
142
125
|
style,
|
|
143
126
|
styleBaseUrl,
|
|
144
127
|
styleUrl,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
useLocalDevFallback
|
|
128
|
+
themes,
|
|
129
|
+
tileBaseUrl
|
|
148
130
|
]);
|
|
149
131
|
const resolvedAnalytics = useMemo(
|
|
150
|
-
() =>
|
|
132
|
+
() => mergeTileflowAnalytics(analytics, runtimeStyle?.analytics),
|
|
151
133
|
[analytics, runtimeStyle]
|
|
152
134
|
);
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
centerRef.current = resolvedCenter;
|
|
137
|
+
zoomRef.current = resolvedZoom;
|
|
138
|
+
mapRef.current?.jumpTo({
|
|
139
|
+
center: resolvedCenter,
|
|
140
|
+
zoom: resolvedZoom
|
|
141
|
+
});
|
|
142
|
+
}, [resolvedCenter, resolvedZoom]);
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
onLoadRef.current = onLoad;
|
|
145
|
+
}, [onLoad]);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
mapNameRef.current = mapName;
|
|
148
|
+
}, [mapName]);
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
resolvedAnalyticsRef.current = resolvedAnalytics;
|
|
151
|
+
}, [resolvedAnalytics]);
|
|
153
152
|
const runtimeImageUrl = useMemo(
|
|
154
|
-
() => imageUrl ?? (isImageMode ?
|
|
155
|
-
center,
|
|
153
|
+
() => imageUrl ?? (isImageMode ? resolveTileflowStaticImageUrl({
|
|
154
|
+
center: staticImageCenter,
|
|
156
155
|
imageSize,
|
|
157
156
|
manifestMap,
|
|
158
|
-
zoom
|
|
157
|
+
zoom: resolvedZoom
|
|
159
158
|
}) : void 0),
|
|
160
|
-
[
|
|
159
|
+
[imageSize, imageUrl, isImageMode, manifestMap, resolvedZoom, staticImageCenter]
|
|
161
160
|
);
|
|
162
161
|
const transformRequest = useMemo(
|
|
163
|
-
() => createTransformRequest(
|
|
164
|
-
|
|
162
|
+
() => createTransformRequest(
|
|
163
|
+
() => resolvedAnalyticsRef.current,
|
|
164
|
+
sessionId,
|
|
165
|
+
stableMapOptions?.transformRequest
|
|
166
|
+
),
|
|
167
|
+
[sessionId, stableMapOptions?.transformRequest]
|
|
165
168
|
);
|
|
166
169
|
useEffect(() => {
|
|
167
170
|
if (!isImageMode || imageUrl || !containerRef.current) {
|
|
@@ -170,7 +173,7 @@ function Map({
|
|
|
170
173
|
}
|
|
171
174
|
const element = containerRef.current;
|
|
172
175
|
const updateImageSize = () => {
|
|
173
|
-
const nextSize =
|
|
176
|
+
const nextSize = normalizeTileflowStaticImageSize({
|
|
174
177
|
height: element.clientHeight,
|
|
175
178
|
width: element.clientWidth
|
|
176
179
|
});
|
|
@@ -190,56 +193,68 @@ function Map({
|
|
|
190
193
|
return;
|
|
191
194
|
}
|
|
192
195
|
const map = new maplibregl.Map({
|
|
196
|
+
...stableMapOptions,
|
|
193
197
|
container: containerRef.current,
|
|
194
198
|
style: runtimeStyle.style,
|
|
195
|
-
center,
|
|
196
|
-
zoom,
|
|
197
|
-
interactive,
|
|
198
|
-
attributionControl: { compact: true },
|
|
199
|
+
center: centerRef.current,
|
|
200
|
+
zoom: zoomRef.current,
|
|
201
|
+
interactive: resolvedInteractive,
|
|
202
|
+
attributionControl: stableMapOptions?.attributionControl ?? { compact: true },
|
|
199
203
|
transformRequest
|
|
200
204
|
});
|
|
201
205
|
mapRef.current = map;
|
|
202
|
-
const controls =
|
|
206
|
+
const controls = resolvedInteractive ? new maplibregl.NavigationControl() : null;
|
|
203
207
|
if (controls) {
|
|
204
208
|
map.addControl(controls, "top-right");
|
|
205
209
|
}
|
|
206
|
-
const markerInstances = markers.map((marker) => {
|
|
207
|
-
const element = document.createElement("div");
|
|
208
|
-
element.className = "tileflow-marker";
|
|
209
|
-
element.style.background = marker.color ?? "#C6A15B";
|
|
210
|
-
element.title = marker.label ?? marker.id;
|
|
211
|
-
return new maplibregl.Marker({ element }).setLngLat(marker.coordinates).addTo(map);
|
|
212
|
-
});
|
|
213
210
|
const resizeObserver = new ResizeObserver(() => {
|
|
214
211
|
map.resize();
|
|
215
212
|
});
|
|
216
213
|
resizeObserver.observe(containerRef.current);
|
|
217
214
|
map.on("load", () => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
215
|
+
const analyticsForLoad = resolvedAnalyticsRef.current;
|
|
216
|
+
const styleId = analyticsForLoad?.styleId ?? (typeof runtimeStyle.style === "string" ? runtimeStyle.style : mapNameRef.current);
|
|
217
|
+
onLoadRef.current?.(map);
|
|
218
|
+
if (analyticsForLoad?.mapId) {
|
|
219
|
+
const sessionKey = `${sessionId}:${analyticsForLoad.mapId}:${styleId ?? ""}`;
|
|
220
|
+
if (!sessionStartsRef.current.has(sessionKey)) {
|
|
221
|
+
sessionStartsRef.current.add(sessionKey);
|
|
222
|
+
startTileflowSession(analyticsForLoad, {
|
|
223
|
+
mapId: analyticsForLoad.mapId,
|
|
224
|
+
sessionId,
|
|
225
|
+
source: "react",
|
|
226
|
+
styleId
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
224
230
|
});
|
|
225
231
|
return () => {
|
|
226
232
|
resizeObserver.disconnect();
|
|
227
|
-
|
|
233
|
+
clearMarkers(markerInstancesRef.current);
|
|
234
|
+
markerInstancesRef.current = [];
|
|
228
235
|
map.remove();
|
|
229
236
|
mapRef.current = null;
|
|
230
237
|
};
|
|
231
|
-
}, [
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
238
|
+
}, [resolvedInteractive, runtimeStyle, sessionId, stableMapOptions, transformRequest]);
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
const map = mapRef.current;
|
|
241
|
+
clearMarkers(markerInstancesRef.current);
|
|
242
|
+
markerInstancesRef.current = [];
|
|
243
|
+
if (!map) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
markerInstancesRef.current = markers.map((marker) => {
|
|
247
|
+
const markerInstance = new maplibregl.Marker({
|
|
248
|
+
color: marker.color ?? "#C6A15B"
|
|
249
|
+
}).setLngLat(marker.coordinates).addTo(map);
|
|
250
|
+
markerInstance.getElement().title = marker.label ?? marker.id;
|
|
251
|
+
return markerInstance;
|
|
252
|
+
});
|
|
253
|
+
return () => {
|
|
254
|
+
clearMarkers(markerInstancesRef.current);
|
|
255
|
+
markerInstancesRef.current = [];
|
|
256
|
+
};
|
|
257
|
+
}, [markers, runtimeStyle]);
|
|
243
258
|
const frameStyle = {
|
|
244
259
|
height,
|
|
245
260
|
minHeight: 240,
|
|
@@ -264,222 +279,70 @@ function Map({
|
|
|
264
279
|
}
|
|
265
280
|
) : null });
|
|
266
281
|
}
|
|
267
|
-
return /* @__PURE__ */ jsx(
|
|
268
|
-
"div",
|
|
269
|
-
{
|
|
270
|
-
className,
|
|
271
|
-
ref: containerRef,
|
|
272
|
-
style: frameStyle
|
|
273
|
-
}
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
function resolveStyleUrl(mapName, styleBaseUrl) {
|
|
277
|
-
const baseUrl = normalizeUrl(styleBaseUrl ?? getDefaultStyleBaseUrl());
|
|
278
|
-
return `${baseUrl}/styles/${mapName}.json`;
|
|
279
|
-
}
|
|
280
|
-
function getDefaultStyleBaseUrl() {
|
|
281
|
-
const globalValue = globalThis.__TILEFLOW_STYLE_BASE_URL__;
|
|
282
|
-
return globalValue ?? "http://localhost:3333";
|
|
283
|
-
}
|
|
284
|
-
function normalizeUrl(value) {
|
|
285
|
-
return value.replace(/\/+$/, "");
|
|
282
|
+
return /* @__PURE__ */ jsx("div", { className, ref: containerRef, style: frameStyle });
|
|
286
283
|
}
|
|
287
|
-
function
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (!analytics || analytics.enabled === false) {
|
|
295
|
-
return void 0;
|
|
296
|
-
}
|
|
297
|
-
return (url) => {
|
|
298
|
-
const apiUrl = getAnalyticsApiUrl(analytics);
|
|
299
|
-
if (!apiUrl || !isTileflowTileUrl(url, apiUrl)) {
|
|
300
|
-
return void 0;
|
|
301
|
-
}
|
|
302
|
-
const nextUrl = new URL(url);
|
|
303
|
-
nextUrl.searchParams.set("session", sessionId);
|
|
304
|
-
if (analytics.mapId) {
|
|
305
|
-
nextUrl.searchParams.set("map", analytics.mapId);
|
|
306
|
-
}
|
|
307
|
-
if (analytics.styleId) {
|
|
308
|
-
nextUrl.searchParams.set("styleId", analytics.styleId);
|
|
284
|
+
function createTransformRequest(getAnalytics, sessionId, userTransformRequest) {
|
|
285
|
+
return (url, resourceType) => {
|
|
286
|
+
const request = userTransformRequest?.(url, resourceType);
|
|
287
|
+
if (isPromiseLike(request)) {
|
|
288
|
+
return request.then(
|
|
289
|
+
(resolvedRequest) => applyTileflowAnalyticsRequest(url, resolvedRequest, getAnalytics(), sessionId) ?? resolvedRequest
|
|
290
|
+
);
|
|
309
291
|
}
|
|
310
|
-
return
|
|
292
|
+
return applyTileflowAnalyticsRequest(url, request, getAnalytics(), sessionId);
|
|
311
293
|
};
|
|
312
294
|
}
|
|
313
|
-
function
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const payload = JSON.stringify({
|
|
319
|
-
mapId: input.mapId,
|
|
320
|
-
metadata: analytics.metadata,
|
|
321
|
-
referrer: typeof document === "undefined" ? void 0 : document.referrer || "",
|
|
322
|
-
sdkVersion: analytics.sdkVersion,
|
|
323
|
-
sessionId: input.sessionId,
|
|
324
|
-
source: analytics.source ?? "react",
|
|
325
|
-
styleId: input.styleId,
|
|
326
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
327
|
-
});
|
|
328
|
-
const blob = new Blob([payload], { type: "application/json" });
|
|
329
|
-
if (navigator.sendBeacon?.(endpoint, blob)) {
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
void fetch(endpoint, {
|
|
333
|
-
body: payload,
|
|
334
|
-
credentials: "omit",
|
|
335
|
-
headers: {
|
|
336
|
-
"Content-Type": "application/json"
|
|
337
|
-
},
|
|
338
|
-
keepalive: true,
|
|
339
|
-
method: "POST"
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
function getAnalyticsApiUrl(analytics) {
|
|
343
|
-
if (analytics.apiUrl) {
|
|
344
|
-
return normalizeUrl(analytics.apiUrl);
|
|
345
|
-
}
|
|
346
|
-
if (analytics.endpoint) {
|
|
347
|
-
const url = new URL(analytics.endpoint);
|
|
348
|
-
return url.origin;
|
|
349
|
-
}
|
|
350
|
-
return "https://api.tileflow.dev";
|
|
351
|
-
}
|
|
352
|
-
function mergeAnalytics(userAnalytics, runtimeAnalytics) {
|
|
353
|
-
if (userAnalytics?.enabled === false) {
|
|
354
|
-
return userAnalytics;
|
|
355
|
-
}
|
|
356
|
-
if (!userAnalytics && !runtimeAnalytics?.mapId) {
|
|
357
|
-
return void 0;
|
|
295
|
+
function applyTileflowAnalyticsRequest(url, request, analytics, sessionId) {
|
|
296
|
+
const requestUrl = request?.url ?? url;
|
|
297
|
+
const nextUrl = resolveTileflowAnalyticsRequestUrl(requestUrl, analytics, sessionId);
|
|
298
|
+
if (!nextUrl) {
|
|
299
|
+
return request;
|
|
358
300
|
}
|
|
359
|
-
return {
|
|
360
|
-
...runtimeAnalytics,
|
|
361
|
-
...userAnalytics,
|
|
362
|
-
apiUrl: userAnalytics?.apiUrl ?? runtimeAnalytics?.apiUrl,
|
|
363
|
-
mapId: userAnalytics?.mapId ?? runtimeAnalytics?.mapId,
|
|
364
|
-
styleId: userAnalytics?.styleId ?? runtimeAnalytics?.styleId
|
|
365
|
-
};
|
|
301
|
+
return request ? { ...request, url: nextUrl } : { url: nextUrl };
|
|
366
302
|
}
|
|
367
|
-
function
|
|
368
|
-
|
|
369
|
-
const parsedUrl = new URL(url);
|
|
370
|
-
const parsedApiUrl = new URL(apiUrl);
|
|
371
|
-
return parsedUrl.origin === parsedApiUrl.origin && isTilePath(parsedUrl);
|
|
372
|
-
} catch {
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
303
|
+
function isPromiseLike(value) {
|
|
304
|
+
return Boolean(value && typeof value.then === "function");
|
|
375
305
|
}
|
|
376
|
-
function
|
|
377
|
-
|
|
306
|
+
function clearMarkers(markerInstances) {
|
|
307
|
+
markerInstances.forEach((marker) => marker.remove());
|
|
378
308
|
}
|
|
379
|
-
function
|
|
380
|
-
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
return ["localhost", "127.0.0.1", "::1"].includes(window.location.hostname);
|
|
309
|
+
function useStableMapOptions(options) {
|
|
310
|
+
return useStableMapOptionValue(options);
|
|
384
311
|
}
|
|
385
|
-
function
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
fetch(url, { cache: "no-store" }).then(async (response) => {
|
|
390
|
-
if (response.status === 404) {
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
if (!response.ok) {
|
|
394
|
-
throw new Error(`Tileflow manifest failed: ${response.status}`);
|
|
395
|
-
}
|
|
396
|
-
return await response.json();
|
|
397
|
-
})
|
|
398
|
-
);
|
|
312
|
+
function useStableMapOptionValue(value) {
|
|
313
|
+
const valueRef = useRef(value);
|
|
314
|
+
if (!areEquivalentMapOptionValues(valueRef.current, value)) {
|
|
315
|
+
valueRef.current = value;
|
|
399
316
|
}
|
|
400
|
-
return
|
|
317
|
+
return valueRef.current;
|
|
401
318
|
}
|
|
402
|
-
function
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
return null;
|
|
319
|
+
function areEquivalentMapOptionValues(left, right, seen = /* @__PURE__ */ new WeakMap()) {
|
|
320
|
+
if (Object.is(left, right)) {
|
|
321
|
+
return true;
|
|
406
322
|
}
|
|
407
|
-
if (typeof
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
apiUrl: analytics2?.apiUrl ?? manifest.apiUrl,
|
|
411
|
-
mapId: analytics2?.mapId,
|
|
412
|
-
styleId: analytics2?.styleId,
|
|
413
|
-
styleUrl: entry
|
|
414
|
-
};
|
|
323
|
+
if (!left || !right || typeof left !== "object" || typeof right !== "object") {
|
|
324
|
+
return false;
|
|
415
325
|
}
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
418
|
-
return
|
|
326
|
+
const seenRight = seen.get(left);
|
|
327
|
+
if (seenRight) {
|
|
328
|
+
return seenRight === right;
|
|
419
329
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
mapId: entry.mapId ?? analytics?.mapId,
|
|
424
|
-
styleId: entry.styleId ?? analytics?.styleId,
|
|
425
|
-
styleUrl
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
function resolveStaticImageUrl(input) {
|
|
429
|
-
if (!input.manifestMap?.mapId || !input.imageSize) {
|
|
430
|
-
return void 0;
|
|
431
|
-
}
|
|
432
|
-
const apiUrl = normalizeUrl(
|
|
433
|
-
input.manifestMap.apiUrl ?? "https://api.tileflow.dev"
|
|
434
|
-
);
|
|
435
|
-
const url = new URL(`/maps/${input.manifestMap.mapId}/static.png`, apiUrl);
|
|
436
|
-
url.searchParams.set("center", input.center.join(","));
|
|
437
|
-
url.searchParams.set("zoom", String(input.zoom));
|
|
438
|
-
url.searchParams.set("width", String(input.imageSize.width));
|
|
439
|
-
url.searchParams.set("height", String(input.imageSize.height));
|
|
440
|
-
return url.toString();
|
|
441
|
-
}
|
|
442
|
-
function normalizeStaticImageSize(input) {
|
|
443
|
-
let width = clampDimension(input.width);
|
|
444
|
-
let height = clampDimension(input.height);
|
|
445
|
-
const pixelCount = width * height;
|
|
446
|
-
if (pixelCount > maxStaticImagePixels) {
|
|
447
|
-
const scale = Math.sqrt(maxStaticImagePixels / pixelCount);
|
|
448
|
-
width = clampDimension(width * scale);
|
|
449
|
-
height = clampDimension(height * scale);
|
|
330
|
+
seen.set(left, right);
|
|
331
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
332
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areEquivalentMapOptionValues(value, right[index], seen));
|
|
450
333
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
function clampDimension(value) {
|
|
454
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
455
|
-
return 640;
|
|
334
|
+
if (!isPlainObject(left) || !isPlainObject(right)) {
|
|
335
|
+
return false;
|
|
456
336
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
337
|
+
const leftKeys = Object.keys(left);
|
|
338
|
+
const rightKeys = Object.keys(right);
|
|
339
|
+
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
340
|
+
(key) => Object.hasOwn(right, key) && areEquivalentMapOptionValues(left[key], right[key], seen)
|
|
460
341
|
);
|
|
461
342
|
}
|
|
462
|
-
function
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const match = url.pathname.match(/\/(?:v1\/)?maps\/([^/]+)\/style\.json$/);
|
|
466
|
-
if (!match?.[1]) {
|
|
467
|
-
return void 0;
|
|
468
|
-
}
|
|
469
|
-
return {
|
|
470
|
-
apiUrl: url.origin,
|
|
471
|
-
mapId: match[1],
|
|
472
|
-
styleId: url.toString()
|
|
473
|
-
};
|
|
474
|
-
} catch {
|
|
475
|
-
return void 0;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
function getLocationOrigin() {
|
|
479
|
-
if (typeof window === "undefined") {
|
|
480
|
-
return "http://localhost";
|
|
481
|
-
}
|
|
482
|
-
return window.location.origin;
|
|
343
|
+
function isPlainObject(value) {
|
|
344
|
+
const prototype = Object.getPrototypeOf(value);
|
|
345
|
+
return prototype === Object.prototype || prototype === null;
|
|
483
346
|
}
|
|
484
347
|
export {
|
|
485
348
|
Map
|
package/dist/static.js
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tileflow/react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.10",
|
|
4
4
|
"description": "React components for rendering Tileflow maps with MapLibre.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"homepage": "https://tileflow.dev",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
@@ -40,23 +41,21 @@
|
|
|
40
41
|
"access": "public"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
|
-
"@tileflow/core": "0.1.0-alpha.
|
|
44
|
-
"@tileflow/static": "0.1.0-alpha.
|
|
44
|
+
"@tileflow/core": "0.1.0-alpha.10",
|
|
45
|
+
"@tileflow/static": "0.1.0-alpha.10"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
48
|
"maplibre-gl": ">=5",
|
|
48
|
-
"react": ">=18"
|
|
49
|
-
"react-dom": ">=18"
|
|
49
|
+
"react": ">=18"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"maplibre-gl": "^5.24.0",
|
|
53
53
|
"react": "^19.2.7",
|
|
54
|
-
"react
|
|
55
|
-
"@types/react": "^19.2.17",
|
|
56
|
-
"@types/react-dom": "^19.2.3"
|
|
54
|
+
"@types/react": "^19.2.17"
|
|
57
55
|
},
|
|
58
56
|
"scripts": {
|
|
59
|
-
"build": "tsup
|
|
57
|
+
"build": "tsup",
|
|
58
|
+
"verify": "pnpm run build && tsx --test test/*.test.ts",
|
|
60
59
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
61
60
|
"lint": "tsc -p tsconfig.json --noEmit"
|
|
62
61
|
}
|