@tileflow/react 0.1.0-alpha.36 → 0.1.0-alpha.38

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
@@ -19,6 +19,12 @@ npm install --save-dev --save-exact tileflow@alpha
19
19
  The supported peer ranges are React/React DOM 18–19 and MapLibre GL JS `>=6.4.1 <7`. The worker
20
20
  must come from the same MapLibre installation as the main module.
21
21
 
22
+ Prepared styles may retain relative asset URLs. The component loads them through Core's shared
23
+ resource loader before creating the map and before changing or blending themes. Sprites, fonts,
24
+ data and model resources resolve against their declaring documents. Pending document loads are
25
+ cancelled when the map is replaced or unmounted; `mapOptions.transformRequest` also applies to
26
+ style and HTTP TileJSON requests.
27
+
22
28
  ## Prepare a map
23
29
 
24
30
  Create `tileflow.config.ts` at the application root:
@@ -103,7 +109,7 @@ Add `timeOfDay="dusk"` beside `theme` to select a map's optional `day`, `dawn`,
103
109
  `night` preset. For Streets, `theme="light"` with this preset selects the existing `dusk` palette.
104
110
  Maps without this capability ignore the option; omitting it keeps the selected theme unchanged.
105
111
  Changing it uses the existing theme transition. `themeBlend` takes precedence. See the
