@tileflow/react 0.1.0-alpha.2 → 0.1.0-alpha.21
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/GENERATED_OUTPUT_LICENSE.md +21 -0
- package/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +260 -5
- package/TRADEMARKS.md +10 -0
- package/dist/index.d.ts +57 -33
- package/dist/index.js +1194 -354
- package/dist/static.d.ts +8 -4
- package/dist/static.js +200 -49
- package/package.json +22 -14
package/dist/static.d.ts
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties, ImgHTMLAttributes } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { StaticMapResult } from '@tileflow/static/client';
|
|
4
|
+
import { StaticSceneInput } from '@tileflow/static/scene';
|
|
4
5
|
|
|
5
6
|
type StaticMapBaseProps = StaticSceneInput & {
|
|
6
7
|
alt?: string;
|
|
7
8
|
className?: string;
|
|
9
|
+
captureId?: string;
|
|
8
10
|
imageStyle?: CSSProperties;
|
|
9
11
|
keepPreviousImage?: boolean;
|
|
10
|
-
loading?: ImgHTMLAttributes<HTMLImageElement>[
|
|
12
|
+
loading?: ImgHTMLAttributes<HTMLImageElement>['loading'];
|
|
11
13
|
onError?: (error: Error) => void;
|
|
12
|
-
onImageLoad?: ImgHTMLAttributes<HTMLImageElement>[
|
|
14
|
+
onImageLoad?: ImgHTMLAttributes<HTMLImageElement>['onLoad'];
|
|
13
15
|
onReady?: (result: StaticMapResult) => void;
|
|
14
16
|
style?: CSSProperties;
|
|
15
17
|
};
|
|
16
18
|
type StaticMapImageProps = {
|
|
17
19
|
createUrl?: never;
|
|
20
|
+
idempotencyKey?: never;
|
|
18
21
|
imageUrl: string;
|
|
19
22
|
};
|
|
20
23
|
type StaticMapCreateProps = {
|
|
21
24
|
createUrl: string;
|
|
25
|
+
idempotencyKey: string;
|
|
22
26
|
imageUrl?: never;
|
|
23
27
|
};
|
|
24
28
|
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;
|
|
29
|
+
declare function StaticMap({ alt, camera, className, captureId, createUrl, imageStyle, imageUrl, idempotencyKey, keepPreviousImage, loading, map, onError, onImageLoad, onReady, overlays, size, style, theme, }: StaticMapProps): react.JSX.Element;
|
|
26
30
|
|
|
27
31
|
export { StaticMap, type StaticMapProps };
|
package/dist/static.js
CHANGED
|
@@ -1,19 +1,119 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
// src/static-map.tsx
|
|
2
4
|
import {
|
|
3
5
|
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
4
7
|
useMemo,
|
|
5
8
|
useRef,
|
|
6
9
|
useState
|
|
7
10
|
} from "react";
|
|
8
|
-
import {
|
|
11
|
+
import { normalizeTileflowCaptureId } from "@tileflow/core/capture";
|
|
12
|
+
import {
|
|
13
|
+
prepareStaticMapRequest,
|
|
14
|
+
stableStringify as stableStringify2,
|
|
15
|
+
validateStaticMapIdempotencyKey
|
|
16
|
+
} from "@tileflow/static/client";
|
|
17
|
+
|
|
18
|
+
// src/static-map-request.ts
|
|
19
|
+
import {
|
|
20
|
+
requestStaticMapUntilReady,
|
|
21
|
+
stableStringify
|
|
22
|
+
} from "@tileflow/static/client";
|
|
9
23
|
var inFlightRequests = /* @__PURE__ */ new Map();
|
|
24
|
+
function createStaticMapRequestKey(input) {
|
|
25
|
+
return stableStringify({
|
|
26
|
+
createUrl: input.createUrl,
|
|
27
|
+
idempotencyKey: input.idempotencyKey,
|
|
28
|
+
sceneKey: input.request.sceneKey
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function resolveStaticMap(input) {
|
|
32
|
+
throwIfAborted(input.signal);
|
|
33
|
+
const requestKey = createStaticMapRequestKey(input);
|
|
34
|
+
let entry = inFlightRequests.get(requestKey);
|
|
35
|
+
if (!entry) {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const promise = requestStaticMapUntilReady(input.request, {
|
|
38
|
+
createUrl: input.createUrl,
|
|
39
|
+
fetch: input.fetch,
|
|
40
|
+
idempotencyKey: input.idempotencyKey,
|
|
41
|
+
maxWaitMs: input.maxWaitMs,
|
|
42
|
+
pollIntervalMs: 0,
|
|
43
|
+
signal: controller.signal
|
|
44
|
+
});
|
|
45
|
+
const created = {
|
|
46
|
+
controller,
|
|
47
|
+
consumers: 0,
|
|
48
|
+
promise,
|
|
49
|
+
settled: false
|
|
50
|
+
};
|
|
51
|
+
const settle = () => {
|
|
52
|
+
created.settled = true;
|
|
53
|
+
if (inFlightRequests.get(requestKey) === created) {
|
|
54
|
+
inFlightRequests.delete(requestKey);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
void promise.then(settle, settle);
|
|
58
|
+
inFlightRequests.set(requestKey, created);
|
|
59
|
+
entry = created;
|
|
60
|
+
}
|
|
61
|
+
entry.consumers += 1;
|
|
62
|
+
try {
|
|
63
|
+
return await raceWithAbort(entry.promise, input.signal);
|
|
64
|
+
} finally {
|
|
65
|
+
entry.consumers -= 1;
|
|
66
|
+
if (entry.consumers === 0 && !entry.settled) {
|
|
67
|
+
if (inFlightRequests.get(requestKey) === entry) {
|
|
68
|
+
inFlightRequests.delete(requestKey);
|
|
69
|
+
}
|
|
70
|
+
entry.controller.abort();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function raceWithAbort(operation, signal) {
|
|
75
|
+
if (!signal) return operation;
|
|
76
|
+
if (signal.aborted) return Promise.reject(abortReason(signal));
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const onAbort = () => {
|
|
79
|
+
cleanup();
|
|
80
|
+
reject(abortReason(signal));
|
|
81
|
+
};
|
|
82
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
83
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
84
|
+
operation.then(
|
|
85
|
+
(value) => {
|
|
86
|
+
cleanup();
|
|
87
|
+
resolve(value);
|
|
88
|
+
},
|
|
89
|
+
(error) => {
|
|
90
|
+
cleanup();
|
|
91
|
+
reject(error);
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function throwIfAborted(signal) {
|
|
97
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
98
|
+
}
|
|
99
|
+
function abortReason(signal) {
|
|
100
|
+
if (signal.reason instanceof Error) return signal.reason;
|
|
101
|
+
const error = new Error("Static map request was aborted");
|
|
102
|
+
error.name = "AbortError";
|
|
103
|
+
return error;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/static-map.tsx
|
|
107
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
108
|
function StaticMap({
|
|
11
109
|
alt = "",
|
|
12
110
|
camera,
|
|
13
111
|
className,
|
|
112
|
+
captureId,
|
|
14
113
|
createUrl,
|
|
15
114
|
imageStyle,
|
|
16
115
|
imageUrl,
|
|
116
|
+
idempotencyKey,
|
|
17
117
|
keepPreviousImage = false,
|
|
18
118
|
loading = "lazy",
|
|
19
119
|
map,
|
|
@@ -22,24 +122,65 @@ function StaticMap({
|
|
|
22
122
|
onReady,
|
|
23
123
|
overlays,
|
|
24
124
|
size,
|
|
25
|
-
style
|
|
125
|
+
style,
|
|
126
|
+
theme
|
|
26
127
|
}) {
|
|
27
128
|
const [result, setResult] = useState(
|
|
28
129
|
imageUrl ? imageResult(imageUrl) : null
|
|
29
130
|
);
|
|
30
131
|
const [error, setError] = useState(null);
|
|
132
|
+
const [captureState, setCaptureState] = useState("loading");
|
|
133
|
+
const imageLoadRunRef = useRef(0);
|
|
134
|
+
const imageRef = useRef(null);
|
|
135
|
+
const resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
31
136
|
const onErrorRef = useRef(onError);
|
|
32
137
|
const onReadyRef = useRef(onReady);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
138
|
+
const sceneInputKey = stableStringify2({ camera, map, overlays, size, theme });
|
|
139
|
+
const preparedSceneCandidate = useMemo(
|
|
140
|
+
() => prepareSceneRequest({ camera, map, overlays, size, theme }),
|
|
141
|
+
[camera, map, overlays, size, theme]
|
|
36
142
|
);
|
|
37
|
-
const
|
|
143
|
+
const preparedScene = useStablePreparedSceneRequest(preparedSceneCandidate);
|
|
144
|
+
const sceneKey = preparedScene.ok ? preparedScene.request.sceneKey : `invalid:${sceneInputKey}`;
|
|
145
|
+
const intentKey = imageUrl ? `image:${imageUrl}:${sceneKey}` : `create:${createUrl}:${idempotencyKey ?? ""}:${sceneKey}`;
|
|
146
|
+
const visibleResult = preparedScene.ok ? result : null;
|
|
147
|
+
const visibleError = preparedScene.ok ? error : preparedScene.error;
|
|
148
|
+
const effectiveCaptureState = preparedScene.ok ? captureState : "error";
|
|
149
|
+
useLayoutEffect(() => {
|
|
150
|
+
imageLoadRunRef.current += 1;
|
|
151
|
+
setCaptureState("loading");
|
|
152
|
+
const image = imageRef.current;
|
|
153
|
+
if (image?.complete) {
|
|
154
|
+
if (image.naturalWidth === 0) setCaptureState("error");
|
|
155
|
+
else void markImageReady(image);
|
|
156
|
+
}
|
|
157
|
+
}, [intentKey]);
|
|
158
|
+
async function markImageReady(image) {
|
|
159
|
+
const runId = ++imageLoadRunRef.current;
|
|
160
|
+
try {
|
|
161
|
+
if (typeof image.decode === "function") await image.decode();
|
|
162
|
+
} catch {
|
|
163
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
164
|
+
if (runId === imageLoadRunRef.current) setCaptureState("error");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (runId === imageLoadRunRef.current) setCaptureState("idle");
|
|
169
|
+
}
|
|
38
170
|
useEffect(() => {
|
|
39
171
|
onErrorRef.current = onError;
|
|
40
172
|
onReadyRef.current = onReady;
|
|
41
173
|
}, [onError, onReady]);
|
|
42
174
|
useEffect(() => {
|
|
175
|
+
let cancelled = false;
|
|
176
|
+
if (!preparedScene.ok) {
|
|
177
|
+
if (!keepPreviousImage) setResult(null);
|
|
178
|
+
setError(preparedScene.error);
|
|
179
|
+
imageLoadRunRef.current += 1;
|
|
180
|
+
setCaptureState("error");
|
|
181
|
+
onErrorRef.current?.(preparedScene.error);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
43
184
|
if (imageUrl) {
|
|
44
185
|
const nextResult = imageResult(imageUrl);
|
|
45
186
|
setResult(nextResult);
|
|
@@ -47,18 +188,29 @@ function StaticMap({
|
|
|
47
188
|
onReadyRef.current?.(nextResult);
|
|
48
189
|
return;
|
|
49
190
|
}
|
|
50
|
-
let cancelled = false;
|
|
51
191
|
if (!createUrl) {
|
|
52
192
|
return;
|
|
53
193
|
}
|
|
194
|
+
const validation = validateStaticMapIdempotencyKey(idempotencyKey ?? "");
|
|
195
|
+
if (!validation.ok) {
|
|
196
|
+
const keyError = new Error(`Invalid Static Maps idempotency key: ${validation.error}`);
|
|
197
|
+
if (!keepPreviousImage) setResult(null);
|
|
198
|
+
setError(keyError);
|
|
199
|
+
imageLoadRunRef.current += 1;
|
|
200
|
+
setCaptureState("error");
|
|
201
|
+
onErrorRef.current?.(keyError);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
54
204
|
if (!keepPreviousImage) {
|
|
55
205
|
setResult(null);
|
|
56
206
|
}
|
|
57
207
|
setError(null);
|
|
208
|
+
const requestController = new AbortController();
|
|
58
209
|
resolveStaticMap({
|
|
59
210
|
createUrl,
|
|
60
|
-
|
|
61
|
-
|
|
211
|
+
idempotencyKey: validation.key,
|
|
212
|
+
request: preparedScene.request,
|
|
213
|
+
signal: requestController.signal
|
|
62
214
|
}).then((nextResult) => {
|
|
63
215
|
if (cancelled) {
|
|
64
216
|
return;
|
|
@@ -75,17 +227,24 @@ function StaticMap({
|
|
|
75
227
|
setResult(null);
|
|
76
228
|
}
|
|
77
229
|
setError(normalizedError);
|
|
230
|
+
imageLoadRunRef.current += 1;
|
|
231
|
+
setCaptureState("error");
|
|
78
232
|
onErrorRef.current?.(normalizedError);
|
|
79
233
|
});
|
|
80
234
|
return () => {
|
|
81
235
|
cancelled = true;
|
|
236
|
+
requestController.abort();
|
|
82
237
|
};
|
|
83
|
-
}, [createUrl, imageUrl, keepPreviousImage,
|
|
238
|
+
}, [createUrl, idempotencyKey, imageUrl, keepPreviousImage, preparedScene]);
|
|
84
239
|
return /* @__PURE__ */ jsx(
|
|
85
240
|
"div",
|
|
86
241
|
{
|
|
87
|
-
"aria-busy": !
|
|
242
|
+
"aria-busy": !visibleResult && !visibleError,
|
|
88
243
|
className,
|
|
244
|
+
"data-tileflow-capture-id": resolvedCaptureId,
|
|
245
|
+
"data-tileflow-map": map,
|
|
246
|
+
"data-tileflow-theme": preparedScene.ok ? theme : void 0,
|
|
247
|
+
"data-tileflow-state": effectiveCaptureState,
|
|
89
248
|
style: {
|
|
90
249
|
aspectRatio: `${size.width} / ${size.height}`,
|
|
91
250
|
background: "#f4f4f2",
|
|
@@ -93,7 +252,7 @@ function StaticMap({
|
|
|
93
252
|
width: "100%",
|
|
94
253
|
...style
|
|
95
254
|
},
|
|
96
|
-
children:
|
|
255
|
+
children: visibleResult ? /* @__PURE__ */ jsx(
|
|
97
256
|
"img",
|
|
98
257
|
{
|
|
99
258
|
alt,
|
|
@@ -102,10 +261,16 @@ function StaticMap({
|
|
|
102
261
|
onError: () => {
|
|
103
262
|
const imageError = new Error("Static map image failed to load");
|
|
104
263
|
setError(imageError);
|
|
264
|
+
imageLoadRunRef.current += 1;
|
|
265
|
+
setCaptureState("error");
|
|
105
266
|
onErrorRef.current?.(imageError);
|
|
106
267
|
},
|
|
107
|
-
onLoad:
|
|
108
|
-
|
|
268
|
+
onLoad: (event) => {
|
|
269
|
+
onImageLoad?.(event);
|
|
270
|
+
void markImageReady(event.currentTarget);
|
|
271
|
+
},
|
|
272
|
+
ref: imageRef,
|
|
273
|
+
src: visibleResult.imageUrl,
|
|
109
274
|
style: {
|
|
110
275
|
display: "block",
|
|
111
276
|
height: "100%",
|
|
@@ -118,50 +283,36 @@ function StaticMap({
|
|
|
118
283
|
}
|
|
119
284
|
);
|
|
120
285
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
286
|
+
function prepareSceneRequest(scene) {
|
|
287
|
+
try {
|
|
288
|
+
return { ok: true, request: prepareStaticMapRequest(scene) };
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return {
|
|
291
|
+
error: error instanceof Error ? error : new Error("Invalid Tileflow static scene"),
|
|
292
|
+
ok: false
|
|
293
|
+
};
|
|
125
294
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
return await response.json();
|
|
137
|
-
}).finally(() => {
|
|
138
|
-
inFlightRequests.delete(input.requestKey);
|
|
139
|
-
});
|
|
140
|
-
inFlightRequests.set(input.requestKey, promise);
|
|
141
|
-
return promise;
|
|
295
|
+
}
|
|
296
|
+
function useStablePreparedSceneRequest(value) {
|
|
297
|
+
const valueRef = useRef(value);
|
|
298
|
+
const current = valueRef.current;
|
|
299
|
+
const equivalent = current.ok === value.ok && (current.ok && value.ok ? current.request.sceneKey === value.request.sceneKey : !current.ok && !value.ok && current.error.message === value.error.message);
|
|
300
|
+
if (!equivalent) {
|
|
301
|
+
valueRef.current = value;
|
|
302
|
+
}
|
|
303
|
+
return valueRef.current;
|
|
142
304
|
}
|
|
143
305
|
function imageResult(imageUrl) {
|
|
144
306
|
return {
|
|
145
307
|
cached: true,
|
|
146
308
|
hash: "",
|
|
147
309
|
imageUrl,
|
|
148
|
-
|
|
310
|
+
operationId: null,
|
|
311
|
+
remainingUnits: null,
|
|
312
|
+
status: "ready",
|
|
313
|
+
unitCost: 0
|
|
149
314
|
};
|
|
150
315
|
}
|
|
151
|
-
function stableStringify(value) {
|
|
152
|
-
return JSON.stringify(sortJson(value));
|
|
153
|
-
}
|
|
154
|
-
function sortJson(value) {
|
|
155
|
-
if (Array.isArray(value)) {
|
|
156
|
-
return value.map(sortJson);
|
|
157
|
-
}
|
|
158
|
-
if (value && typeof value === "object") {
|
|
159
|
-
return Object.fromEntries(
|
|
160
|
-
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, sortJson(entry)])
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
return value;
|
|
164
|
-
}
|
|
165
316
|
export {
|
|
166
317
|
StaticMap
|
|
167
318
|
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tileflow/react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.21",
|
|
4
|
+
"license": "Apache-2.0",
|
|
4
5
|
"description": "React components for rendering Tileflow maps with MapLibre.",
|
|
5
6
|
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
6
8
|
"homepage": "https://tileflow.dev",
|
|
7
9
|
"repository": {
|
|
8
10
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/tileflow/tileflow.git",
|
|
11
|
+
"url": "git+https://github.com/tileflow/tileflow-sdk.git",
|
|
10
12
|
"directory": "packages/react"
|
|
11
13
|
},
|
|
12
14
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/tileflow/tileflow/issues"
|
|
15
|
+
"url": "https://github.com/tileflow/tileflow-sdk/issues"
|
|
14
16
|
},
|
|
15
17
|
"keywords": [
|
|
16
18
|
"tileflow",
|
|
@@ -22,7 +24,11 @@
|
|
|
22
24
|
"main": "./dist/index.js",
|
|
23
25
|
"types": "./dist/index.d.ts",
|
|
24
26
|
"files": [
|
|
25
|
-
"dist"
|
|
27
|
+
"dist",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"NOTICE",
|
|
30
|
+
"GENERATED_OUTPUT_LICENSE.md",
|
|
31
|
+
"TRADEMARKS.md"
|
|
26
32
|
],
|
|
27
33
|
"exports": {
|
|
28
34
|
".": {
|
|
@@ -40,24 +46,26 @@
|
|
|
40
46
|
"access": "public"
|
|
41
47
|
},
|
|
42
48
|
"dependencies": {
|
|
43
|
-
"@tileflow/
|
|
44
|
-
"@tileflow/
|
|
49
|
+
"@tileflow/core": ">=0.1.0-alpha.22 <0.1.0-beta.0",
|
|
50
|
+
"@tileflow/interactions": ">=0.1.0-alpha.1 <0.1.0-beta.0",
|
|
51
|
+
"@tileflow/static": ">=0.1.0-alpha.18 <0.1.0-beta.0"
|
|
45
52
|
},
|
|
46
53
|
"peerDependencies": {
|
|
47
|
-
"maplibre-gl": ">=5",
|
|
48
|
-
"react": ">=18",
|
|
49
|
-
"react-dom": ">=18"
|
|
54
|
+
"maplibre-gl": ">=5 <7",
|
|
55
|
+
"react": ">=18 <20",
|
|
56
|
+
"react-dom": ">=18 <20"
|
|
50
57
|
},
|
|
51
58
|
"devDependencies": {
|
|
59
|
+
"@types/react": "^19.2.17",
|
|
60
|
+
"@types/react-dom": "^19.2.3",
|
|
52
61
|
"maplibre-gl": "^5.24.0",
|
|
53
62
|
"react": "^19.2.7",
|
|
54
|
-
"react-dom": "^19.2.7"
|
|
55
|
-
"@types/react": "^19.2.17",
|
|
56
|
-
"@types/react-dom": "^19.2.3"
|
|
63
|
+
"react-dom": "^19.2.7"
|
|
57
64
|
},
|
|
58
65
|
"scripts": {
|
|
59
|
-
"build": "tsup
|
|
60
|
-
"
|
|
66
|
+
"build": "tsup",
|
|
67
|
+
"verify": "pnpm run build && tsx --test test/*.test.ts",
|
|
68
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.contract.json",
|
|
61
69
|
"lint": "tsc -p tsconfig.json --noEmit"
|
|
62
70
|
}
|
|
63
71
|
}
|