@trackunit/react-map-adapter-google 0.0.151 → 0.0.154

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/index.cjs.js CHANGED
@@ -5,6 +5,7 @@ var jsxRuntime = require('react/jsx-runtime');
5
5
  var reactGoogleMaps = require('@vis.gl/react-google-maps');
6
6
  var react = require('react');
7
7
  var geoJsonUtils = require('@trackunit/geo-json-utils');
8
+ var reactMapDraw = require('@trackunit/react-map-draw');
8
9
  var markerclusterer = require('@googlemaps/markerclusterer');
9
10
  var reactMapColorUtils = require('@trackunit/react-map-color-utils');
10
11
  var esToolkit = require('es-toolkit');
@@ -55,6 +56,29 @@ const GoogleApiProvider = ({ apiKey, language, children }) => {
55
56
  return (jsxRuntime.jsx(reactGoogleMaps.APIProvider, { apiKey: apiKey, language: language, libraries: libraries, version: GOOGLE_MAPS_API_VERSION, children: children }));
56
57
  };
57
58
 
59
+ /**
60
+ * Builds the Google Maps bridge for the shared drawing engine (ADR 0030).
61
+ *
62
+ * One loader per `google.maps.Map` instance: theme switches and traffic toggles
63
+ * recreate the map, and `GoogleMapsAdapterInstance.connect` hands the controller a
64
+ * fresh loader each time so the engine is rebuilt on the live map. Terra Draw and its
65
+ * Google adapter are only downloaded when the loader runs, which the controller
66
+ * defers until drawing is actually needed.
67
+ */
68
+ const createGoogleDrawEngineLoader = (map) => async () => {
69
+ const { TerraDrawGoogleMapsAdapter } = await import('terra-draw-google-maps-adapter');
70
+ return reactMapDraw.loadTerraDrawEngine({
71
+ adapter: new TerraDrawGoogleMapsAdapter({
72
+ lib: google.maps,
73
+ map,
74
+ // Keep drawn features in their own Data layer so nothing else on the map
75
+ // (or a future layer port feature) shares styling or click handling with them.
76
+ isolatedData: true,
77
+ coordinatePrecision: reactMapDraw.DRAW_COORDINATE_PRECISION,
78
+ }),
79
+ });
80
+ };
81
+
58
82
  let runtimePromise = null;
