@tileflow/react 0.1.0-alpha.19 → 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/static.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, ImgHTMLAttributes } from 'react';
3
- import { StaticSceneInput, StaticMapResult } from '@tileflow/static';
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;
@@ -25,6 +26,6 @@ type StaticMapCreateProps = {
25
26
  imageUrl?: never;
26
27
  };
27
28
  type StaticMapProps = StaticMapBaseProps & (StaticMapCreateProps | StaticMapImageProps);
28
- declare function StaticMap({ alt, camera, className, captureId, createUrl, imageStyle, imageUrl, idempotencyKey, 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;
29
30
 
30
31
  export { StaticMap, type StaticMapProps };
package/dist/static.js CHANGED
@@ -8,26 +8,40 @@ import {
8
8
  useRef,
9
9
  useState
10
10
  } from "react";
11
- import { normalizeTileflowCaptureId } from "@tileflow/core";
11
+ import { normalizeTileflowCaptureId } from "@tileflow/core/capture";
12
12
  import {
13
+ prepareStaticMapRequest,
14
+ stableStringify as stableStringify2,
13
15
  validateStaticMapIdempotencyKey
14
- } from "@tileflow/static";
16
+ } from "@tileflow/static/client";
15
17
 
16
18
  // src/static-map-request.ts
17
19
  import {
18
- staticMapProcessingResultSchema,
19
- staticMapReadyResultSchema
20
- } from "@tileflow/static";
20
+ requestStaticMapUntilReady,
21
+ stableStringify
22
+ } from "@tileflow/static/client";
21
23
  var inFlightRequests = /* @__PURE__ */ new Map();
22
24
  function createStaticMapRequestKey(input) {
23
- return stableStringify(input);
25
+ return stableStringify({
26
+ createUrl: input.createUrl,
27
+ idempotencyKey: input.idempotencyKey,
28
+ sceneKey: input.request.sceneKey
29
+ });
24
30
  }
