Skip to content

Commit 00e013e

Browse files
committed
feature #2838 [Map] Add Circle support (Valmonzo)
This PR was merged into the 2.x branch. Discussion ---------- [Map] Add Circle support | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | yes | Issues | | License | MIT Add Circle support for map (Googlemap and leaflet) <img width="284" alt="Capture d’écran 2025-06-13 à 14 02 13" src="https://github.com/user-attachments/assets/d5566622-4ee6-454c-9682-4d4c0943f50e" /> <img width="902" alt="Capture d’écran 2025-06-13 à 14 00 04" src="https://github.com/user-attachments/assets/7cf80f13-4b34-4bc1-87ba-d7b68afa4da0" /> Commits ------- 0839a1d [Map] Add Circle support
2 parents 4a273ba + 0839a1d commit 00e013e

File tree

43 files changed

+666
-28
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+666
-28
lines changed

src/Map/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# CHANGELOG
22

3+
## 2.28
4+
5+
- Add support for creating `Circle` by passing a `Point` and a radius (in meters) to the `Circle` constructor, e.g.:
6+
```php
7+
$map->addCircle(new Circle(
8+
center: new Point(48.856613, 2.352222), // Paris
9+
radius: 5_000 // 5km
10+
));
11+
```
312
## 2.27
413

514
- The `fitBoundsToMarkers` option is not overridden anymore when using the `Map` LiveComponent, but now respects the value you defined.

src/Map/assets/dist/abstract_map_controller.d.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ export type PolylineDefinition<PolylineOptions, InfoWindowOptions> = WithIdentif
4848
rawOptions?: PolylineOptions;
4949
extra: Record<string, unknown>;
5050
}>;
51+
export type CircleDefinition<CircleOptions, InfoWindowOptions> = WithIdentifier<{
52+
infoWindow?: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
53+
center: Point;
54+
radius: number;
55+
title: string | null;
56+
rawOptions?: CircleOptions;
57+
extra: Record<string, unknown>;
58+
}>;
5159
export type InfoWindowDefinition<InfoWindowOptions> = {
5260
headerContent: string | null;
5361
content: string | null;
@@ -58,7 +66,7 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
5866
extra: Record<string, unknown>;
5967
};
6068
export type InfoWindowWithoutPositionDefinition<InfoWindowOptions> = Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
61-
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon, PolylineOptions, Polyline> extends Controller<HTMLElement> {
69+
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon, PolylineOptions, Polyline, CircleOptions, Circle> extends Controller<HTMLElement> {
6270
static values: {
6371
providerOptions: ObjectConstructor;
6472
center: ObjectConstructor;
@@ -67,6 +75,7 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
6775
markers: ArrayConstructor;
6876
polygons: ArrayConstructor;
6977
polylines: ArrayConstructor;
78+
circles: ArrayConstructor;
7079
options: ObjectConstructor;
7180
};
7281
centerValue: Point | null;
@@ -75,34 +84,39 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
7584
markersValue: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
7685
polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
7786
polylinesValue: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
87+
circlesValue: Array<CircleDefinition<CircleOptions, InfoWindowOptions>>;
7888
optionsValue: MapOptions;
7989
hasCenterValue: boolean;
8090
hasZoomValue: boolean;
8191
hasFitBoundsToMarkersValue: boolean;
8292
hasMarkersValue: boolean;
8393
hasPolygonsValue: boolean;
8494
hasPolylinesValue: boolean;
95+
hasCirclesValue: boolean;
8596
hasOptionsValue: boolean;
8697
protected map: Map;
8798
protected markers: globalThis.Map<string, Marker>;
8899
protected polygons: globalThis.Map<string, Polygon>;
89100
protected polylines: globalThis.Map<string, Polyline>;
101+
protected circles: globalThis.Map<string, Circle>;
90102
protected infoWindows: Array<InfoWindow>;
91103
private isConnected;
92104
private createMarker;
93105
private createPolygon;
94106
private createPolyline;
107+
private createCircle;
95108
protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;
96109
connect(): void;
97110
createInfoWindow({ definition, element, }: {
98111
definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
99-
element: Marker | Polygon | Polyline;
112+
element: Marker | Polygon | Polyline | Circle;
100113
}): InfoWindow;
101114
abstract centerValueChanged(): void;
102115
abstract zoomValueChanged(): void;
103116
markersValueChanged(): void;
104117
polygonsValueChanged(): void;
105118
polylinesValueChanged(): void;
119+
circlesValueChanged(): void;
106120
protected abstract doCreateMap({ center, zoom, options, }: {
107121
center: Point | null;
108122
zoom: number | null;
@@ -121,9 +135,13 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
121135
definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>;
122136
}): Polyline;
123137
protected abstract doRemovePolyline(polyline: Polyline): void;
138+
protected abstract doCreateCircle({ definition, }: {
139+
definition: CircleDefinition<CircleOptions, InfoWindowOptions>;
140+
}): Circle;
141+
protected abstract doRemoveCircle(circle: Circle): void;
124142
protected abstract doCreateInfoWindow({ definition, element, }: {
125143
definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
126-
element: Marker | Polygon | Polyline;
144+
element: Marker | Polygon | Polyline | Circle;
127145
}): InfoWindow;
128146
protected abstract doCreateIcon({ definition, element, }: {
129147
definition: Icon;

src/Map/assets/dist/abstract_map_controller.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class default_1 extends Controller {
1111
this.markers = new Map();
1212
this.polygons = new Map();
1313
this.polylines = new Map();
14+
this.circles = new Map();
1415
this.infoWindows = [];
1516
this.isConnected = false;
1617
}
@@ -20,6 +21,7 @@ class default_1 extends Controller {
2021
this.createMarker = this.createDrawingFactory('marker', this.markers, this.doCreateMarker.bind(this));
2122
this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
2223
this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
24+
this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
2325
this.map = this.doCreateMap({
2426
center: this.hasCenterValue ? this.centerValue : null,
2527
zoom: this.hasZoomValue ? this.zoomValue : null,
@@ -28,6 +30,7 @@ class default_1 extends Controller {
2830
this.markersValue.forEach((definition) => this.createMarker({ definition }));
2931
this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
3032
this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
33+
this.circlesValue.forEach((definition) => this.createCircle({ definition }));
3134
if (this.fitBoundsToMarkersValue) {
3235
this.doFitBoundsToMarkers();
3336
}
@@ -36,6 +39,7 @@ class default_1 extends Controller {
3639
markers: [...this.markers.values()],
3740
polygons: [...this.polygons.values()],
3841
polylines: [...this.polylines.values()],
42+
circles: [...this.circles.values()],
3943
infoWindows: this.infoWindows,
4044
});
4145
this.isConnected = true;
@@ -68,6 +72,12 @@ class default_1 extends Controller {
6872
}
6973
this.onDrawChanged(this.polylines, this.polylinesValue, this.createPolyline, this.doRemovePolyline);
7074
}
75+
circlesValueChanged() {
76+
if (!this.isConnected) {
77+
return;
78+
}
79+
this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
80+
}
7181
createDrawingFactory(type, draws, factory) {
7282
const eventBefore = `${type}:before-create`;
7383
const eventAfter = `${type}:after-create`;
@@ -104,6 +114,7 @@ default_1.values = {
104114
markers: Array,
105115
polygons: Array,
106116
polylines: Array,
117+
circles: Array,
107118
options: Object,
108119
};
109120

src/Map/assets/src/abstract_map_controller.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ export type PolylineDefinition<PolylineOptions, InfoWindowOptions> = WithIdentif
8080
extra: Record<string, unknown>;
8181
}>;
8282

83+
export type CircleDefinition<CircleOptions, InfoWindowOptions> = WithIdentifier<{
84+
infoWindow?: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
85+
center: Point;
86+
radius: number;
87+
title: string | null;
88+
/**
89+
* Raw options passed to the circle constructor, specific to the map provider (e.g.: `L.circle()` for Leaflet).
90+
*/
91+
rawOptions?: CircleOptions;
92+
/**
93+
* Extra data defined by the developer.
94+
* They are not directly used by the Stimulus controller, but they can be used by the developer with event listeners:
95+
* - `ux:map:circle:before-create`
96+
* - `ux:map:circle:after-create`
97+
*/
98+
extra: Record<string, unknown>;
99+
}>;
100+
83101
export type InfoWindowDefinition<InfoWindowOptions> = {
84102
headerContent: string | null;
85103
content: string | null;
@@ -116,6 +134,8 @@ export default abstract class<
116134
Polygon,
117135
PolylineOptions,
118136
Polyline,
137+
CircleOptions,
138+
Circle,
119139
> extends Controller<HTMLElement> {
120140
static values = {
121141
providerOptions: Object,
@@ -125,6 +145,7 @@ export default abstract class<
125145
markers: Array,
126146
polygons: Array,
127147
polylines: Array,
148+
circles: Array,
128149
options: Object,
129150
};
130151

@@ -134,6 +155,7 @@ export default abstract class<
134155
declare markersValue: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
135156
declare polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
136157
declare polylinesValue: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
158+
declare circlesValue: Array<CircleDefinition<CircleOptions, InfoWindowOptions>>;
137159
declare optionsValue: MapOptions;
138160

139161
declare hasCenterValue: boolean;
@@ -142,12 +164,14 @@ export default abstract class<
142164
declare hasMarkersValue: boolean;
143165
declare hasPolygonsValue: boolean;
144166
declare hasPolylinesValue: boolean;
167+
declare hasCirclesValue: boolean;
145168
declare hasOptionsValue: boolean;
146169

147170
protected map: Map;
148171
protected markers = new Map<Identifier, Marker>();
149172
protected polygons = new Map<Identifier, Polygon>();
150173
protected polylines = new Map<Identifier, Polyline>();
174+
protected circles = new Map<Identifier, Circle>();
151175
protected infoWindows: Array<InfoWindow> = [];
152176

153177
private isConnected = false;
@@ -160,6 +184,9 @@ export default abstract class<
160184
private createPolyline: ({
161185
definition,
162186
}: { definition: PolylineDefinition<PolylineOptions, InfoWindowOptions> }) => Polyline;
187+
private createCircle: ({
188+
definition,
189+
}: { definition: CircleDefinition<CircleOptions, InfoWindowOptions> }) => Circle;
163190

164191
protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;
165192

@@ -171,6 +198,7 @@ export default abstract class<
171198
this.createMarker = this.createDrawingFactory('marker', this.markers, this.doCreateMarker.bind(this));
172199
this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
173200
this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
201+
this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
174202

175203
this.map = this.doCreateMap({
176204
center: this.hasCenterValue ? this.centerValue : null,
@@ -180,6 +208,7 @@ export default abstract class<
180208
this.markersValue.forEach((definition) => this.createMarker({ definition }));
181209
this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
182210
this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
211+
this.circlesValue.forEach((definition) => this.createCircle({ definition }));
183212

184213
if (this.fitBoundsToMarkersValue) {
185214
this.doFitBoundsToMarkers();
@@ -190,6 +219,7 @@ export default abstract class<
190219
markers: [...this.markers.values()],
191220
polygons: [...this.polygons.values()],
192221
polylines: [...this.polylines.values()],
222+
circles: [...this.circles.values()],
193223
infoWindows: this.infoWindows,
194224
});
195225

@@ -202,7 +232,7 @@ export default abstract class<
202232
element,
203233
}: {
204234
definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
205-
element: Marker | Polygon | Polyline;
235+
element: Marker | Polygon | Polyline | Circle;
206236
}): InfoWindow {
207237
this.dispatchEvent('info-window:before-create', { definition, element });
208238
const infoWindow = this.doCreateInfoWindow({ definition, element });
@@ -248,6 +278,14 @@ export default abstract class<
248278
this.onDrawChanged(this.polylines, this.polylinesValue, this.createPolyline, this.doRemovePolyline);
249279
}
250280

281+
public circlesValueChanged(): void {
282+
if (!this.isConnected) {
283+
return;
284+
}
285+
286+
this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
287+
}
288+
251289
//endregion
252290

253291
//region Abstract factory methods to be implemented by the concrete classes, they are specific to the map provider
@@ -285,12 +323,20 @@ export default abstract class<
285323

286324
protected abstract doRemovePolyline(polyline: Polyline): void;
287325

326+
protected abstract doCreateCircle({
327+
definition,
328+
}: {
329+
definition: CircleDefinition<CircleOptions, InfoWindowOptions>;
330+
}): Circle;
331+
332+
protected abstract doRemoveCircle(circle: Circle): void;
333+
288334
protected abstract doCreateInfoWindow({
289335
definition,
290336
element,
291337
}: {
292338
definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
293-
element: Marker | Polygon | Polyline;
339+
element: Marker | Polygon | Polyline | Circle;
294340
}): InfoWindow;
295341
protected abstract doCreateIcon({
296342
definition,
@@ -318,11 +364,20 @@ export default abstract class<
318364
draws: typeof this.polylines,
319365
factory: typeof this.doCreatePolyline
320366
): typeof this.doCreatePolyline;
367+
private createDrawingFactory(
368+
type: 'circle',
369+
draws: typeof this.circles,
370+
factory: typeof this.doCreateCircle
371+
): typeof this.doCreateCircle;
321372
private createDrawingFactory<
322-
Factory extends typeof this.doCreateMarker | typeof this.doCreatePolygon | typeof this.doCreatePolyline,
373+
Factory extends
374+
| typeof this.doCreateMarker
375+
| typeof this.doCreatePolygon
376+
| typeof this.doCreatePolyline
377+
| typeof this.doCreateCircle,
323378
Draw extends ReturnType<Factory>,
324379
>(
325-
type: 'marker' | 'polygon' | 'polyline',
380+
type: 'marker' | 'polygon' | 'polyline' | 'circle',
326381
draws: globalThis.Map<WithIdentifier<any>, Draw>,
327382
factory: Factory
328383
): Factory {
@@ -360,6 +415,12 @@ export default abstract class<
360415
factory: typeof this.createPolyline,
361416
remover: typeof this.doRemovePolyline
362417
): void;
418+
private onDrawChanged(
419+
draws: typeof this.circles,
420+
newDrawDefinitions: typeof this.circlesValue,
421+
factory: typeof this.createCircle,
422+
remover: typeof this.doRemoveCircle
423+
): void;
363424
private onDrawChanged<Draw, DrawDefinition extends WithIdentifier<Record<string, unknown>>>(
364425
draws: globalThis.Map<WithIdentifier<any>, Draw>,
365426
newDrawDefinitions: Array<DrawDefinition>,

0 commit comments

Comments
 (0)