UNPKG

41.7 kBJavaScriptView Raw
1'use strict';
2
3exports.__esModule = true;
4
5var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
6
7var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8
9var _react = require('react');
10
11var _react2 = _interopRequireDefault(_react);
12
13var _propTypes = require('prop-types');
14
15var _propTypes2 = _interopRequireDefault(_propTypes);
16
17var _reactDom = require('react-dom');
18
19var _reactDom2 = _interopRequireDefault(_reactDom);
20
21var _google_map_map = require('./google_map_map');
22
23var _google_map_map2 = _interopRequireDefault(_google_map_map);
24
25var _marker_dispatcher = require('./marker_dispatcher');
26
27var _marker_dispatcher2 = _interopRequireDefault(_marker_dispatcher);
28
29var _google_map_markers = require('./google_map_markers');
30
31var _google_map_markers2 = _interopRequireDefault(_google_map_markers);
32
33var _google_map_markers_prerender = require('./google_map_markers_prerender');
34
35var _google_map_markers_prerender2 = _interopRequireDefault(_google_map_markers_prerender);
36
37var _google_heatmap = require('./google_heatmap');
38
39var _google_map_loader = require('./loaders/google_map_loader');
40
41var _google_map_loader2 = _interopRequireDefault(_google_map_loader);
42
43var _geo = require('./utils/geo');
44
45var _geo2 = _interopRequireDefault(_geo);
46
47var _raf = require('./utils/raf');
48
49var _raf2 = _interopRequireDefault(_raf);
50
51var _pick = require('./utils/pick');
52
53var _pick2 = _interopRequireDefault(_pick);
54
55var _omit = require('./utils/omit');
56
57var _omit2 = _interopRequireDefault(_omit);
58
59var _log = require('./utils/math/log2');
60
61var _log2 = _interopRequireDefault(_log);
62
63var _isEmpty = require('./utils/isEmpty');
64
65var _isEmpty2 = _interopRequireDefault(_isEmpty);
66
67var _isNumber = require('./utils/isNumber');
68
69var _isNumber2 = _interopRequireDefault(_isNumber);
70
71var _detect = require('./utils/detect');
72
73var _detect2 = _interopRequireDefault(_detect);
74
75var _shallowEqual = require('./utils/shallowEqual');
76
77var _shallowEqual2 = _interopRequireDefault(_shallowEqual);
78
79var _isPlainObject = require('./utils/isPlainObject');
80
81var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
82
83var _isArraysEqualEps = require('./utils/isArraysEqualEps');
84
85var _isArraysEqualEps2 = _interopRequireDefault(_isArraysEqualEps);
86
87var _detectElementResize = require('./utils/detectElementResize');
88
89var _detectElementResize2 = _interopRequireDefault(_detectElementResize);
90
91var _passiveEvents = require('./utils/passiveEvents');
92
93var _passiveEvents2 = _interopRequireDefault(_passiveEvents);
94
95function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
96
97function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
98
99function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
100
101function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* eslint-disable import/no-extraneous-dependencies, react/forbid-prop-types, react/no-find-dom-node, no-console */
102
103
104// helpers
105
106
107// loaders
108
109
110// utils
111
112
113// consts
114var kEPS = 0.00001;
115var K_GOOGLE_TILE_SIZE = 256;
116// real minZoom calculated here _getMinZoom
117var K_IDLE_TIMEOUT = 100;
118var K_IDLE_CLICK_TIMEOUT = 300;
119var DEFAULT_MIN_ZOOM = 3;
120// Starting with version 3.32, the maps API calls `draw()` each frame during
121// a zoom animation.
122var DRAW_CALLED_DURING_ANIMATION_VERSION = 32;
123var IS_REACT_16 = _reactDom2.default.createPortal !== undefined;
124
125var createPortal = IS_REACT_16 ? _reactDom2.default.createPortal : _reactDom2.default.unstable_renderSubtreeIntoContainer;
126
127function defaultOptions_() /* maps */{
128 return {
129 overviewMapControl: false,
130 streetViewControl: false,
131 rotateControl: true,
132 mapTypeControl: false,
133 // disable poi
134 styles: [{
135 featureType: 'poi',
136 elementType: 'labels',
137 stylers: [{ visibility: 'off' }]
138 }],
139 minZoom: DEFAULT_MIN_ZOOM // dynamically recalculted if possible during init
140 };
141}
142
143var latLng2Obj = function latLng2Obj(latLng) {
144 return (0, _isPlainObject2.default)(latLng) ? latLng : { lat: latLng[0], lng: latLng[1] };
145};
146
147var _checkMinZoom = function _checkMinZoom(zoom, minZoom) {
148 if (process.env.NODE_ENV !== 'production') {
149 if (zoom < minZoom) {
150 console.warn('GoogleMap: ' + // eslint-disable-line
151 'minZoom option is less than recommended ' + 'minZoom option for your map sizes.\n' + 'overrided to value ' + minZoom);
152 }
153 }
154
155 if (minZoom < zoom) {
156 return zoom;
157 }
158 return minZoom;
159};
160
161var isFullScreen = function isFullScreen() {
162 return document.fullscreen || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement;
163};
164
165var GoogleMap = function (_Component) {
166 _inherits(GoogleMap, _Component);
167
168 // eslint-disable-line
169
170 function GoogleMap(props) {
171 _classCallCheck(this, GoogleMap);
172
173 var _this = _possibleConstructorReturn(this, _Component.call(this, props));
174
175 _this._getMinZoom = function () {
176 if (_this.geoService_.getWidth() > 0 || _this.geoService_.getHeight() > 0) {
177 var tilesPerWidth = Math.ceil(_this.geoService_.getWidth() / K_GOOGLE_TILE_SIZE) + 2;
178 var tilesPerHeight = Math.ceil(_this.geoService_.getHeight() / K_GOOGLE_TILE_SIZE) + 2;
179 var maxTilesPerDim = Math.max(tilesPerWidth, tilesPerHeight);
180 return Math.ceil((0, _log2.default)(maxTilesPerDim));
181 }
182 return DEFAULT_MIN_ZOOM;
183 };
184
185 _this._computeMinZoom = function (minZoom) {
186 if (!(0, _isEmpty2.default)(minZoom)) {
187 return minZoom;
188 }
189 return _this._getMinZoom();
190 };
191
192 _this._mapDomResizeCallback = function () {
193 _this.resetSizeOnIdle_ = true;
194 if (_this.maps_) {
195 var originalCenter = _this.props.center || _this.props.defaultCenter;
196 var currentCenter = _this.map_.getCenter();
197 _this.maps_.event.trigger(_this.map_, 'resize');
198 _this.map_.setCenter(_this.props.resetBoundsOnResize ? originalCenter : currentCenter);
199 }
200 };
201
202 _this._setLayers = function (layerTypes) {
203 layerTypes.forEach(function (layerType) {
204 _this.layers_[layerType] = new _this.maps_[layerType]();
205 _this.layers_[layerType].setMap(_this.map_);
206 });
207 };
208
209 _this._renderPortal = function () {
210 return _react2.default.createElement(_google_map_markers2.default, {
211 experimental: _this.props.experimental,
212 onChildClick: _this._onChildClick,
213 onChildMouseDown: _this._onChildMouseDown,
214 onChildMouseEnter: _this._onChildMouseEnter,
215 onChildMouseLeave: _this._onChildMouseLeave,
216 geoService: _this.geoService_,
217 insideMapPanes: true,
218 distanceToMouse: _this.props.distanceToMouse,
219 getHoverDistance: _this._getHoverDistance,
220 dispatcher: _this.markersDispatcher_
221 });
222 };
223
224 _this._initMap = function () {
225 // only initialize the map once
226 if (_this.initialized_) {
227 return;
228 }
229 _this.initialized_ = true;
230
231 var propsCenter = latLng2Obj(_this.props.center || _this.props.defaultCenter);
232 _this.geoService_.setView(propsCenter, _this.props.zoom || _this.props.defaultZoom, 0);
233
234 _this._onBoundsChanged(); // now we can calculate map bounds center etc...
235
236 var bootstrapURLKeys = _extends({}, _this.props.apiKey && { key: _this.props.apiKey }, _this.props.bootstrapURLKeys);
237
238 _this.props.googleMapLoader(bootstrapURLKeys, _this.props.heatmapLibrary).then(function (maps) {
239 if (!_this.mounted_) {
240 return;
241 }
242
243 var centerLatLng = _this.geoService_.getCenter();
244
245 var propsOptions = {
246 zoom: _this.props.zoom || _this.props.defaultZoom,
247 center: new maps.LatLng(centerLatLng.lat, centerLatLng.lng)
248 };
249
250 // Start Heatmap
251 if (_this.props.heatmap.positions) {
252 Object.assign(_this, {
253 heatmap: (0, _google_heatmap.generateHeatmap)(maps, _this.props.heatmap)
254 });
255 (0, _google_heatmap.optionsHeatmap)(_this.heatmap, _this.props.heatmap);
256 }
257 // End Heatmap
258
259 // prevent to exapose full api
260 // next props must be exposed (console.log(Object.keys(pick(maps, isPlainObject))))
261 // "Animation", "ControlPosition", "MapTypeControlStyle", "MapTypeId",
262 // "NavigationControlStyle", "ScaleControlStyle", "StrokePosition",
263 // "SymbolPath", "ZoomControlStyle",
264 // "event", "DirectionsStatus", "DirectionsTravelMode", "DirectionsUnitSystem",
265 // "DistanceMatrixStatus",
266 // "DistanceMatrixElementStatus", "ElevationStatus", "GeocoderLocationType",
267 // "GeocoderStatus", "KmlLayerStatus",
268 // "MaxZoomStatus", "StreetViewStatus", "TransitMode", "TransitRoutePreference",
269 // "TravelMode", "UnitSystem"
270 var mapPlainObjects = (0, _pick2.default)(maps, _isPlainObject2.default);
271 var options = typeof _this.props.options === 'function' ? _this.props.options(mapPlainObjects) : _this.props.options;
272 var defaultOptions = defaultOptions_(mapPlainObjects);
273
274 var draggableOptions = !(0, _isEmpty2.default)(_this.props.draggable) && {
275 draggable: _this.props.draggable
276 };
277
278 var minZoom = _this._computeMinZoom(options.minZoom);
279 _this.minZoom_ = minZoom;
280
281 var preMapOptions = _extends({}, defaultOptions, {
282 minZoom: minZoom
283 }, options, propsOptions);
284
285 _this.defaultDraggableOption_ = !(0, _isEmpty2.default)(preMapOptions.draggable) ? preMapOptions.draggable : _this.defaultDraggableOption_;
286
287 var mapOptions = _extends({}, preMapOptions, draggableOptions);
288
289 mapOptions.minZoom = _checkMinZoom(mapOptions.minZoom, minZoom);
290
291 var map = new maps.Map(_reactDom2.default.findDOMNode(_this.googleMapDom_), mapOptions);
292
293 _this.map_ = map;
294 _this.maps_ = maps;
295
296 _this._setLayers(_this.props.layerTypes);
297
298 // Parse `google.maps.version` to capture the major version number.
299 var versionMatch = maps.version.match(/^3\.(\d+)\./);
300 // The major version is the first (and only) captured group.
301 var mapsVersion = versionMatch && Number(versionMatch[1]);
302
303 // render in overlay
304 var this_ = _this;
305 var overlay = Object.assign(new maps.OverlayView(), {
306 onAdd: function onAdd() {
307 var K_MAX_WIDTH = typeof screen !== 'undefined' ? screen.width + 'px' : '2000px';
308 var K_MAX_HEIGHT = typeof screen !== 'undefined' ? screen.height + 'px' : '2000px';
309
310 var div = document.createElement('div');
311 div.style.backgroundColor = 'transparent';
312 div.style.position = 'absolute';
313 div.style.left = '0px';
314 div.style.top = '0px';
315 div.style.width = K_MAX_WIDTH; // prevents some chrome draw defects
316 div.style.height = K_MAX_HEIGHT;
317
318 if (this_.props.overlayViewDivStyle) {
319 var overlayViewDivStyle = this_.props.overlayViewDivStyle;
320
321 if ((typeof overlayViewDivStyle === 'undefined' ? 'undefined' : _typeof(overlayViewDivStyle)) === 'object') {
322 Object.keys(overlayViewDivStyle).forEach(function (property) {
323 div.style[property] = overlayViewDivStyle[property];
324 });
325 }
326 }
327
328 var panes = this.getPanes();
329 panes.overlayMouseTarget.appendChild(div);
330 this_.geoService_.setMapCanvasProjection(maps, overlay.getProjection());
331
332 if (!IS_REACT_16) {
333 createPortal(this_, this_._renderPortal(), div,
334 // remove prerendered markers
335 function () {
336 return this_.setState({ overlay: div });
337 });
338 } else {
339 this_.setState({ overlay: div });
340 }
341 },
342 onRemove: function onRemove() {
343 var renderedOverlay = this_.state.overlay;
344 if (renderedOverlay && !IS_REACT_16) {
345 _reactDom2.default.unmountComponentAtNode(renderedOverlay);
346 }
347 this_.setState({ overlay: null });
348 },
349 draw: function draw() {
350 this_.updateCounter_++;
351 this_._onBoundsChanged(map, maps, !this_.props.debounced);
352
353 if (!this_.googleApiLoadedCalled_) {
354 this_._onGoogleApiLoaded({ map: map, maps: maps, ref: this_.googleMapDom_ });
355 this_.googleApiLoadedCalled_ = true;
356 }
357
358 if (this_.mouse_) {
359 var latLng = this_.geoService_.fromContainerPixelToLatLng(this_.mouse_);
360 this_.mouse_.lat = latLng.lat;
361 this_.mouse_.lng = latLng.lng;
362 }
363
364 this_._onChildMouseMove();
365
366 if (this_.markersDispatcher_) {
367 this_.markersDispatcher_.emit('kON_CHANGE');
368 if (this_.fireMouseEventOnIdle_) {
369 this_.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
370 }
371 }
372 }
373 });
374
375 _this.overlay_ = overlay;
376
377 overlay.setMap(map);
378 if (_this.props.heatmap.positions) {
379 _this.heatmap.setMap(map);
380 }
381
382 if (_this.props.onTilesLoaded) {
383 maps.event.addListener(map, 'tilesloaded', function () {
384 this_._onTilesLoaded();
385 });
386 }
387
388 maps.event.addListener(map, 'zoom_changed', function () {
389 // recalc position at zoom start
390 if (this_.geoService_.getZoom() !== map.getZoom()) {
391 if (!this_.zoomAnimationInProgress_) {
392 this_.zoomAnimationInProgress_ = true;
393 this_._onZoomAnimationStart(map.zoom);
394 }
395
396 // If draw() is not called each frame during a zoom animation,
397 // simulate it.
398 if (mapsVersion < DRAW_CALLED_DURING_ANIMATION_VERSION) {
399 var TIMEOUT_ZOOM = 300;
400
401 if (new Date().getTime() - _this.zoomControlClickTime_ < TIMEOUT_ZOOM) {
402 // there is strange Google Map Api behavior in chrome when zoom animation of map
403 // is started only on second raf call, if was click on zoom control
404 // or +- keys pressed, so i wait for two rafs before change state
405
406 // this does not fully prevent animation jump
407 // but reduce it's occurence probability
408 (0, _raf2.default)(function () {
409 return (0, _raf2.default)(function () {
410 this_.updateCounter_++;
411 this_._onBoundsChanged(map, maps);
412 });
413 });
414 } else {
415 this_.updateCounter_++;
416 this_._onBoundsChanged(map, maps);
417 }
418 }
419 }
420 });
421
422 maps.event.addListener(map, 'idle', function () {
423 if (_this.resetSizeOnIdle_) {
424 _this._setViewSize();
425 var currMinZoom = _this._computeMinZoom(_this.props.options.minZoom);
426
427 if (currMinZoom !== _this.minZoom_) {
428 _this.minZoom_ = currMinZoom;
429 map.setOptions({ minZoom: currMinZoom });
430 }
431
432 _this.resetSizeOnIdle_ = false;
433 }
434
435 if (this_.zoomAnimationInProgress_) {
436 this_.zoomAnimationInProgress_ = false;
437 this_._onZoomAnimationEnd(map.zoom);
438 }
439
440 this_.updateCounter_++;
441 this_._onBoundsChanged(map, maps);
442
443 this_.dragTime_ = 0;
444
445 if (this_.markersDispatcher_) {
446 this_.markersDispatcher_.emit('kON_CHANGE');
447 }
448 });
449
450 maps.event.addListener(map, 'mouseover', function () {
451 // has advantage over div MouseLeave
452 this_.mouseInMap_ = true;
453 });
454
455 // an alternative way to know the mouse is back within the map
456 // This would not fire when clicking/interacting with google maps
457 // own on-map countrols+markers. This handles an edge case for touch devices
458 // + 'draggable:false' custom option. See #332 for more details.
459 maps.event.addListener(map, 'click', function () {
460 this_.mouseInMap_ = true;
461 });
462
463 maps.event.addListener(map, 'mouseout', function () {
464 // has advantage over div MouseLeave
465 this_.mouseInMap_ = false;
466 this_.mouse_ = null;
467 this_.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
468 });
469
470 maps.event.addListener(map, 'drag', function () {
471 this_.dragTime_ = new Date().getTime();
472 this_._onDrag(map);
473 });
474
475 maps.event.addListener(map, 'dragend', function () {
476 // 'dragend' fires on mouse release.
477 // 'idle' listener waits until drag inertia ends before firing `onDragEnd`
478 var idleListener = maps.event.addListener(map, 'idle', function () {
479 maps.event.removeListener(idleListener);
480 this_._onDragEnd(map);
481 });
482 });
483 // user choosing satellite vs roads, etc
484 maps.event.addListener(map, 'maptypeid_changed', function () {
485 this_._onMapTypeIdChange(map.getMapTypeId());
486 });
487 }).catch(function (e) {
488 // notify callback of load failure
489 _this._onGoogleApiLoaded({
490 map: null,
491 maps: null,
492 ref: _this.googleMapDom_
493 });
494 console.error(e); // eslint-disable-line no-console
495 throw e;
496 });
497 };
498
499 _this._onGoogleApiLoaded = function () {
500 if (_this.props.onGoogleApiLoaded) {
501 var _this$props;
502
503 if (process.env.NODE_ENV !== 'production' && _this.props.yesIWantToUseGoogleMapApiInternals !== true) {
504 console.warn('GoogleMap: ' + // eslint-disable-line
505 'Usage of internal api objects is dangerous ' + 'and can cause a lot of issues.\n' + 'To hide this warning add yesIWantToUseGoogleMapApiInternals={true} ' + 'to <GoogleMap instance');
506 }
507
508 (_this$props = _this.props).onGoogleApiLoaded.apply(_this$props, arguments);
509 }
510 };
511
512 _this._getHoverDistance = function () {
513 return _this.props.hoverDistance;
514 };
515
516 _this._onDrag = function () {
517 var _this$props2;
518
519 return _this.props.onDrag && (_this$props2 = _this.props).onDrag.apply(_this$props2, arguments);
520 };
521
522 _this._onDragEnd = function () {
523 var _this$props3;
524
525 return _this.props.onDragEnd && (_this$props3 = _this.props).onDragEnd.apply(_this$props3, arguments);
526 };
527
528 _this._onMapTypeIdChange = function () {
529 var _this$props4;
530
531 return _this.props.onMapTypeIdChange && (_this$props4 = _this.props).onMapTypeIdChange.apply(_this$props4, arguments);
532 };
533
534 _this._onZoomAnimationStart = function () {
535 var _this$props5;
536
537 return _this.props.onZoomAnimationStart && (_this$props5 = _this.props).onZoomAnimationStart.apply(_this$props5, arguments);
538 };
539
540 _this._onZoomAnimationEnd = function () {
541 var _this$props6;
542
543 return _this.props.onZoomAnimationEnd && (_this$props6 = _this.props).onZoomAnimationEnd.apply(_this$props6, arguments);
544 };
545
546 _this._onTilesLoaded = function () {
547 return _this.props.onTilesLoaded && _this.props.onTilesLoaded();
548 };
549
550 _this._onChildClick = function () {
551 if (_this.props.onChildClick) {
552 var _this$props7;
553
554 return (_this$props7 = _this.props).onChildClick.apply(_this$props7, arguments);
555 }
556 return undefined;
557 };
558
559 _this._onChildMouseDown = function (hoverKey, childProps) {
560 _this.childMouseDownArgs_ = [hoverKey, childProps];
561 if (_this.props.onChildMouseDown) {
562 _this.props.onChildMouseDown(hoverKey, childProps, _extends({}, _this.mouse_));
563 }
564 };
565
566 _this._onChildMouseUp = function () {
567 if (_this.childMouseDownArgs_) {
568 if (_this.props.onChildMouseUp) {
569 var _this$props8;
570
571 (_this$props8 = _this.props).onChildMouseUp.apply(_this$props8, _this.childMouseDownArgs_.concat([_extends({}, _this.mouse_)]));
572 }
573 _this.childMouseDownArgs_ = null;
574 _this.childMouseUpTime_ = new Date().getTime();
575 }
576 };
577
578 _this._onChildMouseMove = function () {
579 if (_this.childMouseDownArgs_) {
580 if (_this.props.onChildMouseMove) {
581 var _this$props9;
582
583 (_this$props9 = _this.props).onChildMouseMove.apply(_this$props9, _this.childMouseDownArgs_.concat([_extends({}, _this.mouse_)]));
584 }
585 }
586 };
587
588 _this._onChildMouseEnter = function () {
589 if (_this.props.onChildMouseEnter) {
590 var _this$props10;
591
592 return (_this$props10 = _this.props).onChildMouseEnter.apply(_this$props10, arguments);
593 }
594 return undefined;
595 };
596
597 _this._onChildMouseLeave = function () {
598 if (_this.props.onChildMouseLeave) {
599 var _this$props11;
600
601 return (_this$props11 = _this.props).onChildMouseLeave.apply(_this$props11, arguments);
602 }
603 return undefined;
604 };
605
606 _this._setViewSize = function () {
607 if (!_this.mounted_) return;
608 if (isFullScreen()) {
609 _this.geoService_.setViewSize(window.innerWidth, window.innerHeight);
610 } else {
611 var mapDom = _reactDom2.default.findDOMNode(_this.googleMapDom_);
612 _this.geoService_.setViewSize(mapDom.clientWidth, mapDom.clientHeight);
613 }
614 _this._onBoundsChanged();
615 };
616
617 _this._onWindowResize = function () {
618 _this.resetSizeOnIdle_ = true;
619 };
620
621 _this._onMapMouseMove = function (e) {
622 if (!_this.mouseInMap_) return;
623
624 var currTime = new Date().getTime();
625 var K_RECALC_CLIENT_RECT_MS = 50;
626
627 if (currTime - _this.mouseMoveTime_ > K_RECALC_CLIENT_RECT_MS) {
628 _this.boundingRect_ = e.currentTarget.getBoundingClientRect();
629 }
630 _this.mouseMoveTime_ = currTime;
631
632 var mousePosX = e.clientX - _this.boundingRect_.left;
633 var mousePosY = e.clientY - _this.boundingRect_.top;
634
635 if (!_this.mouse_) {
636 _this.mouse_ = { x: 0, y: 0, lat: 0, lng: 0 };
637 }
638
639 _this.mouse_.x = mousePosX;
640 _this.mouse_.y = mousePosY;
641
642 var latLng = _this.geoService_.fromContainerPixelToLatLng(_this.mouse_);
643 _this.mouse_.lat = latLng.lat;
644 _this.mouse_.lng = latLng.lng;
645
646 _this._onChildMouseMove();
647
648 if (currTime - _this.dragTime_ < K_IDLE_TIMEOUT) {
649 _this.fireMouseEventOnIdle_ = true;
650 } else {
651 _this.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
652 _this.fireMouseEventOnIdle_ = false;
653 }
654 };
655
656 _this._onClick = function () {
657 var _this$props12;
658
659 return _this.props.onClick && !_this.childMouseDownArgs_ && new Date().getTime() - _this.childMouseUpTime_ > K_IDLE_CLICK_TIMEOUT && _this.dragTime_ === 0 && (_this$props12 = _this.props).onClick.apply(_this$props12, arguments);
660 };
661
662 _this._onMapClick = function (event) {
663 if (_this.markersDispatcher_) {
664 // support touch events and recalculate mouse position on click
665 _this._onMapMouseMove(event);
666 var currTime = new Date().getTime();
667 if (currTime - _this.dragTime_ > K_IDLE_TIMEOUT) {
668 if (_this.mouse_) {
669 _this._onClick(_extends({}, _this.mouse_, {
670 event: event
671 }));
672 }
673
674 _this.markersDispatcher_.emit('kON_CLICK', event);
675 }
676 }
677 };
678
679 _this._onMapMouseDownNative = function (event) {
680 if (!_this.mouseInMap_) return;
681
682 _this._onMapMouseDown(event);
683 };
684
685 _this._onMapMouseDown = function (event) {
686 if (_this.markersDispatcher_) {
687 var currTime = new Date().getTime();
688 if (currTime - _this.dragTime_ > K_IDLE_TIMEOUT) {
689 // Hovered marker detected at mouse move could be deleted at mouse down time
690 // so it will be good to force hovered marker recalculation
691 _this._onMapMouseMove(event);
692 _this.markersDispatcher_.emit('kON_MDOWN', event);
693 }
694 }
695 };
696
697 _this._onMapMouseDownCapture = function () {
698 if ((0, _detect2.default)().isChrome) {
699 // to fix strange zoom in chrome
700 _this.zoomControlClickTime_ = new Date().getTime();
701 }
702 };
703
704 _this._onKeyDownCapture = function () {
705 if ((0, _detect2.default)().isChrome) {
706 _this.zoomControlClickTime_ = new Date().getTime();
707 }
708 };
709
710 _this._isCenterDefined = function (center) {
711 return center && ((0, _isPlainObject2.default)(center) && (0, _isNumber2.default)(center.lat) && (0, _isNumber2.default)(center.lng) || center.length === 2 && (0, _isNumber2.default)(center[0]) && (0, _isNumber2.default)(center[1]));
712 };
713
714 _this._onBoundsChanged = function (map, maps, callExtBoundsChange) {
715 if (map) {
716 var gmC = map.getCenter();
717 _this.geoService_.setView([gmC.lat(), gmC.lng()], map.getZoom(), 0);
718 }
719
720 if ((_this.props.onChange || _this.props.onBoundsChange) && _this.geoService_.canProject()) {
721 var zoom = _this.geoService_.getZoom();
722 var bounds = _this.geoService_.getBounds();
723 var centerLatLng = _this.geoService_.getCenter();
724
725 if (!(0, _isArraysEqualEps2.default)(bounds, _this.prevBounds_, kEPS)) {
726 if (callExtBoundsChange !== false) {
727 var marginBounds = _this.geoService_.getBounds(_this.props.margin);
728 if (_this.props.onBoundsChange) {
729 _this.props.onBoundsChange(_this.centerIsObject_ ? _extends({}, centerLatLng) : [centerLatLng.lat, centerLatLng.lng], zoom, bounds, marginBounds);
730 }
731
732 if (_this.props.onChange) {
733 _this.props.onChange({
734 center: _extends({}, centerLatLng),
735 zoom: zoom,
736 bounds: {
737 nw: {
738 lat: bounds[0],
739 lng: bounds[1]
740 },
741 se: {
742 lat: bounds[2],
743 lng: bounds[3]
744 },
745 sw: {
746 lat: bounds[4],
747 lng: bounds[5]
748 },
749 ne: {
750 lat: bounds[6],
751 lng: bounds[7]
752 }
753 },
754 marginBounds: {
755 nw: {
756 lat: marginBounds[0],
757 lng: marginBounds[1]
758 },
759 se: {
760 lat: marginBounds[2],
761 lng: marginBounds[3]
762 },
763 sw: {
764 lat: marginBounds[4],
765 lng: marginBounds[5]
766 },
767 ne: {
768 lat: marginBounds[6],
769 lng: marginBounds[7]
770 }
771 },
772
773 size: _this.geoService_.hasSize() ? {
774 width: _this.geoService_.getWidth(),
775 height: _this.geoService_.getHeight()
776 } : {
777 width: 0,
778 height: 0
779 }
780 });
781 }
782
783 _this.prevBounds_ = bounds;
784 }
785 }
786 }
787 };
788
789 _this._registerChild = function (ref) {
790 _this.googleMapDom_ = ref;
791 };
792
793 _this.mounted_ = false;
794 _this.initialized_ = false;
795 _this.googleApiLoadedCalled_ = false;
796
797 _this.map_ = null;
798 _this.maps_ = null;
799 _this.prevBounds_ = null;
800 _this.heatmap = null;
801
802 _this.layers_ = {};
803
804 _this.mouse_ = null;
805 _this.mouseMoveTime_ = 0;
806 _this.boundingRect_ = null;
807 _this.mouseInMap_ = true;
808
809 _this.dragTime_ = 0;
810 _this.fireMouseEventOnIdle_ = false;
811 _this.updateCounter_ = 0;
812
813 _this.markersDispatcher_ = new _marker_dispatcher2.default(_this);
814 _this.geoService_ = new _geo2.default(K_GOOGLE_TILE_SIZE);
815 _this.centerIsObject_ = (0, _isPlainObject2.default)(_this.props.center);
816
817 _this.minZoom_ = DEFAULT_MIN_ZOOM;
818 _this.defaultDraggableOption_ = true;
819
820 _this.zoomControlClickTime_ = 0;
821
822 _this.childMouseDownArgs_ = null;
823 _this.childMouseUpTime_ = 0;
824
825 _this.googleMapDom_ = null;
826
827 if (process.env.NODE_ENV !== 'production') {
828 if (_this.props.apiKey) {
829 console.warn('GoogleMap: ' + // eslint-disable-line no-console
830 'apiKey is deprecated, use ' + 'bootstrapURLKeys={{key: YOUR_API_KEY}} instead.');
831 }
832
833 if (_this.props.onBoundsChange) {
834 console.warn('GoogleMap: ' + // eslint-disable-line no-console
835 'onBoundsChange is deprecated, use ' + 'onChange({center, zoom, bounds, ...other}) instead.');
836 }
837
838 if ((0, _isEmpty2.default)(_this.props.center) && (0, _isEmpty2.default)(_this.props.defaultCenter)) {
839 console.warn('GoogleMap: center or defaultCenter property must be defined' // eslint-disable-line no-console
840 );
841 }
842
843 if ((0, _isEmpty2.default)(_this.props.zoom) && (0, _isEmpty2.default)(_this.props.defaultZoom)) {
844 console.warn('GoogleMap: zoom or defaultZoom property must be defined' // eslint-disable-line no-console
845 );
846 }
847 }
848
849 if (_this._isCenterDefined(_this.props.center || _this.props.defaultCenter)) {
850 var propsCenter = latLng2Obj(_this.props.center || _this.props.defaultCenter);
851 _this.geoService_.setView(propsCenter, _this.props.zoom || _this.props.defaultZoom, 0);
852 }
853
854 _this.zoomAnimationInProgress_ = false;
855
856 _this.state = {
857 overlay: null
858 };
859 return _this;
860 }
861
862 GoogleMap.prototype.componentDidMount = function componentDidMount() {
863 var _this2 = this;
864
865 this.mounted_ = true;
866 (0, _passiveEvents2.default)(window, 'resize', this._onWindowResize, false);
867 (0, _passiveEvents2.default)(window, 'keydown', this._onKeyDownCapture, true);
868 var mapDom = _reactDom2.default.findDOMNode(this.googleMapDom_);
869 // gmap can't prevent map drag if mousedown event already occured
870 // the only workaround I find is prevent mousedown native browser event
871
872 if (mapDom) {
873 (0, _passiveEvents2.default)(mapDom, 'mousedown', this._onMapMouseDownNative, true);
874 }
875
876 (0, _passiveEvents2.default)(window, 'mouseup', this._onChildMouseUp, false);
877 var bootstrapURLKeys = _extends({}, this.props.apiKey && { key: this.props.apiKey }, this.props.bootstrapURLKeys);
878
879 this.props.googleMapLoader(bootstrapURLKeys, this.props.heatmapLibrary); // we can start load immediatly
880
881 setTimeout(function () {
882 // to detect size
883 _this2._setViewSize();
884 if (_this2._isCenterDefined(_this2.props.center || _this2.props.defaultCenter)) {
885 _this2._initMap();
886 }
887 }, 0, this);
888 if (this.props.resetBoundsOnResize) {
889 var that = this;
890 _detectElementResize2.default.addResizeListener(mapDom, that._mapDomResizeCallback);
891 }
892 };
893
894 GoogleMap.prototype.UNSAFE_componentWillReceiveProps = function UNSAFE_componentWillReceiveProps(nextProps) {
895 var _this3 = this;
896
897 if (process.env.NODE_ENV !== 'production') {
898 if (!(0, _shallowEqual2.default)(this.props.defaultCenter, nextProps.defaultCenter)) {
899 console.warn("GoogleMap: defaultCenter prop changed. You can't change default props.");
900 }
901
902 if (!(0, _shallowEqual2.default)(this.props.defaultZoom, nextProps.defaultZoom)) {
903 console.warn("GoogleMap: defaultZoom prop changed. You can't change default props.");
904 }
905 }
906
907 if (!this._isCenterDefined(this.props.center) && this._isCenterDefined(nextProps.center)) {
908 setTimeout(function () {
909 return _this3._initMap();
910 }, 0);
911 }
912
913 if (this.map_) {
914 var centerLatLng = this.geoService_.getCenter();
915 if (this._isCenterDefined(nextProps.center)) {
916 var nextPropsCenter = latLng2Obj(nextProps.center);
917 var currCenter = this._isCenterDefined(this.props.center) ? latLng2Obj(this.props.center) : null;
918
919 if (!currCenter || Math.abs(nextPropsCenter.lat - currCenter.lat) + Math.abs(nextPropsCenter.lng - currCenter.lng) > kEPS) {
920 if (Math.abs(nextPropsCenter.lat - centerLatLng.lat) + Math.abs(nextPropsCenter.lng - centerLatLng.lng) > kEPS) {
921 this.map_.panTo({
922 lat: nextPropsCenter.lat,
923 lng: nextPropsCenter.lng
924 });
925 }
926 }
927 }
928
929 if (!(0, _isEmpty2.default)(nextProps.zoom)) {
930 // if zoom chaged by user
931 if (Math.abs(nextProps.zoom - this.props.zoom) > 0) {
932 this.map_.setZoom(nextProps.zoom);
933 }
934 }
935
936 if (!(0, _isEmpty2.default)(this.props.draggable) && (0, _isEmpty2.default)(nextProps.draggable)) {
937 // reset to default
938 this.map_.setOptions({ draggable: this.defaultDraggableOption_ });
939 } else if (!(0, _shallowEqual2.default)(this.props.draggable, nextProps.draggable)) {
940 // also prevent this on window 'mousedown' event to prevent map move
941 this.map_.setOptions({ draggable: nextProps.draggable });
942 }
943
944 // use shallowEqual to try avoid calling map._setOptions if only the ref changes
945 if (!(0, _isEmpty2.default)(nextProps.options) && !(0, _shallowEqual2.default)(this.props.options, nextProps.options)) {
946 var mapPlainObjects = (0, _pick2.default)(this.maps_, _isPlainObject2.default);
947 var options = typeof nextProps.options === 'function' ? nextProps.options(mapPlainObjects) : nextProps.options;
948 // remove zoom, center and draggable options as these are managed by google-maps-react
949 options = (0, _omit2.default)(options, ['zoom', 'center', 'draggable']);
950
951 if ('minZoom' in options) {
952 var minZoom = this._computeMinZoom(options.minZoom);
953 options.minZoom = _checkMinZoom(options.minZoom, minZoom);
954 }
955
956 this.map_.setOptions(options);
957 }
958
959 if (!(0, _shallowEqual2.default)(nextProps.layerTypes, this.props.layerTypes)) {
960 Object.keys(this.layers_).forEach(function (layerKey) {
961 _this3.layers_[layerKey].setMap(null);
962 delete _this3.layers_[layerKey];
963 });
964 this._setLayers(nextProps.layerTypes);
965 }
966
967 if (this.heatmap && !(0, _shallowEqual2.default)(nextProps.heatmap.positions, this.props.heatmap.positions)) {
968 this.heatmap.setData(nextProps.heatmap.positions.map(function (p) {
969 return {
970 location: new _this3.maps_.LatLng(p.lat, p.lng),
971 weight: p.weight
972 };
973 }));
974 }
975 }
976 };
977
978 GoogleMap.prototype.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
979 // draggable does not affect inner components
980 return !(0, _shallowEqual2.default)((0, _omit2.default)(this.props, ['draggable']), (0, _omit2.default)(nextProps, ['draggable'])) || !(0, _shallowEqual2.default)(this.state, nextState);
981 };
982
983 GoogleMap.prototype.componentDidUpdate = function componentDidUpdate(prevProps) {
984 this.markersDispatcher_.emit('kON_CHANGE');
985
986 if (!(0, _shallowEqual2.default)(this.props.hoverDistance, prevProps.hoverDistance)) {
987 this.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
988 }
989 };
990
991 GoogleMap.prototype.componentWillUnmount = function componentWillUnmount() {
992 this.mounted_ = false;
993 var mapDom = _reactDom2.default.findDOMNode(this.googleMapDom_);
994 if (mapDom) {
995 mapDom.removeEventListener('mousedown', this._onMapMouseDownNative, true);
996 }
997 window.removeEventListener('resize', this._onWindowResize);
998 window.removeEventListener('keydown', this._onKeyDownCapture);
999 window.removeEventListener('mouseup', this._onChildMouseUp, false);
1000 if (this.props.resetBoundsOnResize) {
1001 _detectElementResize2.default.removeResizeListener(mapDom, this._mapDomResizeCallback);
1002 }
1003
1004 if (this.overlay_) {
1005 // this triggers overlay_.onRemove(), which will unmount the <GoogleMapMarkers/>
1006 this.overlay_.setMap(null);
1007 }
1008
1009 if (this.maps_ && this.map_ && this.props.shouldUnregisterMapOnUnmount) {
1010 // fix google, as otherwise listeners works even without map
1011 this.map_.setOptions({ scrollwheel: false });
1012 this.maps_.event.clearInstanceListeners(this.map_);
1013 }
1014
1015 if (this.props.shouldUnregisterMapOnUnmount) {
1016 this.map_ = null;
1017 this.maps_ = null;
1018 }
1019 this.markersDispatcher_.dispose();
1020
1021 this.resetSizeOnIdle_ = false;
1022
1023 if (this.props.shouldUnregisterMapOnUnmount) {
1024 delete this.map_;
1025 delete this.markersDispatcher_;
1026 }
1027 };
1028 // calc minZoom if map size available
1029 // it's better to not set minZoom less than this calculation gives
1030 // otherwise there is no homeomorphism between screen coordinates and map
1031 // (one map coordinate can have different screen coordinates)
1032
1033
1034 // this method works only if this.props.onChildMouseDown was called
1035
1036
1037 // this method works only if this.props.onChildMouseDown was called
1038
1039
1040 // K_IDLE_CLICK_TIMEOUT - looks like 300 is enough
1041
1042
1043 // gmap can't prevent map drag if mousedown event already occured
1044 // the only workaround I find is prevent mousedown native browser event
1045
1046
1047 GoogleMap.prototype.render = function render() {
1048 var overlay = this.state.overlay;
1049 var mapMarkerPrerender = !overlay ? _react2.default.createElement(_google_map_markers_prerender2.default, {
1050 experimental: this.props.experimental,
1051 onChildClick: this._onChildClick,
1052 onChildMouseDown: this._onChildMouseDown,
1053 onChildMouseEnter: this._onChildMouseEnter,
1054 onChildMouseLeave: this._onChildMouseLeave,
1055 geoService: this.geoService_,
1056 insideMapPanes: false,
1057 distanceToMouse: this.props.distanceToMouse,
1058 getHoverDistance: this._getHoverDistance,
1059 dispatcher: this.markersDispatcher_
1060 }) : null;
1061
1062 return _react2.default.createElement(
1063 'div',
1064 {
1065 style: this.props.style,
1066 onMouseMove: this._onMapMouseMove,
1067 onMouseDownCapture: this._onMapMouseDownCapture,
1068 onClick: this._onMapClick
1069 },
1070 _react2.default.createElement(_google_map_map2.default, { registerChild: this._registerChild }),
1071 IS_REACT_16 && overlay && createPortal(this._renderPortal(), overlay),
1072 mapMarkerPrerender
1073 );
1074 };
1075
1076 return GoogleMap;
1077}(_react.Component);
1078
1079GoogleMap.propTypes = {
1080 apiKey: _propTypes2.default.string,
1081 bootstrapURLKeys: _propTypes2.default.any,
1082
1083 defaultCenter: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.shape({
1084 lat: _propTypes2.default.number,
1085 lng: _propTypes2.default.number
1086 })]),
1087 center: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.shape({
1088 lat: _propTypes2.default.number,
1089 lng: _propTypes2.default.number
1090 })]),
1091 defaultZoom: _propTypes2.default.number,
1092 zoom: _propTypes2.default.number,
1093 onBoundsChange: _propTypes2.default.func,
1094 onChange: _propTypes2.default.func,
1095 onClick: _propTypes2.default.func,
1096 onChildClick: _propTypes2.default.func,
1097 onChildMouseDown: _propTypes2.default.func,
1098 onChildMouseUp: _propTypes2.default.func,
1099 onChildMouseMove: _propTypes2.default.func,
1100 onChildMouseEnter: _propTypes2.default.func,
1101 onChildMouseLeave: _propTypes2.default.func,
1102 onZoomAnimationStart: _propTypes2.default.func,
1103 onZoomAnimationEnd: _propTypes2.default.func,
1104 onDrag: _propTypes2.default.func,
1105 onDragEnd: _propTypes2.default.func,
1106 onMapTypeIdChange: _propTypes2.default.func,
1107 onTilesLoaded: _propTypes2.default.func,
1108 options: _propTypes2.default.any,
1109 distanceToMouse: _propTypes2.default.func,
1110 hoverDistance: _propTypes2.default.number,
1111 debounced: _propTypes2.default.bool,
1112 margin: _propTypes2.default.array,
1113 googleMapLoader: _propTypes2.default.any,
1114 onGoogleApiLoaded: _propTypes2.default.func,
1115 yesIWantToUseGoogleMapApiInternals: _propTypes2.default.bool,
1116 draggable: _propTypes2.default.bool,
1117 style: _propTypes2.default.any,
1118 resetBoundsOnResize: _propTypes2.default.bool,
1119 layerTypes: _propTypes2.default.arrayOf(_propTypes2.default.string), // ['TransitLayer', 'TrafficLayer']
1120 shouldUnregisterMapOnUnmount: _propTypes2.default.bool
1121};
1122GoogleMap.defaultProps = {
1123 distanceToMouse: function distanceToMouse(pt, mousePos /* , markerProps */) {
1124 return Math.sqrt((pt.x - mousePos.x) * (pt.x - mousePos.x) + (pt.y - mousePos.y) * (pt.y - mousePos.y));
1125 },
1126
1127 hoverDistance: 30,
1128 debounced: true,
1129 options: defaultOptions_,
1130 googleMapLoader: _google_map_loader2.default,
1131 yesIWantToUseGoogleMapApiInternals: false,
1132 style: {
1133 width: '100%',
1134 height: '100%',
1135 margin: 0,
1136 padding: 0,
1137 position: 'relative'
1138 },
1139 layerTypes: [],
1140 heatmap: {},
1141 heatmapLibrary: false,
1142 shouldUnregisterMapOnUnmount: true
1143};
1144GoogleMap.googleMapLoader = _google_map_loader2.default;
1145exports.default = GoogleMap;
\No newline at end of file