@tileflow/svelte 0.1.0-alpha.9

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 ADDED
@@ -0,0 +1,16 @@
1
+ # @tileflow/svelte
2
+
3
+ Svelte component for rendering Tileflow maps with MapLibre.
4
+
5
+ Install `maplibre-gl` alongside the package and import its CSS once in your app.
6
+
7
+ ```svelte
8
+ <script lang="ts">
9
+ import {TileflowMap} from '@tileflow/svelte';
10
+ import 'maplibre-gl/dist/maplibre-gl.css';
11
+ </script>
12
+
13
+ <TileflowMap map="madrid" center={[-3.7038, 40.4168]} zoom={12} />
14
+ ```
15
+
16
+ Docs: https://tileflow.dev/docs
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@tileflow/svelte",
3
+ "version": "0.1.0-alpha.9",
4
+ "description": "Svelte component for rendering Tileflow maps with MapLibre.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "svelte": "./src/index.js",
8
+ "homepage": "https://tileflow.dev",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/tileflow/tileflow.git",
12
+ "directory": "packages/svelte"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/tileflow/tileflow/issues"
16
+ },
17
+ "keywords": [
18
+ "tileflow",
19
+ "svelte",
20
+ "maps",
21
+ "maplibre",
22
+ "tiles"
23
+ ],
24
+ "types": "./src/index.d.ts",
25
+ "files": [
26
+ "src"
27
+ ],
28
+ "exports": {
29
+ ".": {
30
+ "types": "./src/index.d.ts",
31
+ "svelte": "./src/index.js",
32
+ "import": "./src/index.js",
33
+ "default": "./src/index.js"
34
+ },
35
+ "./TileflowMap.svelte": {
36
+ "types": "./src/TileflowMap.svelte.d.ts",
37
+ "svelte": "./src/TileflowMap.svelte",
38
+ "default": "./src/TileflowMap.svelte"
39
+ }
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "@tileflow/core": "0.1.0-alpha.9"
46
+ },
47
+ "peerDependencies": {
48
+ "maplibre-gl": ">=5",
49
+ "svelte": ">=5"
50
+ },
51
+ "devDependencies": {
52
+ "maplibre-gl": "^5.24.0",
53
+ "svelte": "^5.43.17",
54
+ "svelte-check": "^4.3.4"
55
+ },
56
+ "scripts": {
57
+ "build": "svelte-check --tsconfig ./tsconfig.json",
58
+ "typecheck": "svelte-check --tsconfig ./tsconfig.json",
59
+ "lint": "svelte-check --tsconfig ./tsconfig.json"
60
+ }
61
+ }
@@ -0,0 +1,279 @@
1
+ <script lang="ts">
2
+ import maplibregl, {
3
+ type LngLatLike,
4
+ type Map as MapLibreMap,
5
+ type RequestParameters,
6
+ type StyleSpecification,
7
+ } from 'maplibre-gl';
8
+ import {createEventDispatcher, onMount} from 'svelte';
9
+ import {
10
+ createTileflowSessionId,
11
+ defaultTileflowManifestUrl,
12
+ loadTileflowManifest,
13
+ mergeTileflowAnalytics,
14
+ normalizeTileflowStaticImageSize,
15
+ resolveTileflowAnalyticsRequestUrl,
16
+ resolveTileflowManifestMap,
17
+ resolveTileflowRuntimeStyle,
18
+ resolveTileflowStaticImageUrl,
19
+ shouldLoadTileflowManifest,
20
+ startTileflowSession,
21
+ type MapLibreStyle,
22
+ type TileflowAnalytics,
23
+ type TileflowConfig,
24
+ type TileflowMapMarker,
25
+ type TileflowProjectThemes,
26
+ type TileflowRuntimeManifestMap,
27
+ } from '@tileflow/core';
28
+
29
+ export let alt = '';
30
+ export let analytics: TileflowAnalytics | undefined = undefined;
31
+ export let center: [number, number] = [0, 20];
32
+ export let className = '';
33
+ export let config: TileflowConfig | undefined = undefined;
34
+ export let height: number | string = 420;
35
+ export let imageLoading: HTMLImageElement['loading'] = 'eager';
36
+ export let imageUrl: string | undefined = undefined;
37
+ export let interactive = true;
38
+ export let manifestUrl = defaultTileflowManifestUrl;
39
+ export let map: string | undefined = undefined;
40
+ export let mapStyle: MapLibreStyle | undefined = undefined;
41
+ export let markers: TileflowMapMarker[] = [];
42
+ export let mode: 'interactive' | 'image' = 'interactive';
43
+ export let preferLocalDev = true;
44
+ export let styleBaseUrl: string | undefined = undefined;
45
+ export let styleUrl: string | undefined = undefined;
46
+ export let themes: TileflowProjectThemes | undefined = undefined;
47
+ export let tileBaseUrl: string | undefined = undefined;
48
+ export let zoom = 2;
49
+
50
+ let container: HTMLDivElement;
51
+ let imageSize: {height: number; width: number} | null = null;
52
+ let imageResizeObserver: ResizeObserver | null = null;
53
+ let mapInstance: MapLibreMap | null = null;
54
+ let mapResizeObserver: ResizeObserver | null = null;
55
+ let manifestMap: TileflowRuntimeManifestMap | null = null;
56
+ let manifestLoadId = 0;
57
+ let mounted = false;
58
+ let refreshRunId = 0;
59
+
60
+ const dispatch = createEventDispatcher<{load: MapLibreMap}>();
61
+ const sessionId = createTileflowSessionId();
62
+
63
+ $: isImageMode = mode === 'image';
64
+ $: runtimeStyle = isImageMode
65
+ ? null
66
+ : resolveTileflowRuntimeStyle({
67
+ config,
68
+ manifestMap,
69
+ map,
70
+ preferLocalDev,
71
+ style: mapStyle,
72
+ styleBaseUrl,
73
+ styleUrl,
74
+ themes,
75
+ tileBaseUrl,
76
+ });
77
+ $: resolvedAnalytics = mergeTileflowAnalytics(analytics, runtimeStyle?.analytics);
78
+ $: runtimeImageUrl =
79
+ imageUrl ??
80
+ (isImageMode
81
+ ? resolveTileflowStaticImageUrl({
82
+ center,
83
+ imageSize,
84
+ manifestMap,
85
+ zoom,
86
+ })
87
+ : undefined);
88
+ $: frameStyle = `height: ${formatHeight(height)}; min-height: 240px; overflow: hidden; position: relative; width: 100%;`;
89
+
90
+ $: if (mounted) {
91
+ analytics;
92
+ center;
93
+ config;
94
+ height;
95
+ imageUrl;
96
+ interactive;
97
+ manifestUrl;
98
+ map;
99
+ mapStyle;
100
+ markers;
101
+ mode;
102
+ preferLocalDev;
103
+ styleBaseUrl;
104
+ styleUrl;
105
+ themes;
106
+ tileBaseUrl;
107
+ zoom;
108
+ void refresh();
109
+ }
110
+
111
+ onMount(() => {
112
+ mounted = true;
113
+
114
+ return () => {
115
+ mounted = false;
116
+ refreshRunId += 1;
117
+ manifestLoadId += 1;
118
+ imageResizeObserver?.disconnect();
119
+ destroyMap();
120
+ };
121
+ });
122
+
123
+ async function refresh() {
124
+ const runId = ++refreshRunId;
125
+ await refreshManifest();
126
+
127
+ if (!mounted || runId !== refreshRunId) {
128
+ return;
129
+ }
130
+
131
+ updateImageResizeObserver();
132
+ recreateMap();
133
+ }
134
+
135
+ async function refreshManifest() {
136
+ const shouldLoad = shouldLoadTileflowManifest({
137
+ config,
138
+ imageMode: isImageMode,
139
+ imageUrl,
140
+ map,
141
+ style: mapStyle,
142
+ styleBaseUrl,
143
+ styleUrl,
144
+ });
145
+
146
+ if (!shouldLoad) {
147
+ manifestLoadId += 1;
148
+ manifestMap = null;
149
+ return;
150
+ }
151
+
152
+ const loadId = ++manifestLoadId;
153
+ manifestMap = null;
154
+
155
+ try {
156
+ const manifest = await loadTileflowManifest(manifestUrl);
157
+
158
+ if (mounted && loadId === manifestLoadId) {
159
+ manifestMap = manifest && map ? resolveTileflowManifestMap(manifest, map) : null;
160
+ }
161
+ } catch (error) {
162
+ if (mounted && loadId === manifestLoadId) {
163
+ console.error('Failed to load Tileflow manifest', error);
164
+ manifestMap = null;
165
+ }
166
+ }
167
+ }
168
+
169
+ function updateImageResizeObserver() {
170
+ imageResizeObserver?.disconnect();
171
+ imageResizeObserver = null;
172
+ imageSize = null;
173
+
174
+ if (!isImageMode || imageUrl || !container) {
175
+ return;
176
+ }
177
+
178
+ const updateImageSize = () => {
179
+ imageSize = normalizeTileflowStaticImageSize({
180
+ height: container.clientHeight,
181
+ width: container.clientWidth,
182
+ });
183
+ };
184
+
185
+ imageResizeObserver = new ResizeObserver(updateImageSize);
186
+ imageResizeObserver.observe(container);
187
+ updateImageSize();
188
+ }
189
+
190
+ function destroyMap() {
191
+ mapResizeObserver?.disconnect();
192
+ mapResizeObserver = null;
193
+ mapInstance?.remove();
194
+ mapInstance = null;
195
+ }
196
+
197
+ function recreateMap() {
198
+ destroyMap();
199
+
200
+ if (!container || !runtimeStyle || isImageMode) {
201
+ return;
202
+ }
203
+
204
+ const runtime = runtimeStyle;
205
+ const maplibreMap = new maplibregl.Map({
206
+ attributionControl: {compact: true},
207
+ center: center as LngLatLike,
208
+ container,
209
+ interactive,
210
+ style: runtime.style as StyleSpecification | string,
211
+ transformRequest: createTransformRequest(resolvedAnalytics, sessionId),
212
+ zoom,
213
+ });
214
+
215
+ mapInstance = maplibreMap;
216
+
217
+ if (interactive) {
218
+ maplibreMap.addControl(new maplibregl.NavigationControl(), 'top-right');
219
+ }
220
+
221
+ for (const marker of markers) {
222
+ const markerInstance = new maplibregl.Marker({
223
+ color: marker.color ?? '#C6A15B',
224
+ })
225
+ .setLngLat(marker.coordinates)
226
+ .addTo(maplibreMap);
227
+
228
+ markerInstance.getElement().title = marker.label ?? marker.id;
229
+ }
230
+
231
+ mapResizeObserver = new ResizeObserver(() => {
232
+ maplibreMap.resize();
233
+ });
234
+ mapResizeObserver.observe(container);
235
+
236
+ maplibreMap.on('load', () => {
237
+ dispatch('load', maplibreMap);
238
+ startTileflowSession(resolvedAnalytics, {
239
+ mapId: resolvedAnalytics?.mapId,
240
+ sessionId,
241
+ source: 'svelte',
242
+ styleId:
243
+ resolvedAnalytics?.styleId ?? (typeof runtime.style === 'string' ? runtime.style : map),
244
+ });
245
+ });
246
+ }
247
+
248
+ function createTransformRequest(analyticsInput: TileflowAnalytics | undefined, session: string) {
249
+ if (!analyticsInput || analyticsInput.enabled === false) {
250
+ return undefined;
251
+ }
252
+
253
+ return (url: string): RequestParameters | undefined => {
254
+ const nextUrl = resolveTileflowAnalyticsRequestUrl(url, analyticsInput, session);
255
+
256
+ if (!nextUrl) {
257
+ return undefined;
258
+ }
259
+
260
+ return {url: nextUrl};
261
+ };
262
+ }
263
+
264
+ function formatHeight(value: number | string): string {
265
+ return typeof value === 'number' ? `${value}px` : value;
266
+ }
267
+ </script>
268
+
269
+ <div bind:this={container} class={className} style={frameStyle}>
270
+ {#if isImageMode && runtimeImageUrl}
271
+ <img
272
+ {alt}
273
+ decoding="async"
274
+ loading={imageLoading}
275
+ src={runtimeImageUrl}
276
+ style="display: block; height: 100%; object-fit: cover; width: 100%;"
277
+ />
278
+ {/if}
279
+ </div>
@@ -0,0 +1,8 @@
1
+ import type {SvelteComponentTyped} from 'svelte';
2
+ import type {Map as MapLibreMap} from 'maplibre-gl';
3
+ import type {TileflowMapProps} from './index';
4
+
5
+ export default class TileflowMap extends SvelteComponentTyped<
6
+ TileflowMapProps,
7
+ {load: CustomEvent<MapLibreMap>}
8
+ > {}
package/src/index.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import type {SvelteComponentTyped} from 'svelte';
2
+ import type {Map as MapLibreMap} from 'maplibre-gl';
3
+ import type {
4
+ MapLibreStyle,
5
+ TileflowAnalytics,
6
+ TileflowConfig,
7
+ TileflowMapMarker,
8
+ TileflowProjectThemes,
9
+ } from '@tileflow/core';
10
+
11
+ export type TileflowMapMode = 'interactive' | 'image';
12
+
13
+ export type TileflowMapProps = {
14
+ alt?: string;
15
+ analytics?: TileflowAnalytics;
16
+ center?: [number, number];
17
+ className?: string;
18
+ config?: TileflowConfig;
19
+ height?: number | string;
20
+ imageLoading?: HTMLImageElement['loading'];
21
+ imageUrl?: string;
22
+ interactive?: boolean;
23
+ manifestUrl?: string;
24
+ map?: string;
25
+ mapStyle?: MapLibreStyle;
26
+ markers?: TileflowMapMarker[];
27
+ mode?: TileflowMapMode;
28
+ preferLocalDev?: boolean;
29
+ styleBaseUrl?: string;
30
+ styleUrl?: string;
31
+ themes?: TileflowProjectThemes;
32
+ tileBaseUrl?: string;
33
+ zoom?: number;
34
+ };
35
+
36
+ export default class TileflowMap extends SvelteComponentTyped<
37
+ TileflowMapProps,
38
+ {load: CustomEvent<MapLibreMap>}
39
+ > {}
40
+
41
+ export {TileflowMap};
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export {default, default as TileflowMap} from './TileflowMap.svelte';