UNPKG

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