@tileflow/svelte 0.1.0-alpha.34 → 0.1.0-alpha.36

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
@@ -16,6 +16,12 @@ npm install @tileflow/svelte@alpha @tileflow/core@alpha @tileflow/maps@alpha "ma
16
16
  npm install --save-dev --save-exact tileflow@alpha
17
17
  ```
18
18
 
19
+ Prepared styles may retain relative asset URLs. The component loads them through Core's shared
20
+ resource loader before creating the map and before changing or blending themes. Sprites, fonts,
21
+ data and model resources resolve against their declaring documents. Pending document loads are
22
+ cancelled when the map is replaced or unmounted; `mapOptions.transformRequest` also applies to
23
+ style and HTTP TileJSON requests.
24
+
19
25
  ## Prepare a map
20
26
 
21
27
  Create `tileflow.config.ts` at the application root:
@@ -94,7 +100,7 @@ Add `timeOfDay="dusk"` beside `theme` to select a map's optional `day`, `dawn`,
94
100
  `night` preset. For Streets, `theme="light"` with this preset selects the existing `dusk` palette.
95
101
  Maps without this capability ignore the option; omitting it keeps the selected theme unchanged.
96
102
  Changing it uses the existing theme transition. `themeBlend` takes precedence. See the
97
- [authoring contract](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/README.md#style-with-themes-and-semantic-modules).
103
+ [authoring contract](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/REFERENCE.md#style-with-themes-and-semantic-modules).
98
104
 
99
105
  Theme and `timeOfDay` changes cross-fade over 450 ms by default, respecting reduced motion.
100
106
  `themeTransition={{duration: 700}}` customizes the duration; use `duration: 0` for an immediate change.
@@ -193,6 +199,26 @@ snippets reconciles existing runtimes rather than recreating MapLibre.
193
199
  Text/field descriptors render as text. A `view` names an application snippet; it is not serialized
194
200
  HTML. Keep tooltips non-interactive and put buttons, links, and forms in popups.
195
201
 
202
+ ## Cluster points and draw density
203
+
204
+ Use `annotationClusters` for up to 1,000 annotations, or `dataSources` and `dataOverlays` for larger
205
+ GeoJSON sets. Clicking a cluster zooms in and reports a `cluster` target through `onInteractionEvent`.
206
+
207
+ <!-- docs:check -->
208
+
209
+ ```ts
210
+ import {clusterOverlay, geojsonSource} from '@tileflow/core/runtime';
211
+
212
+ export const dataSources = {stores: geojsonSource({cluster: true, clusterRadius: 50})};
213
+ export const dataOverlays = {
214
+ stores: clusterOverlay({id: 'stores', placement: 'above-labels', source: 'stores'}),
215
+ };
216
+ ```
217
+
218
+ Pass plain data: `$state` proxies cannot reach MapLibre's worker, so use `$state.raw`. See the
219
+ [interactions reference](https://github.com/tileflow/tileflow-sdk/blob/main/packages/interactions/REFERENCE.md)
220
+ for heatmaps, events, and polygon drawing with Terra Draw.
221
+
196
222
  ## Display an existing image
197
223
 
198
224
  `mode="image"` displays an explicit `imageUrl` or derives an image URL from the selected manifest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/svelte",
3
- "version": "0.1.0-alpha.34",
3
+ "version": "0.1.0-alpha.36",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Svelte component for rendering Tileflow maps with MapLibre.",
6
6
  "type": "module",
@@ -44,15 +44,15 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@tileflow/core": ">=0.1.0-alpha.43 <0.1.0-beta.0",
48
- "@tileflow/interactions": ">=0.1.0-alpha.7 <0.1.0-beta.0"
47
+ "@tileflow/core": ">=0.1.0-alpha.48 <0.1.0-beta.0",
48
+ "@tileflow/interactions": ">=0.1.0-alpha.8 <0.1.0-beta.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "maplibre-gl": ">=6.4.1 <7",
52
52
  "svelte": ">=5 <6"
53
53
  },
54
54
  "devDependencies": {
55
- "maplibre-gl": "6.13.0",
55
+ "maplibre-gl": "6.16.0",
56
56
  "svelte": "^5.57.2",
57
57
  "svelte-check": "^4.7.7"
58
58
  },
@@ -42,6 +42,7 @@
42
42
  type TileflowMap3DAttachment,
43
43
  createTileflowTransformRequest,
44
44
  getTileflowSystemColorScheme,
45
+ loadTileflowRuntimeStyle,
45
46
  loadTileflowStyleFonts,
46
47
  registerTileflowContourProtocol,
47
48
  registerTileflowPathGroundProtocol,
@@ -70,9 +71,11 @@
70
71
  type TileflowInteractionViewContext,
71
72
  } from '@tileflow/interactions';
72
73
  import {
74
+ createTileflowMapLibreDataRuntime,
73
75
  createTileflowMapLibreDomRuntime,
74
76
  createTileflowMapLibreInteractionCoordinator,
75
77
  createTileflowMapLibreSemanticDomRuntime,
78
+ type TileflowMapLibreDataRuntime,
76
79
  type TileflowMapLibreDomRenderTarget,
77
80
  type TileflowMapLibreDomRuntime,
78
81
  type TileflowMapLibreInteractionCoordinator,
@@ -86,7 +89,10 @@
86
89
 
87
90
  export let alt: NonNullable<TileflowMapProps['alt']> = '';
88
91
  export let analytics: TileflowMapProps['analytics'] = undefined;
92
+ export let annotationClusters: TileflowMapProps['annotationClusters'] = undefined;
89
93
  export let annotations: TileflowMapProps['annotations'] = undefined;
94
+ export let dataOverlays: TileflowMapProps['dataOverlays'] = undefined;
95
+ export let dataSources: TileflowMapProps['dataSources'] = undefined;
90
96
 
91
97
  export let center: TileflowMapProps['center'] = undefined;
92
98
  export let captureId: TileflowMapProps['captureId'] = undefined;
@@ -125,6 +131,7 @@
125
131
  let mapFairUseNotice: TileflowFairUseNoticeController | null = null;
126
132
  let mapLifecycleAttachment: TileflowMapLifecycleAttachment | null = null;
127
133
  let annotationRuntime: TileflowMapLibreDomRuntime<TileflowAnnotation> | null = null;
134
+ let dataRuntime: TileflowMapLibreDataRuntime<TileflowAnnotation> | null = null;
128
135
  let annotationRuntimeDiagnosticsUnsubscribe: (() => void) | null = null;
129
136
  let annotationRuntimeEventsUnsubscribe: (() => void) | null = null;
130
137
  let annotationRuntimeTargetsUnsubscribe: (() => void) | null = null;
@@ -166,6 +173,7 @@
166
173
  let manifestResolutionKey = '';
167
174
  let manifestResolutionState: 'error' | 'loading' | 'ready' = 'loading';
168
175
  let mapLoadId = 0;
176
+ let resourceLifetime: AbortController | undefined;
169
177
  let mounted = false;
170
178
  let refreshRunId = 0;
171
179
  let readinessRunId = 0;
@@ -264,7 +272,10 @@
264
272
  defaultInteractionState,
265
273
  );
266
274
  $: hasInteractionConfiguration =
275
+ annotationClusters !== undefined ||
267
276
  annotations !== undefined ||
277
+ dataOverlays !== undefined ||
278
+ dataSources !== undefined ||
268
279
  interactions !== undefined ||
269
280
  interactionState !== undefined ||
270
281
  defaultInteractionState !== undefined ||
@@ -350,6 +361,14 @@
350
361
  syncAnnotations();
351
362
  }
352
363
 
364
+ $: if (mounted) {
365
+ annotationClusters;
366
+ annotationResolution;
367
+ dataOverlays;
368
+ dataSources;
369
+ syncDataLayers();
370
+ }
371
+
353
372
  $: if (mounted) {
354
373
  interactions;
355
374
  syncSemanticInteractions();
@@ -451,9 +470,13 @@
451
470
 
452
471
  function destroyMap() {
453
472
  mapLoadId += 1;
473
+ resourceLifetime?.abort();
474
+ resourceLifetime = undefined;
454
475
  readinessRunId += 1;
455
476
  const lifecycleAttachment = mapLifecycleAttachment;
456
477
  const fairUseNotice = mapFairUseNotice;
478
+ dataRuntime?.dispose();
479
+ dataRuntime = null;
457
480
  const annotationInteractionsForMap = annotationRuntime;
458
481
  const semanticInteractionsForMap = semanticRuntime;
459
482
  const detachAnnotationCoordinator = annotationRuntimeCoordinatorDetach;
@@ -552,15 +575,36 @@
552
575
  const loadId = mapLoadId;
553
576
  const targetContainer = container;
554
577
  const runtime = runtimeStyle;
578
+ const session = createTileflowSessionController({source: 'svelte'});
579
+ const lifetime = new AbortController();
580
+ resourceLifetime = lifetime;
581
+ const resourceTransform = createTileflowTransformRequest({
582
+ getAnalytics: () => resolvedAnalytics,
583
+ sessionController: session,
584
+ sessionId: session.sessionId,
585
+ transformRequest: mapOptions?.transformRequest ?? undefined,
586
+ });
587
+ const styleLoadOptions = {
588
+ signal: lifetime.signal,
589
+ transformRequest: (url: string, type: 'Style' | 'Source') =>
590
+ resourceTransform(
591
+ url,
592
+ type as Parameters<NonNullable<TileflowMapOptions['transformRequest']>>[1],
593
+ ),
594
+ };
555
595
  let maplibregl: Awaited<ReturnType<typeof loadTileflowMapLibre>>;
556
-
596
+ let prepared: Awaited<ReturnType<typeof loadTileflowRuntimeStyle>>;
557
597
  try {
558
- [maplibregl] = await Promise.all([
598
+ [maplibregl, prepared] = await Promise.all([
559
599
  loadTileflowMapLibre(),
560
- loadTileflowStyleFonts(runtime.style, {fontFaces: runtime.fontFaces}),
600
+ loadTileflowRuntimeStyle(runtime, styleLoadOptions).then(async (loaded) => {
601
+ await loadTileflowStyleFonts(loaded.style, {fontFaces: loaded.fontFaces});
602
+ return loaded;
603
+ }),
561
604
  ]);
562
605
  } catch (error) {
563
606
  if (mounted && runId === refreshRunId && loadId === mapLoadId) {
607
+ lifetime.abort();
564
608
  console.error('Failed to load the Tileflow map runtime', error);
565
609
  mapCaptureState = 'error';
566
610
  }
@@ -577,7 +621,6 @@
577
621
  return;
578
622
  }
579
623
 
580
- const session = createTileflowSessionController({source: 'svelte'});
581
624
  const sessionStarter = createTileflowSessionStarter({
582
625
  getSessionId: () => session.sessionId,
583
626
  sessionId: session.sessionId,
@@ -603,7 +646,7 @@
603
646
  container: targetContainer,
604
647
  interactive: resolvedInteractive,
605
648
  pitch: resolvedPitch,
606
- style: runtime.style as StyleSpecification | string,
649
+ style: prepared.style as StyleSpecification,
607
650
  transformRequest: createTileflowTransformRequest({
608
651
  getAnalytics: () => resolvedAnalytics,
609
652
  sessionController: session,
@@ -625,7 +668,9 @@
625
668
  runtime.theme === undefined
626
669
  ? null
627
670
  : createTileflowThemeController({
628
- initial: runtime,
671
+ initial: prepared.family ? prepared : runtime,
672
+ initialStyle: prepared.style,
673
+ styleLoadOptions,
629
674
  map: maplibreMap,
630
675
  onTransition(transition) {
631
676
  onThemeChange?.(transition);
@@ -712,6 +757,22 @@
712
757
  annotationRuntimeDiagnostics = annotationInteractionsForMap.getDiagnostics();
713
758
  syncAnnotationRenderTargets(annotationInteractionsForMap.getRenderTargets());
714
759
  syncAnnotations();
760
+ // Created ahead of semantic POIs: a click on a cluster belongs to the cluster.
761
+ dataRuntime = createTileflowMapLibreDataRuntime<
762
+ MapLibreMap,
763
+ MapLibreMarker,
764
+ TileflowAnnotation
765
+ >({
766
+ createMarker: ({element}) => new maplibregl.Marker({element}),
767
+ document: targetContainer.ownerDocument,
768
+ map: maplibreMap,
769
+ onDiagnostic: (diagnostic) => onInteractionDiagnostic?.(diagnostic),
770
+ onInteractionEvent(event) {
771
+ if (!interactionRuntimesDisposing) onInteractionEvent?.(event);
772
+ },
773
+ setHiddenAnnotations: annotationInteractionsForMap.setHiddenAnnotations,
774
+ });
775
+ syncDataLayers();
715
776
  } catch (error) {
716
777
  console.error('Failed to initialize Tileflow annotations', error);
717
778
  setBridgeInteractionDiagnostic(
@@ -898,6 +959,15 @@
898
959
  }
899
960
  }
900
961
 
962
+ function syncDataLayers() {
963
+ dataRuntime?.reconcile({
964
+ annotationClusters,
965
+ annotations: annotationResolution.ok && !isImageMode ? annotationResolution.annotations : [],
966
+ dataOverlays,
967
+ dataSources,
968
+ });
969
+ }
970
+
901
971
  function syncSemanticInteractions() {
902
972
  const bindings =
903
973
  interactionBindingResolution.ok && !isImageMode ? interactionBindingResolution.bindings : [];
package/src/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import type {TileflowGeoJsonSource, TileflowMapLibreOverlay} from '@tileflow/core';
1
2
  import type {TileflowTimeOfDay} from '@tileflow/core/runtime';
3
+ import type {TileflowAnnotationClusterOptions} from '@tileflow/interactions/maplibre';
2
4
  import type {Map as MapLibreMap, MapOptions as MapLibreMapOptions} from 'maplibre-gl';
3
5
  import type {Snippet, SvelteComponentTyped} from 'svelte';
4
6
  import type {
@@ -68,9 +70,18 @@ type TileflowMapBaseProps = {
68
70
  export type TileflowMapAnnotationProps<
69
71
  TAnnotation extends TileflowAnnotation = TileflowAnnotation,
70
72
  > = {
73
+ /** Group `annotations` into accessible cluster buttons. Up to 1,000 annotations. */
74
+ annotationClusters?: TileflowAnnotationClusterOptions;
71
75
  annotations?: readonly TAnnotation[];
72
76
  };
73
77
 
78
+ export type TileflowMapDataProps = {
79
+ /** Overlays drawn over `dataSources` or compiled sources, keyed as in `defineMap`. */
80
+ dataOverlays?: Readonly<Record<string, TileflowMapLibreOverlay>>;
81
+ /** Application GeoJSON, keyed as in `defineMap`. A compiled source of the same key is filled. */
82
+ dataSources?: Readonly<Record<string, TileflowGeoJsonSource>>;
83
+ };
84
+
74
85
  export type TileflowMapInteractionStateProps =
75
86
  | {
76
87
  defaultInteractionState?: TileflowInteractionState;
@@ -87,6 +98,7 @@ type TileflowMapStyleSourceProps = {
87
98
 
88
99
  type TileflowMapInteractiveProps<TAnnotation extends TileflowAnnotation> =
89
100
  TileflowMapAnnotationProps<TAnnotation> &
101
+ TileflowMapDataProps &
90
102
  TileflowMapInteractionStateProps & {
91
103
  interactions?: readonly TileflowInteractionBinding[];
92
104
  marker?: TileflowMapAnnotationSnippet<TAnnotation>;
@@ -99,7 +111,10 @@ type TileflowMapInteractiveProps<TAnnotation extends TileflowAnnotation> =
99
111
  };
100
112
 
101
113
  type TileflowMapImageProps = {
114
+ annotationClusters?: never;
102
115
  annotations?: never;
116
+ dataOverlays?: never;
117
+ dataSources?: never;
103
118
  defaultInteractionState?: never;
104
119
  interactions?: never;
105
120
  interactionState?: never;