@tileflow/react 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 CHANGED
@@ -99,6 +99,12 @@ for assets.
99
99
  explicit light/dark mapping, which Streets supplies. Theme changes preserve the MapLibre instance,
100
100
  camera, and interactions, and roll back on failure. `onThemeChange` reports transitions.
101
101
 
102
+ `themeTransition={{duration: 450}}` cross-fades theme changes over that many milliseconds.
103
+ `themeBlend={{themes: ['night', 'dusk', 'day'], position}}` shows a continuous blend of the map's
104
+ themes instead of `theme`: position 0 is the first theme and 1.5 is halfway from the second to the
105
+ third. Moving the position updates the map at once. See
106
+ [theme transitions and blends](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/theme-transitions-and-blends.md).
107
+
102
108
  `mapOptions` accepts native MapLibre options except `container` and `style`. Direct camera props
103
109
  take priority over `mapOptions`, then the manifest view, then shared defaults. `Map` always selects
104
110
  a Tileflow map through its manifest; it has no renderer discriminator or direct-style mode.
package/dist/index.d.ts CHANGED
@@ -1,13 +1,20 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, ImgHTMLAttributes, ReactNode } from 'react';
3
3
  import { MapOptions, Map as Map$1 } from 'maplibre-gl';
4
- import { TileflowThemeTransition } from '@tileflow/core/browser';
5
- import { TileflowRuntimeSource, TileflowThemeSelection, TileflowAnalytics } from '@tileflow/core/runtime';
4
+ import { TileflowThemeTransitionOptions, TileflowThemeTransition } from '@tileflow/core/browser';
5
+ import { TileflowRuntimeSource, TileflowThemeSelection, TileflowThemeBlendSelection, TileflowAnalytics } from '@tileflow/core/runtime';
6
6
  import { TileflowAnnotation, TileflowInteractionState, TileflowAnnotationViewContext, TileflowInteractionBinding, TileflowInteractionViewContext, TileflowInteractionDiagnostic, TileflowInteractionEvent } from '@tileflow/interactions';
7
7
 
8
8
  type TileflowMapStyleSourceProps = {
9
9
  source: TileflowRuntimeSource;
10
10
  theme?: TileflowThemeSelection;
11
+ /**
12
+ * Shows a continuous blend of the map's themes instead of `theme`. Moving `position` changes the
13
+ * map at once; changing `themes` prepares a new blend.
14
+ */
15
+ themeBlend?: TileflowThemeBlendSelection;
16
+ /** Cross-fades theme changes, including entering and leaving a blend. */
17
+ themeTransition?: TileflowThemeTransitionOptions;
11
18
  };
12
19
 
13
20
  type TileflowMapSource = TileflowRuntimeSource;
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  } from "react";
12
12
  import { createPortal } from "react-dom";
