@tileflow/react 0.1.0-alpha.27 → 0.1.0-alpha.29

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.
@@ -202,7 +208,7 @@ Tileflow credentials on that server, not in React props or a browser bundle.
202
208
  ```tsx
203
209
  import {useState} from 'react';
204
210
  import {StaticMap} from '@tileflow/react/static';
205
- import type {StaticMapAttributionEntry} from '@tileflow/static';
211
+ import type {StaticMapAttributionEntry} from '@tileflow/static/client';
206
212
 
207
213
  export function StaticPreview({idempotencyKey}: {idempotencyKey: string}) {
208
214
  const [entries, setEntries] = useState<readonly StaticMapAttributionEntry[]>([]);
package/dist/index.d.ts CHANGED
@@ -1,13 +1,21 @@
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';
4
+ import { TileflowBrowserThemeBlendSelection, TileflowThemeTransitionOptions, TileflowThemeTransition } from '@tileflow/core/browser';
5
5
  import { TileflowRuntimeSource, TileflowThemeSelection, 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. `hold` keeps labels, icons, and mixed
14
+ * artwork back during a camera move; clearing it brings them in at once.
15
+ */
16
+ themeBlend?: TileflowBrowserThemeBlendSelection;
17
+ /** Cross-fades theme changes, including entering and leaving a blend. */
18
+ themeTransition?: TileflowThemeTransitionOptions;
11
19
  };
12
20
 
13
21
  type TileflowMapSource = TileflowRuntimeSource;
package/dist/index.js CHANGED
@@ -31,12 +31,14 @@ import {
31
31
  defaultTileflowRuntimeView,
32
32
  loadTileflowManifest,
33
33
  mergeTileflowAnalytics,
34
+ nearestTileflowBlendTheme,
34
35
  normalizeTileflowRuntimeCenter,
35
36
  normalizeTileflowStaticImageSize,
36
37
  resolveTileflowManifestMap,
37
38
  resolveTileflowMapMode,
38
39
  resolveTileflowRuntimeStyle,
39
40
  resolveTileflowRuntimeTheme,
41
+ resolveTileflowRuntimeThemeBlend,
40
42
  resolveTileflowRuntimeView,
41
43
  resolveTileflowStaticImageUrl
42
44
  } from "@tileflow/core/runtime";
@@ -165,6 +167,9 @@ function prepareTileflowReactInteractionState(interactionState, defaultInteracti
165
167
  }
166
168
 
167
169
  // src/map-style-inputs.ts
170
+ import {
171
+ validateTileflowBrowserThemeBlendSelection
172
+ } from "@tileflow/core/browser";
168
173
  import {
169
174
  validateTileflowRuntimeSource,
170
175
  validateTileflowThemeSelection
@@ -175,8 +180,25 @@ function validateTileflowMapStyleInputs(input) {
175
180
  if (input.theme !== void 0 && !validateTileflowThemeSelection(input.theme)) {
176
181
  return { error: 'theme must be a concrete portable theme name or "system"', ok: false };
177
182
  }
183
+ if (input.themeBlend !== void 0 && !validateTileflowBrowserThemeBlendSelection(input.themeBlend)) {
184
+ return {
185
+ error: "themeBlend must name two to eight concrete themes, a position from 0 to themes.length - 1, and an optional boolean hold",
186
+ ok: false
187
+ };
188
+ }
189
+ if (input.themeTransition !== void 0 && !isThemeTransition(input.themeTransition)) {
190
+ return {
191
+ error: "themeTransition.duration must be a number of milliseconds from 0 to 5000",
192
+ ok: false
193
+ };
194
+ }
178
195
  return { ok: true };
179
196
  }
197
+ function isThemeTransition(value) {
198
+ if (!value || typeof value !== "object") return false;
199
+ const { duration } = value;
200
+ return duration === void 0 || typeof duration === "number" && Number.isFinite(duration) && duration >= 0 && duration <= 5e3;
201
+ }
180
202
  function assertTileflowMapStyleInputs(input) {
181
203
  const validation = validateTileflowMapStyleInputs(input);
182
204
  if (!validation.ok) {
@@ -238,6 +260,8 @@ function Map2(props) {
238
260
  mapOptions,
239
261
  analytics,
240
262
  theme,
263
+ themeBlend,
264
+ themeTransition,
241
265
  interactionState,
242
266
  interactions = defaultInteractions,
243
267
  onInteractionDiagnostic,
@@ -326,19 +350,32 @@ function Map2(props) {
326
350
  const manifestRequestKey = JSON.stringify([manifestUrl, mapName]);
327
351
  const currentManifestResolution = manifestResolution.key === manifestRequestKey ? manifestResolution : { key: manifestRequestKey, map: null, state: "loading" };
328
352
  const manifestMap = currentManifestResolution.state === "ready" ? currentManifestResolution.map : null;
353
+ const themeBlendKey = themeBlend ? JSON.stringify(themeBlend.themes) : void 0;
354
+ const themeBlendPosition = themeBlend?.position;
355
+ const themeBlendHold = themeBlend?.hold === true;
356
+ const blendThemeName = themeBlend ? nearestTileflowBlendTheme(themeBlend) : void 0;
329
357
  const themeResolution = useMemo(() => {
358
+ const selection = blendThemeName ?? theme;
330
359
  if (!manifestMap) {
331
- return { error: false, name: theme && theme !== "system" ? theme : void 0 };
360
+ return {
361
+ error: false,
362
+ name: selection && selection !== "system" ? selection : void 0
363
+ };
332
364
  }
333
365
  try {
366
+ if (themeBlendKey) {
367
+ for (const name of JSON.parse(themeBlendKey)) {
368
+ resolveTileflowRuntimeTheme(manifestMap, name);
369
+ }
370
+ }
334
371
  return {
335
372
  error: false,
336
- name: resolveTileflowRuntimeTheme(manifestMap, theme, systemColorScheme).name
373
+ name: resolveTileflowRuntimeTheme(manifestMap, selection, systemColorScheme).name
337
374
  };
338
375
  } catch {
339
376
  return { error: true, name: void 0 };
340
377
  }
341
- }, [manifestMap, systemColorScheme, theme]);
378
+ }, [blendThemeName, manifestMap, systemColorScheme, theme, themeBlendKey]);
342
379
  const resolvedThemeName = themeResolution.name;
343
380
  const manifestView = resolveTileflowRuntimeView({ manifestMap });
344
381
  const manifestCenter = useStableMapOptionValue(
@@ -477,6 +514,22 @@ function Map2(props) {
477
514
  });
478
515
  }, [isImageMode, manifestMap, runtimeSource, systemColorScheme, themeResolution]);
479
516
  runtimeStyleRef.current = runtimeStyle;
517
+ const blendStyles = useMemo(() => {
518
+ if (isImageMode || !manifestMap || !themeBlendKey) return null;
519
+ try {
520
+ return resolveTileflowRuntimeThemeBlend({
521
+ blend: { position: 0, themes: JSON.parse(themeBlendKey) },
522
+ manifestMap,
523
+ source: runtimeSource
524
+ });
525
+ } catch {
526
+ return null;
527
+ }
528
+ }, [isImageMode, manifestMap, runtimeSource, themeBlendKey]);
529
+ const blendRequestRef = useRef(null);
530
+ blendRequestRef.current = blendStyles && themeBlendPosition !== void 0 ? { hold: themeBlendHold, position: themeBlendPosition, themes: blendStyles } : null;
531
+ const themeTransitionRef = useRef(themeTransition);
532
+ themeTransitionRef.current = themeTransition;
480
533
  const runtimeMapIdentity = `${manifestUrl}\0${runtimeSource.map}`;
481
534
  const runtimeStyleReady = runtimeStyle !== null || themeResolution.error && mapRef.current !== null;
482
535
  const resolvedAnalytics = useMemo(
@@ -695,8 +748,6 @@ function Map2(props) {
695
748
  });
696
749
  registerCleanup(() => worldRequestBridge.dispose());
697
750
  const transformRequest = createTileflowTransformRequest({
698
- always: true,
699
- asyncAnalyticsTiming: "resolution",
700
751
  getAnalytics: () => resolvedAnalyticsRef.current,
701
752
  sessionController: session,
702
753
  sessionId: session.sessionId,
@@ -919,9 +970,15 @@ function Map2(props) {
919
970
  });
920
971
  registerCleanup(() => lifecycle.dispose());
921
972
  const latestRuntimeStyle = runtimeStyleRef.current;
922
- if (themeController && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
973
+ const latestBlend = blendRequestRef.current;
974
+ if (themeController && latestBlend) {
975
+ if (latestRuntimeStyle) setActiveRuntimeResource(latestRuntimeStyle);
976
+ void themeController.setBlend(latestBlend, { transition: themeTransitionRef.current });
977
+ } else if (themeController && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
923
978
  setActiveRuntimeResource(latestRuntimeStyle);
924
- void themeController.setTheme(latestRuntimeStyle);
979
+ void themeController.setTheme(latestRuntimeStyle, {
980
+ transition: themeTransitionRef.current
981
+ });
925
982
  }
926
983
  }).catch((error) => {
927
984
  try {
@@ -942,19 +999,33 @@ function Map2(props) {
942
999
  }, [resolvedInteractive, runtimeMapIdentity, runtimeStyleReady, stableMapOptions]);
943
1000
  useEffect(() => {
944
1001
  const controller = themeControllerRef.current;
945
- if (!controller) return;
1002
+ if (!controller || themeBlendKey) return;
946
1003
  if (themeResolution.error) {
947
1004
  void controller.setTheme(controller.getCurrent());
948
1005
  return;
949
1006
  }
950
- if (!runtimeStyle || controller.getCurrent() === runtimeStyle) return;
1007
+ if (!runtimeStyle || controller.getCurrent() === runtimeStyle && !controller.getBlend())
1008
+ return;
951
1009
  setActiveRuntimeResource(runtimeStyle);
952
- void controller.setTheme(runtimeStyle).then((result) => {
1010
+ void controller.setTheme(runtimeStyle, { transition: themeTransitionRef.current }).then((result) => {
953
1011
  if (result.status === "failed") {
954
1012
  console.error("Failed to change the Tileflow map theme", result.error);
955
1013
  }
956
1014
  });
957
- }, [runtimeStyle, themeResolution.error]);
1015
+ }, [runtimeStyle, themeBlendKey, themeResolution.error]);
1016
+ useEffect(() => {
1017
+ const controller = themeControllerRef.current;
1018
+ if (!controller || !blendStyles || themeBlendPosition === void 0) return;
1019
+ if (runtimeStyle) setActiveRuntimeResource(runtimeStyle);
1020
+ void controller.setBlend(
1021
+ { hold: themeBlendHold, position: themeBlendPosition, themes: blendStyles },
1022
+ { transition: themeTransitionRef.current }
1023
+ ).then((result) => {
1024
+ if (result.status === "failed") {
1025
+ console.error("Failed to blend the Tileflow map themes", result.error);
1026
+ }
1027
+ });
1028
+ }, [blendStyles, runtimeStyle, themeBlendHold, themeBlendPosition]);
958
1029
  useLayoutEffect(() => {
959
1030
  if (!isImageMode) return;
960
1031
  readinessRunRef.current += 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/react",
3
- "version": "0.1.0-alpha.27",
3
+ "version": "0.1.0-alpha.29",
4
4
  "license": "Apache-2.0",
5
5
  "description": "React components for rendering Tileflow maps with MapLibre.",
6
6
  "type": "module",
@@ -43,9 +43,9 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@tileflow/core": ">=0.1.0-alpha.29 <0.1.0-beta.0",
46
+ "@tileflow/core": ">=0.1.0-alpha.32 <0.1.0-beta.0",
47
47
  "@tileflow/interactions": ">=0.1.0-alpha.4 <0.1.0-beta.0",
48
- "@tileflow/static": ">=0.1.0-alpha.27 <0.1.0-beta.0"
48
+ "@tileflow/static": ">=0.1.0-alpha.28 <0.1.0-beta.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "maplibre-gl": ">=6.4.1 <7",
@@ -53,11 +53,11 @@
53
53
  "react-dom": ">=18 <20"
54
54
  },
55
55
  "devDependencies": {
56
- "@types/react": "^19.2.17",
57
- "@types/react-dom": "^19.2.3",
58
- "maplibre-gl": "6.9.0",
59
- "react": "^19.2.7",
60
- "react-dom": "^19.2.7"
56
+ "@types/react": "^19.3.0",
57
+ "@types/react-dom": "^19.3.0",
58
+ "maplibre-gl": "6.13.0",
59
+ "react": "^19.3.0",
60
+ "react-dom": "^19.3.0"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsup",