@tileflow/react 0.1.0-alpha.1 → 0.1.0-alpha.11
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 +37 -1
- package/dist/index.d.ts +8 -22
- package/dist/index.js +180 -293
- package/dist/static.js +2 -0
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -2,20 +2,56 @@
|
|
|
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
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
|
+
|
|
13
44
|
For simple hosted static images, use image mode:
|
|
14
45
|
|
|
15
46
|
```tsx
|
|
16
47
|
<Map map="madrid" mode="image" center={[-3.7038, 40.4168]} zoom={12} />
|
|
17
48
|
```
|
|
18
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
|
+
|
|
19
55
|
For full static scenes with bounds, overlays, and server-side precache, use
|
|
20
56
|
`StaticMap` from `@tileflow/react/static` or helpers from `@tileflow/static`.
|
|
21
57
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties, ImgHTMLAttributes } from 'react';
|
|
3
|
-
import { Map as Map$1 } from 'maplibre-gl';
|
|
4
|
-
import { TileflowConfig, MapLibreStyle,
|
|
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
10
|
mode?: 'interactive' | 'image';
|
|
@@ -29,7 +14,7 @@ type MapProps = {
|
|
|
29
14
|
imageUrl?: string;
|
|
30
15
|
styleBaseUrl?: string;
|
|
31
16
|
tileBaseUrl?: string;
|
|
32
|
-
themes?:
|
|
17
|
+
themes?: TileflowProjectThemes;
|
|
33
18
|
manifestUrl?: string;
|
|
34
19
|
preferLocalDev?: boolean;
|
|
35
20
|
alt?: string;
|
|
@@ -39,10 +24,11 @@ type MapProps = {
|
|
|
39
24
|
height?: CSSProperties['height'];
|
|
40
25
|
imageLoading?: ImgHTMLAttributes<HTMLImageElement>['loading'];
|
|
41
26
|
interactive?: boolean;
|
|
27
|
+
mapOptions?: TileflowMapOptions;
|
|
42
28
|
markers?: MapMarker[];
|
|
43
29
|
analytics?: TileflowAnalytics;
|
|
44
30
|
onLoad?: (map: Map$1) => void;
|
|
45
31
|
};
|
|
46
|
-
declare function Map({ map: mapName, mode, config, style, styleUrl, imageUrl, styleBaseUrl, tileBaseUrl, themes, 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;
|
|
47
33
|
|
|
48
|
-
export { Map, type MapMarker, type MapProps };
|
|
34
|
+
export { Map, type MapMarker, type MapProps, type TileflowMapOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
// src/map.tsx
|
|
2
4
|
import {
|
|
3
5
|
useEffect,
|
|
@@ -7,15 +9,22 @@ import {
|
|
|
7
9
|
} from "react";
|
|
8
10
|
import maplibregl from "maplibre-gl";
|
|
9
11
|
import {
|
|
10
|
-
|
|
12
|
+
createTileflowSessionId,
|
|
13
|
+
defaultTileflowManifestUrl,
|
|
14
|
+
loadTileflowManifest,
|
|
15
|
+
mergeTileflowAnalytics,
|
|
16
|
+
normalizeTileflowStaticImageSize,
|
|
17
|
+
resolveTileflowAnalyticsRequestUrl,
|
|
18
|
+
resolveTileflowManifestMap,
|
|
19
|
+
resolveTileflowMapMode,
|
|
20
|
+
resolveTileflowRuntimeStyle,
|
|
21
|
+
resolveTileflowStaticImageUrl,
|
|
22
|
+
shouldLoadTileflowManifest,
|
|
23
|
+
startTileflowSession
|
|
11
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",
|
|
@@ -26,49 +35,62 @@ function Map({
|
|
|
26
35
|
styleBaseUrl,
|
|
27
36
|
tileBaseUrl,
|
|
28
37
|
themes,
|
|
29
|
-
manifestUrl =
|
|
38
|
+
manifestUrl = defaultTileflowManifestUrl,
|
|
30
39
|
preferLocalDev = true,
|
|
31
40
|
alt = "",
|
|
32
|
-
center
|
|
33
|
-
zoom
|
|
41
|
+
center,
|
|
42
|
+
zoom,
|
|
34
43
|
className,
|
|
35
44
|
height = 420,
|
|
36
45
|
imageLoading = "eager",
|
|
37
|
-
interactive
|
|
46
|
+
interactive,
|
|
47
|
+
mapOptions,
|
|
38
48
|
markers = defaultMarkers,
|
|
39
49
|
analytics,
|
|
40
50
|
onLoad
|
|
41
51
|
}) {
|
|
42
52
|
const containerRef = useRef(null);
|
|
43
53
|
const mapRef = useRef(null);
|
|
44
|
-
const
|
|
54
|
+
const markerInstancesRef = useRef([]);
|
|
55
|
+
const stableMapOptions = useStableMapOptions(mapOptions);
|
|
56
|
+
const resolvedCenter = useStableMapOptionValue(
|
|
57
|
+
center ?? stableMapOptions?.center ?? defaultCenter
|
|
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(), []);
|
|
45
69
|
const [manifestMap, setManifestMap] = useState(null);
|
|
46
70
|
const [imageSize, setImageSize] = useState(null);
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
() => Boolean(!isImageMode && mapName && !style && !styleUrl && !config && styleBaseUrl),
|
|
50
|
-
[config, isImageMode, mapName, style, styleBaseUrl, styleUrl]
|
|
51
|
-
);
|
|
52
|
-
const useLocalDevFallback = useMemo(
|
|
53
|
-
() => Boolean(
|
|
54
|
-
!isImageMode && mapName && !style && !styleUrl && !config && !styleBaseUrl && preferLocalDev && isLocalDevHost()
|
|
55
|
-
),
|
|
56
|
-
[config, isImageMode, mapName, preferLocalDev, style, styleBaseUrl, styleUrl]
|
|
57
|
-
);
|
|
71
|
+
const resolvedMode = resolveTileflowMapMode({ imageUrl, mode, preferLocalDev });
|
|
72
|
+
const isImageMode = resolvedMode === "image";
|
|
58
73
|
useEffect(() => {
|
|
59
|
-
const shouldLoadManifest =
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
const shouldLoadManifest = shouldLoadTileflowManifest({
|
|
75
|
+
config,
|
|
76
|
+
imageMode: isImageMode,
|
|
77
|
+
imageUrl,
|
|
78
|
+
map: mapName,
|
|
79
|
+
style,
|
|
80
|
+
styleBaseUrl,
|
|
81
|
+
styleUrl
|
|
82
|
+
});
|
|
62
83
|
if (!shouldLoadManifest) {
|
|
63
84
|
setManifestMap(null);
|
|
64
85
|
return;
|
|
65
86
|
}
|
|
66
87
|
let cancelled = false;
|
|
67
|
-
|
|
88
|
+
setManifestMap(null);
|
|
89
|
+
loadTileflowManifest(manifestUrl).then((manifest) => {
|
|
68
90
|
if (cancelled) {
|
|
69
91
|
return;
|
|
70
92
|
}
|
|
71
|
-
setManifestMap(manifest ?
|
|
93
|
+
setManifestMap(manifest && mapName ? resolveTileflowManifestMap(manifest, mapName) : null);
|
|
72
94
|
}).catch((error) => {
|
|
73
95
|
if (!cancelled) {
|
|
74
96
|
console.error("Failed to load Tileflow manifest", error);
|
|
@@ -78,78 +100,71 @@ function Map({
|
|
|
78
100
|
return () => {
|
|
79
101
|
cancelled = true;
|
|
80
102
|
};
|
|
81
|
-
}, [
|
|
82
|
-
config,
|
|
83
|
-
imageUrl,
|
|
84
|
-
isImageMode,
|
|
85
|
-
manifestUrl,
|
|
86
|
-
mapName,
|
|
87
|
-
style,
|
|
88
|
-
styleUrl,
|
|
89
|
-
useExplicitStyleBaseUrl
|
|
90
|
-
]);
|
|
103
|
+
}, [config, imageUrl, isImageMode, manifestUrl, mapName, style, styleUrl, styleBaseUrl]);
|
|
91
104
|
const runtimeStyle = useMemo(() => {
|
|
92
105
|
if (isImageMode) {
|
|
93
106
|
return null;
|
|
94
107
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 }) };
|
|
108
|
+
return resolveTileflowRuntimeStyle({
|
|
109
|
+
config,
|
|
110
|
+
manifestMap,
|
|
111
|
+
map: mapName,
|
|
112
|
+
preferLocalDev,
|
|
113
|
+
style,
|
|
114
|
+
styleBaseUrl,
|
|
115
|
+
styleUrl,
|
|
116
|
+
themes,
|
|
117
|
+
tileBaseUrl
|
|
118
|
+
});
|
|
124
119
|
}, [
|
|
125
120
|
config,
|
|
126
121
|
isImageMode,
|
|
127
122
|
manifestMap,
|
|
128
123
|
mapName,
|
|
124
|
+
preferLocalDev,
|
|
129
125
|
style,
|
|
130
126
|
styleBaseUrl,
|
|
131
127
|
styleUrl,
|
|
132
128
|
themes,
|
|
133
|
-
tileBaseUrl
|
|
134
|
-
useExplicitStyleBaseUrl,
|
|
135
|
-
useLocalDevFallback
|
|
129
|
+
tileBaseUrl
|
|
136
130
|
]);
|
|
137
131
|
const resolvedAnalytics = useMemo(
|
|
138
|
-
() =>
|
|
132
|
+
() => mergeTileflowAnalytics(analytics, runtimeStyle?.analytics),
|
|
139
133
|
[analytics, runtimeStyle]
|
|
140
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]);
|
|
141
152
|
const runtimeImageUrl = useMemo(
|
|
142
|
-
() => imageUrl ?? (isImageMode ?
|
|
143
|
-
center,
|
|
153
|
+
() => imageUrl ?? (isImageMode ? resolveTileflowStaticImageUrl({
|
|
154
|
+
center: staticImageCenter,
|
|
144
155
|
imageSize,
|
|
145
156
|
manifestMap,
|
|
146
|
-
zoom
|
|
157
|
+
zoom: resolvedZoom
|
|
147
158
|
}) : void 0),
|
|
148
|
-
[
|
|
159
|
+
[imageSize, imageUrl, isImageMode, manifestMap, resolvedZoom, staticImageCenter]
|
|
149
160
|
);
|
|
150
161
|
const transformRequest = useMemo(
|
|
151
|
-
() => createTransformRequest(
|
|
152
|
-
|
|
162
|
+
() => createTransformRequest(
|
|
163
|
+
() => resolvedAnalyticsRef.current,
|
|
164
|
+
sessionId,
|
|
165
|
+
stableMapOptions?.transformRequest
|
|
166
|
+
),
|
|
167
|
+
[sessionId, stableMapOptions?.transformRequest]
|
|
153
168
|
);
|
|
154
169
|
useEffect(() => {
|
|
155
170
|
if (!isImageMode || imageUrl || !containerRef.current) {
|
|
@@ -158,7 +173,7 @@ function Map({
|
|
|
158
173
|
}
|
|
159
174
|
const element = containerRef.current;
|
|
160
175
|
const updateImageSize = () => {
|
|
161
|
-
const nextSize =
|
|
176
|
+
const nextSize = normalizeTileflowStaticImageSize({
|
|
162
177
|
height: element.clientHeight,
|
|
163
178
|
width: element.clientWidth
|
|
164
179
|
});
|
|
@@ -178,56 +193,68 @@ function Map({
|
|
|
178
193
|
return;
|
|
179
194
|
}
|
|
180
195
|
const map = new maplibregl.Map({
|
|
196
|
+
...stableMapOptions,
|
|
181
197
|
container: containerRef.current,
|
|
182
198
|
style: runtimeStyle.style,
|
|
183
|
-
center,
|
|
184
|
-
zoom,
|
|
185
|
-
interactive,
|
|
186
|
-
attributionControl: { compact: true },
|
|
199
|
+
center: centerRef.current,
|
|
200
|
+
zoom: zoomRef.current,
|
|
201
|
+
interactive: resolvedInteractive,
|
|
202
|
+
attributionControl: stableMapOptions?.attributionControl ?? { compact: true },
|
|
187
203
|
transformRequest
|
|
188
204
|
});
|
|
189
205
|
mapRef.current = map;
|
|
190
|
-
const controls =
|
|
206
|
+
const controls = resolvedInteractive ? new maplibregl.NavigationControl() : null;
|
|
191
207
|
if (controls) {
|
|
192
208
|
map.addControl(controls, "top-right");
|
|
193
209
|
}
|
|
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
210
|
const resizeObserver = new ResizeObserver(() => {
|
|
202
211
|
map.resize();
|
|
203
212
|
});
|
|
204
213
|
resizeObserver.observe(containerRef.current);
|
|
205
214
|
map.on("load", () => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
+
}
|
|
212
230
|
});
|
|
213
231
|
return () => {
|
|
214
232
|
resizeObserver.disconnect();
|
|
215
|
-
|
|
233
|
+
clearMarkers(markerInstancesRef.current);
|
|
234
|
+
markerInstancesRef.current = [];
|
|
216
235
|
map.remove();
|
|
217
236
|
mapRef.current = null;
|
|
218
237
|
};
|
|
219
|
-
}, [
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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]);
|
|
231
258
|
const frameStyle = {
|
|
232
259
|
height,
|
|
233
260
|
minHeight: 240,
|
|
@@ -254,208 +281,68 @@ function Map({
|
|
|
254
281
|
}
|
|
255
282
|
return /* @__PURE__ */ jsx("div", { className, ref: containerRef, style: frameStyle });
|
|
256
283
|
}
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
function normalizeUrl(value) {
|
|
266
|
-
return value.replace(/\/+$/, "");
|
|
267
|
-
}
|
|
268
|
-
function createSessionId() {
|
|
269
|
-
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
270
|
-
return `ses_${crypto.randomUUID()}`;
|
|
271
|
-
}
|
|
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;
|
|
277
|
-
}
|
|
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);
|
|
287
|
-
}
|
|
288
|
-
if (analytics.styleId) {
|
|
289
|
-
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
|
+
);
|
|
290
291
|
}
|
|
291
|
-
return
|
|
292
|
+
return applyTileflowAnalyticsRequest(url, request, getAnalytics(), sessionId);
|
|
292
293
|
};
|
|
293
294
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
296
|
-
|
|
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;
|
|
297
300
|
}
|
|
298
|
-
|
|
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"
|
|
318
|
-
},
|
|
319
|
-
keepalive: true,
|
|
320
|
-
method: "POST"
|
|
321
|
-
});
|
|
301
|
+
return request ? { ...request, url: nextUrl } : { url: nextUrl };
|
|
322
302
|
}
|
|
323
|
-
function
|
|
324
|
-
|
|
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";
|
|
332
|
-
}
|
|
333
|
-
function mergeAnalytics(userAnalytics, runtimeAnalytics) {
|
|
334
|
-
if (userAnalytics?.enabled === false) {
|
|
335
|
-
return userAnalytics;
|
|
336
|
-
}
|
|
337
|
-
if (!userAnalytics && !runtimeAnalytics?.mapId) {
|
|
338
|
-
return void 0;
|
|
339
|
-
}
|
|
340
|
-
return {
|
|
341
|
-
...runtimeAnalytics,
|
|
342
|
-
...userAnalytics,
|
|
343
|
-
apiUrl: userAnalytics?.apiUrl ?? runtimeAnalytics?.apiUrl,
|
|
344
|
-
mapId: userAnalytics?.mapId ?? runtimeAnalytics?.mapId,
|
|
345
|
-
styleId: userAnalytics?.styleId ?? runtimeAnalytics?.styleId
|
|
346
|
-
};
|
|
347
|
-
}
|
|
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
|
-
}
|
|
303
|
+
function isPromiseLike(value) {
|
|
304
|
+
return Boolean(value && typeof value.then === "function");
|
|
356
305
|
}
|
|
357
|
-
function
|
|
358
|
-
|
|
306
|
+
function clearMarkers(markerInstances) {
|
|
307
|
+
markerInstances.forEach((marker) => marker.remove());
|
|
359
308
|
}
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
return ["localhost", "127.0.0.1", "::1"].includes(window.location.hostname);
|
|
309
|
+
function useStableMapOptions(options) {
|
|
310
|
+
return useStableMapOptionValue(options);
|
|
365
311
|
}
|
|
366
|
-
function
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
);
|
|
312
|
+
function useStableMapOptionValue(value) {
|
|
313
|
+
const valueRef = useRef(value);
|
|
314
|
+
if (!areEquivalentMapOptionValues(valueRef.current, value)) {
|
|
315
|
+
valueRef.current = value;
|
|
380
316
|
}
|
|
381
|
-
return
|
|
317
|
+
return valueRef.current;
|
|
382
318
|
}
|
|
383
|
-
function
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return null;
|
|
319
|
+
function areEquivalentMapOptionValues(left, right, seen = /* @__PURE__ */ new WeakMap()) {
|
|
320
|
+
if (Object.is(left, right)) {
|
|
321
|
+
return true;
|
|
387
322
|
}
|
|
388
|
-
if (typeof
|
|
389
|
-
|
|
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;
|
|
400
|
-
}
|
|
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
|
-
};
|
|
408
|
-
}
|
|
409
|
-
function resolveStaticImageUrl(input) {
|
|
410
|
-
if (!input.manifestMap?.mapId || !input.imageSize) {
|
|
411
|
-
return void 0;
|
|
323
|
+
if (!left || !right || typeof left !== "object" || typeof right !== "object") {
|
|
324
|
+
return false;
|
|
412
325
|
}
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
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);
|
|
326
|
+
const seenRight = seen.get(left);
|
|
327
|
+
if (seenRight) {
|
|
328
|
+
return seenRight === right;
|
|
429
329
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
434
|
-
return 640;
|
|
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));
|
|
435
333
|
}
|
|
436
|
-
|
|
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;
|
|
334
|
+
if (!isPlainObject(left) || !isPlainObject(right)) {
|
|
335
|
+
return false;
|
|
452
336
|
}
|
|
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)
|
|
341
|
+
);
|
|
453
342
|
}
|
|
454
|
-
function
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
return window.location.origin;
|
|
343
|
+
function isPlainObject(value) {
|
|
344
|
+
const prototype = Object.getPrototypeOf(value);
|
|
345
|
+
return prototype === Object.prototype || prototype === null;
|
|
459
346
|
}
|
|
460
347
|
export {
|
|
461
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.11",
|
|
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/
|
|
44
|
-
"@tileflow/
|
|
44
|
+
"@tileflow/core": "0.1.0-alpha.11",
|
|
45
|
+
"@tileflow/static": "0.1.0-alpha.11"
|
|
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
|
}
|