@trackunit/react-map-adapter-google 0.0.137 → 0.0.140-alpha-b0d834f66ac.0

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
@@ -1849,6 +1849,44 @@ class GoogleMapsLayerPort {
1849
1849
  const canvas = this.canvas;
1850
1850
  if (canvas === null)
1851
1851
  return;
1852
+ // No markers to draw (everything clustered or DOM-promoted, or the data
1853
+ // layer emptied the list in place): render an empty canvas and stop,
1854
+ // regardless of how the viewport would size. Handling this up front
1855
+ // keeps both sizing paths free of empty-list special cases, and stops
1856
+ // the fallback below from arming a retry that can never succeed
1857
+ // (observed as no-op retry churn at world view when everything is
1858
+ // clustered).
1859
+ if (canvasMarkers.length === 0) {
1860
+ const clearCtx = canvas.getContext("2d");
1861
+ if (clearCtx !== null) {
1862
+ // The canvas is not resized on this path, so a previous draw's
1863
+ // scale transform may still be active — reset it or the clear may
1864
+ // not cover the whole backing store.
1865
+ clearCtx.setTransform(1, 0, 0, 1, 0, 0);
1866
+ clearCtx.clearRect(0, 0, canvas.width, canvas.height);
1867
+ }
1868
+ drawnMarkers = [];
1869
+ // The dots are gone, so any active marker hover must end too — no
1870
+ // mousemove will fire over an empty canvas to end it later. Deferred
1871
+ // because draw() runs inside Google Maps' render pass: a consumer
1872
+ // reacting to hover-end may synchronously push new layer state,
1873
+ // re-entering draw().
1874
+ if (lastHoveredFeatureId !== null) {
1875
+ const hoverEndId = lastHoveredFeatureId;
1876
+ lastHoveredFeatureId = null;
1877
+ queueMicrotask(() => {
1878
+ layerPort.emitEntityInteraction("hover-end", {
1879
+ type: "marker",
1880
+ id: hoverEndId,
1881
+ position: [0, 0],
1882
+ clusterId: null,
1883
+ });
1884
+ });
1885
+ }
1886
+ canvas.style.cursor = reactMapAdapterShared.MAP_CURSORS.default;
1887
+ this.boundsDeferScheduled = false;
1888
+ return;
1889
+ }
1852
1890
  // Read viewport state from the map this overlay is CURRENTLY attached to.
1853
1891
  // reconnectAllSources() re-parents the overlay onto recreated map instances
1854
1892
  // (theme or renderingType changes); the closed-over `map` parameter keeps
@@ -1868,6 +1906,11 @@ class GoogleMapsLayerPort {
1868
1906
  if (bounds) {
1869
1907
  const ne = projection.fromLatLngToDivPixel(bounds.getNorthEast());
1870
1908
  const sw = projection.fromLatLngToDivPixel(bounds.getSouthWest());
1909
+ // Runtime verification (SAGA-879, PR #25976): fromLatLngToDivPixel
1910
+ // projects to the world copy nearest the map center, so 180°-crossing
1911
+ // viewports keep a positive width and take this path. Only a
1912
+ // full-world viewport (bounds lng -180..180, raw width ~0) fails the
1913
+ // guard, deliberately falling through to the marker-bbox fallback.
1871
1914
  if (ne !== null && sw !== null && ne.x - sw.x > 1 && sw.y - ne.y > 1) {
1872
1915
  width = ne.x - sw.x + pad * 2;
1873
1916
  height = sw.y - ne.y + pad * 2;
@@ -1899,6 +1942,9 @@ class GoogleMapsLayerPort {
1899
1942
  maxY = pt.y;
1900
1943
  }
1901
1944
  if (!isFinite(minX)) {
1945
+ // Markers exist but are not projectable yet (the empty list is
1946
+ // handled at the top of draw(), so this is always the transient
1947
+ // case) — retry once the frame after the projection settles.
1902
1948
  if (!this.boundsDeferScheduled) {
1903
1949
  this.boundsDeferScheduled = true;
1904
1950
  requestAnimationFrame(() => {
package/index.esm.js CHANGED
@@ -1848,6 +1848,44 @@ class GoogleMapsLayerPort {
1848
1848
  const canvas = this.canvas;
1849
1849
  if (canvas === null)
1850
1850
  return;
1851
+ // No markers to draw (everything clustered or DOM-promoted, or the data
1852
+ // layer emptied the list in place): render an empty canvas and stop,
1853
+ // regardless of how the viewport would size. Handling this up front
1854
+ // keeps both sizing paths free of empty-list special cases, and stops
1855
+ // the fallback below from arming a retry that can never succeed
1856
+ // (observed as no-op retry churn at world view when everything is
1857
+ // clustered).
1858
+ if (canvasMarkers.length === 0) {
1859
+ const clearCtx = canvas.getContext("2d");
1860
+ if (clearCtx !== null) {
1861
+ // The canvas is not resized on this path, so a previous draw's
1862
+ // scale transform may still be active — reset it or the clear may
1863
+ // not cover the whole backing store.
1864
+ clearCtx.setTransform(1, 0, 0, 1, 0, 0);
1865
+ clearCtx.clearRect(0, 0, canvas.width, canvas.height);
1866
+ }
1867
+ drawnMarkers = [];
1868
+ // The dots are gone, so any active marker hover must end too — no
1869
+ // mousemove will fire over an empty canvas to end it later. Deferred
1870
+ // because draw() runs inside Google Maps' render pass: a consumer
1871
+ // reacting to hover-end may synchronously push new layer state,
1872
+ // re-entering draw().
1873
+ if (lastHoveredFeatureId !== null) {
1874
+ const hoverEndId = lastHoveredFeatureId;
1875
+ lastHoveredFeatureId = null;
1876
+ queueMicrotask(() => {
1877
+ layerPort.emitEntityInteraction("hover-end", {
1878
+ type: "marker",
1879
+ id: hoverEndId,
1880
+ position: [0, 0],
1881
+ clusterId: null,
1882
+ });
1883
+ });
1884
+ }
1885
+ canvas.style.cursor = MAP_CURSORS.default;
1886
+ this.boundsDeferScheduled = false;
1887
+ return;
1888
+ }
1851
1889
  // Read viewport state from the map this overlay is CURRENTLY attached to.
1852
1890
  // reconnectAllSources() re-parents the overlay onto recreated map instances
1853
1891
  // (theme or renderingType changes); the closed-over `map` parameter keeps
@@ -1867,6 +1905,11 @@ class GoogleMapsLayerPort {
1867
1905
  if (bounds) {
1868
1906
  const ne = projection.fromLatLngToDivPixel(bounds.getNorthEast());
1869
1907
  const sw = projection.fromLatLngToDivPixel(bounds.getSouthWest());
1908
+ // Runtime verification (SAGA-879, PR #25976): fromLatLngToDivPixel
1909
+ // projects to the world copy nearest the map center, so 180°-crossing
1910
+ // viewports keep a positive width and take this path. Only a
1911
+ // full-world viewport (bounds lng -180..180, raw width ~0) fails the
1912
+ // guard, deliberately falling through to the marker-bbox fallback.
1870
1913
  if (ne !== null && sw !== null && ne.x - sw.x > 1 && sw.y - ne.y > 1) {
1871
1914
  width = ne.x - sw.x + pad * 2;
1872
1915
  height = sw.y - ne.y + pad * 2;
@@ -1898,6 +1941,9 @@ class GoogleMapsLayerPort {
1898
1941
  maxY = pt.y;
1899
1942
  }
1900
1943
  if (!isFinite(minX)) {
1944
+ // Markers exist but are not projectable yet (the empty list is
1945
+ // handled at the top of draw(), so this is always the transient
1946
+ // case) — retry once the frame after the projection settles.
1901
1947
  if (!this.boundsDeferScheduled) {
1902
1948
  this.boundsDeferScheduled = true;
1903
1949
  requestAnimationFrame(() => {
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/react/map-adapter-google/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["export {};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-map-adapter-google",
3
- "version": "0.0.137",
3
+ "version": "0.0.140-alpha-b0d834f66ac.0",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -14,12 +14,12 @@
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.148",
18
- "@trackunit/react-map-color-utils": "0.0.123",
19
- "@trackunit/geo-json-utils": "1.17.1",
17
+ "@trackunit/react-map-adapter-shared": "0.0.150-alpha-b0d834f66ac.0",
18
+ "@trackunit/react-map-color-utils": "0.0.125-alpha-b0d834f66ac.0",
19
+ "@trackunit/geo-json-utils": "1.17.3-alpha-b0d834f66ac.0",
20
20
  "@googlemaps/markerclusterer": "^2.6.2",
21
21
  "@vis.gl/react-google-maps": "^1.7.1",
22
- "es-toolkit": "^1.39.10",
22
+ "es-toolkit": "^1.52.0",
23
23
  "react-device-detect": "^2.2.3",
24
24
  "zod": "^3.25.76"
25
25
  },