@tileflow/svelte 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 +13 -0
- package/package.json +6 -4
- package/src/TileflowMap.svelte +81 -2
- package/src/TileflowMap.svelte.d.ts +1 -1
- package/src/index.d.ts +1 -0
package/README.md
CHANGED
|
@@ -26,4 +26,17 @@ Tileflow resolves from the map name, manifest, or explicit style props. Direct
|
|
|
26
26
|
Tileflow props such as `center`, `zoom`, and `interactive` take priority when
|
|
27
27
|
provided.
|
|
28
28
|
|
|
29
|
+
## Headless capture readiness
|
|
30
|
+
|
|
31
|
+
Use `captureId` to disambiguate repeated named maps:
|
|
32
|
+
|
|
33
|
+
```svelte
|
|
34
|
+
<TileflowMap map="madrid" captureId="checkout-map" />
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The root exposes `data-tileflow-map`, optional `data-tileflow-capture-id`, and
|
|
38
|
+
`data-tileflow-state="loading|idle|error"`. It becomes idle only after MapLibre idle plus two
|
|
39
|
+
animation frames (or image decode/load in image mode), returns to loading for new style/data work,
|
|
40
|
+
and marks terminal load errors. Application capture selects exactly one ready target.
|
|
41
|
+
|
|
29
42
|
Docs: https://tileflow.dev/docs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tileflow/svelte",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.16",
|
|
4
4
|
"description": "Svelte component for rendering Tileflow maps with MapLibre.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
"homepage": "https://tileflow.dev",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/tileflow/tileflow.git",
|
|
11
|
+
"url": "git+https://github.com/tileflow/tileflow-sdk.git",
|
|
12
12
|
"directory": "packages/svelte"
|
|
13
13
|
},
|
|
14
14
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/tileflow/tileflow/issues"
|
|
15
|
+
"url": "https://github.com/tileflow/tileflow-sdk/issues"
|
|
16
16
|
},
|
|
17
17
|
"keywords": [
|
|
18
18
|
"tileflow",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@tileflow/core": "0.1.0-alpha.
|
|
45
|
+
"@tileflow/core": "0.1.0-alpha.16"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"maplibre-gl": ">=5",
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "svelte-check --tsconfig ./tsconfig.json",
|
|
58
|
+
"test": "node --test test/*.test.js",
|
|
59
|
+
"verify": "pnpm run build && pnpm run test",
|
|
58
60
|
"typecheck": "svelte-check --tsconfig ./tsconfig.json",
|
|
59
61
|
"lint": "svelte-check --tsconfig ./tsconfig.json"
|
|
60
62
|
}
|
package/src/TileflowMap.svelte
CHANGED
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
type RequestTransformFunction,
|
|
8
8
|
type StyleSpecification,
|
|
9
9
|
} from 'maplibre-gl';
|
|
10
|
-
import {createEventDispatcher, onMount} from 'svelte';
|
|
10
|
+
import {createEventDispatcher, onMount, tick} from 'svelte';
|
|
11
11
|
import {
|
|
12
12
|
createTileflowSessionId,
|
|
13
13
|
defaultTileflowManifestUrl,
|
|
14
14
|
loadTileflowManifest,
|
|
15
15
|
mergeTileflowAnalytics,
|
|
16
|
+
normalizeTileflowCaptureId,
|
|
16
17
|
normalizeTileflowStaticImageSize,
|
|
17
18
|
resolveTileflowAnalyticsRequestUrl,
|
|
18
19
|
resolveTileflowManifestMap,
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
const defaultZoom = 2;
|
|
38
39
|
|
|
39
40
|
export let center: [number, number] | undefined = undefined;
|
|
41
|
+
export let captureId: string | undefined = undefined;
|
|
40
42
|
export let className = '';
|
|
41
43
|
export let config: TileflowConfig | undefined = undefined;
|
|
42
44
|
export let height: number | string = 420;
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
export let zoom: number | undefined = undefined;
|
|
58
60
|
|
|
59
61
|
let container: HTMLDivElement;
|
|
62
|
+
let captureState: 'error' | 'idle' | 'loading' = 'loading';
|
|
60
63
|
let imageSize: {height: number; width: number} | null = null;
|
|
61
64
|
let imageResizeObserver: ResizeObserver | null = null;
|
|
62
65
|
let mapInstance: MapLibreMap | null = null;
|
|
@@ -66,12 +69,14 @@
|
|
|
66
69
|
let manifestLoadId = 0;
|
|
67
70
|
let mounted = false;
|
|
68
71
|
let refreshRunId = 0;
|
|
72
|
+
let readinessRunId = 0;
|
|
69
73
|
|
|
70
74
|
const dispatch = createEventDispatcher<{load: MapLibreMap}>();
|
|
71
75
|
const sessionId = createTileflowSessionId();
|
|
72
76
|
const sessionStarts = new Set<string>();
|
|
73
77
|
|
|
74
78
|
$: resolvedMode = resolveTileflowMapMode({imageUrl, mode, preferLocalDev});
|
|
79
|
+
$: resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
75
80
|
$: isImageMode = resolvedMode === 'image';
|
|
76
81
|
$: imageCenter = center ?? defaultCenter;
|
|
77
82
|
$: imageZoom = zoom ?? defaultZoom;
|
|
@@ -134,6 +139,11 @@
|
|
|
134
139
|
syncMarkers();
|
|
135
140
|
}
|
|
136
141
|
|
|
142
|
+
$: if (mounted && isImageMode) {
|
|
143
|
+
runtimeImageUrl;
|
|
144
|
+
void markImageLoading();
|
|
145
|
+
}
|
|
146
|
+
|
|
137
147
|
onMount(() => {
|
|
138
148
|
mounted = true;
|
|
139
149
|
|
|
@@ -214,6 +224,7 @@
|
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
function destroyMap() {
|
|
227
|
+
readinessRunId += 1;
|
|
217
228
|
mapResizeObserver?.disconnect();
|
|
218
229
|
mapResizeObserver = null;
|
|
219
230
|
clearMarkers();
|
|
@@ -223,6 +234,7 @@
|
|
|
223
234
|
|
|
224
235
|
function recreateMap() {
|
|
225
236
|
destroyMap();
|
|
237
|
+
captureState = 'loading';
|
|
226
238
|
|
|
227
239
|
if (!container || !runtimeStyle || isImageMode) {
|
|
228
240
|
return;
|
|
@@ -275,6 +287,28 @@
|
|
|
275
287
|
}
|
|
276
288
|
}
|
|
277
289
|
});
|
|
290
|
+
maplibreMap.on('dataloading', () => {
|
|
291
|
+
readinessRunId += 1;
|
|
292
|
+
captureState = 'loading';
|
|
293
|
+
});
|
|
294
|
+
maplibreMap.on('styledataloading', () => {
|
|
295
|
+
readinessRunId += 1;
|
|
296
|
+
captureState = 'loading';
|
|
297
|
+
});
|
|
298
|
+
maplibreMap.on('idle', () => {
|
|
299
|
+
const runId = ++readinessRunId;
|
|
300
|
+
requestAnimationFrame(() => {
|
|
301
|
+
requestAnimationFrame(() => {
|
|
302
|
+
if (runId === readinessRunId && mapInstance === maplibreMap) {
|
|
303
|
+
captureState = 'idle';
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
maplibreMap.on('error', () => {
|
|
309
|
+
readinessRunId += 1;
|
|
310
|
+
captureState = 'error';
|
|
311
|
+
});
|
|
278
312
|
|
|
279
313
|
syncMarkers();
|
|
280
314
|
}
|
|
@@ -313,6 +347,42 @@
|
|
|
313
347
|
markerInstances = [];
|
|
314
348
|
}
|
|
315
349
|
|
|
350
|
+
async function handleImageLoad(event: Event) {
|
|
351
|
+
await markImageReady(event.currentTarget as HTMLImageElement);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function markImageReady(image: HTMLImageElement) {
|
|
355
|
+
const runId = ++readinessRunId;
|
|
356
|
+
try {
|
|
357
|
+
if (typeof image.decode === 'function') await image.decode();
|
|
358
|
+
} catch {
|
|
359
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
360
|
+
if (runId === readinessRunId) captureState = 'error';
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (runId === readinessRunId) captureState = 'idle';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function markImageLoading() {
|
|
368
|
+
const runId = ++readinessRunId;
|
|
369
|
+
captureState = 'loading';
|
|
370
|
+
await tick();
|
|
371
|
+
if (!mounted || runId !== readinessRunId) return;
|
|
372
|
+
const image = container?.querySelector('img');
|
|
373
|
+
if (!image?.complete) return;
|
|
374
|
+
if (image.naturalWidth === 0) {
|
|
375
|
+
captureState = 'error';
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
await markImageReady(image);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function handleImageError() {
|
|
382
|
+
readinessRunId += 1;
|
|
383
|
+
captureState = 'error';
|
|
384
|
+
}
|
|
385
|
+
|
|
316
386
|
function createTransformRequest(
|
|
317
387
|
getAnalytics: () => TileflowAnalytics | undefined,
|
|
318
388
|
session: string,
|
|
@@ -358,12 +428,21 @@
|
|
|
358
428
|
}
|
|
359
429
|
</script>
|
|
360
430
|
|
|
361
|
-
<div
|
|
431
|
+
<div
|
|
432
|
+
bind:this={container}
|
|
433
|
+
class={className}
|
|
434
|
+
data-tileflow-capture-id={resolvedCaptureId}
|
|
435
|
+
data-tileflow-map={map}
|
|
436
|
+
data-tileflow-state={captureState}
|
|
437
|
+
style={frameStyle}
|
|
438
|
+
>
|
|
362
439
|
{#if isImageMode && runtimeImageUrl}
|
|
363
440
|
<img
|
|
364
441
|
{alt}
|
|
365
442
|
decoding="async"
|
|
366
443
|
loading={imageLoading}
|
|
444
|
+
on:error={handleImageError}
|
|
445
|
+
on:load={handleImageLoad}
|
|
367
446
|
src={runtimeImageUrl}
|
|
368
447
|
style="display: block; height: 100%; object-fit: cover; width: 100%;"
|
|
369
448
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {SvelteComponentTyped} from 'svelte';
|
|
2
1
|
import type {Map as MapLibreMap} from 'maplibre-gl';
|
|
2
|
+
import type {SvelteComponentTyped} from 'svelte';
|
|
3
3
|
import type {TileflowMapProps} from './index';
|
|
4
4
|
|
|
5
5
|
export default class TileflowMap extends SvelteComponentTyped<
|