@tileflow/svelte 0.1.0-alpha.14 → 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 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.14",
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.14"
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
  }
@@ -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 bind:this={container} class={className} style={frameStyle}>
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<
package/src/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export type TileflowMapProps = {
15
15
  alt?: string;
16
16
  analytics?: TileflowAnalytics;
17
17
  center?: [number, number];
18
+ captureId?: string;
18
19
  className?: string;
19
20
  config?: TileflowConfig;
20
21
  height?: number | string;