chayns-components
Version:
A set of beautiful React components for developing chayns® applications.
277 lines (271 loc) • 7.97 kB
JavaScript
/**
* @component
*/
/* global google */
import Coordinates from 'coordinate-parser';
import PropTypes from 'prop-types';
import React, { createRef, PureComponent } from 'react';
import Input from '../../react-chayns-input/component/Input';
import TappPortal from '../../react-chayns-tapp_portal/component/TappPortal';
import debounce from '../../utils/debounce';
import AutocompleteItem from './AutocompleteItem';
import GoogleMap from './GoogleMap/GoogleMap';
import "./styles.css";
/** Uses the `toJSON()` method to return a human readable-object */
const toLiteral = value => JSON.parse(JSON.stringify(value));
const ADDRESS = 1;
const COORDS = 2;
const noop = () => {};
/**
* A location input with a map and text input.
*
* This requires the Google Maps JavaScript API with the places library enabled.
* You can find more information about the Maps API
* [here](https://developers.google.com/maps/documentation/javascript/overview).
*/
export default class PositionInput extends PureComponent {
constructor(props) {
var _this;
super(props);
_this = this;
this.setAddress = function (position, triggerChange) {
if (triggerChange === void 0) {
triggerChange = true;
}
const {
onPositionChange
} = _this.props;
_this.geocoder.geocode({
location: position
}, (result, status) => {
if (status === google.maps.GeocoderStatus.OK) {
_this.setState({
value: result[0].formatted_address
});
if (triggerChange) {
onPositionChange(position, result[0].formatted_address);
}
}
});
};
this.handleUserPan = map => {
const {
onPositionChange
} = this.props;
const {
currentInputType
} = this.state;
const center = toLiteral(map.getCenter());
if (currentInputType === COORDS) {
onPositionChange(center);
this.setState({
value: `${center.lat.toFixed(4)} ${center.lng.toFixed(4)}`
});
} else {
this.setAddress(center);
}
};
this.handleInputChange = value => {
this.setState({
value,
overlayPosition: this.mapOverlayRef.current.getBoundingClientRect()
});
try {
const {
onPositionChange
} = this.props;
const {
latitude: lat,
longitude: lng
} = new Coordinates(value);
const position = {
lat,
lng
};
this.mapRef.current.panTo(position);
onPositionChange(position, value);
this.setState({
currentInputType: COORDS,
addresses: []
});
} catch (e) {
// Invalid coordinates
this.getAddresses(value);
this.setState({
currentInputType: ADDRESS
});
}
};
this.changePosition = value => {
const {
currentInputType
} = this.state;
this.mapRef.current.panTo(value);
if (currentInputType === COORDS) {
this.setState({
value: `${value.lat.toFixed(4)} ${value.lng.toFixed(4)}`
});
} else {
this.setAddress(value, false);
}
};
this.getAddresses = value => {
if (value) {
const {
defaultPosition: {
lat,
lng
}
} = this.props;
this.autocomplete.getPlacePredictions({
location: new google.maps.LatLng(lat, lng),
radius: 10000,
input: value
}, (result, status) => {
if (status === google.maps.places.PlacesServiceStatus.OK) {
this.setState({
addresses: result.map(a => a.description)
});
}
});
}
};
this.selectAddress = value => {
this.setState({
value,
addresses: []
});
this.geocoder.geocode({
address: value
}, (results, status) => {
if (status === google.maps.GeocoderStatus.OK) {
const {
onPositionChange
} = this.props;
const position = toLiteral(results[0].geometry.location);
this.mapRef.current.panTo(position);
onPositionChange(position, value);
}
});
};
if (!window.google) {
throw new Error('The google maps JS API could not be found. Did you forget to include the script? See https://developers.google.com/maps/documentation/javascript/get-api-key for more details.');
}
this.autocomplete = new google.maps.places.AutocompleteService();
this.geocoder = new google.maps.Geocoder();
this.state = {
value: '',
addresses: [],
currentInputType: ADDRESS,
overlayPosition: {
width: 0,
left: 0,
bottom: 0
}
};
/** @type {React.RefObject<google.maps.Map>} */
this.mapRef = /*#__PURE__*/createRef();
this.mapOverlayRef = /*#__PURE__*/createRef();
this.getAddresses = debounce(this.getAddresses, 500);
this.setAddress(props.defaultPosition, false);
}
render() {
const {
defaultPosition,
mapOptions,
children,
parent
} = this.props;
const {
value,
addresses,
currentInputType,
overlayPosition
} = this.state;
return /*#__PURE__*/React.createElement("div", {
className: "cc__map"
}, /*#__PURE__*/React.createElement("div", {
className: "map--crosshair"
}, "+"), /*#__PURE__*/React.createElement(GoogleMap, {
mapRef: this.mapRef,
containerStyle: {
height: '100%',
position: 'relative'
},
options: {
...mapOptions,
center: defaultPosition
},
onDragend: this.handleUserPan
}), children && /*#__PURE__*/React.createElement("div", {
className: "map--overlay",
ref: this.mapOverlayRef
}, children(value, this.handleInputChange, this.changePosition), /*#__PURE__*/React.createElement(TappPortal, {
parent: parent
}, /*#__PURE__*/React.createElement("div", {
className: "map--autocomplete_popup",
style: {
left: `${overlayPosition.left}px`,
top: `${overlayPosition.bottom}px`,
width: `${overlayPosition.width}px`
}
}, !!value && currentInputType === ADDRESS && addresses.map((a, index) => /*#__PURE__*/React.createElement(AutocompleteItem, {
index: index,
address: a,
onClick: this.selectAddress,
key: a
}))))));
}
}
PositionInput.propTypes = {
/**
* The position that will be used as a starting point.
*/
defaultPosition: PropTypes.shape({
lat: PropTypes.number.isRequired,
lng: PropTypes.number.isRequired
}).isRequired,
/**
* This will be called when the position selection changes.
*/
onPositionChange: PropTypes.func,
/**
* An object with options for the Google Map. These options are documented
* [here](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions).
*/
mapOptions: PropTypes.object,
// eslint-disable-line react/forbid-prop-types
/**
* A render function for creating a custom input overlay. It receives the
* currently selected position as its first argument and an onChange-method
* as its second argument.
*/
children: PropTypes.func,
/**
* A DOM element that the overlay should be rendered into.
*/
parent: PropTypes.node
};
PositionInput.defaultProps = {
onPositionChange: noop,
mapOptions: {
zoom: 15,
gestureHandling: 'greedy',
disableDefaultUI: true,
styles: [{
featureType: 'poi',
elementType: 'labels',
stylers: [{
visibility: 'off'
}]
}]
},
children: (value, onChange) => /*#__PURE__*/React.createElement(Input, {
placeholder: "Position",
value: value,
onChange: onChange
}),
parent: null
};
PositionInput.displayName = 'PositionInput';
//# sourceMappingURL=PositionInput.js.map