@react-google-maps/marker-clusterer 2.3.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.
@@ -0,0 +1,371 @@
1
+ /* global google */
2
+ /* eslint-disable filenames/match-regex */
3
+ import { Cluster } from './Cluster'
4
+
5
+ import { ClusterIconStyle, ClusterIconInfo } from './types'
6
+
7
+ export class ClusterIcon {
8
+ cluster: Cluster
9
+ className: string
10
+ styles: ClusterIconStyle[]
11
+ center: google.maps.LatLng | undefined
12
+ div: HTMLDivElement | null
13
+ sums: ClusterIconInfo | null
14
+ visible: boolean
15
+ url: string
16
+ height: number
17
+ width: number
18
+ anchorText: number[]
19
+ anchorIcon: number[]
20
+ textColor: string
21
+ textSize: number
22
+ textDecoration: string
23
+ fontWeight: string
24
+ fontStyle: string
25
+ fontFamily: string
26
+ backgroundPosition: string
27
+
28
+ boundsChangedListener: google.maps.MapsEventListener | null
29
+
30
+ constructor(cluster: Cluster, styles: ClusterIconStyle[]) {
31
+ cluster.getClusterer().extend(ClusterIcon, google.maps.OverlayView)
32
+ this.cluster = cluster
33
+ this.className = this.cluster.getClusterer().getClusterClass()
34
+ this.styles = styles
35
+ this.center = undefined
36
+ this.div = null
37
+ this.sums = null
38
+ this.visible = false
39
+ this.boundsChangedListener = null
40
+ this.url = ''
41
+ this.height = 0
42
+ this.width = 0
43
+ this.anchorText = [0, 0]
44
+ this.anchorIcon = [0, 0]
45
+ this.textColor = 'black'
46
+ this.textSize = 11
47
+ this.textDecoration = 'none'
48
+ this.fontWeight = 'bold'
49
+ this.fontStyle = 'normal'
50
+ this.fontFamily = 'Arial,sans-serif'
51
+ this.backgroundPosition = '0 0'
52
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
53
+ // @ts-ignore
54
+ this.setMap(cluster.getMap()) // Note: this causes onAdd to be called
55
+ }
56
+
57
+ onAdd() {
58
+ let cMouseDownInCluster: boolean
59
+ let cDraggingMapByCluster: boolean
60
+
61
+ this.div = document.createElement('div')
62
+ this.div.className = this.className
63
+ if (this.visible) {
64
+ this.show()
65
+ }
66
+
67
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
68
+ // @ts-ignore
69
+ this.getPanes().overlayMouseTarget.appendChild(this.div)
70
+
71
+ // Fix for Issue 157
72
+ this.boundsChangedListener = google.maps.event.addListener(
73
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
74
+ // @ts-ignore
75
+ this.getMap(),
76
+ 'boundschanged',
77
+ function boundsChanged() {
78
+ cDraggingMapByCluster = cMouseDownInCluster
79
+ }
80
+ )
81
+
82
+ google.maps.event.addDomListener(this.div, 'mousedown', function onMouseDown() {
83
+ cMouseDownInCluster = true
84
+ cDraggingMapByCluster = false
85
+ })
86
+
87
+ // eslint-disable-next-line @getify/proper-arrows/this, @getify/proper-arrows/name
88
+ google.maps.event.addDomListener(
89
+ this.div,
90
+ 'click',
91
+ // eslint-disable-next-line @getify/proper-arrows/this, @getify/proper-arrows/name
92
+ (event: Event) => {
93
+ cMouseDownInCluster = false
94
+
95
+ if (!cDraggingMapByCluster) {
96
+ const markerClusterer = this.cluster.getClusterer()
97
+
98
+ /**
99
+ * This event is fired when a cluster marker is clicked.
100
+ * @name MarkerClusterer#click
101
+ * @param {Cluster} c The cluster that was clicked.
102
+ * @event
103
+ */
104
+ google.maps.event.trigger(markerClusterer, 'click', this.cluster)
105
+ google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster) // deprecated name
106
+
107
+ // The default click handler follows. Disable it by setting
108
+ // the zoomOnClick property to false.
109
+ if (markerClusterer.getZoomOnClick()) {
110
+ // Zoom into the cluster.
111
+ const maxZoom = markerClusterer.getMaxZoom()
112
+
113
+ const bounds = this.cluster.getBounds()
114
+
115
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
116
+ // @ts-ignore
117
+ markerClusterer.getMap().fitBounds(bounds)
118
+
119
+ // There is a fix for Issue 170 here:
120
+ setTimeout(function timeout() {
121
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
122
+ // @ts-ignore
123
+ markerClusterer.getMap().fitBounds(bounds)
124
+
125
+ // Don't zoom beyond the max zoom level
126
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
127
+ // @ts-ignore
128
+ if (maxZoom !== null && markerClusterer.getMap().getZoom() > maxZoom) {
129
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
130
+ // @ts-ignore
131
+ markerClusterer.getMap().setZoom(maxZoom + 1)
132
+ }
133
+ }, 100)
134
+ }
135
+
136
+ // Prevent event propagation to the map:
137
+ event.cancelBubble = true
138
+
139
+ if (event.stopPropagation) {
140
+ event.stopPropagation()
141
+ }
142
+ }
143
+ }
144
+ )
145
+
146
+ google.maps.event.addDomListener(
147
+ this.div,
148
+ 'mouseover',
149
+ // eslint-disable-next-line @getify/proper-arrows/this, @getify/proper-arrows/name
150
+ () => {
151
+ /**
152
+ * This event is fired when the mouse moves over a cluster marker.
153
+ * @name MarkerClusterer#mouseover
154
+ * @param {Cluster} c The cluster that the mouse moved over.
155
+ * @event
156
+ */
157
+ google.maps.event.trigger(this.cluster.getClusterer(), 'mouseover', this.cluster)
158
+ }
159
+ )
160
+
161
+ // eslint-disable-next-line @getify/proper-arrows/this, @getify/proper-arrows/name
162
+ google.maps.event.addDomListener(
163
+ this.div,
164
+ 'mouseout',
165
+ // eslint-disable-next-line @getify/proper-arrows/this, @getify/proper-arrows/name
166
+ () => {
167
+ /**
168
+ * This event is fired when the mouse moves out of a cluster marker.
169
+ * @name MarkerClusterer#mouseout
170
+ * @param {Cluster} c The cluster that the mouse moved out of.
171
+ * @event
172
+ */
173
+ google.maps.event.trigger(this.cluster.getClusterer(), 'mouseout', this.cluster)
174
+ }
175
+ )
176
+ }
177
+
178
+ onRemove() {
179
+ if (this.div && this.div.parentNode) {
180
+ this.hide()
181
+
182
+ if (this.boundsChangedListener !== null) {
183
+ google.maps.event.removeListener(this.boundsChangedListener)
184
+ }
185
+
186
+ google.maps.event.clearInstanceListeners(this.div)
187
+
188
+ this.div.parentNode.removeChild(this.div)
189
+
190
+ this.div = null
191
+ }
192
+ }
193
+
194
+ draw() {
195
+ if (this.visible && this.div !== null && this.center) {
196
+ const { x, y } = this.getPosFromLatLng(this.center)
197
+
198
+ this.div.style.top = y + 'px'
199
+ this.div.style.left = x + 'px'
200
+ }
201
+ }
202
+
203
+ hide() {
204
+ if (this.div) {
205
+ this.div.style.display = 'none'
206
+ }
207
+
208
+ this.visible = false
209
+ }
210
+
211
+ show() {
212
+ if (this.div && this.center) {
213
+ let img = '',
214
+ divTitle = ''
215
+
216
+ // NOTE: values must be specified in px units
217
+ const bp = this.backgroundPosition.split(' ')
218
+
219
+ const spriteH = parseInt(bp[0].replace(/^\s+|\s+$/g, ''), 10)
220
+
221
+ const spriteV = parseInt(bp[1].replace(/^\s+|\s+$/g, ''), 10)
222
+
223
+ const pos = this.getPosFromLatLng(this.center)
224
+
225
+ if (this.sums === null || typeof this.sums.title === 'undefined' || this.sums.title === '') {
226
+ divTitle = this.cluster.getClusterer().getTitle()
227
+ } else {
228
+ divTitle = this.sums.title
229
+ }
230
+
231
+ this.div.style.cssText = this.createCss(pos)
232
+
233
+ img =
234
+ "<img alt='" +
235
+ divTitle +
236
+ "' src='" +
237
+ this.url +
238
+ "' style='position: absolute; top: " +
239
+ spriteV +
240
+ 'px; left: ' +
241
+ spriteH +
242
+ 'px; '
243
+
244
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
245
+ //@ts-ignore
246
+ if (!this.cluster.getClusterer().enableRetinaIcons) {
247
+ img +=
248
+ 'clip: rect(' +
249
+ -1 * spriteV +
250
+ 'px, ' +
251
+ (-1 * spriteH + this.width) +
252
+ 'px, ' +
253
+ (-1 * spriteV + this.height) +
254
+ 'px, ' +
255
+ -1 * spriteH +
256
+ 'px);'
257
+ }
258
+
259
+ img += "'>"
260
+
261
+ this.div.innerHTML =
262
+ img +
263
+ "<div style='" +
264
+ 'position: absolute;' +
265
+ 'top: ' +
266
+ this.anchorText[0] +
267
+ 'px;' +
268
+ 'left: ' +
269
+ this.anchorText[1] +
270
+ 'px;' +
271
+ 'color: ' +
272
+ this.textColor +
273
+ ';' +
274
+ 'font-size: ' +
275
+ this.textSize +
276
+ 'px;' +
277
+ 'font-family: ' +
278
+ this.fontFamily +
279
+ ';' +
280
+ 'font-weight: ' +
281
+ this.fontWeight +
282
+ ';' +
283
+ 'font-style: ' +
284
+ this.fontStyle +
285
+ ';' +
286
+ 'text-decoration: ' +
287
+ this.textDecoration +
288
+ ';' +
289
+ 'text-align: center;' +
290
+ 'width: ' +
291
+ this.width +
292
+ 'px;' +
293
+ 'line-height:' +
294
+ this.height +
295
+ 'px;' +
296
+ "'>" +
297
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
298
+ // @ts-ignore
299
+ this.sums.text +
300
+ '</div>'
301
+
302
+ this.div.title = divTitle
303
+
304
+ this.div.style.display = ''
305
+ }
306
+
307
+ this.visible = true
308
+ }
309
+
310
+ useStyle(sums: ClusterIconInfo) {
311
+ this.sums = sums
312
+
313
+ const style = this.styles[Math.min(this.styles.length - 1, Math.max(0, sums.index - 1))]
314
+
315
+ this.url = style.url
316
+ this.height = style.height
317
+ this.width = style.width
318
+
319
+ if (style.className)
320
+ this.className = `${this.className} ${style.className}`
321
+
322
+ this.anchorText = style.anchorText || [0, 0]
323
+ this.anchorIcon = style.anchorIcon || [this.height / 2, this.width / 2]
324
+
325
+ this.textColor = style.textColor || 'black'
326
+
327
+ this.textSize = style.textSize || 11
328
+
329
+ this.textDecoration = style.textDecoration || 'none'
330
+
331
+ this.fontWeight = style.fontWeight || 'bold'
332
+
333
+ this.fontStyle = style.fontStyle || 'normal'
334
+
335
+ this.fontFamily = style.fontFamily || 'Arial,sans-serif'
336
+
337
+ this.backgroundPosition = style.backgroundPosition || '0 0'
338
+ }
339
+
340
+ setCenter(center: google.maps.LatLng) {
341
+ this.center = center
342
+ }
343
+
344
+ createCss(pos: google.maps.Point): string {
345
+ const style = []
346
+
347
+ style.push('cursor: pointer;')
348
+
349
+ style.push('position: absolute; top: ' + pos.y + 'px; left: ' + pos.x + 'px;')
350
+
351
+ style.push('width: ' + this.width + 'px; height: ' + this.height + 'px;')
352
+
353
+ return style.join('')
354
+ }
355
+
356
+ getPosFromLatLng(latlng: google.maps.LatLng): google.maps.Point {
357
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
358
+ // @ts-ignore
359
+ const pos = this.getProjection().fromLatLngToDivPixel(latlng)
360
+
361
+ pos.x -= this.anchorIcon[1]
362
+
363
+ pos.y -= this.anchorIcon[0]
364
+
365
+ // pos.x = pos.x
366
+
367
+ // pos.y = pos.y
368
+
369
+ return pos
370
+ }
371
+ }