13
13
  import {
14
+ attachTileflowAtmosphere,
14
15
  attachTileflowFairUseNotice,
15
16
  attachTileflowMapLifecycle,
16
17
  createTileflowSessionStarter,
@@ -30,12 +31,14 @@ import {
30
31
  defaultTileflowRuntimeView,
31
32
  loadTileflowManifest,
32
33
  mergeTileflowAnalytics,
34
+ nearestTileflowBlendTheme,
33
35
  normalizeTileflowRuntimeCenter,
34
36
  normalizeTileflowStaticImageSize,
35
37
  resolveTileflowManifestMap,
36
38
  resolveTileflowMapMode,
37
39
  resolveTileflowRuntimeStyle,
38
40
  resolveTileflowRuntimeTheme,
41
+ resolveTileflowRuntimeThemeBlend,
39
42
  resolveTileflowRuntimeView,
40
43
  resolveTileflowStaticImageUrl
41
44
  } from "@tileflow/core/runtime";
@@ -166,6 +169,7 @@ function prepareTileflowReactInteractionState(interactionState, defaultInteracti
166
169
  // src/map-style-inputs.ts
167
170
  import {
168
171
  validateTileflowRuntimeSource,
172
+ validateTileflowThemeBlendSelection,
169
173
  validateTileflowThemeSelection
170
174
  } from "@tileflow/core/runtime";
171
175
  function validateTileflowMapStyleInputs(input) {
@@ -174,8 +178,25 @@ function validateTileflowMapStyleInputs(input) {
174
178
  if (input.theme !== void 0 && !validateTileflowThemeSelection(input.theme)) {
175
179
  return { error: 'theme must be a concrete portable theme name or "system"', ok: false };
176
180
  }
181
+ if (input.themeBlend !== void 0 && !validateTileflowThemeBlendSelection(input.themeBlend)) {
182
+ return {
183
+ error: "themeBlend must name two to eight concrete themes and a position from 0 to themes.length - 1",
184
+ ok: false
185
+ };
186
+ }
187
+ if (input.themeTransition !== void 0 && !isThemeTransition(input.themeTransition)) {
188
+ return {
189
+ error: "themeTransition.duration must be a number of milliseconds from 0 to 5000",
190
+ ok: false
191
+ };
192
+ }
177
193
  return { ok: true };
178
194
  }
195
+ function isThemeTransition(value) {
196
+ if (!value || typeof value !== "object") return false;
197
+ const { duration } = value;
198
+ return duration === void 0 || typeof duration === "number" && Number.isFinite(duration) && duration >= 0 && duration <= 5e3;
199
+ }
179
200
  function assertTileflowMapStyleInputs(input) {
180
201
  const validation = validateTileflowMapStyleInputs(input);
181
202
  if (!validation.ok) {
@@ -237,6 +258,8 @@ function Map2(props) {
237
258
  mapOptions,
238
259
  analytics,
239
260
  theme,
261
+ themeBlend,
262
+ themeTransition,
240
263
  interactionState,
241
264
  interactions = defaultInteractions,
242
265
  onInteractionDiagnostic,
@@ -325,19 +348,31 @@ function Map2(props) {
325
348
  const manifestRequestKey = JSON.stringify([manifestUrl, mapName]);
326
349
  const currentManifestResolution = manifestResolution.key === manifestRequestKey ? manifestResolution : { key: manifestRequestKey, map: null, state: "loading" };
327
350
  const manifestMap = currentManifestResolution.state === "ready" ? currentManifestResolution.map : null;
351
+ const themeBlendKey = themeBlend ? JSON.stringify(themeBlend.themes) : void 0;
352
+ const themeBlendPosition = themeBlend?.position;
353
+ const blendThemeName = themeBlend ? nearestTileflowBlendTheme(themeBlend) : void 0;
328
354
  const themeResolution = useMemo(() => {
355
+ const selection = blendThemeName ?? theme;
329
356
  if (!manifestMap) {
330
- return { error: false, name: theme && theme !== "system" ? theme : void 0 };
357
+ return {
358
+ error: false,
359
+ name: selection && selection !== "system" ? selection : void 0
360
+ };
331
361
  }
332
362
  try {
363
+ if (themeBlendKey) {
364
+ for (const name of JSON.parse(themeBlendKey)) {
365
+ resolveTileflowRuntimeTheme(manifestMap, name);
366
+ }
367
+ }
333
368
  return {
334
369
  error: false,
335
- name: resolveTileflowRuntimeTheme(manifestMap, theme, systemColorScheme).name
370
+ name: resolveTileflowRuntimeTheme(manifestMap, selection, systemColorScheme).name
336
371
  };
337
372
  } catch {
338
373
  return { error: true, name: void 0 };
339
374
  }
340
- }, [manifestMap, systemColorScheme, theme]);
375
+ }, [blendThemeName, manifestMap, systemColorScheme, theme, themeBlendKey]);
341
376
  const resolvedThemeName = themeResolution.name;
342
377
  const manifestView = resolveTileflowRuntimeView({ manifestMap });
343
378
  const manifestCenter = useStableMapOptionValue(
@@ -476,6 +511,22 @@ function Map2(props) {
476
511
  });
477
512
  }, [isImageMode, manifestMap, runtimeSource, systemColorScheme, themeResolution]);
478
513
  runtimeStyleRef.current = runtimeStyle;
514
+ const blendStyles = useMemo(() => {
515
+ if (isImageMode || !manifestMap || !themeBlendKey) return null;
516
+ try {
517
+ return resolveTileflowRuntimeThemeBlend({
518
+ blend: { position: 0, themes: JSON.parse(themeBlendKey) },
519
+ manifestMap,
520
+ source: runtimeSource
521
+ });
522
+ } catch {
523
+ return null;
524
+ }
525
+ }, [isImageMode, manifestMap, runtimeSource, themeBlendKey]);
526
+ const blendRequestRef = useRef(null);
527
+ blendRequestRef.current = blendStyles && themeBlendPosition !== void 0 ? { position: themeBlendPosition, themes: blendStyles } : null;
528
+ const themeTransitionRef = useRef(themeTransition);
529
+ themeTransitionRef.current = themeTransition;
479
530
  const runtimeMapIdentity = `${manifestUrl}\0${runtimeSource.map}`;
480
531
  const runtimeStyleReady = runtimeStyle !== null || themeResolution.error && mapRef.current !== null;
481
532
  const resolvedAnalytics = useMemo(
@@ -719,6 +770,7 @@ function Map2(props) {
719
770
  mapRef.current = map;
720
771
  setupResources.map = map;
721
772
  registerCleanup(() => map.remove());
773
+ registerCleanup(attachTileflowAtmosphere(map));
722
774
  const themeController = initialRuntimeStyle.theme === void 0 ? void 0 : createTileflowThemeController({
723
775
  initial: initialRuntimeStyle,
724
776
  map,
@@ -917,9 +969,15 @@ function Map2(props) {
917
969
  });
918
970
  registerCleanup(() => lifecycle.dispose());
919
971
  const latestRuntimeStyle = runtimeStyleRef.current;
920
- if (themeController && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
972
+ const latestBlend = blendRequestRef.current;
973
+ if (themeController && latestBlend) {
974
+ if (latestRuntimeStyle) setActiveRuntimeResource(latestRuntimeStyle);
975
+ void themeController.setBlend(latestBlend, { transition: themeTransitionRef.current });
976
+ } else if (themeController && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
921
977
  setActiveRuntimeResource(latestRuntimeStyle);
922
- void themeController.setTheme(latestRuntimeStyle);
978
+ void themeController.setTheme(latestRuntimeStyle, {
979
+ transition: themeTransitionRef.current
980
+ });
923
981
  }
924
982
  }).catch((error) => {
925
983
  try {
@@ -940,19 +998,33 @@ function Map2(props) {
940
998
  }, [resolvedInteractive, runtimeMapIdentity, runtimeStyleReady, stableMapOptions]);
941
999
  useEffect(() => {
942
1000
  const controller = themeControllerRef.current;
943
- if (!controller) return;
1001
+ if (!controller || themeBlendKey) return;
944
1002
  if (themeResolution.error) {
945
1003
  void controller.setTheme(controller.getCurrent());
946
1004
  return;
947
1005
  }
948
- if (!runtimeStyle || controller.getCurrent() === runtimeStyle) return;
1006
+ if (!runtimeStyle || controller.getCurrent() === runtimeStyle && !controller.getBlend())
1007
+ return;
949
1008
  setActiveRuntimeResource(runtimeStyle);
950
- void controller.setTheme(runtimeStyle).then((result) => {
1009
+ void controller.setTheme(runtimeStyle, { transition: themeTransitionRef.current }).then((result) => {
951
1010
  if (result.status === "failed") {
952
1011
  console.error("Failed to change the Tileflow map theme", result.error);
953
1012
  }
954
1013
  });
955
- }, [runtimeStyle, themeResolution.error]);
1014
+ }, [runtimeStyle, themeBlendKey, themeResolution.error]);
1015
+ useEffect(() => {
1016
+ const controller = themeControllerRef.current;
1017
+ if (!controller || !blendStyles || themeBlendPosition === void 0) return;
1018
+ if (runtimeStyle) setActiveRuntimeResource(runtimeStyle);
1019
+ void controller.setBlend(
1020
+ { position: themeBlendPosition, themes: blendStyles },
1021
+ { transition: themeTransitionRef.current }
1022
+ ).then((result) => {
1023
+ if (result.status === "failed") {
1024
+ console.error("Failed to blend the Tileflow map themes", result.error);
1025
+ }
1026
+ });
1027
+ }, [blendStyles, runtimeStyle, themeBlendPosition]);
956
1028
  useLayoutEffect(() => {
957
1029
  if (!isImageMode) return;
958
1030
  readinessRunRef.current += 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/react",
3
- "version": "0.1.0-alpha.26",
3
+ "version": "0.1.0-alpha.28",
4
4
  "license": "Apache-2.0",
5
5
  "description": "React components for rendering Tileflow maps with MapLibre.",
6
6
  "type": "module",
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@tileflow/core": ">=0.1.0-alpha.27 <0.1.0-beta.0",
46
+ "@tileflow/core": ">=0.1.0-alpha.30 <0.1.0-beta.0",
47
47
  "@tileflow/interactions": ">=0.1.0-alpha.4 <0.1.0-beta.0",
48
48
  "@tileflow/static": ">=0.1.0-alpha.27 <0.1.0-beta.0"
49
49
  },