@tileflow/svelte 0.1.0-alpha.25 → 0.1.0-alpha.27
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 +6 -0
- package/package.json +2 -2
- package/src/TileflowMap.svelte +68 -6
- package/src/index.d.ts +9 -1
- package/src/style-source.d.ts +2 -0
- package/src/style-source.js +31 -1
package/README.md
CHANGED
|
@@ -90,6 +90,12 @@ Omitting `theme` uses `defaultTheme`. `system` needs an explicit light/dark mapp
|
|
|
90
90
|
supplies. Theme changes keep the MapLibre instance, camera, and interactions, and roll back on
|
|
91
91
|
failure. `onThemeChange` reports transitions.
|
|
92
92
|
|
|
93
|
+
`themeTransition={{duration: 450}}` cross-fades theme changes over that many milliseconds.
|
|
94
|
+
`themeBlend={{themes: ['night', 'dusk', 'day'], position}}` shows a continuous blend of the map's
|
|
95
|
+
themes instead of `theme`: position 0 is the first theme and 1.5 is halfway from the second to the
|
|
96
|
+
third. Moving the position updates the map at once. See
|
|
97
|
+
[theme transitions and blends](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/theme-transitions-and-blends.md).
|
|
98
|
+
|
|
93
99
|
`mapOptions` accepts native MapLibre options except `container` and `style`. Direct props win over
|
|
94
100
|
those options, then the manifest view, then shared defaults. `TileflowMap` always selects a Tileflow
|
|
95
101
|
map through its manifest; it has no renderer discriminator or direct-style mode. The obsolete
|
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.27",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Svelte component for rendering Tileflow maps with MapLibre.",
|
|
6
6
|
"type": "module",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@tileflow/core": ">=0.1.0-alpha.
|
|
47
|
+
"@tileflow/core": ">=0.1.0-alpha.30 <0.1.0-beta.0",
|
|
48
48
|
"@tileflow/interactions": ">=0.1.0-alpha.4 <0.1.0-beta.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
package/src/TileflowMap.svelte
CHANGED
|
@@ -15,17 +15,21 @@
|
|
|
15
15
|
defaultTileflowRuntimeView,
|
|
16
16
|
loadTileflowManifest,
|
|
17
17
|
mergeTileflowAnalytics,
|
|
18
|
+
nearestTileflowBlendTheme,
|
|
18
19
|
normalizeTileflowRuntimeCenter,
|
|
19
20
|
normalizeTileflowStaticImageSize,
|
|
20
21
|
resolveTileflowManifestMap,
|
|
21
22
|
resolveTileflowMapMode,
|
|
22
23
|
resolveTileflowRuntimeStyle,
|
|
23
24
|
resolveTileflowRuntimeTheme,
|
|
25
|
+
resolveTileflowRuntimeThemeBlend,
|
|
24
26
|
resolveTileflowRuntimeView,
|
|
25
27
|
resolveTileflowStaticImageUrl,
|
|
26
28
|
type TileflowRuntimeManifestMap,
|
|
29
|
+
type TileflowRuntimeStyle,
|
|
27
30
|
} from '@tileflow/core/runtime';
|
|
28
31
|
import {
|
|
32
|
+
attachTileflowAtmosphere,
|
|
29
33
|
attachTileflowFairUseNotice,
|
|
30
34
|
attachTileflowMapLifecycle,
|
|
31
35
|
createTileflowSessionStarter,
|
|
@@ -95,6 +99,8 @@
|
|
|
95
99
|
export let popup: TileflowMapProps['popup'] = undefined;
|
|
96
100
|
export let source: TileflowMapProps['source'];
|
|
97
101
|
export let theme: TileflowMapProps['theme'] = undefined;
|
|
102
|
+
export let themeBlend: TileflowMapProps['themeBlend'] = undefined;
|
|
103
|
+
export let themeTransition: TileflowMapProps['themeTransition'] = undefined;
|
|
98
104
|
export let tooltip: TileflowMapProps['tooltip'] = undefined;
|
|
99
105
|
export let zoom: TileflowMapProps['zoom'] = undefined;
|
|
100
106
|
|
|
@@ -166,7 +172,7 @@
|
|
|
166
172
|
: {defaultInteractionState: initialInteractionState}),
|
|
167
173
|
});
|
|
168
174
|
$: resolvedMode = resolveTileflowMapMode({mode});
|
|
169
|
-
$: assertTileflowMapStyleInputs({source, theme});
|
|
175
|
+
$: assertTileflowMapStyleInputs({source, theme, themeBlend, themeTransition});
|
|
170
176
|
$: resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
171
177
|
$: isImageMode = resolvedMode === 'image';
|
|
172
178
|
$: mapName = source.map;
|
|
@@ -182,7 +188,10 @@
|
|
|
182
188
|
colorScheme: systemColorScheme,
|
|
183
189
|
manifestMap,
|
|
184
190
|
theme,
|
|
191
|
+
themeBlend,
|
|
185
192
|
});
|
|
193
|
+
$: themeBlendKey = themeBlend ? JSON.stringify(themeBlend.themes) : undefined;
|
|
194
|
+
$: themeBlendPosition = themeBlend?.position;
|
|
186
195
|
$: resolvedThemeName = themeResolution.name;
|
|
187
196
|
$: manifestView = resolveTileflowRuntimeView({manifestMap});
|
|
188
197
|
$: manifestCenter = normalizeTileflowRuntimeCenter(manifestView.center);
|
|
@@ -203,6 +212,7 @@
|
|
|
203
212
|
source,
|
|
204
213
|
theme: themeResolution.name,
|
|
205
214
|
});
|
|
215
|
+
$: blendStyles = resolveBlendStyles({isImageMode, key: themeBlendKey, manifestMap, source});
|
|
206
216
|
$: resolvedAnalytics = mergeTileflowAnalytics(analytics, runtimeStyle?.analytics);
|
|
207
217
|
$: runtimeImageUrl =
|
|
208
218
|
imageUrl ??
|
|
@@ -294,8 +304,15 @@
|
|
|
294
304
|
syncSystemColorSchemeSubscription();
|
|
295
305
|
}
|
|
296
306
|
|
|
307
|
+
$: if (mounted) {
|
|
308
|
+
blendStyles;
|
|
309
|
+
themeBlendPosition;
|
|
310
|
+
void switchBlend();
|
|
311
|
+
}
|
|
312
|
+
|
|
297
313
|
$: if (mounted) {
|
|
298
314
|
runtimeStyle;
|
|
315
|
+
themeBlendKey;
|
|
299
316
|
void switchTheme();
|
|
300
317
|
}
|
|
301
318
|
|
|
@@ -574,6 +591,7 @@
|
|
|
574
591
|
});
|
|
575
592
|
|
|
576
593
|
mapInstance = maplibreMap;
|
|
594
|
+
attachTileflowAtmosphere(maplibreMap);
|
|
577
595
|
themeController =
|
|
578
596
|
runtime.theme === undefined
|
|
579
597
|
? null
|
|
@@ -753,14 +771,34 @@
|
|
|
753
771
|
return () => subscription.unsubscribe();
|
|
754
772
|
},
|
|
755
773
|
});
|
|
756
|
-
if (themeController &&
|
|
774
|
+
if (themeController && blendStyles && themeBlend) {
|
|
775
|
+
void switchBlend();
|
|
776
|
+
} else if (themeController && runtimeStyle && runtimeStyle !== runtime) {
|
|
757
777
|
activeRuntimeResource = runtimeStyle;
|
|
758
|
-
void themeController.setTheme(runtimeStyle);
|
|
778
|
+
void themeController.setTheme(runtimeStyle, {transition: themeTransition});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
async function switchBlend() {
|
|
783
|
+
const position = themeBlendPosition;
|
|
784
|
+
if (!themeController || !blendStyles || position === undefined) return;
|
|
785
|
+
if (runtimeStyle) activeRuntimeResource = runtimeStyle;
|
|
786
|
+
const result = await themeController.setBlend(
|
|
787
|
+
{position, themes: blendStyles},
|
|
788
|
+
{transition: themeTransition},
|
|
789
|
+
);
|
|
790
|
+
if (result.status === 'failed') {
|
|
791
|
+
console.error('Failed to blend the Tileflow map themes', result.error);
|
|
759
792
|
}
|
|
760
793
|
}
|
|
761
794
|
|
|
762
795
|
async function switchTheme() {
|
|
763
796
|
if (!themeController) return;
|
|
797
|
+
if (themeBlend) {
|
|
798
|
+
// The blend names its nearest theme; the blend itself is applied by switchBlend.
|
|
799
|
+
if (runtimeStyle) activeRuntimeResource = runtimeStyle;
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
764
802
|
if (themeResolution.error) {
|
|
765
803
|
await themeController.setTheme(themeController.getCurrent());
|
|
766
804
|
return;
|
|
@@ -768,7 +806,7 @@
|
|
|
768
806
|
const next = runtimeStyle;
|
|
769
807
|
if (!next) return;
|
|
770
808
|
activeRuntimeResource = next;
|
|
771
|
-
const result = await themeController.setTheme(next);
|
|
809
|
+
const result = await themeController.setTheme(next, {transition: themeTransition});
|
|
772
810
|
if (result.status === 'failed') {
|
|
773
811
|
console.error('Failed to change the Tileflow map theme', result.error);
|
|
774
812
|
}
|
|
@@ -1204,24 +1242,48 @@
|
|
|
1204
1242
|
colorScheme: 'dark' | 'light';
|
|
1205
1243
|
manifestMap: TileflowRuntimeManifestMap | null;
|
|
1206
1244
|
theme: TileflowMapProps['theme'];
|
|
1245
|
+
themeBlend: TileflowMapProps['themeBlend'];
|
|
1207
1246
|
}): Readonly<{error: boolean; name: string | undefined}> {
|
|
1247
|
+
// A blend shows its nearest theme until the blend style is ready, and names it while blending.
|
|
1248
|
+
const selection = input.themeBlend ? nearestTileflowBlendTheme(input.themeBlend) : input.theme;
|
|
1208
1249
|
if (!input.manifestMap) {
|
|
1209
1250
|
return {
|
|
1210
1251
|
error: false,
|
|
1211
|
-
name:
|
|
1252
|
+
name: selection && selection !== 'system' ? selection : undefined,
|
|
1212
1253
|
};
|
|
1213
1254
|
}
|
|
1214
1255
|
|
|
1215
1256
|
try {
|
|
1257
|
+
for (const name of input.themeBlend?.themes ?? []) {
|
|
1258
|
+
resolveTileflowRuntimeTheme(input.manifestMap, name);
|
|
1259
|
+
}
|
|
1216
1260
|
return {
|
|
1217
1261
|
error: false,
|
|
1218
|
-
name: resolveTileflowRuntimeTheme(input.manifestMap,
|
|
1262
|
+
name: resolveTileflowRuntimeTheme(input.manifestMap, selection, input.colorScheme).name,
|
|
1219
1263
|
};
|
|
1220
1264
|
} catch {
|
|
1221
1265
|
return {error: true, name: undefined};
|
|
1222
1266
|
}
|
|
1223
1267
|
}
|
|
1224
1268
|
|
|
1269
|
+
function resolveBlendStyles(input: {
|
|
1270
|
+
isImageMode: boolean;
|
|
1271
|
+
key: string | undefined;
|
|
1272
|
+
manifestMap: TileflowRuntimeManifestMap | null;
|
|
1273
|
+
source: TileflowMapProps['source'];
|
|
1274
|
+
}): TileflowRuntimeStyle[] | null {
|
|
1275
|
+
if (input.isImageMode || !input.manifestMap || !input.key) return null;
|
|
1276
|
+
try {
|
|
1277
|
+
return resolveTileflowRuntimeThemeBlend({
|
|
1278
|
+
blend: {position: 0, themes: JSON.parse(input.key) as string[]},
|
|
1279
|
+
manifestMap: input.manifestMap,
|
|
1280
|
+
source: input.source,
|
|
1281
|
+
});
|
|
1282
|
+
} catch {
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1225
1287
|
function resolveRuntimeResolutionState(input: {
|
|
1226
1288
|
currentManifestResolutionState: 'error' | 'loading' | 'ready';
|
|
1227
1289
|
imageSize: {height: number; width: number} | null;
|
package/src/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type {Map as MapLibreMap, MapOptions as MapLibreMapOptions} from 'maplibre-gl';
|
|
2
2
|
import type {Snippet, SvelteComponentTyped} from 'svelte';
|
|
3
|
-
import type {TileflowThemeTransition} from '@tileflow/core/browser';
|
|
3
|
+
import type {TileflowThemeTransition, TileflowThemeTransitionOptions} from '@tileflow/core/browser';
|
|
4
4
|
import type {
|
|
5
5
|
TileflowAnalytics,
|
|
6
6
|
TileflowRuntimeSource,
|
|
7
|
+
TileflowThemeBlendSelection,
|
|
7
8
|
TileflowThemeSelection,
|
|
8
9
|
} from '@tileflow/core/runtime';
|
|
9
10
|
import type {
|
|
@@ -45,6 +46,13 @@ type TileflowMapBaseProps = {
|
|
|
45
46
|
mapOptions?: TileflowMapOptions;
|
|
46
47
|
onThemeChange?: (transition: TileflowThemeTransition) => void;
|
|
47
48
|
theme?: TileflowThemeSelection;
|
|
49
|
+
/**
|
|
50
|
+
* Shows a continuous blend of the map's themes instead of `theme`. Moving `position` changes the
|
|
51
|
+
* map at once; changing `themes` prepares a new blend.
|
|
52
|
+
*/
|
|
53
|
+
themeBlend?: TileflowThemeBlendSelection;
|
|
54
|
+
/** Cross-fades theme changes, including entering and leaving a blend. */
|
|
55
|
+
themeTransition?: TileflowThemeTransitionOptions;
|
|
48
56
|
zoom?: number;
|
|
49
57
|
};
|
|
50
58
|
|
package/src/style-source.d.ts
CHANGED
package/src/style-source.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
validateTileflowRuntimeSource,
|
|
3
|
+
validateTileflowThemeBlendSelection,
|
|
3
4
|
validateTileflowThemeSelection,
|
|
4
5
|
} from '@tileflow/core/runtime';
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {{source?: unknown, theme?: unknown, themeBlend?: unknown, themeTransition?: unknown}}
|
|
9
|
+
* TileflowMapStyleInput
|
|
10
|
+
*/
|
|
7
11
|
/** @typedef {{ok: true} | {error: string; ok: false}} TileflowMapStyleInputValidation */
|
|
8
12
|
|
|
9
13
|
/**
|
|
@@ -16,9 +20,35 @@ export function validateTileflowMapStyleInputs(input) {
|
|
|
16
20
|
if (input.theme !== undefined && !validateTileflowThemeSelection(input.theme)) {
|
|
17
21
|
return {error: 'theme must be a concrete portable theme name or "system"', ok: false};
|
|
18
22
|
}
|
|
23
|
+
if (input.themeBlend !== undefined && !validateTileflowThemeBlendSelection(input.themeBlend)) {
|
|
24
|
+
return {
|
|
25
|
+
error:
|
|
26
|
+
'themeBlend must name two to eight concrete themes and a position from 0 to themes.length - 1',
|
|
27
|
+
ok: false,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (input.themeTransition !== undefined && !isThemeTransition(input.themeTransition)) {
|
|
31
|
+
return {
|
|
32
|
+
error: 'themeTransition.duration must be a number of milliseconds from 0 to 5000',
|
|
33
|
+
ok: false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
19
36
|
return {ok: true};
|
|
20
37
|
}
|
|
21
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @param {unknown} value
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function isThemeTransition(value) {
|
|
44
|
+
if (!value || typeof value !== 'object') return false;
|
|
45
|
+
const {duration} = /** @type {{duration?: unknown}} */ (value);
|
|
46
|
+
return (
|
|
47
|
+
duration === undefined ||
|
|
48
|
+
(typeof duration === 'number' && Number.isFinite(duration) && duration >= 0 && duration <= 5000)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
22
52
|
/** @param {TileflowMapStyleInput} input */
|
|
23
53
|
export function assertTileflowMapStyleInputs(input) {
|
|
24
54
|
const validation = validateTileflowMapStyleInputs(input);
|