react-google-autocomplete
Advanced tools
Comparing version 1.3.0 to 2.0.0
@@ -0,1 +1,3 @@ | ||
import { HTMLProps } from "react"; | ||
export type OptionType = { | ||
@@ -12,3 +14,6 @@ componentRestrictions?: {}; | ||
export interface ReactGoogleAutocomplete { | ||
export interface ReactGoogleAutocomplete< | ||
T = { current: null }, | ||
B = { current: null } | ||
> extends HTMLProps<HTMLInputElement> { | ||
onPlaceSelected?: ( | ||
@@ -18,10 +23,7 @@ places: Record<string, unknown>, | ||
) => void; | ||
types?: string[]; | ||
componentRestrictions?: {}; | ||
bounds?: {}; | ||
fields?: string[]; | ||
inputAutocompleteValue?: string; | ||
options?: OptionType; | ||
apiKey?: string; | ||
style?: CSSStyleDeclaration; | ||
ref?: T; | ||
autocompleteRef?: B; | ||
} |
323
lib/index.js
@@ -1,2 +0,2 @@ | ||
'use strict'; | ||
"use strict"; | ||
@@ -6,13 +6,12 @@ Object.defineProperty(exports, "__esModule", { | ||
}); | ||
exports.ReactCustomGoogleAutocomplete = undefined; | ||
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 _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
var _react = require('react'); | ||
var _react = require("react"); | ||
var _react2 = _interopRequireDefault(_react); | ||
var _propTypes = require('prop-types'); | ||
var _propTypes = require("prop-types"); | ||
@@ -25,141 +24,152 @@ var _propTypes2 = _interopRequireDefault(_propTypes); | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var GOOGLE_MAP_SCRIPT_BASE_URL = "https://maps.googleapis.com/maps/api/js"; | ||
var isBrowser = typeof window !== "undefined" && window.document; | ||
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 ReactGoogleAutocomplete(props) { | ||
var onPlaceSelected = props.onPlaceSelected, | ||
apiKey = props.apiKey, | ||
_props$inputAutocompl = props.inputAutocompleteValue, | ||
inputAutocompleteValue = _props$inputAutocompl === undefined ? "new-password" : _props$inputAutocompl, | ||
_props$options = props.options; | ||
_props$options = _props$options === undefined ? {} : _props$options; | ||
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; } | ||
var _props$options$types = _props$options.types, | ||
types = _props$options$types === undefined ? ["(cities)"] : _props$options$types, | ||
componentRestrictions = _props$options.componentRestrictions, | ||
_props$options$fields = _props$options.fields, | ||
fields = _props$options$fields === undefined ? ["address_components", "geometry.location", "place_id", "formatted_address"] : _props$options$fields, | ||
bounds = _props$options.bounds, | ||
options = _objectWithoutProperties(_props$options, ["types", "componentRestrictions", "fields", "bounds"]), | ||
_props$googleMapsScri = props.googleMapsScriptBaseUrl, | ||
googleMapsScriptBaseUrl = _props$googleMapsScri === undefined ? GOOGLE_MAP_SCRIPT_BASE_URL : _props$googleMapsScri, | ||
refProp = props.refProp, | ||
autocompleteRef = props.autocompleteRef, | ||
rest = _objectWithoutProperties(props, ["onPlaceSelected", "apiKey", "inputAutocompleteValue", "options", "googleMapsScriptBaseUrl", "refProp", "autocompleteRef"]); | ||
var ReactGoogleAutocomplete = function (_React$Component) { | ||
_inherits(ReactGoogleAutocomplete, _React$Component); | ||
var inputRef = (0, _react.useRef)(null); | ||
var event = (0, _react.useRef)(null); | ||
var autocomplete = (0, _react.useRef)(null); | ||
var googleMapsScript = (0, _react.useRef)(null); | ||
var observerHack = (0, _react.useRef)(null); | ||
var googleMapsScriptUrl = googleMapsScriptBaseUrl + "?key=" + apiKey + "&libraries=places"; | ||
function ReactGoogleAutocomplete(props) { | ||
_classCallCheck(this, ReactGoogleAutocomplete); | ||
var handleLoadScript = (0, _react.useCallback)(function () { | ||
if (!isBrowser) return Promise.resolve(); | ||
var _this = _possibleConstructorReturn(this, (ReactGoogleAutocomplete.__proto__ || Object.getPrototypeOf(ReactGoogleAutocomplete)).call(this, props)); | ||
var _document$querySelect = document.querySelectorAll("script[src*=\"" + googleMapsScriptBaseUrl + "\""), | ||
_document$querySelect2 = _slicedToArray(_document$querySelect, 1), | ||
scriptElement = _document$querySelect2[0]; | ||
_this.handleLoadScript = function () { | ||
var googleMapsScriptUrl = 'https://maps.googleapis.com/maps/api/js?key=' + _this.props.apiKey + '&libraries=places'; | ||
// Check if script exists already | ||
if (document.querySelectorAll('script[src="' + googleMapsScriptUrl + '"]').length > 0) { | ||
return Promise.resolve(); | ||
} | ||
_this.googleMapsScript = document.createElement('script'); | ||
_this.googleMapsScript.src = googleMapsScriptUrl; | ||
document.body.appendChild(_this.googleMapsScript); | ||
if (scriptElement) { | ||
return new Promise(function (resolve) { | ||
_this.googleMapsScript.addEventListener('load', function () { | ||
scriptElement.addEventListener("load", function () { | ||
return resolve(); | ||
}); | ||
}); | ||
}; | ||
} | ||
_this.autocomplete = null; | ||
_this.event = null; | ||
return _this; | ||
} | ||
googleMapsScript.current = document.createElement("script"); | ||
googleMapsScript.current.src = googleMapsScriptUrl; | ||
_createClass(ReactGoogleAutocomplete, [{ | ||
key: 'componentDidMount', | ||
value: function componentDidMount() { | ||
var _this2 = this; | ||
document.body.appendChild(googleMapsScript.current); | ||
// TODO: only take options as configuration object, remove config props from the components props. | ||
var _props = this.props, | ||
_props$types = _props.types, | ||
types = _props$types === undefined ? ['(cities)'] : _props$types, | ||
componentRestrictions = _props.componentRestrictions, | ||
bounds = _props.bounds, | ||
apiKey = _props.apiKey, | ||
_props$fields = _props.fields, | ||
fields = _props$fields === undefined ? ['address_components', 'geometry.location', 'place_id', 'formatted_address'] : _props$fields, | ||
_props$options = _props.options, | ||
options = _props$options === undefined ? {} : _props$options; | ||
var config = _extends({}, options, { | ||
types: types, | ||
bounds: bounds, | ||
fields: fields | ||
return new Promise(function (resolve) { | ||
googleMapsScript.current.addEventListener("load", function () { | ||
return resolve(); | ||
}); | ||
}); | ||
}, [googleMapsScriptBaseUrl, googleMapsScriptUrl]); | ||
if (componentRestrictions) { | ||
config.componentRestrictions = componentRestrictions; | ||
} | ||
// Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445 | ||
(0, _react.useEffect)(function () { | ||
if (isBrowser && window.MutationObserver && inputRef.current) { | ||
observerHack.current = new MutationObserver(function () { | ||
observerHack.current.disconnect(); | ||
this.disableAutofill(); | ||
inputRef.current.autocomplete = inputAutocompleteValue; | ||
}); | ||
observerHack.current.observe(inputRef.current, { | ||
attributes: true, | ||
attributeFilter: ["autocomplete"] | ||
}); | ||
} | ||
}, [inputAutocompleteValue]); | ||
var handleAutoComplete = function handleAutoComplete() { | ||
_this2.autocomplete = new google.maps.places.Autocomplete(_this2.refs.input, config); | ||
(0, _react.useEffect)(function () { | ||
if (autocomplete.current) { | ||
autocomplete.current.setFields(fields); | ||
} | ||
}, [fields]); | ||
_this2.event = _this2.autocomplete.addListener('place_changed', _this2.onSelected.bind(_this2)); | ||
}; | ||
(0, _react.useEffect)(function () { | ||
if (autocomplete.current) { | ||
autocomplete.current.setBounds(bounds); | ||
} | ||
}, [bounds]); | ||
if (apiKey) { | ||
this.handleLoadScript().then(function () { | ||
return handleAutoComplete(); | ||
}); | ||
} else { | ||
handleAutoComplete(); | ||
} | ||
(0, _react.useEffect)(function () { | ||
if (autocomplete.current) { | ||
autocomplete.current.setComponentRestrictions(componentRestrictions); | ||
} | ||
}, { | ||
key: 'disableAutofill', | ||
value: function disableAutofill() { | ||
var _this3 = this; | ||
}, [componentRestrictions]); | ||
// Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445 | ||
if (window.MutationObserver) { | ||
var observerHack = new MutationObserver(function () { | ||
observerHack.disconnect(); | ||
if (_this3.refs && _this3.refs.input) { | ||
_this3.refs.input.autocomplete = _this3.props.inputAutocompleteValue || 'new-password'; | ||
} | ||
}); | ||
observerHack.observe(this.refs.input, { | ||
attributes: true, | ||
attributeFilter: ['autocomplete'] | ||
}); | ||
} | ||
(0, _react.useEffect)(function () { | ||
if (autocomplete.current) { | ||
autocomplete.current.setOptions(options); | ||
} | ||
}, { | ||
key: 'componentWillUnmount', | ||
value: function componentWillUnmount() { | ||
if (this.event) this.event.remove(); | ||
}, [options]); | ||
(0, _react.useEffect)(function () { | ||
var config = _extends({}, options, { | ||
types: types, | ||
bounds: bounds, | ||
componentRestrictions: componentRestrictions | ||
}); | ||
if (autocomplete.current) return; | ||
var handleAutoComplete = function handleAutoComplete() { | ||
// eslint-disable-next-line no-undef | ||
autocomplete.current = new google.maps.places.Autocomplete(inputRef.current, config); | ||
if (autocompleteRef) autocompleteRef.current = autocomplete.current; | ||
event.current = autocomplete.current.addListener("place_changed", function () { | ||
if (onPlaceSelected && autocomplete && autocomplete.current) { | ||
onPlaceSelected(autocomplete.current.getPlace(), inputRef.current, autocomplete.current); | ||
} | ||
}); | ||
}; | ||
if (apiKey) { | ||
handleLoadScript().then(function () { | ||
return handleAutoComplete(); | ||
}); | ||
} else { | ||
handleAutoComplete(); | ||
} | ||
}, { | ||
key: 'onSelected', | ||
value: function onSelected() { | ||
if (this.props.onPlaceSelected && this.autocomplete) { | ||
this.props.onPlaceSelected(this.autocomplete.getPlace(), this.refs.input); | ||
} | ||
} | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
var _props2 = this.props, | ||
onPlaceSelected = _props2.onPlaceSelected, | ||
types = _props2.types, | ||
componentRestrictions = _props2.componentRestrictions, | ||
bounds = _props2.bounds, | ||
options = _props2.options, | ||
apiKey = _props2.apiKey, | ||
inputAutocompleteValue = _props2.inputAutocompleteValue, | ||
rest = _objectWithoutProperties(_props2, ['onPlaceSelected', 'types', 'componentRestrictions', 'bounds', 'options', 'apiKey', 'inputAutocompleteValue']); | ||
return _react2.default.createElement('input', _extends({ ref: 'input' }, rest)); | ||
return function () { | ||
return event.current ? event.current.remove() : undefined; | ||
}; | ||
}, [types, options, fields, componentRestrictions, apiKey, onPlaceSelected, handleLoadScript, autocompleteRef, bounds]); | ||
return _react2.default.createElement("input", _extends({ | ||
ref: function ref(el) { | ||
inputRef.current = el; | ||
if (refProp) refProp.current = el; | ||
} | ||
}]); | ||
}, rest)); | ||
} | ||
return ReactGoogleAutocomplete; | ||
}(_react2.default.Component); | ||
ReactGoogleAutocomplete.propTypes = { | ||
apiKey: _propTypes2.default.string, | ||
ref: _propTypes2.default.oneOfType([ | ||
// Either a function | ||
_propTypes2.default.func, | ||
// Or the instance of a DOM native element (see the note about SSR) | ||
_propTypes2.default.shape({ current: _propTypes2.default.instanceOf(Element) })]), | ||
autocompleteRef: _propTypes2.default.oneOfType([_propTypes2.default.func, _propTypes2.default.shape({ current: _propTypes2.default.instanceOf(Element) })]), | ||
googleMapsScriptBaseUrl: _propTypes2.default.string, | ||
onPlaceSelected: _propTypes2.default.func, | ||
types: _propTypes2.default.arrayOf(_propTypes2.default.string), | ||
componentRestrictions: _propTypes2.default.object, | ||
bounds: _propTypes2.default.object, | ||
fields: _propTypes2.default.array, | ||
inputAutocompleteValue: _propTypes2.default.string, | ||
@@ -175,80 +185,7 @@ options: _propTypes2.default.shape({ | ||
types: _propTypes2.default.arrayOf(_propTypes2.default.string) | ||
}), | ||
apiKey: _propTypes2.default.string | ||
}) | ||
}; | ||
exports.default = ReactGoogleAutocomplete; | ||
var ReactCustomGoogleAutocomplete = exports.ReactCustomGoogleAutocomplete = function (_React$Component2) { | ||
_inherits(ReactCustomGoogleAutocomplete, _React$Component2); | ||
function ReactCustomGoogleAutocomplete(props) { | ||
_classCallCheck(this, ReactCustomGoogleAutocomplete); | ||
var _this4 = _possibleConstructorReturn(this, (ReactCustomGoogleAutocomplete.__proto__ || Object.getPrototypeOf(ReactCustomGoogleAutocomplete)).call(this, props)); | ||
_this4.service = new google.maps.places.AutocompleteService(); | ||
return _this4; | ||
} | ||
_createClass(ReactCustomGoogleAutocomplete, [{ | ||
key: 'onChange', | ||
value: function onChange(e) { | ||
var _this5 = this; | ||
var _props$types2 = this.props.types, | ||
types = _props$types2 === undefined ? ['(cities)'] : _props$types2; | ||
if (e.target.value) { | ||
this.service.getPlacePredictions({ input: e.target.value, types: types }, function (predictions, status) { | ||
if (status === 'OK' && predictions && predictions.length > 0) { | ||
_this5.props.onOpen(predictions); | ||
} else { | ||
_this5.props.onClose(); | ||
} | ||
}); | ||
} else { | ||
this.props.onClose(); | ||
} | ||
} | ||
}, { | ||
key: 'componentDidMount', | ||
value: function componentDidMount() { | ||
var _this6 = this; | ||
if (this.props.input.value) { | ||
this.placeService = new google.maps.places.PlacesService(this.refs.div); | ||
this.placeService.getDetails({ placeId: this.props.input.value }, function (e, status) { | ||
if (status === 'OK') { | ||
_this6.refs.input.value = e.formatted_address; | ||
} | ||
}); | ||
} | ||
} | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
var _this7 = this; | ||
return _react2.default.createElement( | ||
'div', | ||
null, | ||
_react2.default.cloneElement(this.props.input, _extends({}, this.props, { | ||
ref: 'input', | ||
onChange: function onChange(e) { | ||
_this7.onChange(e); | ||
} | ||
})), | ||
_react2.default.createElement('div', { ref: 'div' }) | ||
); | ||
} | ||
}]); | ||
return ReactCustomGoogleAutocomplete; | ||
}(_react2.default.Component); | ||
ReactCustomGoogleAutocomplete.propTypes = { | ||
input: _propTypes2.default.node.isRequired, | ||
onOpen: _propTypes2.default.func.isRequired, | ||
onClose: _propTypes2.default.func.isRequired | ||
}; | ||
exports.default = (0, _react.forwardRef)(function (props, ref) { | ||
return _react2.default.createElement(ReactGoogleAutocomplete, _extends({}, props, { refProp: ref })); | ||
}); |
{ | ||
"name": "react-google-autocomplete", | ||
"version": "1.3.0", | ||
"version": "2.0.0", | ||
"description": "React component for google autocomplete.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
102
README.md
@@ -0,1 +1,5 @@ | ||
![](https://img.badgesize.io/ErrorPro/react-google-autocomplete/master/lib/index.js?compression=gzip&label=gzip) | ||
![](https://img.badgesize.io/ErrorPro/react-google-autocomplete/master/lib/index.js?compression=brotli&label=brotli) | ||
[![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://GitHub.com/ErrorPro/react-google-autocomplete/master/LICENSE) | ||
## React google autocomplete | ||
@@ -16,7 +20,7 @@ | ||
apiKey={YOUR_GOOGLE_MAPS_API_KEY} | ||
onPlaceSelected={() => "do something on select"} | ||
onPlaceSelected={(place) => console.log(place)} | ||
/> | ||
``` | ||
Alternatively if not passing the `apiKey` prop, you can include google autocomplete link api in your app. Somewhere in index.html or somewhere else. | ||
Alternatively if not passing the `apiKey` prop, you can include google autocomplete link api in your app. Somewhere in index.html or somewhere else. More info [here](https://developers.google.com/maps/documentation/places/web-service/autocomplete) | ||
@@ -30,4 +34,29 @@ ```html | ||
## Example | ||
## Props | ||
- `apiKey`: pass to automatically load the Google maps scripts. The api key can be found in your [google cloud console.](https://developers.google.com/maps/documentation/javascript/get-api-key) | ||
- `ref`: [React ref](https://reactjs.org/docs/hooks-reference.html#useref) to be assigned the underlying text input ref. | ||
- `autocompleteRef`: [React ref](https://reactjs.org/docs/hooks-reference.html#useref) to be assigned the [google autocomplete instance](https://developers.google.com/maps/documentation/javascript/reference/places-widget#Autocomplete). | ||
- `onPlaceSelected: (place: `[PlaceResult](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult)`, inputRef, autocompleteRef) => void`: The function gets invoked every time a user chooses location. | ||
- `options`: [Google autocomplete options.](https://developers.google.com/maps/documentation/javascript/reference/places-widget#AutocompleteOptions) | ||
- `options.types`: By default it uses (cities). | ||
- `options.fields`: By default it uses `address_components`, `geometry.location`, `place_id`, `formatted_address`. | ||
- `inputAutocompleteValue`: Autocomplete value to be set to the underlying input. | ||
- `googleMapsScriptBaseUrl`: Provide custom google maps url. By default `https://maps.googleapis.com/maps/api/js` | ||
- `defaultValue` prop is used for setting up the default value e.g `defaultValue={'Amsterdam'}`. | ||
You can pass any prop specified for the hmtl [input tag](https://www.w3schools.com/tags/tag_input.asp). You can also set [options.fields](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult) prop if you need extra information, now it defaults to basic data in order to control expenses. | ||
## Examples | ||
### Simple autocomplete with options | ||
```js | ||
@@ -37,2 +66,3 @@ import Autocomplete from "react-google-autocomplete"; | ||
<Autocomplete | ||
apiKey={YOUR_GOOGLE_MAPS_API_KEY} | ||
style={{ width: "90%" }} | ||
@@ -42,12 +72,53 @@ onPlaceSelected={(place) => { | ||
}} | ||
types={["(regions)"]} | ||
componentRestrictions={{ country: "ru" }} | ||
options={{ | ||
types: ["(regions)"], | ||
componentRestrictions: { country: "ru" }, | ||
}} | ||
defaultValue="Amsterdam" | ||
/>; | ||
``` | ||
## Typescript | ||
### Passing refs | ||
We are planning on adding a full support for TS and Flow in the later releases. | ||
```js | ||
import Autocomplete from "react-google-autocomplete"; | ||
const inputRef = useRef(null); | ||
useEffect(() => { | ||
// focus on mount | ||
inputRef.current.focus() | ||
}, []) | ||
<Autocomplete | ||
ref={inputRef} | ||
onPlaceSelected={(place) => { | ||
console.log(place); | ||
}} | ||
/>; | ||
``` | ||
### Getting access to the google autocomplete instance | ||
```js | ||
import Autocomplete from "react-google-autocomplete"; | ||
const autocompleteRef = useRef(null); | ||
<Autocomplete | ||
autocompleteRef={autocompleteRef} | ||
onPlaceSelected={(place, inputRef, theSameAutocompletRef) => { | ||
console.log(place); | ||
}} | ||
/>; | ||
<button onClick={() => autocompleteRef.current.getPlace()}>Read place</button>; | ||
``` | ||
### Typescript | ||
We are planning on adding full support for TS and Flow in the later releases. | ||
```ts | ||
import Autocomplete, { | ||
@@ -59,14 +130,17 @@ ReactGoogleAutocomplete, | ||
<AutocompleteTS key="123" /> | ||
<AutocompleteTS apiKey="123" />; | ||
``` | ||
The component has one function called `onPlaceSelected`. The function gets invoked every time a user chooses location. | ||
A `types` props means type of places in [google place API](https://developers.google.com/places/web-service/autocomplete#place_types). By default it uses (cities). | ||
A [componentRestrictions](https://developers.google.com/maps/documentation/javascript/reference#ComponentRestrictions) prop by default is empty. | ||
A [bounds](https://developers.google.com/maps/documentation/javascript/reference#AutocompleteOptions) prop by default is empty. | ||
You also can pass any props you want to the final input. You can also set [fields](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult) prop if you need extra information, now it defaults to basic data in order to control expenses. | ||
The `options`(optional) prop is the optional configuration to your Autocomplete instance. You can see full options [here](https://developers.google.com/maps/documentation/javascript/places-autocomplete#add_autocomplete) | ||
### More examples(dynamic props, MaterialUI) how to use the lib could be found in `examples/index.js` | ||
[Video of the example](https://api.monosnap.com/file/download?id=vIjRwTxVyMj0Sd2Gjhsfie2SPk1y4l) | ||
### TODO | ||
- Check that it fully works with SSR | ||
- Add eslint config(base-airbnb) | ||
- Rewrite the lib to TS and add flow support | ||
## Contribution | ||
If you would like to see something in this library please create an issue and I will implement it as soon as possible. |
315
src/index.js
@@ -1,200 +0,187 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import React, { useRef, forwardRef, useEffect, useCallback } from "react"; | ||
import PropTypes from "prop-types"; | ||
export default class ReactGoogleAutocomplete extends React.Component { | ||
static propTypes = { | ||
onPlaceSelected: PropTypes.func, | ||
types: PropTypes.arrayOf(PropTypes.string), | ||
componentRestrictions: PropTypes.object, | ||
bounds: PropTypes.object, | ||
fields: PropTypes.array, | ||
inputAutocompleteValue: PropTypes.string, | ||
options: PropTypes.shape({ | ||
componentRestrictions: PropTypes.object, | ||
bounds: PropTypes.object, | ||
location: PropTypes.object, | ||
offset: PropTypes.number, | ||
origin: PropTypes.object, | ||
radius: PropTypes.number, | ||
sessionToken: PropTypes.object, | ||
types: PropTypes.arrayOf(PropTypes.string) | ||
}), | ||
apiKey: PropTypes.string | ||
}; | ||
const GOOGLE_MAP_SCRIPT_BASE_URL = "https://maps.googleapis.com/maps/api/js"; | ||
const isBrowser = typeof window !== "undefined" && window.document; | ||
constructor(props) { | ||
super(props); | ||
this.autocomplete = null; | ||
this.event = null; | ||
} | ||
componentDidMount() { | ||
// TODO: only take options as configuration object, remove config props from the components props. | ||
const { | ||
types = ['(cities)'], | ||
function ReactGoogleAutocomplete(props) { | ||
const { | ||
onPlaceSelected, | ||
apiKey, | ||
inputAutocompleteValue = "new-password", | ||
options: { | ||
types = ["(cities)"], | ||
componentRestrictions, | ||
bounds, | ||
apiKey, | ||
fields = [ | ||
'address_components', | ||
'geometry.location', | ||
'place_id', | ||
'formatted_address' | ||
"address_components", | ||
"geometry.location", | ||
"place_id", | ||
"formatted_address", | ||
], | ||
options = {} | ||
} = this.props; | ||
const config = { | ||
...options, | ||
types, | ||
bounds, | ||
fields | ||
}; | ||
...options | ||
} = {}, | ||
googleMapsScriptBaseUrl = GOOGLE_MAP_SCRIPT_BASE_URL, | ||
refProp, | ||
autocompleteRef, | ||
...rest | ||
} = props; | ||
const inputRef = useRef(null); | ||
const event = useRef(null); | ||
const autocomplete = useRef(null); | ||
const googleMapsScript = useRef(null); | ||
const observerHack = useRef(null); | ||
const googleMapsScriptUrl = `${googleMapsScriptBaseUrl}?key=${apiKey}&libraries=places`; | ||
if (componentRestrictions) { | ||
config.componentRestrictions = componentRestrictions; | ||
const handleLoadScript = useCallback(() => { | ||
if (!isBrowser) return Promise.resolve(); | ||
const [scriptElement] = document.querySelectorAll( | ||
`script[src*="${googleMapsScriptBaseUrl}"` | ||
); | ||
if (scriptElement) { | ||
return new Promise((resolve) => { | ||
scriptElement.addEventListener("load", () => resolve()); | ||
}); | ||
} | ||
this.disableAutofill(); | ||
googleMapsScript.current = document.createElement("script"); | ||
googleMapsScript.current.src = googleMapsScriptUrl; | ||
const handleAutoComplete = () => { | ||
this.autocomplete = new google.maps.places.Autocomplete( | ||
this.refs.input, | ||
config | ||
); | ||
document.body.appendChild(googleMapsScript.current); | ||
this.event = this.autocomplete.addListener( | ||
'place_changed', | ||
this.onSelected.bind(this) | ||
); | ||
}; | ||
return new Promise((resolve) => { | ||
googleMapsScript.current.addEventListener("load", () => resolve()); | ||
}); | ||
}, [googleMapsScriptBaseUrl, googleMapsScriptUrl]); | ||
if (apiKey) { | ||
this.handleLoadScript().then(() => handleAutoComplete()); | ||
} else { | ||
handleAutoComplete(); | ||
} | ||
} | ||
// Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445 | ||
useEffect(() => { | ||
if (isBrowser && window.MutationObserver && inputRef.current) { | ||
observerHack.current = new MutationObserver(() => { | ||
observerHack.current.disconnect(); | ||
disableAutofill() { | ||
// Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445 | ||
if (window.MutationObserver) { | ||
const observerHack = new MutationObserver(() => { | ||
observerHack.disconnect(); | ||
if (this.refs && this.refs.input) { | ||
this.refs.input.autocomplete = this.props.inputAutocompleteValue || 'new-password'; | ||
} | ||
inputRef.current.autocomplete = inputAutocompleteValue; | ||
}); | ||
observerHack.observe(this.refs.input, { | ||
observerHack.current.observe(inputRef.current, { | ||
attributes: true, | ||
attributeFilter: ['autocomplete'] | ||
attributeFilter: ["autocomplete"], | ||
}); | ||
} | ||
} | ||
}, [inputAutocompleteValue]); | ||
componentWillUnmount() { | ||
if (this.event) this.event.remove(); | ||
} | ||
useEffect(() => { | ||
if (autocomplete.current) { | ||
autocomplete.current.setFields(fields); | ||
} | ||
}, [fields]); | ||
onSelected() { | ||
if (this.props.onPlaceSelected && this.autocomplete) { | ||
this.props.onPlaceSelected(this.autocomplete.getPlace(), this.refs.input); | ||
useEffect(() => { | ||
if (autocomplete.current) { | ||
autocomplete.current.setBounds(bounds); | ||
} | ||
} | ||
}, [bounds]); | ||
handleLoadScript = () => { | ||
const googleMapsScriptUrl = `https://maps.googleapis.com/maps/api/js?key=${this.props.apiKey}&libraries=places`; | ||
useEffect(() => { | ||
if (autocomplete.current) { | ||
autocomplete.current.setComponentRestrictions(componentRestrictions); | ||
} | ||
}, [componentRestrictions]); | ||
// Check if script exists already | ||
if ( | ||
document.querySelectorAll(`script[src="${googleMapsScriptUrl}"]`).length > | ||
0 | ||
) { | ||
return Promise.resolve(); | ||
useEffect(() => { | ||
if (autocomplete.current) { | ||
autocomplete.current.setOptions(options); | ||
} | ||
}, [options]); | ||
this.googleMapsScript = document.createElement('script'); | ||
this.googleMapsScript.src = googleMapsScriptUrl; | ||
document.body.appendChild(this.googleMapsScript); | ||
return new Promise((resolve) => { | ||
this.googleMapsScript.addEventListener('load', () => resolve()); | ||
}); | ||
}; | ||
render() { | ||
const { | ||
onPlaceSelected, | ||
useEffect(() => { | ||
const config = { | ||
...options, | ||
types, | ||
bounds, | ||
componentRestrictions, | ||
bounds, | ||
options, | ||
apiKey, | ||
inputAutocompleteValue, | ||
...rest | ||
} = this.props; | ||
}; | ||
return <input ref="input" {...rest} />; | ||
} | ||
} | ||
if (autocomplete.current) return; | ||
export class ReactCustomGoogleAutocomplete extends React.Component { | ||
static propTypes = { | ||
input: PropTypes.node.isRequired, | ||
onOpen: PropTypes.func.isRequired, | ||
onClose: PropTypes.func.isRequired | ||
}; | ||
const handleAutoComplete = () => { | ||
// eslint-disable-next-line no-undef | ||
autocomplete.current = new google.maps.places.Autocomplete( | ||
inputRef.current, | ||
config | ||
); | ||
constructor(props) { | ||
super(props); | ||
this.service = new google.maps.places.AutocompleteService(); | ||
} | ||
if (autocompleteRef) autocompleteRef.current = autocomplete.current; | ||
onChange(e) { | ||
const { types = ['(cities)'] } = this.props; | ||
event.current = autocomplete.current.addListener("place_changed", () => { | ||
if (onPlaceSelected && autocomplete && autocomplete.current) { | ||
onPlaceSelected( | ||
autocomplete.current.getPlace(), | ||
inputRef.current, | ||
autocomplete.current | ||
); | ||
} | ||
}); | ||
}; | ||
if (e.target.value) { | ||
this.service.getPlacePredictions( | ||
{ input: e.target.value, types }, | ||
(predictions, status) => { | ||
if (status === 'OK' && predictions && predictions.length > 0) { | ||
this.props.onOpen(predictions); | ||
} else { | ||
this.props.onClose(); | ||
} | ||
} | ||
); | ||
if (apiKey) { | ||
handleLoadScript().then(() => handleAutoComplete()); | ||
} else { | ||
this.props.onClose(); | ||
handleAutoComplete(); | ||
} | ||
} | ||
componentDidMount() { | ||
if (this.props.input.value) { | ||
this.placeService = new google.maps.places.PlacesService(this.refs.div); | ||
this.placeService.getDetails( | ||
{ placeId: this.props.input.value }, | ||
(e, status) => { | ||
if (status === 'OK') { | ||
this.refs.input.value = e.formatted_address; | ||
} | ||
} | ||
); | ||
} | ||
} | ||
return () => (event.current ? event.current.remove() : undefined); | ||
}, [ | ||
types, | ||
options, | ||
fields, | ||
componentRestrictions, | ||
apiKey, | ||
onPlaceSelected, | ||
handleLoadScript, | ||
autocompleteRef, | ||
bounds, | ||
]); | ||
render() { | ||
return ( | ||
<div> | ||
{React.cloneElement(this.props.input, { | ||
...this.props, | ||
ref: 'input', | ||
onChange: e => { | ||
this.onChange(e); | ||
} | ||
})} | ||
<div ref="div" /> | ||
</div> | ||
); | ||
} | ||
return ( | ||
<input | ||
ref={(el) => { | ||
inputRef.current = el; | ||
if (refProp) refProp.current = el; | ||
}} | ||
{...rest} | ||
/> | ||
); | ||
} | ||
ReactGoogleAutocomplete.propTypes = { | ||
apiKey: PropTypes.string, | ||
ref: PropTypes.oneOfType([ | ||
// Either a function | ||
PropTypes.func, | ||
// Or the instance of a DOM native element | ||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }), | ||
]), | ||
autocompleteRef: PropTypes.oneOfType([ | ||
PropTypes.func, | ||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }), | ||
]), | ||
googleMapsScriptBaseUrl: PropTypes.string, | ||
onPlaceSelected: PropTypes.func, | ||
inputAutocompleteValue: PropTypes.string, | ||
options: PropTypes.shape({ | ||
componentRestrictions: PropTypes.object, | ||
bounds: PropTypes.object, | ||
location: PropTypes.object, | ||
offset: PropTypes.number, | ||
origin: PropTypes.object, | ||
radius: PropTypes.number, | ||
sessionToken: PropTypes.object, | ||
types: PropTypes.arrayOf(PropTypes.string), | ||
}), | ||
}; | ||
export default forwardRef((props, ref) => ( | ||
<ReactGoogleAutocomplete {...props} refProp={ref} /> | ||
)); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23203
9
416
142
1