@tileflow/svelte 0.1.0-alpha.26 → 0.1.0-alpha.28
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 +4 -4
- package/src/TileflowMap.svelte +69 -13
- package/src/index.d.ts +13 -1
- package/src/style-source.d.ts +2 -0
- package/src/style-source.js +34 -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.28",
|
|
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.32 <0.1.0-beta.0",
|
|
48
48
|
"@tileflow/interactions": ">=0.1.0-alpha.4 <0.1.0-beta.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"svelte": ">=5 <6"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"maplibre-gl": "6.
|
|
56
|
-
"svelte": "^5.
|
|
55
|
+
"maplibre-gl": "6.13.0",
|
|
56
|
+
"svelte": "^5.57.2",
|
|
57
57
|
"svelte-check": "^4.3.4"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
package/src/TileflowMap.svelte
CHANGED
|
@@ -15,15 +15,18 @@
|
|
|
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 {
|
|
29
32
|
attachTileflowAtmosphere,
|
|
@@ -96,6 +99,8 @@
|
|
|
96
99
|
export let popup: TileflowMapProps['popup'] = undefined;
|
|
97
100
|
export let source: TileflowMapProps['source'];
|
|
98
101
|
export let theme: TileflowMapProps['theme'] = undefined;
|
|
102
|
+
export let themeBlend: TileflowMapProps['themeBlend'] = undefined;
|
|
103
|
+
export let themeTransition: TileflowMapProps['themeTransition'] = undefined;
|
|
99
104
|
export let tooltip: TileflowMapProps['tooltip'] = undefined;
|
|
100
105
|
export let zoom: TileflowMapProps['zoom'] = undefined;
|
|
101
106
|
|
|
@@ -167,7 +172,7 @@
|
|
|
167
172
|
: {defaultInteractionState: initialInteractionState}),
|
|
168
173
|
});
|
|
169
174
|
$: resolvedMode = resolveTileflowMapMode({mode});
|
|
170
|
-
$: assertTileflowMapStyleInputs({source, theme});
|
|
175
|
+
$: assertTileflowMapStyleInputs({source, theme, themeBlend, themeTransition});
|
|
171
176
|
$: resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
172
177
|
$: isImageMode = resolvedMode === 'image';
|
|
173
178
|
$: mapName = source.map;
|
|
@@ -183,7 +188,11 @@
|
|
|
183
188
|
colorScheme: systemColorScheme,
|
|
184
189
|
manifestMap,
|
|
185
190
|
theme,
|
|
191
|
+
themeBlend,
|
|
186
192
|
});
|
|
193
|
+
$: themeBlendKey = themeBlend ? JSON.stringify(themeBlend.themes) : undefined;
|
|
194
|
+
$: themeBlendPosition = themeBlend?.position;
|
|
195
|
+
$: themeBlendHold = themeBlend?.hold === true;
|
|
187
196
|
$: resolvedThemeName = themeResolution.name;
|
|
188
197
|
$: manifestView = resolveTileflowRuntimeView({manifestMap});
|
|
189
198
|
$: manifestCenter = normalizeTileflowRuntimeCenter(manifestView.center);
|
|
@@ -204,6 +213,7 @@
|
|
|
204
213
|
source,
|
|
205
214
|
theme: themeResolution.name,
|
|
206
215
|
});
|
|
216
|
+
$: blendStyles = resolveBlendStyles({isImageMode, key: themeBlendKey, manifestMap, source});
|
|
207
217
|
$: resolvedAnalytics = mergeTileflowAnalytics(analytics, runtimeStyle?.analytics);
|
|
208
218
|
$: runtimeImageUrl =
|
|
209
219
|
imageUrl ??
|
|
@@ -295,8 +305,16 @@
|
|
|
295
305
|
syncSystemColorSchemeSubscription();
|
|
296
306
|
}
|
|
297
307
|
|
|
308
|
+
$: if (mounted) {
|
|
309
|
+
blendStyles;
|
|
310
|
+
themeBlendPosition;
|
|
311
|
+
themeBlendHold;
|
|
312
|
+
void switchBlend();
|
|
313
|
+
}
|
|
314
|
+
|
|
298
315
|
$: if (mounted) {
|
|
299
316
|
runtimeStyle;
|
|
317
|
+
themeBlendKey;
|
|
300
318
|
void switchTheme();
|
|
301
319
|
}
|
|
302
320
|
|
|
@@ -563,8 +581,6 @@
|
|
|
563
581
|
pitch: resolvedPitch,
|
|
564
582
|
style: runtime.style as StyleSpecification | string,
|
|
565
583
|
transformRequest: createTileflowTransformRequest({
|
|
566
|
-
always: true,
|
|
567
|
-
asyncAnalyticsTiming: 'request',
|
|
568
584
|
getAnalytics: () => resolvedAnalytics,
|
|
569
585
|
sessionController: session,
|
|
570
586
|
sessionId: session.sessionId,
|
|
@@ -755,14 +771,34 @@
|
|
|
755
771
|
return () => subscription.unsubscribe();
|
|
756
772
|
},
|
|
757
773
|
});
|
|
758
|
-
if (themeController &&
|
|
774
|
+
if (themeController && blendStyles && themeBlend) {
|
|
775
|
+
void switchBlend();
|
|
776
|
+
} else if (themeController && runtimeStyle && runtimeStyle !== runtime) {
|
|
759
777
|
activeRuntimeResource = runtimeStyle;
|
|
760
|
-
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
|
+
{hold: themeBlendHold, position, themes: blendStyles},
|
|
788
|
+
{transition: themeTransition},
|
|
789
|
+
);
|
|
790
|
+
if (result.status === 'failed') {
|
|
791
|
+
console.error('Failed to blend the Tileflow map themes', result.error);
|
|
761
792
|
}
|
|
762
793
|
}
|
|
763
794
|
|
|
764
795
|
async function switchTheme() {
|
|
765
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
|
+
}
|
|
766
802
|
if (themeResolution.error) {
|
|
767
803
|
await themeController.setTheme(themeController.getCurrent());
|
|
768
804
|
return;
|
|
@@ -770,7 +806,7 @@
|
|
|
770
806
|
const next = runtimeStyle;
|
|
771
807
|
if (!next) return;
|
|
772
808
|
activeRuntimeResource = next;
|
|
773
|
-
const result = await themeController.setTheme(next);
|
|
809
|
+
const result = await themeController.setTheme(next, {transition: themeTransition});
|
|
774
810
|
if (result.status === 'failed') {
|
|
775
811
|
console.error('Failed to change the Tileflow map theme', result.error);
|
|
776
812
|
}
|
|
@@ -999,11 +1035,7 @@
|
|
|
999
1035
|
}
|
|
1000
1036
|
|
|
1001
1037
|
type BridgeInteractionDiagnosticSource =
|
|
1002
|
-
| '
|
|
1003
|
-
| 'portal'
|
|
1004
|
-
| 'renderers'
|
|
1005
|
-
| 'semantic-runtime'
|
|
1006
|
-
| 'state';
|
|
1038
|
+
'annotation-runtime' | 'portal' | 'renderers' | 'semantic-runtime' | 'state';
|
|
1007
1039
|
|
|
1008
1040
|
function setBridgeInteractionDiagnostic(
|
|
1009
1041
|
source: BridgeInteractionDiagnosticSource,
|
|
@@ -1206,24 +1238,48 @@
|
|
|
1206
1238
|
colorScheme: 'dark' | 'light';
|
|
1207
1239
|
manifestMap: TileflowRuntimeManifestMap | null;
|
|
1208
1240
|
theme: TileflowMapProps['theme'];
|
|
1241
|
+
themeBlend: TileflowMapProps['themeBlend'];
|
|
1209
1242
|
}): Readonly<{error: boolean; name: string | undefined}> {
|
|
1243
|
+
// A blend shows its nearest theme until the blend style is ready, and names it while blending.
|
|
1244
|
+
const selection = input.themeBlend ? nearestTileflowBlendTheme(input.themeBlend) : input.theme;
|
|
1210
1245
|
if (!input.manifestMap) {
|
|
1211
1246
|
return {
|
|
1212
1247
|
error: false,
|
|
1213
|
-
name:
|
|
1248
|
+
name: selection && selection !== 'system' ? selection : undefined,
|
|
1214
1249
|
};
|
|
1215
1250
|
}
|
|
1216
1251
|
|
|
1217
1252
|
try {
|
|
1253
|
+
for (const name of input.themeBlend?.themes ?? []) {
|
|
1254
|
+
resolveTileflowRuntimeTheme(input.manifestMap, name);
|
|
1255
|
+
}
|
|
1218
1256
|
return {
|
|
1219
1257
|
error: false,
|
|
1220
|
-
name: resolveTileflowRuntimeTheme(input.manifestMap,
|
|
1258
|
+
name: resolveTileflowRuntimeTheme(input.manifestMap, selection, input.colorScheme).name,
|
|
1221
1259
|
};
|
|
1222
1260
|
} catch {
|
|
1223
1261
|
return {error: true, name: undefined};
|
|
1224
1262
|
}
|
|
1225
1263
|
}
|
|
1226
1264
|
|
|
1265
|
+
function resolveBlendStyles(input: {
|
|
1266
|
+
isImageMode: boolean;
|
|
1267
|
+
key: string | undefined;
|
|
1268
|
+
manifestMap: TileflowRuntimeManifestMap | null;
|
|
1269
|
+
source: TileflowMapProps['source'];
|
|
1270
|
+
}): TileflowRuntimeStyle[] | null {
|
|
1271
|
+
if (input.isImageMode || !input.manifestMap || !input.key) return null;
|
|
1272
|
+
try {
|
|
1273
|
+
return resolveTileflowRuntimeThemeBlend({
|
|
1274
|
+
blend: {position: 0, themes: JSON.parse(input.key) as string[]},
|
|
1275
|
+
manifestMap: input.manifestMap,
|
|
1276
|
+
source: input.source,
|
|
1277
|
+
});
|
|
1278
|
+
} catch {
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1227
1283
|
function resolveRuntimeResolutionState(input: {
|
|
1228
1284
|
currentManifestResolutionState: 'error' | 'loading' | 'ready';
|
|
1229
1285
|
imageSize: {height: number; width: number} | null;
|
package/src/index.d.ts
CHANGED
|
@@ -1,6 +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 {
|
|
3
|
+
import type {
|
|
4
|
+
TileflowBrowserThemeBlendSelection,
|
|
5
|
+
TileflowThemeTransition,
|
|
6
|
+
TileflowThemeTransitionOptions,
|
|
7
|
+
} from '@tileflow/core/browser';
|
|
4
8
|
import type {
|
|
5
9
|
TileflowAnalytics,
|
|
6
10
|
TileflowRuntimeSource,
|
|
@@ -45,6 +49,14 @@ type TileflowMapBaseProps = {
|
|
|
45
49
|
mapOptions?: TileflowMapOptions;
|
|
46
50
|
onThemeChange?: (transition: TileflowThemeTransition) => void;
|
|
47
51
|
theme?: TileflowThemeSelection;
|
|
52
|
+
/**
|
|
53
|
+
* Shows a continuous blend of the map's themes instead of `theme`. Moving `position` changes the
|
|
54
|
+
* map at once; changing `themes` prepares a new blend. `hold` keeps labels, icons, and mixed
|
|
55
|
+
* artwork back during a camera move; clearing it brings them in at once.
|
|
56
|
+
*/
|
|
57
|
+
themeBlend?: TileflowBrowserThemeBlendSelection;
|
|
58
|
+
/** Cross-fades theme changes, including entering and leaving a blend. */
|
|
59
|
+
themeTransition?: TileflowThemeTransitionOptions;
|
|
48
60
|
zoom?: number;
|
|
49
61
|
};
|
|
50
62
|
|
package/src/style-source.d.ts
CHANGED
package/src/style-source.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import {validateTileflowBrowserThemeBlendSelection} from '@tileflow/core/browser';
|
|
1
2
|
import {
|
|
2
3
|
validateTileflowRuntimeSource,
|
|
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,38 @@ 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 (
|
|
24
|
+
input.themeBlend !== undefined &&
|
|
25
|
+
!validateTileflowBrowserThemeBlendSelection(input.themeBlend)
|
|
26
|
+
) {
|
|
27
|
+
return {
|
|
28
|
+
error:
|
|
29
|
+
'themeBlend must name two to eight concrete themes, a position from 0 to themes.length - 1, and an optional boolean hold',
|
|
30
|
+
ok: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (input.themeTransition !== undefined && !isThemeTransition(input.themeTransition)) {
|
|
34
|
+
return {
|
|
35
|
+
error: 'themeTransition.duration must be a number of milliseconds from 0 to 5000',
|
|
36
|
+
ok: false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
19
39
|
return {ok: true};
|
|
20
40
|
}
|
|
21
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {unknown} value
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
function isThemeTransition(value) {
|
|
47
|
+
if (!value || typeof value !== 'object') return false;
|
|
48
|
+
const {duration} = /** @type {{duration?: unknown}} */ (value);
|
|
49
|
+
return (
|
|
50
|
+
duration === undefined ||
|
|
51
|
+
(typeof duration === 'number' && Number.isFinite(duration) && duration >= 0 && duration <= 5000)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
22
55
|
/** @param {TileflowMapStyleInput} input */
|
|
23
56
|
export function assertTileflowMapStyleInputs(input) {
|
|
24
57
|
const validation = validateTileflowMapStyleInputs(input);
|