@symfony/ux-google-map 2.26.0 → 2.27.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.
@@ -1,19 +1,16 @@
1
1
  import type { LoaderOptions } from '@googlemaps/js-api-loader';
2
+ import type { CircleDefinition, Icon, InfoWindowDefinition, MapDefinition, MarkerDefinition, PolygonDefinition, PolylineDefinition, RectangleDefinition } from '@symfony/ux-map';
2
3
  import AbstractMapController from '@symfony/ux-map';
3
- import type { Icon, InfoWindowWithoutPositionDefinition, MarkerDefinition, Point, PolygonDefinition, PolylineDefinition } from '@symfony/ux-map';
4
4
  type MapOptions = Pick<google.maps.MapOptions, 'mapId' | 'gestureHandling' | 'backgroundColor' | 'disableDoubleClickZoom' | 'zoomControl' | 'zoomControlOptions' | 'mapTypeControl' | 'mapTypeControlOptions' | 'streetViewControl' | 'streetViewControlOptions' | 'fullscreenControl' | 'fullscreenControlOptions'>;
5
- export default class extends AbstractMapController<MapOptions, google.maps.Map, google.maps.marker.AdvancedMarkerElementOptions, google.maps.marker.AdvancedMarkerElement, google.maps.InfoWindowOptions, google.maps.InfoWindow, google.maps.PolygonOptions, google.maps.Polygon, google.maps.PolylineOptions, google.maps.Polyline> {
5
+ export default class extends AbstractMapController<MapOptions, google.maps.MapOptions, google.maps.Map, google.maps.marker.AdvancedMarkerElementOptions, google.maps.marker.AdvancedMarkerElement, google.maps.InfoWindowOptions, google.maps.InfoWindow, google.maps.PolygonOptions, google.maps.Polygon, google.maps.PolylineOptions, google.maps.Polyline, google.maps.CircleOptions, google.maps.Circle, google.maps.RectangleOptions, google.maps.Rectangle> {
6
6
  providerOptionsValue: Pick<LoaderOptions, 'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version' | 'libraries'>;
7
7
  map: google.maps.Map;
8
- parser: DOMParser;
9
8
  connect(): Promise<void>;
10
9
  centerValueChanged(): void;
11
10
  zoomValueChanged(): void;
12
11
  protected dispatchEvent(name: string, payload?: Record<string, unknown>): void;
13
- protected doCreateMap({ center, zoom, options, }: {
14
- center: Point | null;
15
- zoom: number | null;
16
- options: MapOptions;
12
+ protected doCreateMap({ definition }: {
13
+ definition: MapDefinition<MapOptions, google.maps.MapOptions>;
17
14
  }): google.maps.Map;
18
15
  protected doCreateMarker({ definition, }: {
19
16
  definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>;
@@ -27,13 +24,21 @@ export default class extends AbstractMapController<MapOptions, google.maps.Map,
27
24
  definition: PolylineDefinition<google.maps.PolylineOptions, google.maps.InfoWindowOptions>;
28
25
  }): google.maps.Polyline;
29
26
  protected doRemovePolyline(polyline: google.maps.Polyline): void;
27
+ protected doCreateCircle({ definition }: {
28
+ definition: CircleDefinition<google.maps.CircleOptions, google.maps.InfoWindowOptions>;
29
+ }): google.maps.Circle;
30
+ protected doRemoveCircle(circle: google.maps.Circle): void;
31
+ protected doCreateRectangle({ definition, }: {
32
+ definition: RectangleDefinition<google.maps.RectangleOptions, google.maps.InfoWindowOptions>;
33
+ }): google.maps.Rectangle;
34
+ protected doRemoveRectangle(rectangle: google.maps.Rectangle): void;
30
35
  protected doCreateInfoWindow({ definition, element, }: {
31
- definition: InfoWindowWithoutPositionDefinition<google.maps.InfoWindowOptions>;
32
- element: google.maps.marker.AdvancedMarkerElement | google.maps.Polygon | google.maps.Polyline;
36
+ definition: Omit<InfoWindowDefinition<google.maps.InfoWindowOptions>, 'position'>;
37
+ element: google.maps.marker.AdvancedMarkerElement | google.maps.Polygon | google.maps.Polyline | google.maps.Circle | google.maps.Rectangle;
33
38
  }): google.maps.InfoWindow;
34
39
  protected doFitBoundsToMarkers(): void;
35
40
  private createTextOrElement;
36
- protected doCreateIcon({ definition, element, }: {
41
+ protected doCreateIcon({ definition, element }: {
37
42
  definition: Icon;
38
43
  element: google.maps.marker.AdvancedMarkerElement;
39
44
  }): void;
@@ -12,23 +12,31 @@ class default_1 extends Controller {
12
12
  this.markers = new Map();
13
13
  this.polygons = new Map();
14
14
  this.polylines = new Map();
15
+ this.circles = new Map();
16
+ this.rectangles = new Map();
15
17
  this.infoWindows = [];
16
18
  this.isConnected = false;
17
19
  }
18
20
  connect() {
19
- const options = this.optionsValue;
20
- this.dispatchEvent('pre-connect', { options });
21
+ const extra = this.hasExtraValue ? this.extraValue : {};
22
+ const mapDefinition = {
23
+ center: this.hasCenterValue ? this.centerValue : null,
24
+ zoom: this.hasZoomValue ? this.zoomValue : null,
25
+ options: this.optionsValue,
26
+ extra,
27
+ };
28
+ this.dispatchEvent('pre-connect', mapDefinition);
21
29
  this.createMarker = this.createDrawingFactory('marker', this.markers, this.doCreateMarker.bind(this));
22
30
  this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
23
31
  this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
24
- this.map = this.doCreateMap({
25
- center: this.hasCenterValue ? this.centerValue : null,
26
- zoom: this.hasZoomValue ? this.zoomValue : null,
27
- options,
28
- });
32
+ this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
33
+ this.createRectangle = this.createDrawingFactory('rectangle', this.rectangles, this.doCreateRectangle.bind(this));
34
+ this.map = this.doCreateMap({ definition: mapDefinition });
29
35
  this.markersValue.forEach((definition) => this.createMarker({ definition }));
30
36
  this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
31
37
  this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
38
+ this.circlesValue.forEach((definition) => this.createCircle({ definition }));
39
+ this.rectanglesValue.forEach((definition) => this.createRectangle({ definition }));
32
40
  if (this.fitBoundsToMarkersValue) {
33
41
  this.doFitBoundsToMarkers();
34
42
  }
@@ -37,7 +45,10 @@ class default_1 extends Controller {
37
45
  markers: [...this.markers.values()],
38
46
  polygons: [...this.polygons.values()],
39
47
  polylines: [...this.polylines.values()],
48
+ circles: [...this.circles.values()],
49
+ rectangles: [...this.rectangles.values()],
40
50
  infoWindows: this.infoWindows,
51
+ extra,
41
52
  });
42
53
  this.isConnected = true;
43
54
  }
@@ -69,11 +80,26 @@ class default_1 extends Controller {
69
80
  }
70
81
  this.onDrawChanged(this.polylines, this.polylinesValue, this.createPolyline, this.doRemovePolyline);
71
82
  }
83
+ circlesValueChanged() {
84
+ if (!this.isConnected) {
85
+ return;
86
+ }
87
+ this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
88
+ }
89
+ rectanglesValueChanged() {
90
+ if (!this.isConnected) {
91
+ return;
92
+ }
93
+ this.onDrawChanged(this.rectangles, this.rectanglesValue, this.createRectangle, this.doRemoveRectangle);
94
+ }
72
95
  createDrawingFactory(type, draws, factory) {
73
96
  const eventBefore = `${type}:before-create`;
74
97
  const eventAfter = `${type}:after-create`;
75
98
  return ({ definition }) => {
76
99
  this.dispatchEvent(eventBefore, { definition });
100
+ if (typeof definition.rawOptions !== 'undefined') {
101
+ console.warn(`[Symfony UX Map] The event "${eventBefore}" added a deprecated "rawOptions" property to the definition, it will be removed in a next major version, replace it with "bridgeOptions" instead.`, definition);
102
+ }
77
103
  const drawing = factory({ definition });
78
104
  this.dispatchEvent(eventAfter, { [type]: drawing, definition });
79
105
  draws.set(definition['@id'], drawing);
@@ -105,34 +131,51 @@ default_1.values = {
105
131
  markers: Array,
106
132
  polygons: Array,
107
133
  polylines: Array,
134
+ circles: Array,
135
+ rectangles: Array,
108
136
  options: Object,
137
+ extra: Object,
109
138
  };
110
139
 
111
140
  let _google;
141
+ let _loading = false;
142
+ let _loaded = false;
143
+ let _onLoadedCallbacks = [];
112
144
  const parser = new DOMParser();
113
145
  class map_controller extends default_1 {
114
146
  async connect() {
115
- if (!_google) {
116
- _google = { maps: {} };
117
- let { libraries = [], ...loaderOptions } = this.providerOptionsValue;
118
- const loader = new Loader(loaderOptions);
119
- libraries = ['core', ...libraries.filter((library) => library !== 'core')];
120
- const librariesImplementations = await Promise.all(libraries.map((library) => loader.importLibrary(library)));
121
- librariesImplementations.map((libraryImplementation, index) => {
122
- if (typeof libraryImplementation !== 'object' || libraryImplementation === null) {
123
- return;
124
- }
125
- const library = libraries[index];
126
- if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) {
127
- _google.maps[library] = libraryImplementation;
128
- }
129
- else {
130
- _google.maps = { ..._google.maps, ...libraryImplementation };
131
- }
132
- });
147
+ const onLoaded = () => super.connect();
148
+ if (_loaded) {
149
+ onLoaded();
150
+ return;
151
+ }
152
+ if (_loading) {
153
+ _onLoadedCallbacks.push(onLoaded);
154
+ return;
133
155
  }
134
- super.connect();
135
- this.parser = new DOMParser();
156
+ _loading = true;
157
+ _google = { maps: {} };
158
+ let { libraries = [], ...loaderOptions } = this.providerOptionsValue;
159
+ const loader = new Loader(loaderOptions);
160
+ libraries = ['core', ...libraries.filter((library) => library !== 'core')];
161
+ const librariesImplementations = await Promise.all(libraries.map((library) => loader.importLibrary(library)));
162
+ librariesImplementations.map((libraryImplementation, index) => {
163
+ if (typeof libraryImplementation !== 'object' || libraryImplementation === null) {
164
+ return;
165
+ }
166
+ const library = libraries[index];
167
+ if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) {
168
+ _google.maps[library] = libraryImplementation;
169
+ }
170
+ else {
171
+ _google.maps = { ..._google.maps, ...libraryImplementation };
172
+ }
173
+ });
174
+ _loading = false;
175
+ _loaded = true;
176
+ onLoaded();
177
+ _onLoadedCallbacks.forEach((callback) => callback());
178
+ _onLoadedCallbacks = [];
136
179
  }
137
180
  centerValueChanged() {
138
181
  if (this.map && this.hasCenterValue && this.centerValue) {
@@ -145,33 +188,33 @@ class map_controller extends default_1 {
145
188
  }
146
189
  }
147
190
  dispatchEvent(name, payload = {}) {
191
+ payload.google = _google;
148
192
  this.dispatch(name, {
149
193
  prefix: 'ux:map',
150
- detail: {
151
- ...payload,
152
- google: _google,
153
- },
194
+ detail: payload,
154
195
  });
155
196
  }
156
- doCreateMap({ center, zoom, options, }) {
197
+ doCreateMap({ definition }) {
198
+ const { center, zoom, options, bridgeOptions = {} } = definition;
157
199
  options.zoomControl = typeof options.zoomControlOptions !== 'undefined';
158
200
  options.mapTypeControl = typeof options.mapTypeControlOptions !== 'undefined';
159
201
  options.streetViewControl = typeof options.streetViewControlOptions !== 'undefined';
160
202
  options.fullscreenControl = typeof options.fullscreenControlOptions !== 'undefined';
161
203
  return new _google.maps.Map(this.element, {
162
- ...options,
163
204
  center,
164
205
  zoom,
206
+ ...options,
207
+ ...bridgeOptions,
165
208
  });
166
209
  }
167
210
  doCreateMarker({ definition, }) {
168
- const { '@id': _id, position, title, infoWindow, icon, extra, rawOptions = {}, ...otherOptions } = definition;
211
+ const { '@id': _id, position, title, infoWindow, icon, rawOptions = {}, bridgeOptions = {} } = definition;
169
212
  const marker = new _google.maps.marker.AdvancedMarkerElement({
170
213
  position,
171
214
  title,
172
- ...otherOptions,
173
- ...rawOptions,
174
215
  map: this.map,
216
+ ...rawOptions,
217
+ ...bridgeOptions,
175
218
  });
176
219
  if (infoWindow) {
177
220
  this.createInfoWindow({ definition: infoWindow, element: marker });
@@ -185,11 +228,12 @@ class map_controller extends default_1 {
185
228
  marker.map = null;
186
229
  }
187
230
  doCreatePolygon({ definition, }) {
188
- const { '@id': _id, points, title, infoWindow, rawOptions = {} } = definition;
231
+ const { '@id': _id, points, title, infoWindow, rawOptions = {}, bridgeOptions = {} } = definition;
189
232
  const polygon = new _google.maps.Polygon({
190
- ...rawOptions,
191
233
  paths: points,
192
234
  map: this.map,
235
+ ...rawOptions,
236
+ ...bridgeOptions,
193
237
  });
194
238
  if (title) {
195
239
  polygon.set('title', title);
@@ -203,11 +247,12 @@ class map_controller extends default_1 {
203
247
  polygon.setMap(null);
204
248
  }
205
249
  doCreatePolyline({ definition, }) {
206
- const { '@id': _id, points, title, infoWindow, rawOptions = {} } = definition;
250
+ const { '@id': _id, points, title, infoWindow, rawOptions = {}, bridgeOptions = {} } = definition;
207
251
  const polyline = new _google.maps.Polyline({
208
- ...rawOptions,
209
252
  path: points,
210
253
  map: this.map,
254
+ ...rawOptions,
255
+ ...bridgeOptions,
211
256
  });
212
257
  if (title) {
213
258
  polyline.set('title', title);
@@ -220,41 +265,77 @@ class map_controller extends default_1 {
220
265
  doRemovePolyline(polyline) {
221
266
  polyline.setMap(null);
222
267
  }
268
+ doCreateCircle({ definition }) {
269
+ const { '@id': _id, center, radius, title, infoWindow, rawOptions = {}, bridgeOptions = {} } = definition;
270
+ const circle = new _google.maps.Circle({
271
+ center,
272
+ radius,
273
+ map: this.map,
274
+ ...rawOptions,
275
+ ...bridgeOptions,
276
+ });
277
+ if (title) {
278
+ circle.set('title', title);
279
+ }
280
+ if (infoWindow) {
281
+ this.createInfoWindow({ definition: infoWindow, element: circle });
282
+ }
283
+ return circle;
284
+ }
285
+ doRemoveCircle(circle) {
286
+ circle.setMap(null);
287
+ }
288
+ doCreateRectangle({ definition, }) {
289
+ const { northEast, southWest, title, infoWindow, rawOptions = {}, bridgeOptions = {} } = definition;
290
+ const rectangle = new _google.maps.Rectangle({
291
+ bounds: new _google.maps.LatLngBounds(southWest, northEast),
292
+ map: this.map,
293
+ ...rawOptions,
294
+ ...bridgeOptions,
295
+ });
296
+ if (title) {
297
+ rectangle.set('title', title);
298
+ }
299
+ if (infoWindow) {
300
+ this.createInfoWindow({ definition: infoWindow, element: rectangle });
301
+ }
302
+ return rectangle;
303
+ }
304
+ doRemoveRectangle(rectangle) {
305
+ rectangle.setMap(null);
306
+ }
223
307
  doCreateInfoWindow({ definition, element, }) {
224
- const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
225
- const infoWindow = new _google.maps.InfoWindow({
308
+ const { headerContent, content, opened, autoClose, rawOptions = {}, bridgeOptions = {} } = definition;
309
+ let position = null;
310
+ if (element instanceof google.maps.Circle) {
311
+ position = element.getCenter();
312
+ }
313
+ else if (element instanceof google.maps.Rectangle) {
314
+ position = element.getBounds()?.getCenter() || null;
315
+ }
316
+ else if (element instanceof google.maps.Polygon || element instanceof google.maps.Polyline) ;
317
+ const infoWindowOptions = {
226
318
  headerContent: this.createTextOrElement(headerContent),
227
319
  content: this.createTextOrElement(content),
228
- ...otherOptions,
320
+ position,
229
321
  ...rawOptions,
230
- });
231
- if (element instanceof google.maps.marker.AdvancedMarkerElement) {
232
- element.addListener('click', () => {
233
- if (definition.autoClose) {
234
- this.closeInfoWindowsExcept(infoWindow);
235
- }
236
- infoWindow.open({ map: this.map, anchor: element });
237
- });
238
- if (definition.opened) {
239
- infoWindow.open({ map: this.map, anchor: element });
322
+ ...bridgeOptions,
323
+ };
324
+ const infoWindow = new _google.maps.InfoWindow(infoWindowOptions);
325
+ element.addListener('click', (event) => {
326
+ if (autoClose) {
327
+ this.closeInfoWindowsExcept(infoWindow);
240
328
  }
241
- }
242
- else if (element instanceof google.maps.Polygon) {
243
- element.addListener('click', (event) => {
244
- if (definition.autoClose) {
245
- this.closeInfoWindowsExcept(infoWindow);
246
- }
329
+ if (infoWindowOptions.position === null) {
247
330
  infoWindow.setPosition(event.latLng);
248
- infoWindow.open(this.map);
249
- });
250
- if (definition.opened) {
251
- const bounds = new google.maps.LatLngBounds();
252
- element.getPath().forEach((point) => {
253
- bounds.extend(point);
254
- });
255
- infoWindow.setPosition(bounds.getCenter());
256
- infoWindow.open({ map: this.map, anchor: element });
257
331
  }
332
+ infoWindow.open({ map: this.map, anchor: element });
333
+ });
334
+ if (opened) {
335
+ if (autoClose) {
336
+ this.closeInfoWindowsExcept(infoWindow);
337
+ }
338
+ infoWindow.open({ map: this.map, anchor: element });
258
339
  }
259
340
  return infoWindow;
260
341
  }
@@ -282,7 +363,7 @@ class map_controller extends default_1 {
282
363
  }
283
364
  return content;
284
365
  }
285
- doCreateIcon({ definition, element, }) {
366
+ doCreateIcon({ definition, element }) {
286
367
  const { type, width, height } = definition;
287
368
  if (type === IconTypes.Svg) {
288
369
  element.content = parser.parseFromString(definition.html, 'image/svg+xml').documentElement;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@symfony/ux-google-map",
3
3
  "description": "GoogleMaps bridge for Symfony UX Map, integrate interactive maps in your Symfony applications",
4
4
  "license": "MIT",
5
- "version": "2.26.0",
5
+ "version": "2.27.0",
6
6
  "keywords": [
7
7
  "symfony-ux",
8
8
  "google-maps",
@@ -50,7 +50,7 @@
50
50
  "devDependencies": {
51
51
  "@googlemaps/js-api-loader": "^1.16.6",
52
52
  "@hotwired/stimulus": "^3.0.0",
53
- "@symfony/ux-map": "2.26.0",
53
+ "@symfony/ux-map": "2.27.0",
54
54
  "@types/google.maps": "^3.55.9"
55
55
  }
56
56
  }