UNPKG

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
'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;