106
- [authoring contract](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/README.md#style-with-themes-and-semantic-modules).
112
+ [authoring contract](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/REFERENCE.md#style-with-themes-and-semantic-modules).
107
113
 
108
114
  Theme and `timeOfDay` changes cross-fade over 450 ms by default, respecting reduced motion.
109
115
  `themeTransition={{duration: 700}}` customizes the duration; use `duration: 0` for an immediate change.
@@ -239,6 +245,26 @@ annotation-only. See the
239
245
  Text/field content uses safe text defaults. A `view` is an application renderer name, not HTML.
240
246
  Keep tooltips non-interactive; put buttons, links, and forms in popups.
241
247
 
248
+ ## Cluster points and draw density
249
+
250
+ Use `annotationClusters` for up to 1,000 annotations, or `dataSources` and `dataOverlays` for larger
251
+ GeoJSON sets. Clicking a cluster zooms in and reports a `cluster` target through `onInteractionEvent`.
252
+
253
+ <!-- docs:check -->
254
+
255
+ ```ts
256
+ import {clusterOverlay, geojsonSource} from '@tileflow/core/runtime';
257
+
258
+ export const dataSources = {stores: geojsonSource({cluster: true, clusterRadius: 50})};
259
+ export const dataOverlays = {
260
+ stores: clusterOverlay({id: 'stores', placement: 'above-labels', source: 'stores'}),
261
+ };
262
+ ```
263
+
264
+ See the
265
+ [interactions reference](https://github.com/tileflow/tileflow-sdk/blob/main/packages/interactions/REFERENCE.md)
266
+ for heatmaps, events, and polygon drawing with Terra Draw.
267
+
242
268
  ## Display an image or create a render
243
269
 
244
270
  `<Map mode="image">` displays an explicit `imageUrl` or derives an image URL from the selected
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { Map as Map$1, MapOptions } from 'maplibre-gl';
2
- import { TileflowBoundaryValues } from '@tileflow/core';
2
+ import { TileflowBoundaryValues, TileflowMapLibreOverlay, TileflowGeoJsonSource } from '@tileflow/core';
3
3
  import { CreateBoundaryFeatureStateOptions, TileflowBrowserThemeBlendSelection, TileflowThemeTransitionOptions, TileflowViewMode, TileflowThemeTransition } from '@tileflow/core/browser';
4
4
  import * as react from 'react';
5
5
  import { CSSProperties, ImgHTMLAttributes, ReactNode } from 'react';
6
6
  import { TileflowRuntimeSource, TileflowThemeSelection, TileflowTimeOfDay, TileflowAnalytics } from '@tileflow/core/runtime';
7
7
  import { TileflowAnnotation, TileflowInteractionState, TileflowAnnotationViewContext, TileflowInteractionBinding, TileflowInteractionViewContext, TileflowInteractionDiagnostic, TileflowInteractionEvent } from '@tileflow/interactions';
8
+ import { TileflowAnnotationClusterOptions } from '@tileflow/interactions/maplibre';
8
9
 
9
10
  /**
10
11
  * Keep a map's Boundaries feature-state equal to `values`. Pass the map from `<Map onLoad>` and
@@ -65,7 +66,13 @@ type TileflowInteractionRendererProps<TAnnotation extends TileflowAnnotation> =
65
66
  renderTooltip?: TileflowInteractionRenderer<TAnnotation>;
66
67
  }>;
67
68
  type MapInteractiveProps<TAnnotation extends TileflowAnnotation> = MapSharedProps & TileflowInteractionStateInputProps & TileflowInteractionRendererProps<TAnnotation> & {
69
+ /** Group `annotations` into accessible cluster buttons. Up to 1,000 annotations. */
70
+ annotationClusters?: TileflowAnnotationClusterOptions;
68
71
  annotations?: readonly TAnnotation[];
72
+ /** Overlays drawn over `dataSources` or compiled sources, keyed as in `defineMap`. */
73
+ dataOverlays?: Readonly<Record<string, TileflowMapLibreOverlay>>;
74
+ /** Application GeoJSON, keyed as in `defineMap`. A compiled source of the same key is filled. */
75
+ dataSources?: Readonly<Record<string, TileflowGeoJsonSource>>;
69
76
  mode?: 'interactive';
70
77
  onInteractionDiagnostic?: (diagnostic: TileflowInteractionDiagnostic) => void;
71
78
  onInteractionEvent?: (event: TileflowInteractionEvent<TAnnotation>) => void;
@@ -73,7 +80,10 @@ type MapInteractiveProps<TAnnotation extends TileflowAnnotation> = MapSharedProp
73
80
  renderMarker?: TileflowAnnotationRenderer<TAnnotation>;
74
81
  };
75
82
  type MapImageProps = MapSharedProps & {
83
+ annotationClusters?: never;
76
84
  annotations?: never;
85
+ dataOverlays?: never;
86
+ dataSources?: never;
77
87
  defaultInteractionState?: never;
78
88
  interactionState?: never;
79
89
  interactions?: never;
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  createTileflowThemeController,
52
52
  createTileflowTransformRequest,
53
53
  getTileflowSystemColorScheme,
54
+ loadTileflowRuntimeStyle,
54
55
  loadTileflowStyleFonts,
55
56
  registerTileflowContourProtocol,
56
57
  registerTileflowPathGroundProtocol,
@@ -79,6 +80,7 @@ import {
79
80
  resolveTileflowStaticImageUrl
80
81
  } from "@tileflow/core/runtime";
81
82
  import {
83
+ createTileflowMapLibreDataRuntime,
82
84
  createTileflowMapLibreDomRuntime,
83
85
  createTileflowMapLibreInteractionCoordinator,
84
86
  createTileflowMapLibreSemanticDomRuntime
@@ -284,7 +286,10 @@ var defaultInteractions = [];
284
286
  function Map2(props) {
285
287
  assertTileflowMapStyleInputs(props);
286
288
  const {
289
+ annotationClusters,
287
290
  annotations,
291
+ dataOverlays,
292
+ dataSources,
288
293
  captureId,
289
294
  defaultInteractionState,
290
295
  mode = "interactive",
@@ -361,6 +366,8 @@ function Map2(props) {
361
366
  const interactionCoordinatorRef = useRef(
362
367
  void 0
363
368
  );
369
+ const dataRuntimeRef = useRef(void 0);
370
+ const dataInputRef = useRef({});
364
371
  const annotationsRef = useRef(preparedAnnotations.annotations);
365
372
  const annotationTitlesRef = useRef(preparedAnnotations.titles);
366
373
  const preparedInteractionsRef = useRef(preparedInteractions);
@@ -455,7 +462,7 @@ function Map2(props) {
455
462
  const mapNameRef = useRef(mapName);
456
463
  const resolvedAnalyticsRef = useRef(void 0);
457
464
  const readinessRunRef = useRef(0);
458
- const hasAdditionalInteractionConfiguration = annotations !== void 0 || defaultInteractionState !== void 0 || interactionState !== void 0 || onInteractionDiagnostic !== void 0 || onInteractionEvent !== void 0 || onInteractionStateChange !== void 0 || renderMarker !== void 0 || renderPopup !== void 0 || renderTooltip !== void 0 || props.interactions !== void 0;
465
+ const hasAdditionalInteractionConfiguration = annotationClusters !== void 0 || annotations !== void 0 || dataOverlays !== void 0 || dataSources !== void 0 || defaultInteractionState !== void 0 || interactionState !== void 0 || onInteractionDiagnostic !== void 0 || onInteractionEvent !== void 0 || onInteractionStateChange !== void 0 || renderMarker !== void 0 || renderPopup !== void 0 || renderTooltip !== void 0 || props.interactions !== void 0;
459
466
  const modeDiagnostic = useMemo(
460
467
  () => isImageMode ? imageModeMapInteractionDiagnostic(
461
468
  preparedAnnotations.annotations.length,
@@ -682,6 +689,15 @@ function Map2(props) {
682
689
  onInteractionDiagnosticRef.current?.(diagnostic);
683
690
  }
684
691
  }, [preparedAnnotations]);
692
+ dataInputRef.current = {
693
+ annotationClusters,
694
+ annotations: preparedAnnotations.annotations,
695
+ dataOverlays,
696
+ dataSources
697
+ };
698
+ useEffect2(() => {
699
+ dataRuntimeRef.current?.reconcile(dataInputRef.current);
700
+ }, [annotationClusters, dataOverlays, dataSources, preparedAnnotations]);
685
701
  useEffect2(() => {
686
702
  const runtime = semanticRuntimeRef.current;
687
703
  if (!runtime || !preparedInteractions.ok) return;
@@ -748,12 +764,28 @@ function Map2(props) {
748
764
  const container = containerRef.current;
749
765
  let cancelled = false;
750
766
  let disposeMap;
767
+ const resourceLifetime = new AbortController();
768
+ const session = createTileflowSessionController({ source: "react" });
769
+ const resourceTransform = createTileflowTransformRequest({
770
+ getAnalytics: () => resolvedAnalyticsRef.current,
771
+ sessionController: session,
772
+ sessionId: session.sessionId,
773
+ transformRequest: stableMapOptions?.transformRequest ?? void 0
774
+ });
775
+ const styleLoadOptions = {
776
+ signal: resourceLifetime.signal,
777
+ transformRequest: (url, type) => resourceTransform(
778
+ url,
779
+ type
780
+ )
781
+ };
751
782
  void Promise.all([
752
783
  loadTileflowMapLibre(),
753
- loadTileflowStyleFonts(initialRuntimeStyle.style, {
754
- fontFaces: initialRuntimeStyle.fontFaces
784
+ loadTileflowRuntimeStyle(initialRuntimeStyle, styleLoadOptions).then(async (prepared) => {
785
+ await loadTileflowStyleFonts(prepared.style, { fontFaces: prepared.fontFaces });
786
+ return prepared;
755
787
  })
756
- ]).then(([maplibregl]) => {
788
+ ]).then(([maplibregl, prepared]) => {
757
789
  if (cancelled || containerRef.current !== container) {
758
790
  return;
759
791
  }
@@ -787,7 +819,6 @@ function Map2(props) {
787
819
  setRendererBridgeDiagnostic(void 0);
788
820
  }
789
821
  };
790
- const session = createTileflowSessionController({ source: "react" });
791
822
  const sessionStarter = createTileflowSessionStarter({
792
823
  getSessionId: () => session.sessionId,
793
824
  sessionId: session.sessionId,
@@ -816,7 +847,7 @@ function Map2(props) {
816
847
  stableMapOptions?.canvasContextAttributes
817
848
  ),
818
849
  container,
819
- style: initialRuntimeStyle.style,
850
+ style: prepared.style,
820
851
  bearing: bearingRef.current,
821
852
  center: centerRef.current,
822
853
  pitch: pitchRef.current,
@@ -839,7 +870,9 @@ function Map2(props) {
839
870
  registerCleanup(attachTileflowLabelPlacement(map));
840
871
  registerCleanup(attachTileflowHorizonQueries(map));
841
872
  const themeController = initialRuntimeStyle.theme === void 0 ? void 0 : createTileflowThemeController({
842
- initial: initialRuntimeStyle,
873
+ initial: prepared.family ? prepared : initialRuntimeStyle,
874
+ initialStyle: prepared.style,
875
+ styleLoadOptions,
843
876
  map,
844
877
  onTransition(transition) {
845
878
  onThemeChangeRef.current?.(transition);
@@ -933,6 +966,23 @@ function Map2(props) {
933
966
  interactionRuntimeRef.current = interactionRuntime;
934
967
  setupResources.annotationRuntime = interactionRuntime;
935
968
  registerCleanup(() => interactionRuntime.dispose());
969
+ const dataRuntime = createTileflowMapLibreDataRuntime({
970
+ createMarker: ({ element }) => wrapMapLibrePositioned(new maplibregl.Marker({ element })),
971
+ document: container.ownerDocument,
972
+ map,
973
+ onDiagnostic(diagnostic) {
974
+ onInteractionDiagnosticRef.current?.(diagnostic);
975
+ },
976
+ onInteractionEvent(event) {
977
+ onInteractionEventRef.current?.(event);
978
+ },
979
+ setHiddenAnnotations: interactionRuntime.setHiddenAnnotations
980
+ });
981
+ dataRuntimeRef.current = dataRuntime;
982
+ registerCleanup(() => {
983
+ dataRuntime.dispose();
984
+ if (dataRuntimeRef.current === dataRuntime) dataRuntimeRef.current = void 0;
985
+ });
936
986
  const semanticRuntime = createTileflowMapLibreSemanticDomRuntime({
937
987
  cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
938
988
  createOverlay({ container: overlayContainer, kind }) {
@@ -1011,6 +1061,7 @@ function Map2(props) {
1011
1061
  setAnnotationBridgeDiagnostic(diagnostic);
1012
1062
  onInteractionDiagnosticRef.current?.(diagnostic);
1013
1063
  }
1064
+ dataRuntime.reconcile(dataInputRef.current);
1014
1065
  const initialInteractions = preparedInteractionsRef.current;
1015
1066
  if (initialInteractions.ok) {
1016
1067
  try {
@@ -1070,6 +1121,7 @@ function Map2(props) {
1070
1121
  });
1071
1122
  }
1072
1123
  }).catch((error) => {
1124
+ resourceLifetime.abort();
1073
1125
  try {
1074
1126
  disposeMap?.();
1075
1127
  } catch (cleanupError) {
@@ -1082,6 +1134,7 @@ function Map2(props) {
1082
1134
  });
1083
1135
  return () => {
1084
1136
  cancelled = true;
1137
+ resourceLifetime.abort();
1085
1138
  readinessRunRef.current += 1;
1086
1139
  disposeMap?.();
1087
1140
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/react",
3
- "version": "0.1.0-alpha.36",
3
+ "version": "0.1.0-alpha.38",
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.43 <0.1.0-beta.0",
47
- "@tileflow/interactions": ">=0.1.0-alpha.7 <0.1.0-beta.0",
48
- "@tileflow/static": ">=0.1.0-alpha.29 <0.1.0-beta.0"
46
+ "@tileflow/core": ">=0.1.0-alpha.48 <0.1.0-beta.0",
47
+ "@tileflow/interactions": ">=0.1.0-alpha.8 <0.1.0-beta.0",
48
+ "@tileflow/static": ">=0.1.0-alpha.30 <0.1.0-beta.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "maplibre-gl": ">=6.4.1 <7",
@@ -55,7 +55,7 @@
55
55
  "devDependencies": {
56
56
  "@types/react": "^19.3.0",
57
57
  "@types/react-dom": "^19.3.0",
58
- "maplibre-gl": "6.13.0",
58
+ "maplibre-gl": "6.16.0",
59
59
  "react": "^19.3.0",
60
60
  "react-dom": "^19.3.0"
61
61
  },