59
83
  /**
60
84
  * Lazily dynamic-imports {@link createDeckGlGoogleMapsOverlayRuntime} and caches the promise.
@@ -2900,6 +2924,7 @@ class GoogleMapsAdapterInstance {
2900
2924
  initializationFailed: reactMapAdapterShared.INITIAL_MAP_STATE.initializationFailed,
2901
2925
  appearance: reactMapAdapterShared.INITIAL_MAP_STATE.appearance,
2902
2926
  tileSize: reactMapAdapterShared.INITIAL_MAP_STATE.tileSize,
2927
+ drawing: reactMapAdapterShared.INITIAL_MAP_STATE.drawing,
2903
2928
  };
2904
2929
  this.cameraListeners = new Set();
2905
2930
  this.statusListeners = new Set();
@@ -2933,6 +2958,11 @@ class GoogleMapsAdapterInstance {
2933
2958
  };
2934
2959
  this.layers = new GoogleMapsLayerPort();
2935
2960
  this.layers.setTheme(this.appearance.theme);
2961
+ // Engine load failures are not surfaced here (same as the deck.gl runtime): the
2962
+ // controller keeps mode and value and loads again on the next connect, setMode
2963
+ // or setValue that needs an engine.
2964
+ this.draw = new reactMapDraw.DrawController();
2965
+ this.draw.subscribe(() => this.syncDrawingStatus());
2936
2966
  this.state = reactMapAdapterShared.computeInitialState(this.appearance, config.initialViewport);
2937
2967
  this.cachedCameraState = {
2938
2968
  center: this.state.center,
@@ -2945,6 +2975,7 @@ class GoogleMapsAdapterInstance {
2945
2975
  initializationFailed: this.state.initializationFailed,
2946
2976
  appearance: this.state.appearance,
2947
2977
  tileSize: this.state.tileSize,
2978
+ drawing: this.state.drawing,
2948
2979
  };
2949
2980
  }
2950
2981
  // ============================================================================
@@ -2981,11 +3012,13 @@ class GoogleMapsAdapterInstance {
2981
3012
  // `state` to defaults while `this.appearance` keeps the real values, so a
2982
3013
  // StrictMode destroy → revive would otherwise report stale defaults, and the
2983
3014
  // appearance controls' sync-back would overwrite persisted preferences with them.
2984
- this.state = { ...this.state, appearance: this.appearance };
3015
+ this.state = { ...this.state, appearance: this.appearance, drawing: this.draw.getMode() !== "idle" };
2985
3016
  this.updateStatusCache();
2986
3017
  this.layers.connect(map);
2987
- // Theme changes recreate the google.maps.Map, so traffic must re-attach on every (re)connect
3018
+ // Theme changes recreate the google.maps.Map, so traffic and drawing must re-attach on every (re)connect.
3019
+ // The draw controller rebuilds its engine from the committed value on the new map (ADR 0030).
2988
3020
  this.syncTrafficLayer();
3021
+ this.draw.connect(createGoogleDrawEngineLoader(map));
2989
3022
  this.setupEventListeners();
2990
3023
  this.layers.setPointerMoveReporter(position => {
2991
3024
  this.reportPointerMove(position);
@@ -3216,6 +3249,7 @@ class GoogleMapsAdapterInstance {
3216
3249
  this.removeGoogleEventListeners();
3217
3250
  this.trafficLayer?.setMap(null);
3218
3251
  this.trafficLayer = null;
3252
+ this.draw.disconnect();
3219
3253
  this.layers.setPointerMoveReporter(null);
3220
3254
  this.layers.destroy();
3221
3255
  // Clear our listeners
@@ -3373,8 +3407,17 @@ class GoogleMapsAdapterInstance {
3373
3407
  this.cachedCameraState = { center, zoom, bounds, isIdle };
3374
3408
  }
3375
3409
  updateStatusCache() {
3376
- const { isReady, initializationFailed, appearance, tileSize } = this.state;
3377
- this.cachedStatus = { isReady, initializationFailed, appearance, tileSize };
3410
+ const { isReady, initializationFailed, appearance, tileSize, drawing } = this.state;
3411
+ this.cachedStatus = { isReady, initializationFailed, appearance, tileSize, drawing };
3412
+ }
3413
+ /** Mirrors the draw controller's mode into `MapStatus.drawing` (ADR 0030). */
3414
+ syncDrawingStatus() {
3415
+ const drawing = this.draw.getMode() !== "idle";
3416
+ if (this.state.drawing === drawing)
3417
+ return;
3418
+ this.state = { ...this.state, drawing };
3419
+ this.updateStatusCache();
3420
+ this.notifyStatusListeners();
3378
3421
  }