25
31
  async function resolveStaticMap(input) {
26
32
  throwIfAborted(input.signal);
27
- let entry = inFlightRequests.get(input.requestKey);
33
+ const requestKey = createStaticMapRequestKey(input);
34
+ let entry = inFlightRequests.get(requestKey);
28
35
  if (!entry) {
29
36
  const controller = new AbortController();
30
- const promise = requestStaticMapUntilReady({ ...input, signal: controller.signal });
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
+ });
31
45
  const created = {
32
46
  controller,
33
47
  consumers: 0,
@@ -36,12 +50,12 @@ async function resolveStaticMap(input) {
36
50
  };
37
51
  const settle = () => {
38
52
  created.settled = true;
39
- if (inFlightRequests.get(input.requestKey) === created) {
40
- inFlightRequests.delete(input.requestKey);
53
+ if (inFlightRequests.get(requestKey) === created) {
54
+ inFlightRequests.delete(requestKey);
41
55
  }
42
56
  };
43
57
  void promise.then(settle, settle);
44
- inFlightRequests.set(input.requestKey, created);
58
+ inFlightRequests.set(requestKey, created);
45
59
  entry = created;
46
60
  }
47
61
  entry.consumers += 1;
@@ -50,97 +64,13 @@ async function resolveStaticMap(input) {
50
64
  } finally {
51
65
  entry.consumers -= 1;
52
66
  if (entry.consumers === 0 && !entry.settled) {
53
- if (inFlightRequests.get(input.requestKey) === entry) {
54
- inFlightRequests.delete(input.requestKey);
67
+ if (inFlightRequests.get(requestKey) === entry) {
68
+ inFlightRequests.delete(requestKey);
55
69
  }
56
70
  entry.controller.abort();
57
71
  }
58
72
  }
59
73
  }
60
- async function requestStaticMapUntilReady(input) {
61
- const maxWaitMs = input.maxWaitMs ?? 3e4;
62
- if (!Number.isSafeInteger(maxWaitMs) || maxWaitMs < 1 || maxWaitMs > 12e4) {
63
- throw new Error("Static map maxWaitMs must be an integer from 1 to 120000");
64
- }
65
- return runWithinBudget(maxWaitMs, input.signal, async (signal) => {
66
- const body = JSON.stringify(input.scene);
67
- const fetcher = input.fetch ?? fetch;
68
- let operationId = null;
69
- while (true) {
70
- const response = await fetcher(input.createUrl, {
71
- body,
72
- headers: {
73
- "Content-Type": "application/json",
74
- "Idempotency-Key": input.idempotencyKey
75
- },
76
- method: "POST",
77
- signal
78
- });
79
- throwIfAborted(signal);
80
- if (!response.ok) throw new Error(`Static map endpoint failed: ${response.status}`);
81
- let value;
82
- try {
83
- value = await response.json();
84
- } catch {
85
- throw new Error("Static map endpoint returned invalid JSON");
86
- }
87
- throwIfAborted(signal);
88
- if (response.status !== 202) {
89
- const ready = staticMapReadyResultSchema.safeParse(value);
90
- if (!ready.success) throw new Error("Static map endpoint returned an invalid response");
91
- assertStaticMapOperationIdentity(operationId, ready.data.operationId);
92
- return ready.data;
93
- }
94
- const pending = staticMapProcessingResultSchema.safeParse(value);
95
- if (!pending.success) throw new Error("Static map endpoint returned an invalid response");
96
- assertStaticMapOperationIdentity(operationId, pending.data.operationId);
97
- operationId ??= pending.data.operationId;
98
- await delay(pending.data.retryAfterMs, signal);
99
- }
100
- });
101
- }
102
- function stableStringify(value) {
103
- return JSON.stringify(sortJson(value));
104
- }
105
- function assertStaticMapOperationIdentity(expected, actual) {
106
- if (expected !== null && actual !== expected) {
107
- throw new Error("Static map endpoint changed operation identity while polling");
108
- }
109
- }
110
- async function runWithinBudget(maxWaitMs, externalSignal, operation) {
111
- const controller = new AbortController();
112
- const timeoutError = new Error(`Static map request timed out after ${maxWaitMs}ms`);
113
- const abortFromConsumer = () => controller.abort(externalSignal?.reason);
114
- if (externalSignal?.aborted) abortFromConsumer();
115
- else externalSignal?.addEventListener("abort", abortFromConsumer, { once: true });
116
- const timeout = setTimeout(() => controller.abort(timeoutError), maxWaitMs);
117
- try {
118
- throwIfAborted(controller.signal);
119
- const pending = operation(controller.signal);
120
- return await raceWithAbort(pending, controller.signal);
121
- } finally {
122
- clearTimeout(timeout);
123
- externalSignal?.removeEventListener("abort", abortFromConsumer);
124
- }
125
- }
126
- function delay(milliseconds, signal) {
127
- if (milliseconds === 0) {
128
- throwIfAborted(signal);
129
- return Promise.resolve();
130
- }
131
- return new Promise((resolve, reject) => {
132
- const timeout = setTimeout(() => {
133
- signal.removeEventListener("abort", onAbort);
134
- resolve();
135
- }, milliseconds);
136
- const onAbort = () => {
137
- clearTimeout(timeout);
138
- reject(abortReason(signal));
139
- };
140
- if (signal.aborted) onAbort();
141
- else signal.addEventListener("abort", onAbort, { once: true });
142
- });
143
- }
144
74
  function raceWithAbort(operation, signal) {
145
75
  if (!signal) return operation;
146
76
  if (signal.aborted) return Promise.reject(abortReason(signal));
@@ -172,15 +102,6 @@ function abortReason(signal) {
172
102
  error.name = "AbortError";
173
103
  return error;
174
104
  }
175
- function sortJson(value) {
176
- if (Array.isArray(value)) return value.map(sortJson);
177
- if (value && typeof value === "object") {
178
- return Object.fromEntries(
179
- Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, sortJson(entry)])
180
- );
181
- }
182
- return value;
183
- }
184
105
 
185
106
  // src/static-map.tsx
186
107
  import { jsx } from "react/jsx-runtime";
