google-map-react
Version:
isomorphic google map react component, allows render react components on the google map
1,090 lines (863 loc) • 39.8 kB
JavaScript
'use strict';
exports.__esModule = true;
var _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; };
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _google_map_map = require('./google_map_map');
var _google_map_map2 = _interopRequireDefault(_google_map_map);
var _marker_dispatcher = require('./marker_dispatcher');
var _marker_dispatcher2 = _interopRequireDefault(_marker_dispatcher);
var _google_map_markers = require('./google_map_markers');
var _google_map_markers2 = _interopRequireDefault(_google_map_markers);
var _google_map_markers_prerender = require('./google_map_markers_prerender');
var _google_map_markers_prerender2 = _interopRequireDefault(_google_map_markers_prerender);
var _google_heatmap = require('./google_heatmap');
var _google_map_loader = require('./loaders/google_map_loader');
var _google_map_loader2 = _interopRequireDefault(_google_map_loader);
var _geo = require('./utils/geo');
var _geo2 = _interopRequireDefault(_geo);
var _raf = require('./utils/raf');
var _raf2 = _interopRequireDefault(_raf);
var _pick = require('./utils/pick');
var _pick2 = _interopRequireDefault(_pick);
var _omit = require('./utils/omit');
var _omit2 = _interopRequireDefault(_omit);
var _log = require('./utils/math/log2');
var _log2 = _interopRequireDefault(_log);
var _isEmpty = require('./utils/isEmpty');
var _isEmpty2 = _interopRequireDefault(_isEmpty);
var _isNumber = require('./utils/isNumber');
var _isNumber2 = _interopRequireDefault(_isNumber);
var _detect = require('./utils/detect');
var _detect2 = _interopRequireDefault(_detect);
var _shallowEqual = require('./utils/shallowEqual');
var _shallowEqual2 = _interopRequireDefault(_shallowEqual);
var _isPlainObject = require('./utils/isPlainObject');
var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
var _isArraysEqualEps = require('./utils/isArraysEqualEps');
var _isArraysEqualEps2 = _interopRequireDefault(_isArraysEqualEps);
var _detectElementResize = require('./utils/detectElementResize');
var _detectElementResize2 = _interopRequireDefault(_detectElementResize);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _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; }
function _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 */
// helpers
// loaders
// utils
// consts
var kEPS = 0.00001;
var K_GOOGLE_TILE_SIZE = 256;
// real minZoom calculated here _getMinZoom
var K_IDLE_TIMEOUT = 100;
var K_IDLE_CLICK_TIMEOUT = 300;
var DEFAULT_MIN_ZOOM = 3;
// Starting with version 3.32, the maps API calls `draw()` each frame during
// a zoom animation.
var DRAW_CALLED_DURING_ANIMATION_VERSION = 32;
function defaultOptions_() /* maps */{
return {
overviewMapControl: false,
streetViewControl: false,
rotateControl: true,
mapTypeControl: false,
// disable poi
styles: [{
featureType: 'poi',
elementType: 'labels',
stylers: [{ visibility: 'off' }]
}],
minZoom: DEFAULT_MIN_ZOOM // dynamically recalculted if possible during init
};
}
var latLng2Obj = function latLng2Obj(latLng) {
return (0, _isPlainObject2.default)(latLng) ? latLng : { lat: latLng[0], lng: latLng[1] };
};
var _checkMinZoom = function _checkMinZoom(zoom, minZoom) {
if (process.env.NODE_ENV !== 'production') {
if (zoom < minZoom) {
console.warn('GoogleMap: ' + // eslint-disable-line
'minZoom option is less than recommended ' + 'minZoom option for your map sizes.\n' + 'overrided to value ' + minZoom);
}
}
if (minZoom < zoom) {
return zoom;
}
return minZoom;
};
var isFullScreen = function isFullScreen() {
return document.fullscreen || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement;
};
var GoogleMap = function (_Component) {
_inherits(GoogleMap, _Component);
// eslint-disable-line
function GoogleMap(props) {
_classCallCheck(this, GoogleMap);
var _this = _possibleConstructorReturn(this, _Component.call(this, props));
_this._getMinZoom = function () {
if (_this.geoService_.getWidth() > 0 || _this.geoService_.getHeight() > 0) {
var tilesPerWidth = Math.ceil(_this.geoService_.getWidth() / K_GOOGLE_TILE_SIZE) + 2;
var tilesPerHeight = Math.ceil(_this.geoService_.getHeight() / K_GOOGLE_TILE_SIZE) + 2;
var maxTilesPerDim = Math.max(tilesPerWidth, tilesPerHeight);
return Math.ceil((0, _log2.default)(maxTilesPerDim));
}
return DEFAULT_MIN_ZOOM;
};
_this._computeMinZoom = function (minZoom) {
if (!(0, _isEmpty2.default)(minZoom)) {
return minZoom;
}
return _this._getMinZoom();
};
_this._mapDomResizeCallback = function () {
_this.resetSizeOnIdle_ = true;
if (_this.maps_) {
var originalCenter = _this.props.center || _this.props.defaultCenter;
var currentCenter = _this.map_.getCenter();
_this.maps_.event.trigger(_this.map_, 'resize');
_this.map_.setCenter(_this.props.resetBoundsOnResize ? originalCenter : currentCenter);
}
};
_this._setLayers = function (layerTypes) {
layerTypes.forEach(function (layerType) {
_this.layers_[layerType] = new _this.maps_[layerType]();
_this.layers_[layerType].setMap(_this.map_);
});
};
_this._initMap = function () {
// only initialize the map once
if (_this.initialized_) {
return;
}
_this.initialized_ = true;
var propsCenter = latLng2Obj(_this.props.center || _this.props.defaultCenter);
_this.geoService_.setView(propsCenter, _this.props.zoom || _this.props.defaultZoom, 0);
_this._onBoundsChanged(); // now we can calculate map bounds center etc...
var bootstrapURLKeys = _extends({}, _this.props.apiKey && { key: _this.props.apiKey }, _this.props.bootstrapURLKeys);
_this.props.googleMapLoader(bootstrapURLKeys, _this.props.heatmapLibrary).then(function (maps) {
if (!_this.mounted_) {
return;
}
var centerLatLng = _this.geoService_.getCenter();
var propsOptions = {
zoom: _this.props.zoom || _this.props.defaultZoom,
center: new maps.LatLng(centerLatLng.lat, centerLatLng.lng)
};
// Start Heatmap
if (_this.props.heatmap.positions) {
Object.assign(_this, {
heatmap: (0, _google_heatmap.generateHeatmap)(maps, _this.props.heatmap)
});
(0, _google_heatmap.optionsHeatmap)(_this.heatmap, _this.props.heatmap);
}
// End Heatmap
// prevent to exapose full api
// next props must be exposed (console.log(Object.keys(pick(maps, isPlainObject))))
// "Animation", "ControlPosition", "MapTypeControlStyle", "MapTypeId",
// "NavigationControlStyle", "ScaleControlStyle", "StrokePosition",
// "SymbolPath", "ZoomControlStyle",
// "event", "DirectionsStatus", "DirectionsTravelMode", "DirectionsUnitSystem",
// "DistanceMatrixStatus",
// "DistanceMatrixElementStatus", "ElevationStatus", "GeocoderLocationType",
// "GeocoderStatus", "KmlLayerStatus",
// "MaxZoomStatus", "StreetViewStatus", "TransitMode", "TransitRoutePreference",
// "TravelMode", "UnitSystem"
var mapPlainObjects = (0, _pick2.default)(maps, _isPlainObject2.default);
var options = typeof _this.props.options === 'function' ? _this.props.options(mapPlainObjects) : _this.props.options;
var defaultOptions = defaultOptions_(mapPlainObjects);
var draggableOptions = !(0, _isEmpty2.default)(_this.props.draggable) && {
draggable: _this.props.draggable
};
var minZoom = _this._computeMinZoom(options.minZoom);
_this.minZoom_ = minZoom;
var preMapOptions = _extends({}, defaultOptions, {
minZoom: minZoom
}, options, propsOptions);
_this.defaultDraggableOption_ = !(0, _isEmpty2.default)(preMapOptions.draggable) ? preMapOptions.draggable : _this.defaultDraggableOption_;
var mapOptions = _extends({}, preMapOptions, draggableOptions);
mapOptions.minZoom = _checkMinZoom(mapOptions.minZoom, minZoom);
var map = new maps.Map(_reactDom2.default.findDOMNode(_this.googleMapDom_), mapOptions);
_this.map_ = map;
_this.maps_ = maps;
_this._setLayers(_this.props.layerTypes);
// Parse `google.maps.version` to capture the major version number.
var versionMatch = maps.version.match(/^3\.(\d+)\./);
// The major version is the first (and only) captured group.
var mapsVersion = versionMatch && Number(versionMatch[1]);
// render in overlay
var this_ = _this;
var overlay = Object.assign(new maps.OverlayView(), {
onAdd: function onAdd() {
var K_MAX_WIDTH = typeof screen !== 'undefined' ? screen.width + 'px' : '2000px';
var K_MAX_HEIGHT = typeof screen !== 'undefined' ? screen.height + 'px' : '2000px';
var div = document.createElement('div');
this.div = div;
div.style.backgroundColor = 'transparent';
div.style.position = 'absolute';
div.style.left = '0px';
div.style.top = '0px';
div.style.width = K_MAX_WIDTH; // prevents some chrome draw defects
div.style.height = K_MAX_HEIGHT;
var panes = this.getPanes();
panes.overlayMouseTarget.appendChild(div);
this_.geoService_.setMapCanvasProjection(maps, overlay.getProjection());
_reactDom2.default.unstable_renderSubtreeIntoContainer(this_, _react2.default.createElement(_google_map_markers2.default, {
experimental: this_.props.experimental,
onChildClick: this_._onChildClick,
onChildMouseDown: this_._onChildMouseDown,
onChildMouseEnter: this_._onChildMouseEnter,
onChildMouseLeave: this_._onChildMouseLeave,
geoService: this_.geoService_,
projectFromLeftTop: true,
distanceToMouse: this_.props.distanceToMouse,
getHoverDistance: this_._getHoverDistance,
dispatcher: this_.markersDispatcher_
}), div,
// remove prerendered markers
function () {
return this_.setState({ overlayCreated: true });
});
},
onRemove: function onRemove() {
if (this.div) {
_reactDom2.default.unmountComponentAtNode(this.div);
}
},
draw: function draw() {
var div = overlay.div;
var overlayProjection = overlay.getProjection();
var ptx = overlayProjection.fromLatLngToDivPixel(overlayProjection.fromContainerPixelToLatLng({ x: 0, y: 0 }));
// need round for safari still can't find what need for firefox
var ptxRounded = (0, _detect2.default)().isSafari ? { x: Math.round(ptx.x), y: Math.round(ptx.y) } : { x: ptx.x, y: ptx.y };
this_.updateCounter_++;
this_._onBoundsChanged(map, maps, !this_.props.debounced);
if (!this_.googleApiLoadedCalled_) {
this_._onGoogleApiLoaded({ map: map, maps: maps });
this_.googleApiLoadedCalled_ = true;
}
div.style.left = ptxRounded.x + 'px';
div.style.top = ptxRounded.y + 'px';
if (this_.markersDispatcher_) {
this_.markersDispatcher_.emit('kON_CHANGE');
}
}
});
_this.overlay_ = overlay;
overlay.setMap(map);
if (_this.props.heatmap.positions) {
_this.heatmap.setMap(map);
}
maps.event.addListener(map, 'zoom_changed', function () {
// recalc position at zoom start
if (this_.geoService_.getZoom() !== map.getZoom()) {
if (!this_.zoomAnimationInProgress_) {
this_.zoomAnimationInProgress_ = true;
this_._onZoomAnimationStart();
}
// If draw() is not called each frame during a zoom animation,
// simulate it.
if (mapsVersion < DRAW_CALLED_DURING_ANIMATION_VERSION) {
var TIMEOUT_ZOOM = 300;
if (new Date().getTime() - _this.zoomControlClickTime_ < TIMEOUT_ZOOM) {
// there is strange Google Map Api behavior in chrome when zoom animation of map
// is started only on second raf call, if was click on zoom control
// or +- keys pressed, so i wait for two rafs before change state
// this does not fully prevent animation jump
// but reduce it's occurence probability
(0, _raf2.default)(function () {
return (0, _raf2.default)(function () {
this_.updateCounter_++;
this_._onBoundsChanged(map, maps);
});
});
} else {
this_.updateCounter_++;
this_._onBoundsChanged(map, maps);
}
}
}
});
maps.event.addListener(map, 'idle', function () {
if (_this.resetSizeOnIdle_) {
_this._setViewSize();
var currMinZoom = _this._computeMinZoom(_this.props.options.minZoom);
if (currMinZoom !== _this.minZoom_) {
_this.minZoom_ = currMinZoom;
map.setOptions({ minZoom: currMinZoom });
}
_this.resetSizeOnIdle_ = false;
}
if (this_.zoomAnimationInProgress_) {
this_.zoomAnimationInProgress_ = false;
this_._onZoomAnimationEnd();
}
this_.updateCounter_++;
this_._onBoundsChanged(map, maps);
if (_this.mouse_) {
var latLng = _this.geoService_.unproject(_this.mouse_, true);
_this.mouse_.lat = latLng.lat;
_this.mouse_.lng = latLng.lng;
}
_this._onChildMouseMove();
this_.dragTime_ = 0;
var div = overlay.div;
var overlayProjection = overlay.getProjection();
if (div && overlayProjection) {
var ptx = overlayProjection.fromLatLngToDivPixel(overlayProjection.fromContainerPixelToLatLng({ x: 0, y: 0 }));
// need round for safari still can't find what need for firefox
var ptxRounded = (0, _detect2.default)().isSafari ? { x: Math.round(ptx.x), y: Math.round(ptx.y) } : { x: ptx.x, y: ptx.y };
div.style.left = ptxRounded.x + 'px';
div.style.top = ptxRounded.y + 'px';
}
if (this_.markersDispatcher_) {
this_.markersDispatcher_.emit('kON_CHANGE');
if (this_.fireMouseEventOnIdle_) {
this_.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
}
}
});
maps.event.addListener(map, 'mouseover', function () {
// has advantage over div MouseLeave
this_.mouseInMap_ = true;
});
// an alternative way to know the mouse is back within the map
// This would not fire when clicking/interacting with google maps
// own on-map countrols+markers. This handles an edge case for touch devices
// + 'draggable:false' custom option. See #332 for more details.
maps.event.addListener(map, 'click', function () {
this_.mouseInMap_ = true;
});
maps.event.addListener(map, 'mouseout', function () {
// has advantage over div MouseLeave
this_.mouseInMap_ = false;
this_.mouse_ = null;
this_.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
});
maps.event.addListener(map, 'drag', function () {
this_.dragTime_ = new Date().getTime();
this_._onDrag();
});
// user choosing satellite vs roads, etc
maps.event.addListener(map, 'maptypeid_changed', function () {
this_._onMapTypeIdChange(map.getMapTypeId());
});
}).catch(function (e) {
// notify callback of load failure
_this._onGoogleApiLoaded({ map: null, maps: null });
console.error(e); // eslint-disable-line no-console
throw e;
});
};
_this._onGoogleApiLoaded = function () {
if (_this.props.onGoogleApiLoaded) {
var _this$props;
if (process.env.NODE_ENV !== 'production' && _this.props.yesIWantToUseGoogleMapApiInternals !== true) {
console.warn('GoogleMap: ' + // eslint-disable-line
'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');
}
(_this$props = _this.props).onGoogleApiLoaded.apply(_this$props, arguments);
}
};
_this._getHoverDistance = function () {
return _this.props.hoverDistance;
};
_this._onDrag = function () {
var _this$props2;
return _this.props.onDrag && (_this$props2 = _this.props).onDrag.apply(_this$props2, arguments);
};
_this._onMapTypeIdChange = function () {
var _this$props3;
return _this.props.onMapTypeIdChange && (_this$props3 = _this.props).onMapTypeIdChange.apply(_this$props3, arguments);
};
_this._onZoomAnimationStart = function () {
var _this$props4;
return _this.props.onZoomAnimationStart && (_this$props4 = _this.props).onZoomAnimationStart.apply(_this$props4, arguments);
};
_this._onZoomAnimationEnd = function () {
var _this$props5;
return _this.props.onZoomAnimationEnd && (_this$props5 = _this.props).onZoomAnimationEnd.apply(_this$props5, arguments);
};
_this._onChildClick = function () {
if (_this.props.onChildClick) {
var _this$props6;
return (_this$props6 = _this.props).onChildClick.apply(_this$props6, arguments);
}
return undefined;
};
_this._onChildMouseDown = function (hoverKey, childProps) {
_this.childMouseDownArgs_ = [hoverKey, childProps];
if (_this.props.onChildMouseDown) {
_this.props.onChildMouseDown(hoverKey, childProps, _extends({}, _this.mouse_));
}
};
_this._onChildMouseUp = function () {
if (_this.childMouseDownArgs_) {
if (_this.props.onChildMouseUp) {
var _this$props7;
(_this$props7 = _this.props).onChildMouseUp.apply(_this$props7, _this.childMouseDownArgs_.concat([_extends({}, _this.mouse_)]));
}
_this.childMouseDownArgs_ = null;
_this.childMouseUpTime_ = new Date().getTime();
}
};
_this._onChildMouseMove = function () {
if (_this.childMouseDownArgs_) {
if (_this.props.onChildMouseMove) {
var _this$props8;
(_this$props8 = _this.props).onChildMouseMove.apply(_this$props8, _this.childMouseDownArgs_.concat([_extends({}, _this.mouse_)]));
}
}
};
_this._onChildMouseEnter = function () {
if (_this.props.onChildMouseEnter) {
var _this$props9;
return (_this$props9 = _this.props).onChildMouseEnter.apply(_this$props9, arguments);
}
return undefined;
};
_this._onChildMouseLeave = function () {
if (_this.props.onChildMouseLeave) {
var _this$props10;
return (_this$props10 = _this.props).onChildMouseLeave.apply(_this$props10, arguments);
}
return undefined;
};
_this._setViewSize = function () {
if (!_this.mounted_) return;
if (isFullScreen()) {
_this.geoService_.setViewSize(window.innerWidth, window.innerHeight);
} else {
var mapDom = _reactDom2.default.findDOMNode(_this.googleMapDom_);
_this.geoService_.setViewSize(mapDom.clientWidth, mapDom.clientHeight);
}
_this._onBoundsChanged();
};
_this._onWindowResize = function () {
_this.resetSizeOnIdle_ = true;
};
_this._onMapMouseMove = function (e) {
if (!_this.mouseInMap_) return;
var currTime = new Date().getTime();
var K_RECALC_CLIENT_RECT_MS = 50;
if (currTime - _this.mouseMoveTime_ > K_RECALC_CLIENT_RECT_MS) {
_this.boundingRect_ = e.currentTarget.getBoundingClientRect();
}
_this.mouseMoveTime_ = currTime;
var mousePosX = e.clientX - _this.boundingRect_.left;
var mousePosY = e.clientY - _this.boundingRect_.top;
if (!_this.mouse_) {
_this.mouse_ = { x: 0, y: 0, lat: 0, lng: 0 };
}
_this.mouse_.x = mousePosX;
_this.mouse_.y = mousePosY;
var latLng = _this.geoService_.unproject(_this.mouse_, true);
_this.mouse_.lat = latLng.lat;
_this.mouse_.lng = latLng.lng;
_this._onChildMouseMove();
if (currTime - _this.dragTime_ < K_IDLE_TIMEOUT) {
_this.fireMouseEventOnIdle_ = true;
} else {
_this.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
_this.fireMouseEventOnIdle_ = false;
}
};
_this._onClick = function () {
var _this$props11;
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);
};
_this._onMapClick = function (event) {
if (_this.markersDispatcher_) {
// support touch events and recalculate mouse position on click
_this._onMapMouseMove(event);
var currTime = new Date().getTime();
if (currTime - _this.dragTime_ > K_IDLE_TIMEOUT) {
if (_this.mouse_) {
_this._onClick(_extends({}, _this.mouse_, {
event: event
}));
}
_this.markersDispatcher_.emit('kON_CLICK', event);
}
}
};
_this._onMapMouseDownNative = function (event) {
if (!_this.mouseInMap_) return;
_this._onMapMouseDown(event);
};
_this._onMapMouseDown = function (event) {
if (_this.markersDispatcher_) {
var currTime = new Date().getTime();
if (currTime - _this.dragTime_ > K_IDLE_TIMEOUT) {
// Hovered marker detected at mouse move could be deleted at mouse down time
// so it will be good to force hovered marker recalculation
_this._onMapMouseMove(event);
_this.markersDispatcher_.emit('kON_MDOWN', event);
}
}
};
_this._onMapMouseDownCapture = function () {
if ((0, _detect2.default)().isChrome) {
// to fix strange zoom in chrome
_this.zoomControlClickTime_ = new Date().getTime();
}
};
_this._onKeyDownCapture = function () {
if ((0, _detect2.default)().isChrome) {
_this.zoomControlClickTime_ = new Date().getTime();
}
};
_this._isCenterDefined = function (center) {
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]));
};
_this._onBoundsChanged = function (map, maps, callExtBoundsChange) {
if (map) {
var gmC = map.getCenter();
_this.geoService_.setView([gmC.lat(), gmC.lng()], map.getZoom(), 0);
}
if ((_this.props.onChange || _this.props.onBoundsChange) && _this.geoService_.canProject()) {
var zoom = _this.geoService_.getZoom();
var bounds = _this.geoService_.getBounds();
var centerLatLng = _this.geoService_.getCenter();
if (!(0, _isArraysEqualEps2.default)(bounds, _this.prevBounds_, kEPS)) {
if (callExtBoundsChange !== false) {
var marginBounds = _this.geoService_.getBounds(_this.props.margin);
if (_this.props.onBoundsChange) {
_this.props.onBoundsChange(_this.centerIsObject_ ? _extends({}, centerLatLng) : [centerLatLng.lat, centerLatLng.lng], zoom, bounds, marginBounds);
}
if (_this.props.onChange) {
_this.props.onChange({
center: _extends({}, centerLatLng),
zoom: zoom,
bounds: {
nw: {
lat: bounds[0],
lng: bounds[1]
},
se: {
lat: bounds[2],
lng: bounds[3]
},
sw: {
lat: bounds[4],
lng: bounds[5]
},
ne: {
lat: bounds[6],
lng: bounds[7]
}
},
marginBounds: {
nw: {
lat: marginBounds[0],
lng: marginBounds[1]
},
se: {
lat: marginBounds[2],
lng: marginBounds[3]
},
sw: {
lat: marginBounds[4],
lng: marginBounds[5]
},
ne: {
lat: marginBounds[6],
lng: marginBounds[7]
}
},
size: _this.geoService_.hasSize() ? {
width: _this.geoService_.getWidth(),
height: _this.geoService_.getHeight()
} : {
width: 0,
height: 0
}
});
}
_this.prevBounds_ = bounds;
}
}
}
};
_this._registerChild = function (ref) {
_this.googleMapDom_ = ref;
};
_this.mounted_ = false;
_this.initialized_ = false;
_this.googleApiLoadedCalled_ = false;
_this.map_ = null;
_this.maps_ = null;
_this.prevBounds_ = null;
_this.heatmap = null;
_this.layers_ = {};
_this.mouse_ = null;
_this.mouseMoveTime_ = 0;
_this.boundingRect_ = null;
_this.mouseInMap_ = true;
_this.dragTime_ = 0;
_this.fireMouseEventOnIdle_ = false;
_this.updateCounter_ = 0;
_this.markersDispatcher_ = new _marker_dispatcher2.default(_this);
_this.geoService_ = new _geo2.default(K_GOOGLE_TILE_SIZE);
_this.centerIsObject_ = (0, _isPlainObject2.default)(_this.props.center);
_this.minZoom_ = DEFAULT_MIN_ZOOM;
_this.defaultDraggableOption_ = true;
_this.zoomControlClickTime_ = 0;
_this.childMouseDownArgs_ = null;
_this.childMouseUpTime_ = 0;
_this.googleMapDom_ = null;
if (process.env.NODE_ENV !== 'production') {
if (_this.props.apiKey) {
console.warn('GoogleMap: ' + // eslint-disable-line no-console
'apiKey is deprecated, use ' + 'bootstrapURLKeys={{key: YOUR_API_KEY}} instead.');
}
if (_this.props.onBoundsChange) {
console.warn('GoogleMap: ' + // eslint-disable-line no-console
'onBoundsChange is deprecated, use ' + 'onChange({center, zoom, bounds, ...other}) instead.');
}
if ((0, _isEmpty2.default)(_this.props.center) && (0, _isEmpty2.default)(_this.props.defaultCenter)) {
console.warn('GoogleMap: center or defaultCenter property must be defined' // eslint-disable-line no-console
);
}
if ((0, _isEmpty2.default)(_this.props.zoom) && (0, _isEmpty2.default)(_this.props.defaultZoom)) {
console.warn('GoogleMap: zoom or defaultZoom property must be defined' // eslint-disable-line no-console
);
}
}
if (_this._isCenterDefined(_this.props.center || _this.props.defaultCenter)) {
var propsCenter = latLng2Obj(_this.props.center || _this.props.defaultCenter);
_this.geoService_.setView(propsCenter, _this.props.zoom || _this.props.defaultZoom, 0);
}
_this.zoomAnimationInProgress_ = false;
_this.state = {
overlayCreated: false
};
return _this;
}
GoogleMap.prototype.componentDidMount = function componentDidMount() {
var _this2 = this;
this.mounted_ = true;
window.addEventListener('resize', this._onWindowResize);
window.addEventListener('keydown', this._onKeyDownCapture, true);
var mapDom = _reactDom2.default.findDOMNode(this.googleMapDom_);
// gmap can't prevent map drag if mousedown event already occured
// the only workaround I find is prevent mousedown native browser event
if (mapDom) {
mapDom.addEventListener('mousedown', this._onMapMouseDownNative, true);
}
window.addEventListener('mouseup', this._onChildMouseUp, false);
var bootstrapURLKeys = _extends({}, this.props.apiKey && { key: this.props.apiKey }, this.props.bootstrapURLKeys);
this.props.googleMapLoader(bootstrapURLKeys, this.props.heatmapLibrary); // we can start load immediatly
setTimeout(function () {
// to detect size
_this2._setViewSize();
if (_this2._isCenterDefined(_this2.props.center || _this2.props.defaultCenter)) {
_this2._initMap();
}
}, 0, this);
if (this.props.resetBoundsOnResize) {
var that = this;
_detectElementResize2.default.addResizeListener(mapDom, that._mapDomResizeCallback);
}
};
GoogleMap.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
var _this3 = this;
if (process.env.NODE_ENV !== 'production') {
if (!(0, _shallowEqual2.default)(this.props.defaultCenter, nextProps.defaultCenter)) {
console.warn("GoogleMap: defaultCenter prop changed. You can't change default props.");
}
if (!(0, _shallowEqual2.default)(this.props.defaultZoom, nextProps.defaultZoom)) {
console.warn("GoogleMap: defaultZoom prop changed. You can't change default props.");
}
}
if (!this._isCenterDefined(this.props.center) && this._isCenterDefined(nextProps.center)) {
setTimeout(function () {
return _this3._initMap();
}, 0);
}
if (this.map_) {
var centerLatLng = this.geoService_.getCenter();
if (this._isCenterDefined(nextProps.center)) {
var nextPropsCenter = latLng2Obj(nextProps.center);
var currCenter = this._isCenterDefined(this.props.center) ? latLng2Obj(this.props.center) : null;
if (!currCenter || Math.abs(nextPropsCenter.lat - currCenter.lat) + Math.abs(nextPropsCenter.lng - currCenter.lng) > kEPS) {
if (Math.abs(nextPropsCenter.lat - centerLatLng.lat) + Math.abs(nextPropsCenter.lng - centerLatLng.lng) > kEPS) {
this.map_.panTo({
lat: nextPropsCenter.lat,
lng: nextPropsCenter.lng
});
}
}
}
if (!(0, _isEmpty2.default)(nextProps.zoom)) {
// if zoom chaged by user
if (Math.abs(nextProps.zoom - this.props.zoom) > 0) {
this.map_.setZoom(nextProps.zoom);
}
}
if (!(0, _isEmpty2.default)(this.props.draggable) && (0, _isEmpty2.default)(nextProps.draggable)) {
// reset to default
this.map_.setOptions({ draggable: this.defaultDraggableOption_ });
} else if (!(0, _shallowEqual2.default)(this.props.draggable, nextProps.draggable)) {
// also prevent this on window 'mousedown' event to prevent map move
this.map_.setOptions({ draggable: nextProps.draggable });
}
// use shallowEqual to try avoid calling map._setOptions if only the ref changes
if (!(0, _isEmpty2.default)(nextProps.options) && !(0, _shallowEqual2.default)(this.props.options, nextProps.options)) {
var mapPlainObjects = (0, _pick2.default)(this.maps_, _isPlainObject2.default);
var options = typeof nextProps.options === 'function' ? nextProps.options(mapPlainObjects) : nextProps.options;
// remove zoom, center and draggable options as these are managed by google-maps-react
options = (0, _omit2.default)(options, ['zoom', 'center', 'draggable']);
if ('minZoom' in options) {
var minZoom = this._computeMinZoom(options.minZoom);
options.minZoom = _checkMinZoom(options.minZoom, minZoom);
}
this.map_.setOptions(options);
}
if (!(0, _shallowEqual2.default)(nextProps.layerTypes, this.props.layerTypes)) {
Object.keys(this.layers_).forEach(function (layerKey) {
_this3.layers_[layerKey].setMap(null);
delete _this3.layers_[layerKey];
});
this._setLayers(nextProps.layerTypes);
}
}
};
GoogleMap.prototype.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
// draggable does not affect inner components
return !(0, _shallowEqual2.default)((0, _omit2.default)(this.props, ['draggable']), (0, _omit2.default)(nextProps, ['draggable'])) || !(0, _shallowEqual2.default)(this.state, nextState);
};
GoogleMap.prototype.componentDidUpdate = function componentDidUpdate(prevProps) {
this.markersDispatcher_.emit('kON_CHANGE');
if (!(0, _shallowEqual2.default)(this.props.hoverDistance, prevProps.hoverDistance)) {
this.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
}
};
GoogleMap.prototype.componentWillUnmount = function componentWillUnmount() {
this.mounted_ = false;
var mapDom = _reactDom2.default.findDOMNode(this.googleMapDom_);
if (mapDom) {
mapDom.removeEventListener('mousedown', this._onMapMouseDownNative, true);
}
window.removeEventListener('resize', this._onWindowResize);
window.removeEventListener('keydown', this._onKeyDownCapture);
window.removeEventListener('mouseup', this._onChildMouseUp, false);
if (this.props.resetBoundsOnResize) {
_detectElementResize2.default.removeResizeListener(mapDom, this._mapDomResizeCallback);
}
if (this.overlay_) {
// this triggers overlay_.onRemove(), which will unmount the <GoogleMapMarkers/>
this.overlay_.setMap(null);
}
if (this.maps_ && this.map_) {
// fix google, as otherwise listeners works even without map
this.map_.setOptions({ scrollwheel: false });
this.maps_.event.clearInstanceListeners(this.map_);
}
this.map_ = null;
this.maps_ = null;
this.markersDispatcher_.dispose();
this.resetSizeOnIdle_ = false;
delete this.map_;
delete this.markersDispatcher_;
};
// calc minZoom if map size available
// it's better to not set minZoom less than this calculation gives
// otherwise there is no homeomorphism between screen coordinates and map
// (one map coordinate can have different screen coordinates)
// this method works only if this.props.onChildMouseDown was called
// this method works only if this.props.onChildMouseDown was called
// K_IDLE_CLICK_TIMEOUT - looks like 300 is enough
// gmap can't prevent map drag if mousedown event already occured
// the only workaround I find is prevent mousedown native browser event
GoogleMap.prototype.render = function render() {
var mapMarkerPrerender = !this.state.overlayCreated ? _react2.default.createElement(_google_map_markers_prerender2.default, {
experimental: this.props.experimental,
onChildClick: this._onChildClick,
onChildMouseDown: this._onChildMouseDown,
onChildMouseEnter: this._onChildMouseEnter,
onChildMouseLeave: this._onChildMouseLeave,
geoService: this.geoService_,
projectFromLeftTop: false,
distanceToMouse: this.props.distanceToMouse,
getHoverDistance: this._getHoverDistance,
dispatcher: this.markersDispatcher_
}) : null;
return _react2.default.createElement(
'div',
{
style: this.props.style,
onMouseMove: this._onMapMouseMove,
onMouseDownCapture: this._onMapMouseDownCapture,
onClick: this._onMapClick
},
_react2.default.createElement(_google_map_map2.default, { registerChild: this._registerChild }),
mapMarkerPrerender
);
};
return GoogleMap;
}(_react.Component);
GoogleMap.propTypes = {
apiKey: _propTypes2.default.string,
bootstrapURLKeys: _propTypes2.default.any,
defaultCenter: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.shape({
lat: _propTypes2.default.number,
lng: _propTypes2.default.number
})]),
center: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.shape({
lat: _propTypes2.default.number,
lng: _propTypes2.default.number
})]),
defaultZoom: _propTypes2.default.number,
zoom: _propTypes2.default.number,
onBoundsChange: _propTypes2.default.func,
onChange: _propTypes2.default.func,
onClick: _propTypes2.default.func,
onChildClick: _propTypes2.default.func,
onChildMouseDown: _propTypes2.default.func,
onChildMouseUp: _propTypes2.default.func,
onChildMouseMove: _propTypes2.default.func,
onChildMouseEnter: _propTypes2.default.func,
onChildMouseLeave: _propTypes2.default.func,
onZoomAnimationStart: _propTypes2.default.func,
onZoomAnimationEnd: _propTypes2.default.func,
onDrag: _propTypes2.default.func,
onMapTypeIdChange: _propTypes2.default.func,
options: _propTypes2.default.any,
distanceToMouse: _propTypes2.default.func,
hoverDistance: _propTypes2.default.number,
debounced: _propTypes2.default.bool,
margin: _propTypes2.default.array,
googleMapLoader: _propTypes2.default.any,
onGoogleApiLoaded: _propTypes2.default.func,
yesIWantToUseGoogleMapApiInternals: _propTypes2.default.bool,
draggable: _propTypes2.default.bool,
style: _propTypes2.default.any,
resetBoundsOnResize: _propTypes2.default.bool,
layerTypes: _propTypes2.default.arrayOf(_propTypes2.default.string) // ['TransitLayer', 'TrafficLayer']
};
GoogleMap.defaultProps = {
distanceToMouse: function distanceToMouse(pt, mousePos /* , markerProps */) {
return Math.sqrt((pt.x - mousePos.x) * (pt.x - mousePos.x) + (pt.y - mousePos.y) * (pt.y - mousePos.y));
},
hoverDistance: 30,
debounced: true,
options: defaultOptions_,
googleMapLoader: _google_map_loader2.default,
yesIWantToUseGoogleMapApiInternals: false,
style: {
width: '100%',
height: '100%',
margin: 0,
padding: 0,
position: 'relative'
},
layerTypes: [],
heatmap: {},
heatmapLibrary: false
};
GoogleMap.googleMapLoader = _google_map_loader2.default;
exports.default = GoogleMap;