@trackunit/react-map-adapter-google 0.0.11 → 0.0.13
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 +58 -0
- package/index.esm.js +58 -0
- package/package.json +4 -4
- package/src/GoogleMapsLayerPort.d.ts +10 -0
package/index.cjs.js
CHANGED
|
@@ -180,6 +180,16 @@ class GoogleMapsLayerPort {
|
|
|
180
180
|
this.interactionHandlers = new Set();
|
|
181
181
|
this.backgroundClickHandlers = new Set();
|
|
182
182
|
this.entityClickedInTick = false;
|
|
183
|
+
/**
|
|
184
|
+
* True for one tick after a point (marker/cluster) click is emitted. Symbol
|
|
185
|
+
* markers render on a full-viewport canvas overlay that is not a Google-native
|
|
186
|
+
* marker, so — unlike `AdvancedMarkerElement` DOM markers, whose `gmp-click`
|
|
187
|
+
* makes Google swallow the underlying map click — a single click on a symbol
|
|
188
|
+
* marker overlapping a polygon fires BOTH the canvas marker click and the
|
|
189
|
+
* polygon's geometric click. This flag lets the trailing shape click bail so
|
|
190
|
+
* the marker the user visibly clicked wins.
|
|
191
|
+
*/
|
|
192
|
+
this.pointEntityClickedInTick = false;
|
|
183
193
|
this.mapClickListener = null;
|
|
184
194
|
// Tracked sources, keyed by source ID
|
|
185
195
|
this.markerSources = new Map();
|
|
@@ -1969,6 +1979,9 @@ class GoogleMapsLayerPort {
|
|
|
1969
1979
|
return new google.maps.Polygon({
|
|
1970
1980
|
paths,
|
|
1971
1981
|
map,
|
|
1982
|
+
// Great-circle edges natively (no densification needed); honours the shape
|
|
1983
|
+
// style flag, defaulting to true per the ShapeStyle.geodesic contract.
|
|
1984
|
+
geodesic: style.geodesic ?? true,
|
|
1972
1985
|
fillColor: style.fill ?? "rgba(0,0,0,0.1)",
|
|
1973
1986
|
fillOpacity: style.fillOpacity ?? this.shapeStyleDefaults.polygon.fillOpacity,
|
|
1974
1987
|
strokeColor: style.stroke ?? "#000000",
|
|
@@ -1982,6 +1995,9 @@ class GoogleMapsLayerPort {
|
|
|
1982
1995
|
return new google.maps.Polyline({
|
|
1983
1996
|
path,
|
|
1984
1997
|
map,
|
|
1998
|
+
// Match the polygon: native great-circle stroke so a tiled outline curves
|
|
1999
|
+
// the same way as its densified fill instead of drawing a straight chord.
|
|
2000
|
+
geodesic: style.geodesic ?? true,
|
|
1985
2001
|
strokeColor: style.stroke ?? "#000000",
|
|
1986
2002
|
strokeWeight: style.strokeWidth ?? this.shapeStyleDefaults.line.strokeWidth,
|
|
1987
2003
|
strokeOpacity: style.strokeOpacity ?? this.shapeStyleDefaults.line.strokeOpacity,
|
|
@@ -1998,6 +2014,11 @@ class GoogleMapsLayerPort {
|
|
|
1998
2014
|
return new google.maps.Polygon({
|
|
1999
2015
|
paths,
|
|
2000
2016
|
map,
|
|
2017
|
+
// Intentionally NOT geodesic: this is clipped tiling geometry. Its original
|
|
2018
|
+
// edges are already densified to great-circle arcs by the fill-tiling pass,
|
|
2019
|
+
// while the clip cuts must stay as straight Web-Mercator segments so they
|
|
2020
|
+
// land exactly on the covering shape's rendered edge. geodesic:true would
|
|
2021
|
+
// re-curve those straight cuts and break the tiling seam.
|
|
2001
2022
|
fillColor: style.fill ?? "rgba(0,0,0,0.1)",
|
|
2002
2023
|
fillOpacity: style.fillOpacity ?? this.shapeStyleDefaults.polygon.fillOpacity,
|
|
2003
2024
|
strokeWeight: 0,
|
|
@@ -2364,10 +2385,47 @@ class GoogleMapsLayerPort {
|
|
|
2364
2385
|
/** Emit entity interaction event to all subscribed handlers */
|
|
2365
2386
|
emitEntityInteraction(type, entity) {
|
|
2366
2387
|
if (type === "click") {
|
|
2388
|
+
// On Google Maps vector maps, a canvas OverlayView (used for symbol
|
|
2389
|
+
// markers and clusters) emits its click event before the tile renderer
|
|
2390
|
+
// dispatches geometric polygon events for the same user gesture. This is
|
|
2391
|
+
// because canvas draw calls and their DOM-level pointer events are
|
|
2392
|
+
// processed in the same microtask / paint frame, which completes before
|
|
2393
|
+
// the vector-tile renderer's async polygon hit-test resolves. In
|
|
2394
|
+
// practice this ordering has been consistent across all tested Google
|
|
2395
|
+
// Maps JS API versions; however it is not contractually guaranteed by
|
|
2396
|
+
// Google's public documentation.
|
|
2397
|
+
//
|
|
2398
|
+
// We exploit this ordering to let the point-entity click (marker /
|
|
2399
|
+
// cluster) win when it spatially overlaps a shape: the marker sets
|
|
2400
|
+
// `pointEntityClickedInTick` first, and the shape click that follows in
|
|
2401
|
+
// the same tick is suppressed here.
|
|
2402
|
+
//
|
|
2403
|
+
// Fallback risk: if Google ever reverses the order, the shape click
|
|
2404
|
+
// would arrive first and be emitted normally, then the marker click
|
|
2405
|
+
// would also arrive and be emitted — resulting in two consecutive
|
|
2406
|
+
// selection events for the same gesture. The user experience degrades to
|
|
2407
|
+
// "shape is briefly selected, then replaced by the marker", which is
|
|
2408
|
+
// recoverable with a second click. No data loss or error occurs.
|
|
2409
|
+
//
|
|
2410
|
+
// A position-based deduplication approach (comparing click coordinates
|
|
2411
|
+
// within a pixel tolerance) would be more robust against ordering
|
|
2412
|
+
// changes, but adds meaningful complexity: we would need to store the
|
|
2413
|
+
// last click coordinate, handle clustered events whose centres may not
|
|
2414
|
+
// match pixel-exactly, and choose a tolerance that works across zoom
|
|
2415
|
+
// levels. The ordering-based guard is simpler and has been reliable
|
|
2416
|
+
// in production.
|
|
2417
|
+
if (entity.type === "shape" && this.pointEntityClickedInTick)
|
|
2418
|
+
return;
|
|
2367
2419
|
this.entityClickedInTick = true;
|
|
2368
2420
|
setTimeout(() => {
|
|
2369
2421
|
this.entityClickedInTick = false;
|
|
2370
2422
|
}, 0);
|
|
2423
|
+
if (entity.type === "marker" || entity.type === "cluster") {
|
|
2424
|
+
this.pointEntityClickedInTick = true;
|
|
2425
|
+
setTimeout(() => {
|
|
2426
|
+
this.pointEntityClickedInTick = false;
|
|
2427
|
+
}, 0);
|
|
2428
|
+
}
|
|
2371
2429
|
}
|
|
2372
2430
|
const event = { type, entity };
|
|
2373
2431
|
this.interactionHandlers.forEach(handler => handler(event));
|
package/index.esm.js
CHANGED
|
@@ -178,6 +178,16 @@ class GoogleMapsLayerPort {
|
|
|
178
178
|
this.interactionHandlers = new Set();
|
|
179
179
|
this.backgroundClickHandlers = new Set();
|
|
180
180
|
this.entityClickedInTick = false;
|
|
181
|
+
/**
|
|
182
|
+
* True for one tick after a point (marker/cluster) click is emitted. Symbol
|
|
183
|
+
* markers render on a full-viewport canvas overlay that is not a Google-native
|
|
184
|
+
* marker, so — unlike `AdvancedMarkerElement` DOM markers, whose `gmp-click`
|
|
185
|
+
* makes Google swallow the underlying map click — a single click on a symbol
|
|
186
|
+
* marker overlapping a polygon fires BOTH the canvas marker click and the
|
|
187
|
+
* polygon's geometric click. This flag lets the trailing shape click bail so
|
|
188
|
+
* the marker the user visibly clicked wins.
|
|
189
|
+
*/
|
|
190
|
+
this.pointEntityClickedInTick = false;
|
|
181
191
|
this.mapClickListener = null;
|
|
182
192
|
// Tracked sources, keyed by source ID
|
|
183
193
|
this.markerSources = new Map();
|
|
@@ -1967,6 +1977,9 @@ class GoogleMapsLayerPort {
|
|
|
1967
1977
|
return new google.maps.Polygon({
|
|
1968
1978
|
paths,
|
|
1969
1979
|
map,
|
|
1980
|
+
// Great-circle edges natively (no densification needed); honours the shape
|
|
1981
|
+
// style flag, defaulting to true per the ShapeStyle.geodesic contract.
|
|
1982
|
+
geodesic: style.geodesic ?? true,
|
|
1970
1983
|
fillColor: style.fill ?? "rgba(0,0,0,0.1)",
|
|
1971
1984
|
fillOpacity: style.fillOpacity ?? this.shapeStyleDefaults.polygon.fillOpacity,
|
|
1972
1985
|
strokeColor: style.stroke ?? "#000000",
|
|
@@ -1980,6 +1993,9 @@ class GoogleMapsLayerPort {
|
|
|
1980
1993
|
return new google.maps.Polyline({
|
|
1981
1994
|
path,
|
|
1982
1995
|
map,
|
|
1996
|
+
// Match the polygon: native great-circle stroke so a tiled outline curves
|
|
1997
|
+
// the same way as its densified fill instead of drawing a straight chord.
|
|
1998
|
+
geodesic: style.geodesic ?? true,
|
|
1983
1999
|
strokeColor: style.stroke ?? "#000000",
|
|
1984
2000
|
strokeWeight: style.strokeWidth ?? this.shapeStyleDefaults.line.strokeWidth,
|
|
1985
2001
|
strokeOpacity: style.strokeOpacity ?? this.shapeStyleDefaults.line.strokeOpacity,
|
|
@@ -1996,6 +2012,11 @@ class GoogleMapsLayerPort {
|
|
|
1996
2012
|
return new google.maps.Polygon({
|
|
1997
2013
|
paths,
|
|
1998
2014
|
map,
|
|
2015
|
+
// Intentionally NOT geodesic: this is clipped tiling geometry. Its original
|
|
2016
|
+
// edges are already densified to great-circle arcs by the fill-tiling pass,
|
|
2017
|
+
// while the clip cuts must stay as straight Web-Mercator segments so they
|
|
2018
|
+
// land exactly on the covering shape's rendered edge. geodesic:true would
|
|
2019
|
+
// re-curve those straight cuts and break the tiling seam.
|
|
1999
2020
|
fillColor: style.fill ?? "rgba(0,0,0,0.1)",
|
|
2000
2021
|
fillOpacity: style.fillOpacity ?? this.shapeStyleDefaults.polygon.fillOpacity,
|
|
2001
2022
|
strokeWeight: 0,
|
|
@@ -2362,10 +2383,47 @@ class GoogleMapsLayerPort {
|
|
|
2362
2383
|
/** Emit entity interaction event to all subscribed handlers */
|
|
2363
2384
|
emitEntityInteraction(type, entity) {
|
|
2364
2385
|
if (type === "click") {
|
|
2386
|
+
// On Google Maps vector maps, a canvas OverlayView (used for symbol
|
|
2387
|
+
// markers and clusters) emits its click event before the tile renderer
|
|
2388
|
+
// dispatches geometric polygon events for the same user gesture. This is
|
|
2389
|
+
// because canvas draw calls and their DOM-level pointer events are
|
|
2390
|
+
// processed in the same microtask / paint frame, which completes before
|
|
2391
|
+
// the vector-tile renderer's async polygon hit-test resolves. In
|
|
2392
|
+
// practice this ordering has been consistent across all tested Google
|
|
2393
|
+
// Maps JS API versions; however it is not contractually guaranteed by
|
|
2394
|
+
// Google's public documentation.
|
|
2395
|
+
//
|
|
2396
|
+
// We exploit this ordering to let the point-entity click (marker /
|
|
2397
|
+
// cluster) win when it spatially overlaps a shape: the marker sets
|
|
2398
|
+
// `pointEntityClickedInTick` first, and the shape click that follows in
|
|
2399
|
+
// the same tick is suppressed here.
|
|
2400
|
+
//
|
|
2401
|
+
// Fallback risk: if Google ever reverses the order, the shape click
|
|
2402
|
+
// would arrive first and be emitted normally, then the marker click
|
|
2403
|
+
// would also arrive and be emitted — resulting in two consecutive
|
|
2404
|
+
// selection events for the same gesture. The user experience degrades to
|
|
2405
|
+
// "shape is briefly selected, then replaced by the marker", which is
|
|
2406
|
+
// recoverable with a second click. No data loss or error occurs.
|
|
2407
|
+
//
|
|
2408
|
+
// A position-based deduplication approach (comparing click coordinates
|
|
2409
|
+
// within a pixel tolerance) would be more robust against ordering
|
|
2410
|
+
// changes, but adds meaningful complexity: we would need to store the
|
|
2411
|
+
// last click coordinate, handle clustered events whose centres may not
|
|
2412
|
+
// match pixel-exactly, and choose a tolerance that works across zoom
|
|
2413
|
+
// levels. The ordering-based guard is simpler and has been reliable
|
|
2414
|
+
// in production.
|
|
2415
|
+
if (entity.type === "shape" && this.pointEntityClickedInTick)
|
|
2416
|
+
return;
|
|
2365
2417
|
this.entityClickedInTick = true;
|
|
2366
2418
|
setTimeout(() => {
|
|
2367
2419
|
this.entityClickedInTick = false;
|
|
2368
2420
|
}, 0);
|
|
2421
|
+
if (entity.type === "marker" || entity.type === "cluster") {
|
|
2422
|
+
this.pointEntityClickedInTick = true;
|
|
2423
|
+
setTimeout(() => {
|
|
2424
|
+
this.pointEntityClickedInTick = false;
|
|
2425
|
+
}, 0);
|
|
2426
|
+
}
|
|
2369
2427
|
}
|
|
2370
2428
|
const event = { type, entity };
|
|
2371
2429
|
this.interactionHandlers.forEach(handler => handler(event));
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-map-adapter-google",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=24.x"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@trackunit/react-map-adapter-shared": "0.0.
|
|
11
|
-
"@trackunit/react-map-color-utils": "0.0.
|
|
12
|
-
"@trackunit/geo-json-utils": "1.14.
|
|
10
|
+
"@trackunit/react-map-adapter-shared": "0.0.26",
|
|
11
|
+
"@trackunit/react-map-color-utils": "0.0.11",
|
|
12
|
+
"@trackunit/geo-json-utils": "1.14.45",
|
|
13
13
|
"@googlemaps/markerclusterer": "^2.6.2",
|
|
14
14
|
"@vis.gl/react-google-maps": "^1.7.1",
|
|
15
15
|
"es-toolkit": "^1.39.10",
|
|
@@ -31,6 +31,16 @@ export declare class GoogleMapsLayerPort implements LayerPort {
|
|
|
31
31
|
private readonly interactionHandlers;
|
|
32
32
|
private readonly backgroundClickHandlers;
|
|
33
33
|
private entityClickedInTick;
|
|
34
|
+
/**
|
|
35
|
+
* True for one tick after a point (marker/cluster) click is emitted. Symbol
|
|
36
|
+
* markers render on a full-viewport canvas overlay that is not a Google-native
|
|
37
|
+
* marker, so — unlike `AdvancedMarkerElement` DOM markers, whose `gmp-click`
|
|
38
|
+
* makes Google swallow the underlying map click — a single click on a symbol
|
|
39
|
+
* marker overlapping a polygon fires BOTH the canvas marker click and the
|
|
40
|
+
* polygon's geometric click. This flag lets the trailing shape click bail so
|
|
41
|
+
* the marker the user visibly clicked wins.
|
|
42
|
+
*/
|
|
43
|
+
private pointEntityClickedInTick;
|
|
34
44
|
private mapClickListener;
|
|
35
45
|
private readonly markerSources;
|
|
36
46
|
/** featureId → element, keyed by sourceId. Cleared when a source is removed. */
|