@@ -201,7 +122,8 @@ function StaticMap({
201
122
  onReady,
202
123
  overlays,
203
124
  size,
204
- style
125
+ style,
126
+ theme
205
127
  }) {
206
128
  const [result, setResult] = useState(
207
129
  imageUrl ? imageResult(imageUrl) : null
@@ -213,11 +135,17 @@ function StaticMap({
213
135
  const resolvedCaptureId = normalizeTileflowCaptureId(captureId);
214
136
  const onErrorRef = useRef(onError);
215
137
  const onReadyRef = useRef(onReady);
216
- const sceneKey = useMemo(
217
- () => stableStringify({ camera, map, overlays, size }),
218
- [camera, map, overlays, size]
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]
219
142
  );
220
- const intentKey = imageUrl ? `image:${imageUrl}` : `create:${createUrl}:${idempotencyKey ?? ""}:${sceneKey}`;
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";
221
149
  useLayoutEffect(() => {
222
150
  imageLoadRunRef.current += 1;
223
151
  setCaptureState("loading");
@@ -244,6 +172,15 @@ function StaticMap({
244
172
  onReadyRef.current = onReady;
245
173
  }, [onError, onReady]);
246
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
+ }
247
184
  if (imageUrl) {
248
185
  const nextResult = imageResult(imageUrl);
249
186
  setResult(nextResult);
@@ -251,7 +188,6 @@ function StaticMap({
251
188
  onReadyRef.current?.(nextResult);
252
189
  return;
253
190
  }
254
- let cancelled = false;
255
191
  if (!createUrl) {
256
192
  return;
257
193
  }
@@ -273,12 +209,7 @@ function StaticMap({
273
209
  resolveStaticMap({
274
210
  createUrl,
275
211
  idempotencyKey: validation.key,
276
- requestKey: createStaticMapRequestKey({
277
- createUrl,
278
- idempotencyKey: validation.key,
279
- sceneKey
280
- }),
281
- scene: JSON.parse(sceneKey),
212
+ request: preparedScene.request,
282
213
  signal: requestController.signal
283
214
  }).then((nextResult) => {
284
215
  if (cancelled) {
@@ -304,15 +235,16 @@ function StaticMap({
304
235
  cancelled = true;
305
236
  requestController.abort();
306
237
  };
307
- }, [createUrl, idempotencyKey, imageUrl, intentKey, keepPreviousImage, sceneKey]);
238
+ }, [createUrl, idempotencyKey, imageUrl, keepPreviousImage, preparedScene]);
308
239
  return /* @__PURE__ */ jsx(
309
240
  "div",
310
241
  {
311
- "aria-busy": !result && !error,
242
+ "aria-busy": !visibleResult && !visibleError,
312
243
  className,
313
244
  "data-tileflow-capture-id": resolvedCaptureId,
314
245
  "data-tileflow-map": map,
315
- "data-tileflow-state": captureState,
246
+ "data-tileflow-theme": preparedScene.ok ? theme : void 0,
247
+ "data-tileflow-state": effectiveCaptureState,
316
248
  style: {
317
249
  aspectRatio: `${size.width} / ${size.height}`,
318
250
  background: "#f4f4f2",
@@ -320,7 +252,7 @@ function StaticMap({
320
252
  width: "100%",
321
253
  ...style
322
254
  },
323
- children: result ? /* @__PURE__ */ jsx(
255
+ children: visibleResult ? /* @__PURE__ */ jsx(
324
256
  "img",
325
257
  {
326
258
  alt,
@@ -338,7 +270,7 @@ function StaticMap({
338
270
  void markImageReady(event.currentTarget);
339
271
  },
340
272
  ref: imageRef,
341
- src: result.imageUrl,
273
+ src: visibleResult.imageUrl,
342
274
  style: {
343
275
  display: "block",
344
276
  height: "100%",
@@ -351,6 +283,25 @@ function StaticMap({
351
283
  }
352
284
  );
353
285
  }
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
+ };
294
+ }
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;
304
+ }
354
305
  function imageResult(imageUrl) {
355
306
  return {
356
307
  cached: true,
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@tileflow/react",
3
- "version": "0.1.0-alpha.19",
3
+ "version": "0.1.0-alpha.20",
4
+ "license": "Apache-2.0",
4
5
  "description": "React components for rendering Tileflow maps with MapLibre.",
5
6
  "type": "module",
6
7
  "sideEffects": false,
@@ -23,7 +24,11 @@
23
24
  "main": "./dist/index.js",
24
25
  "types": "./dist/index.d.ts",
25
26
  "files": [
26
- "dist"
27
+ "dist",
28
+ "LICENSE",
29
+ "NOTICE",
30
+ "GENERATED_OUTPUT_LICENSE.md",
31
+ "TRADEMARKS.md"
27
32
  ],
28
33
  "exports": {
29
34
  ".": {
@@ -41,22 +46,26 @@
41
46
  "access": "public"
42
47
  },
43
48
  "dependencies": {
44
- "@tileflow/core": ">=0.1.0-alpha.19 <0.1.0-beta.0",
45
- "@tileflow/static": ">=0.1.0-alpha.17 <0.1.0-beta.0"
49
+ "@tileflow/core": ">=0.1.0-alpha.21 <0.1.0-beta.0",
50
+ "@tileflow/interactions": ">=0.1.0-alpha.0 <0.1.0-beta.0",
51
+ "@tileflow/static": ">=0.1.0-alpha.18 <0.1.0-beta.0"
46
52
  },
47
53
  "peerDependencies": {
48
- "maplibre-gl": ">=5",
49
- "react": ">=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
- "@types/react": "^19.2.17"
63
+ "react-dom": "^19.2.7"
55
64
  },
56
65
  "scripts": {
57
66
  "build": "tsup",
58
67
  "verify": "pnpm run build && tsx --test test/*.test.ts",
59
- "typecheck": "tsc -p tsconfig.json --noEmit",
68
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.contract.json",
60
69
  "lint": "tsc -p tsconfig.json --noEmit"
61
70
  }
62
71
  }