3379
3422
  notifyCameraListeners() {
3380
3423
  this.cameraListeners.forEach(listener => listener());
package/index.esm.js CHANGED
@@ -4,6 +4,7 @@ import { jsx } from 'react/jsx-runtime';
4
4
  import { APIProvider, RenderingType, APIProviderContext, useMap, useApiLoadingStatus, APILoadingStatus, Map as Map$1 } from '@vis.gl/react-google-maps';
5
5
  import { useMemo, useCallback, useSyncExternalStore, useId, useContext, useEffect } from 'react';
6
6
  import { validateBbox, validatePosition } from '@trackunit/geo-json-utils';
7
+ import { loadTerraDrawEngine, DRAW_COORDINATE_PRECISION, DrawController } from '@trackunit/react-map-draw';
7
8
  import { MarkerClusterer, SuperClusterAlgorithm } from '@googlemaps/markerclusterer';
8
9
  import { colorWithOpacity } from '@trackunit/react-map-color-utils';
9
10
  import { isEqual } from 'es-toolkit';
@@ -54,6 +55,29 @@ const GoogleApiProvider = ({ apiKey, language, children }) => {
54
55
  return (jsx(APIProvider, { apiKey: apiKey, language: language, libraries: libraries, version: GOOGLE_MAPS_API_VERSION, children: children }));
55
56
  };
56
57
 
58
+ /**
59
+ * Builds the Google Maps bridge for the shared drawing engine (ADR 0030).
60
+ *
61
+ * One loader per `google.maps.Map` instance: theme switches and traffic toggles
62
+ * recreate the map, and `GoogleMapsAdapterInstance.connect` hands the controller a
63
+ * fresh loader each time so the engine is rebuilt on the live map. Terra Draw and its
64
+ * Google adapter are only downloaded when the loader runs, which the controller
65
+ * defers until drawing is actually needed.
66
+ */
67
+ const createGoogleDrawEngineLoader = (map) => async () => {
68
+ const { TerraDrawGoogleMapsAdapter } = await import('terra-draw-google-maps-adapter');
69
+ return loadTerraDrawEngine({
70
+ adapter: new TerraDrawGoogleMapsAdapter({
71
+ lib: google.maps,
72
+ map,
73
+ // Keep drawn features in their own Data layer so nothing else on the map
74
+ // (or a future layer port feature) shares styling or click handling with them.
75
+ isolatedData: true,
76
+ coordinatePrecision: DRAW_COORDINATE_PRECISION,
77
+ }),
78
+ });
79
+ };
80
+
57
81
  let runtimePromise = null;
58
82
  /**
59
83
  * Lazily dynamic-imports {@link createDeckGlGoogleMapsOverlayRuntime} and caches the promise.
@@ -2899,6 +2923,7 @@ class GoogleMapsAdapterInstance {
2899
2923
  initializationFailed: INITIAL_MAP_STATE.initializationFailed,
2900
2924
  appearance: INITIAL_MAP_STATE.appearance,
2901
2925
  tileSize: INITIAL_MAP_STATE.tileSize,
2926
+ drawing: INITIAL_MAP_STATE.drawing,
2902
2927
  };
2903
2928
  this.cameraListeners = new Set();
2904
2929
  this.statusListeners = new Set();
@@ -2932,6 +2957,11 @@ class GoogleMapsAdapterInstance {
2932
2957
  };
2933
2958
  this.layers = new GoogleMapsLayerPort();
2934
2959
  this.layers.setTheme(this.appearance.theme);
2960
+ // Engine load failures are not surfaced here (same as the deck.gl runtime): the
2961
+ // controller keeps mode and value and loads again on the next connect, setMode
2962
+ // or setValue that needs an engine.
2963
+ this.draw = new DrawController();
2964
+ this.draw.subscribe(() => this.syncDrawingStatus());
2935
2965
  this.state = computeInitialState(this.appearance, config.initialViewport);
2936
2966
  this.cachedCameraState = {
2937
2967
  center: this.state.center,
@@ -2944,6 +2974,7 @@ class GoogleMapsAdapterInstance {
2944
2974
  initializationFailed: this.state.initializationFailed,
2945
2975
  appearance: this.state.appearance,
2946
2976
  tileSize: this.state.tileSize,
2977
+ drawing: this.state.drawing,
2947
2978
  };
2948
2979
  }
2949
2980
  // ============================================================================
@@ -2980,11 +3011,13 @@ class GoogleMapsAdapterInstance {
2980
3011
  // `state` to defaults while `this.appearance` keeps the real values, so a
2981
3012
  // StrictMode destroy → revive would otherwise report stale defaults, and the
2982
3013
  // appearance controls' sync-back would overwrite persisted preferences with them.
2983
- this.state = { ...this.state, appearance: this.appearance };
3014
+ this.state = { ...this.state, appearance: this.appearance, drawing: this.draw.getMode() !== "idle" };
2984
3015
  this.updateStatusCache();
2985
3016
  this.layers.connect(map);
2986
- // Theme changes recreate the google.maps.Map, so traffic must re-attach on every (re)connect
3017
+ // Theme changes recreate the google.maps.Map, so traffic and drawing must re-attach on every (re)connect.
3018
+ // The draw controller rebuilds its engine from the committed value on the new map (ADR 0030).
2987
3019
  this.syncTrafficLayer();
3020
+ this.draw.connect(createGoogleDrawEngineLoader(map));
2988
3021
  this.setupEventListeners();
2989
3022
  this.layers.setPointerMoveReporter(position => {
2990
3023
  this.reportPointerMove(position);
@@ -3215,6 +3248,7 @@ class GoogleMapsAdapterInstance {
3215
3248
  this.removeGoogleEventListeners();
3216
3249
  this.trafficLayer?.setMap(null);
3217
3250
  this.trafficLayer = null;
3251
+ this.draw.disconnect();
3218
3252
  this.layers.setPointerMoveReporter(null);
3219
3253
  this.layers.destroy();
3220
3254
  // Clear our listeners
@@ -3372,8 +3406,17 @@ class GoogleMapsAdapterInstance {
3372
3406
  this.cachedCameraState = { center, zoom, bounds, isIdle };
3373
3407
  }
3374
3408
  updateStatusCache() {
3375
- const { isReady, initializationFailed, appearance, tileSize } = this.state;
3376
- this.cachedStatus = { isReady, initializationFailed, appearance, tileSize };
3409
+ const { isReady, initializationFailed, appearance, tileSize, drawing } = this.state;
3410
+ this.cachedStatus = { isReady, initializationFailed, appearance, tileSize, drawing };
3411
+ }
3412
+ /** Mirrors the draw controller's mode into `MapStatus.drawing` (ADR 0030). */
3413
+ syncDrawingStatus() {
3414
+ const drawing = this.draw.getMode() !== "idle";
3415
+ if (this.state.drawing === drawing)
3416
+ return;
3417
+ this.state = { ...this.state, drawing };
3418
+ this.updateStatusCache();
3419
+ this.notifyStatusListeners();
3377
3420
  }
3378
3421
  notifyCameraListeners() {
3379
3422
  this.cameraListeners.forEach(listener => listener());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-map-adapter-google",
3
- "version": "0.0.151",
3
+ "version": "0.0.154",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -14,14 +14,16 @@
14
14
  "@loaders.gl/core": "~4.5.2",
15
15
  "@luma.gl/core": "^9.4.1",
16
16
  "@luma.gl/engine": "^9.4.1",
17
- "@trackunit/react-map-adapter-shared": "0.0.161",
18
- "@trackunit/react-map-color-utils": "0.0.135",
19
- "@trackunit/geo-json-utils": "1.18.9",
17
+ "@trackunit/react-map-adapter-shared": "0.0.164",
18
+ "@trackunit/react-map-color-utils": "0.0.138",
19
+ "@trackunit/geo-json-utils": "1.19.0",
20
20
  "@googlemaps/markerclusterer": "^2.6.2",
21
21
  "@vis.gl/react-google-maps": "^1.7.1",
22
22
  "es-toolkit": "^1.52.0",
23
23
  "react-device-detect": "^2.2.3",
24
- "zod": "^3.25.76"
24
+ "zod": "^3.25.76",
25
+ "@trackunit/react-map-draw": "0.0.2",
26
+ "terra-draw-google-maps-adapter": "^1.6.2"
25
27
  },
26
28
  "peerDependencies": {
27
29
  "react": "^19.0.0",
@@ -1,4 +1,5 @@
1
1
  import { type AdapterInstance, type BaseAdapterConfig, type CameraState, type FitBoundsOptions, type GeoJsonBbox, type GeoJsonPosition, type MapEvent, type MapEventHandler, type MapStatus, type MapTheme, type MapType } from "@trackunit/react-map-adapter-shared";
2
+ import { DrawController } from "@trackunit/react-map-draw";
2
3
  import { GoogleMapsLayerPort } from "./GoogleMapsLayerPort";
3
4
  /**
4
5
  * Configuration for Google Maps adapter
@@ -21,6 +22,11 @@ export declare class GoogleMapsAdapterInstance implements AdapterInstance {
21
22
  private readonly config;
22
23
  /** Layer rendering capabilities, provided via MapLayerContext to <Layers>. */
23
24
  readonly layers: GoogleMapsLayerPort;
25
+ /**
26
+ * Drawing capability (ADR 0030). The controller keeps mode and value across map
27
+ * recreations; `connect()` rebinds it to every new `google.maps.Map`.
28
+ */
29
+ readonly draw: DrawController;
24
30
  private map;
25
31
  private isDestroyedFlag;
26
32
  private state;
@@ -104,6 +110,8 @@ export declare class GoogleMapsAdapterInstance implements AdapterInstance {
104
110
  private mapTypeToGoogleMapTypeId;
105
111
  private updateCameraCache;
106
112
  private updateStatusCache;
113
+ /** Mirrors the draw controller's mode into `MapStatus.drawing` (ADR 0030). */
114
+ private syncDrawingStatus;
107
115
  private notifyCameraListeners;
108
116
  private notifyStatusListeners;
109
117
  private emitEvent;
@@ -0,0 +1,11 @@
1
+ import { type DrawEngineLoader } from "@trackunit/react-map-draw";
2
+ /**
3
+ * Builds the Google Maps bridge for the shared drawing engine (ADR 0030).
4
+ *
5
+ * One loader per `google.maps.Map` instance: theme switches and traffic toggles
6
+ * recreate the map, and `GoogleMapsAdapterInstance.connect` hands the controller a
7
+ * fresh loader each time so the engine is rebuilt on the live map. Terra Draw and its
8
+ * Google adapter are only downloaded when the loader runs, which the controller
9
+ * defers until drawing is actually needed.
10
+ */
11
+ export declare const createGoogleDrawEngineLoader: (map: google.maps.Map) => DrawEngineLoader;