A set of composable components for easy use of Google Maps in your Vue 3 projects.

Last update: May 26, 2022

vue3-google-map

Composable components for easy use of Google Maps with Vue 3

vue3-google-map offers a set of composable components for easy use of Google Maps in your Vue 3 projects.

Installation

NPM

npm install vue3-google-map
# OR
yarn add vue3-google-map

CDN

Include the following script tag in your index.html (make sure to include it after Vue 3).

<script src="https://unpkg.com/vue3-google-map">script>

Usage

Please refer to the documentation

Development Setup

Clone the repo and checkout the develop branch.

# install deps
yarn install

# build dist files
yarn build

# run develpment server and serve basic example
yarn dev

# serve docs
yarn docs

Contribution

All contributions are welcome. Before submitting a PR though it would be nice if you created an issue explaining what you want to acheive and why.

License

MIT

GitHub

https://github.com/inocan-group/vue3-google-map
Comments
  • 1. Issue when generating app with v0.7.2+

    My application build fails with the latest release (0.7.6). Here is my build log:

     ERROR  Failed to compile with 1 error                                                            4:03:31 PM
    
     error  in ./node_modules/vue3-google-map/dist/es/index.js
    
    Module parse failed: Unexpected token (1:6510)
    You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
    > import{ref as e,defineComponent as t,provide as l,onBeforeUnmount as o,onMounted as r,toRef as s,watch as i,openBlock as a,createBlock as n,createVNode as y,mergeProps as p,renderSlot as c,inject as f,Fragment as m,createCommentVNode as u,withDirectives as d,vShow as g}from"vue";var T=function e(t,l){if(t===l)return!0;if(t&&l&&"object"==typeof t&&"object"==typeof l){if(t.constructor!==l.constructor)return!1;var o,r,s;if(Array.isArray(t)){if((o=t.length)!=l.length)return!1;for(r=o;0!=r--;)if(!e(t[r],l[r]))return!1;return!0}if(t.constructor===RegExp)return t.source===l.source&&t.flags===l.flags;if(t.valueOf!==Object.prototype.valueOf)return t.valueOf()===l.valueOf();if(t.toString!==Object.prototype.toString)return t.toString()===l.toString();if((o=(s=Object.keys(t)).length)!==Object.keys(l).length)return!1;for(r=o;0!=r--;)if(!Object.prototype.hasOwnProperty.call(l,s[r]))return!1;for(r=o;0!=r--;){var i=s[r];if(!e(t[i],l[i]))return!1}return!0}return t!=t&&l!=l};class h{constructor({apiKey:e,channel:t,client:l,id:o="__googleMapsScriptId",libraries:r=[],language:s,region:i,version:a,mapIds:n,nonce:y,retries:p=3,url:c="https://maps.googleapis.com/maps/api/js"}){if(this.CALLBACK="__googleMapsCallback",this.callbacks=[],this.done=!1,this.loading=!1,this.errors=[],this.version=a,this.apiKey=e,this.channel=t,this.client=l,this.id=o||"__googleMapsScriptId",this.libraries=r,this.language=s,this.region=i,this.mapIds=n,this.nonce=y,this.retries=p,this.url=c,h.instance){if(!T(this.options,h.instance.options))throw new Error(`Loader must not be called again with different options. ${JSON.stringify(this.options)} !== ${JSON.stringify(h.instance.options)}`);return h.instance}h.instance=this}get options(){return{version:this.version,apiKey:this.apiKey,channel:this.channel,client:this.client,id:this.id,libraries:this.libraries,language:this.language,region:this.region,mapIds:this.mapIds,nonce:this.nonce,url:this.url}}createUrl(){let e=this.url;return e+=`?callback=${this.CALLBACK}`,this.apiKey&&(e+=`&key=${this.apiKey}`),this.channel&&(e+=`&channel=${this.channel}`),this.client&&(e+=`&client=${this.client}`),this.libraries.length>0&&(e+=`&libraries=${this.libraries.join(",")}`),this.language&&(e+=`&language=${this.language}`),this.region&&(e+=`&region=${this.region}`),this.version&&(e+=`&v=${this.version}`),this.mapIds&&(e+=`&map_ids=${this.mapIds.join(",")}`),e}load(){return this.loadPromise()}loadPromise(){return new Promise(((e,t)=>{this.loadCallback((l=>{l?t(l):e()}))}))}loadCallback(e){this.callbacks.push(e),this.execute()}setScript(){if(document.getElementById(this.id))return void this.callback();const e=this.createUrl(),t=document.createElement("script");t.id=this.id,t.type="text/javascript",t.src=e,t.onerror=this.loadErrorCallback.bind(this),t.defer=!0,t.async=!0,this.nonce&&(t.nonce=this.nonce),document.head.appendChild(t)}deleteScript(){const e=document.getElementById(this.id);e&&e.remove()}resetIfRetryingFailed(){const e=this.retries+1;this.done&&!this.loading&&this.errors.length>=e&&(this.deleteScript(),this.done=!1,this.loading=!1,this.errors=[],this.onerrorEvent=null)}loadErrorCallback(e){if(this.errors.push(e),this.errors.length<=this.retries){const e=this.errors.length*Math.pow(2,this.errors.length);console.log(`Failed to load Google Maps script, retrying in ${e} ms.`),setTimeout((()=>{this.deleteScript(),this.setScript()}),e)}else this.onerrorEvent=e,this.callback()}setCallback(){window.__googleMapsCallback=this.callback.bind(this)}callback(){this.done=!0,this.loading=!1,this.callbacks.forEach((e=>{e(this.onerrorEvent)})),this.callbacks=[]}execute(){window.google&&window.google.maps&&window.google.maps.version&&(console.warn("Google Maps already loaded outside @googlemaps/js-api-loader.This may result in undesirable behavior as options and script parameters may not match."),this.callback()),this.resetIfRetryingFailed(),this.done?this.callback():this.loading||(this.loading=!0,this.setCallback(),this.setScript())}}const b=Symbol("api"),v=Symbol("map"),k=e(null),C=["bounds_changed","center_changed","click","dblclick","drag","dragend","dragstart","heading_changed","idle","maptypeid_changed","mousemove","mouseout","mouseover","projection_changed","resize","rightclick","tilesloaded","tilt_changed","zoom_changed"],w=["animation_changed","click","dblclick","rightclick","dragstart","dragend","drag","mouseover","mousedown","mouseout","mouseup","draggable_changed","clickable_changed","contextmenu","cursor_changed","flat_changed","rightclick","zindex_changed","icon_changed","position_changed","shape_changed","title_changed","visible_changed"],x=["click","dblclick","drag","dragend","dragstart","mousedown","mousemove","mouseout","mouseover","mouseup","rightclick"],_=x,S=x.concat(["bounds_changed"]),O=x.concat(["center_changed","radius_changed"]);var P=t({props:{apiKey:{type:String,default:""},libraries:Array,region:String,language:String,backgroundColor:String,center:Object,clickableIcons:{type:Boolean,default:void 0},controlSize:Number,disableDefaultUi:{type:Boolean,default:void 0},disableDoubleClickZoom:{type:Boolean,default:void 0},draggable:{type:Boolean,default:void 0},draggableCursor:String,draggingCursor:String,fullscreenControl:{type:Boolean,default:void 0},fullscreenControlPosition:String,gestureHandling:String,heading:Number,keyboardShortcuts:{type:Boolean,default:void 0},mapTypeControl:{type:Boolean,default:void 0},mapTypeControlOptions:Object,mapTypeId:{type:[Number,String]},maxZoom:Number,minZoom:Number,noClear:{type:Boolean,default:void 0},panControl:{type:Boolean,default:void 0},panControlPosition:String,restriction:Object,rotateControl:{type:Boolean,default:void 0},rotateControlPosition:String,scaleControl:{type:Boolean,default:void 0},scaleControlStyle:Number,scrollwheel:{type:Boolean,default:void 0},streetView:Object,streetViewControl:{type:Boolean,default:void 0},streetViewControlPosition:String,styles:Array,tilt:Number,zoom:Number,zoomControl:{type:Boolean,default:void 0},zoomControlPosition:String},emits:C,setup(t,{emit:a}){const n=e(null),y=e(!1),p=e(null),c=e(null);l(v,p),l(b,c);const f=()=>{const e={backgroundColor:t.backgroundColor,center:t.center,clickableIcons:t.clickableIcons,controlSize:t.controlSize,disableDefaultUI:t.disableDefaultUi,disableDoubleClickZoom:t.disableDoubleClickZoom,draggable:t.draggable,draggableCursor:t.draggableCursor,draggingCursor:t.draggingCursor,fullscreenControl:t.fullscreenControl,fullscreenControlOptions:t.fullscreenControlPosition?{position:c.value?.ControlPosition[t.fullscreenControlPosition]}:{},gestureHandling:t.gestureHandling,heading:t.heading,keyboardShortcuts:t.keyboardShortcuts,mapTypeControl:t.mapTypeControl,mapTypeControlOptions:t.mapTypeControlOptions,mapTypeId:t.mapTypeId,maxZoom:t.maxZoom,minZoom:t.minZoom,noClear:t.noClear,panControl:t.panControl,panControlOptions:t.panControlPosition?{position:c.value?.ControlPosition[t.panControlPosition]}:{},restriction:t.restriction,rotateControl:t.rotateControl,rotateControlOptions:t.rotateControlPosition?{position:c.value?.ControlPosition[t.rotateControlPosition]}:{},scaleControl:t.scaleControl,scaleControlOptions:t.scaleControlStyle?{style:t.scaleControlStyle}:{},scrollwheel:t.scrollwheel,streetView:t.streetView,streetViewControl:t.streetViewControl,streetViewControlOptions:t.streetViewControlPosition?{position:c.value?.ControlPosition[t.streetViewControlPosition]}:{},styles:t.styles,tilt:t.tilt,zoom:t.zoom,zoomControl:t.zoomControl,zoomControlOptions:t.zoomControlPosition?{position:c.value?.ControlPosition[t.zoomControlPosition]}:{}};return Object.keys(e).forEach((t=>void 0===e[t]&&delete e[t])),e};return o((()=>{p.value&&c.value?.event.clearInstanceListeners(p.value)})),r((()=>{try{k.value=new h({apiKey:t.apiKey,version:"weekly",libraries:t.libraries||["places"],language:t.language,region:t.region})}catch(e){console.error(e)}finally{k.value.load().then((()=>{const{Map:e}=c.value=google.maps;p.value=new e(n.value,f()),C.forEach((e=>{p.value?.addListener(e,(t=>a(e,t)))})),y.value=!0;const l=Object.keys(t).filter((e=>!["center","zoom"].includes(e))).map((e=>s(t,e)));i([()=>t.center,()=>t.zoom,...l],(([e,t],[l,o])=>{const{center:r,zoom:s,...i}=f();p.value?.setOptions(i),void 0!==t&&t!==o&&p.value?.setZoom(t),e&&(l&&e.lng===l.lng&&e.lat===l.lat||p.value?.panTo(e))}))}))}})),{mapRef:n,ready:y,map:p,api:c}}});P.render=function(e,t,l,o,r,s){return a(),n("div",null,[y("div",p(e.$attrs,{ref:"mapRef"}),null,16),c(e.$slots,"default")])},P.__file="src/components/GoogleMap.vue";const j=(t,l,o,r)=>{let s=null;const a=e(null),n=f(v,e(null)),y=f(b,e(null));return i([n,o],((e,i,p)=>{n.value&&y.value&&(a.value=s=new y.value[t]({...o.value,map:n.value}),l.forEach((e=>{s?.addListener(e,(t=>r(e,t)))}))),p((()=>{s&&(y.value?.event.clearInstanceListeners(s),s.setMap(null))}))}),{immediate:!0}),{component:a}};var I=t({props:{options:{type:Object,required:!0}},emits:w,setup(e,{emit:t}){const l=s(e,"options");return{marker:j("Marker",w,l,t)}},render:()=>null}),z=t({props:{options:{type:Object,required:!0}},emits:x,setup(e,{emit:t}){const l=s(e,"options");return{polyline:j("Polyline",x,l,t)}},render:()=>null}),B=t({props:{options:{type:Object,required:!0}},emits:_,setup(e,{emit:t}){const l=s(e,"options");return{polygon:j("Polygon",_,l,t)}},render:()=>null}),E=t({props:{options:{type:Object,required:!0}},emits:S,setup(e,{emit:t}){const l=s(e,"options");return{rectangle:j("Rectangle",S,l,t)}},render:()=>null}),$=t({props:{options:{type:Object,required:!0}},emits:O,setup(e,{emit:t}){const l=s(e,"options");return{circle:j("Circle",O,l,t)}},render:()=>null}),M=t({props:{position:{type:String,required:!0},index:Number},emits:["content:loaded"],setup(t,{emit:l}){const o=e(null),r=f(v,e(null)),s=f(b,e(null)),a=e(!1),n=i(s,(()=>{s.value&&r.value&&(s.value.event.addListenerOnce(r.value,"tilesloaded",(()=>{a.value=!0,l("content:loaded")})),n())}));return i([r,()=>t.position,()=>t.index],((e,[l,i],a)=>{r.value&&s.value&&(t.index&&(o.value.index=t.index),o.value&&r.value.controls[s.value.ControlPosition[t.position]].push(o.value)),a((()=>{if(r.value&&s.value&&i){let e;r.value.controls[s.value.ControlPosition[i]].forEach(((t,l)=>{t===o.value&&(e=l)})),e&&r.value.controls[s.value.ControlPosition[i]].removeAt(e)}}))}),{immediate:!0}),{controlRef:o,showContent:a}}});const K={ref:"controlRef"};M.render=function(e,t,l,o,r,s){return a(),n(m,null,[u("\n    v-show must be used instead of v-if otherwise there\n    would be no rendered content pushed to the map controls\n  "),d(y("div",K,[c(e.$slots,"default")],512),[[g,e.showContent]])],2112)},M.__file="src/components/CustomControl.vue";const N=[{elementType:"geometry",stylers:[{color:"#1d2c4d"}]},{elementType:"labels.text.fill",stylers:[{color:"#8ec3b9"}]},{elementType:"labels.text.stroke",stylers:[{color:"#1a3646"}]},{featureType:"administrative.country",elementType:"geometry.stroke",stylers:[{color:"#4b6878"}]},{featureType:"administrative.land_parcel",elementType:"labels.text.fill",stylers:[{color:"#64779e"}]},{featureType:"administrative.province",elementType:"geometry.stroke",stylers:[{color:"#4b6878"}]},{featureType:"landscape.man_made",elementType:"geometry.stroke",stylers:[{color:"#334e87"}]},{featureType:"landscape.natural",elementType:"geometry",stylers:[{color:"#023e58"}]},{featureType:"poi",elementType:"geometry",stylers:[{color:"#283d6a"}]},{featureType:"poi",elementType:"labels.text.fill",stylers:[{color:"#6f9ba5"}]},{featureType:"poi",elementType:"labels.text.stroke",stylers:[{color:"#1d2c4d"}]},{featureType:"poi.park",elementType:"geometry.fill",stylers:[{color:"#023e58"}]},{featureType:"poi.park",elementType:"labels.text.fill",stylers:[{color:"#3C7680"}]},{featureType:"road",elementType:"geometry",stylers:[{color:"#304a7d"}]},{featureType:"road",elementType:"labels.text.fill",stylers:[{color:"#98a5be"}]},{featureType:"road",elementType:"labels.text.stroke",stylers:[{color:"#1d2c4d"}]},{featureType:"road.highway",elementType:"geometry",stylers:[{color:"#2c6675"}]},{featureType:"road.highway",elementType:"geometry.stroke",stylers:[{color:"#255763"}]},{featureType:"road.highway",elementType:"labels.text.fill",stylers:[{color:"#b0d5ce"}]},{featureType:"road.highway",elementType:"labels.text.stroke",stylers:[{color:"#023e58"}]},{featureType:"transit",elementType:"labels.text.fill",stylers:[{color:"#98a5be"}]},{featureType:"transit",elementType:"labels.text.stroke",stylers:[{color:"#1d2c4d"}]},{featureType:"transit.line",elementType:"geometry.fill",stylers:[{color:"#283d6a"}]},{featureType:"transit.station",elementType:"geometry",stylers:[{color:"#3a4762"}]},{featureType:"water",elementType:"geometry",stylers:[{color:"#0e1626"}]},{featureType:"water",elementType:"labels.text.fill",stylers:[{color:"#4e6d70"}]}],L=[{elementType:"geometry",stylers:[{color:"#242f3e"}]},{elementType:"labels.text.stroke",stylers:[{color:"#242f3e"}]},{elementType:"labels.text.fill",stylers:[{color:"#746855"}]},{featureType:"administrative.locality",elementType:"labels.text.fill",stylers:[{color:"#d59563"}]},{featureType:"poi",elementType:"labels.text.fill",stylers:[{color:"#d59563"}]},{featureType:"poi.park",elementType:"geometry",stylers:[{color:"#263c3f"}]},{featureType:"poi.park",elementType:"labels.text.fill",stylers:[{color:"#6b9a76"}]},{featureType:"road",elementType:"geometry",stylers:[{color:"#38414e"}]},{featureType:"road",elementType:"geometry.stroke",stylers:[{color:"#212a37"}]},{featureType:"road",elementType:"labels.text.fill",stylers:[{color:"#9ca5b3"}]},{featureType:"road.highway",elementType:"geometry",stylers:[{color:"#746855"}]},{featureType:"road.highway",elementType:"geometry.stroke",stylers:[{color:"#1f2835"}]},{featureType:"road.highway",elementType:"labels.text.fill",stylers:[{color:"#f3d19c"}]},{featureType:"transit",elementType:"geometry",stylers:[{color:"#2f3948"}]},{featureType:"transit.station",elementType:"labels.text.fill",stylers:[{color:"#d59563"}]},{featureType:"water",elementType:"geometry",stylers:[{color:"#17263c"}]},{featureType:"water",elementType:"labels.text.fill",stylers:[{color:"#515c6d"}]},{featureType:"water",elementType:"labels.text.stroke",stylers:[{color:"#17263c"}]}],V=[{featureType:"all",elementType:"labels.text.fill",stylers:[{saturation:36},{color:"#000000"},{lightness:40}]},{featureType:"all",elementType:"labels.text.stroke",stylers:[{visibility:"on"},{color:"#000000"},{lightness:16}]},{featureType:"all",elementType:"labels.icon",stylers:[{visibility:"off"}]},{featureType:"administrative",elementType:"geometry.fill",stylers:[{color:"#000000"},{lightness:20}]},{featureType:"administrative",elementType:"geometry.stroke",stylers:[{color:"#000000"},{lightness:17},{weight:1.2}]},{featureType:"landscape",elementType:"geometry",stylers:[{color:"#000000"},{lightness:20}]},{featureType:"poi",elementType:"geometry",stylers:[{color:"#000000"},{lightness:21}]},{featureType:"road.highway",elementType:"geometry.fill",stylers:[{color:"#000000"},{lightness:17}]},{featureType:"road.highway",elementType:"geometry.stroke",stylers:[{color:"#000000"},{lightness:29},{weight:.2}]},{featureType:"road.arterial",elementType:"geometry",stylers:[{color:"#000000"},{lightness:18}]},{featureType:"road.local",elementType:"geometry",stylers:[{color:"#000000"},{lightness:16}]},{featureType:"transit",elementType:"geometry",stylers:[{color:"#000000"},{lightness:19}]},{featureType:"water",elementType:"geometry",stylers:[{color:"#000000"},{lightness:17}]}],Z=[{featureType:"administrative.land_parcel",elementType:"all",stylers:[{visibility:"off"}]},{featureType:"administrative.land_parcel",elementType:"geometry.stroke",stylers:[{visibility:"off"},{weight:7}]},{featureType:"administrative.locality",elementType:"geometry.stroke",stylers:[{visibility:"on"}]},{featureType:"administrative.locality",elementType:"labels.text.fill",stylers:[{visibility:"on"}]},{featureType:"administrative.locality",elementType:"labels.text.stroke",stylers:[{visibility:"on"}]},{featureType:"administrative.neighborhood",stylers:[{visibility:"off"}]},{featureType:"administrative.neighborhood",elementType:"geometry.fill",stylers:[{color:"#00ff28"},{visibility:"on"},{weight:2}]},{featureType:"administrative.neighborhood",elementType:"geometry.stroke",stylers:[{color:"#00ff28"},{visibility:"on"}]},{featureType:"administrative.neighborhood",elementType:"labels.icon",stylers:[{visibility:"off"}]},{featureType:"administrative.neighborhood",elementType:"labels.text.stroke",stylers:[{visibility:"off"}]},{featureType:"landscape.natural",elementType:"geometry.fill",stylers:[{visibility:"on"}]},{featureType:"poi",elementType:"labels.text",stylers:[{visibility:"off"}]},{featureType:"poi.attraction",stylers:[{visibility:"off"}]},{featureType:"poi.business",elementType:"all",stylers:[{visibility:"off"}]},{featureType:"poi.medical",elementType:"all",stylers:[{visibility:"off"}]},{featureType:"poi.place_of_worship",elementType:"all",stylers:[{visibility:"off"}]},{featureType:"poi.school",elementType:"all",stylers:[{visibility:"off"}]},{featureType:"poi.sports_complex",elementType:"geometry.fill",stylers:[{visibility:"on"}]},{featureType:"poi.sports_complex",elementType:"labels.icon",stylers:[{visibility:"off"}]},{featureType:"road",elementType:"labels.text",stylers:[{visibility:"on"},{lightness:-10},{color:"#b5b5b5"},{weight:.2}]},{featureType:"road",elementType:"labels.icon",stylers:[{visibility:"off"}]},{featureType:"road.local",elementType:"geometry.fill",stylers:[{color:"#fbfbfb"},{lightness:-15},{weight:.5}]},{featureType:"road.local",elementType:"geometry.stroke",stylers:[{visibility:"off"}]},{featureType:"transit",elementType:"all",stylers:[{visibility:"off"}]},{featureType:"transit.station",elementType:"labels.icon",stylers:[{visibility:"off"}]},{featureType:"water",elementType:"labels.text",stylers:[{visibility:"off"}]}],A=[{elementType:"geometry",stylers:[{color:"#ebe3cd"}]},{elementType:"labels.text.fill",stylers:[{color:"#523735"}]},{elementType:"labels.text.stroke",stylers:[{color:"#f5f1e6"}]},{featureType:"administrative",elementType:"geometry.stroke",stylers:[{color:"#c9b2a6"}]},{featureType:"administrative.land_parcel",elementType:"geometry.stroke",stylers:[{color:"#dcd2be"}]},{featureType:"administrative.land_parcel",elementType:"labels.text.fill",stylers:[{color:"#ae9e90"}]},{featureType:"landscape.natural",elementType:"geometry",stylers:[{color:"#dfd2ae"}]},{featureType:"poi",elementType:"geometry",stylers:[{color:"#dfd2ae"}]},{featureType:"poi",elementType:"labels.text.fill",stylers:[{color:"#93817c"}]},{featureType:"poi.park",elementType:"geometry.fill",stylers:[{color:"#a5b076"}]},{featureType:"poi.park",elementType:"labels.text.fill",stylers:[{color:"#447530"}]},{featureType:"road",elementType:"geometry",stylers:[{color:"#f5f1e6"}]},{featureType:"road.arterial",elementType:"geometry",stylers:[{color:"#fdfcf8"}]},{featureType:"road.highway",elementType:"geometry",stylers:[{color:"#f8c967"}]},{featureType:"road.highway",elementType:"geometry.stroke",stylers:[{color:"#e9bc62"}]},{featureType:"road.highway.controlled_access",elementType:"geometry",stylers:[{color:"#e98d58"}]},{featureType:"road.highway.controlled_access",elementType:"geometry.stroke",stylers:[{color:"#db8555"}]},{featureType:"road.local",elementType:"labels.text.fill",stylers:[{color:"#806b63"}]},{featureType:"transit.line",elementType:"geometry",stylers:[{color:"#dfd2ae"}]},{featureType:"transit.line",elementType:"labels.text.fill",stylers:[{color:"#8f7d77"}]},{featureType:"transit.line",elementType:"labels.text.stroke",stylers:[{color:"#ebe3cd"}]},{featureType:"transit.station",elementType:"geometry",stylers:[{color:"#dfd2ae"}]},{featureType:"water",elementType:"geometry.fill",stylers:[{color:"#b9d3c2"}]},{featureType:"water",elementType:"labels.text.fill",stylers:[{color:"#92998d"}]}],R=[{featureType:"all",elementType:"labels",stylers:[{visibility:"on"}]},{featureType:"all",elementType:"labels.text.fill",stylers:[{saturation:36},{color:"#000000"},{lightness:40}]},{featureType:"all",elementType:"labels.text.stroke",stylers:[{visibility:"on"},{color:"#000000"},{lightness:16}]},{featureType:"all",elementType:"labels.icon",stylers:[{visibility:"off"}]},{featureType:"administrative",elementType:"geometry.fill",stylers:[{color:"#000000"},{lightness:20}]},{featureType:"administrative",elementType:"geometry.stroke",stylers:[{color:"#000000"},{lightness:17},{weight:1.2}]},{featureType:"administrative.country",elementType:"labels.text.fill",stylers:[{color:"#e5c163"}]},{featureType:"administrative.locality",elementType:"labels.text.fill",stylers:[{color:"#c4c4c4"}]},{featureType:"administrative.neighborhood",elementType:"labels.text.fill",stylers:[{color:"#e5c163"}]},{featureType:"landscape",elementType:"geometry",stylers:[{color:"#000000"},{lightness:20}]},{featureType:"poi",elementType:"geometry",stylers:[{color:"#000000"},{lightness:21},{visibility:"on"}]},{featureType:"poi.business",elementType:"geometry",stylers:[{visibility:"on"}]},{featureType:"road.highway",elementType:"geometry.fill",stylers:[{color:"#e5c163"},{lightness:0}]},{featureType:"road.highway",elementType:"geometry.stroke",stylers:[{visibility:"off"}]},{featureType:"road.highway",elementType:"labels.text.fill",stylers:[{color:"#ffffff"}]},{featureType:"road.highway",elementType:"labels.text.stroke",stylers:[{color:"#e5c163"}]},{featureType:"road.arterial",elementType:"geometry",stylers:[{color:"#000000"},{lightness:18}]},{featureType:"road.arterial",elementType:"geometry.fill",stylers:[{color:"#575757"}]},{featureType:"road.arterial",elementType:"labels.text.fill",stylers:[{color:"#ffffff"}]},{featureType:"road.arterial",elementType:"labels.text.stroke",stylers:[{color:"#2c2c2c"}]},{featureType:"road.local",elementType:"geometry",stylers:[{color:"#000000"},{lightness:16}]},{featureType:"road.local",elementType:"labels.text.fill",stylers:[{color:"#999999"}]},{featureType:"transit",elementType:"geometry",stylers:[{color:"#000000"},{lightness:19}]},{featureType:"water",elementType:"geometry",stylers:[{color:"#000000"},{lightness:17}]}],q=[...R,...Z],D=[{featureType:"water",elementType:"geometry",stylers:[{color:"#e9e9e9"},{lightness:17}]},{featureType:"landscape",elementType:"geometry",stylers:[{color:"#f5f5f5"},{lightness:20}]},{featureType:"road.highway",elementType:"geometry.fill",stylers:[{color:"#ffffff"},{lightness:17}]},{featureType:"road.highway",elementType:"geometry.stroke",stylers:[{color:"#ffffff"},{lightness:29},{weight:.2}]},{featureType:"road.arterial",elementType:"geometry",stylers:[{color:"#ffffff"},{lightness:18}]},{featureType:"road.local",elementType:"geometry",stylers:[{color:"#ffffff"},{lightness:16}]},{featureType:"poi",elementType:"geometry",stylers:[{color:"#f5f5f5"},{lightness:21}]},{featureType:"poi.park",elementType:"geometry",stylers:[{color:"#dedede"},{lightness:21}]},{elementType:"labels.text.stroke",stylers:[{visibility:"on"},{color:"#ffffff"},{lightness:16}]},{elementType:"labels.text.fill",stylers:[{saturation:36},{color:"#333333"},{lightness:40}]},{elementType:"labels.icon",stylers:[{visibility:"off"}]},{featureType:"transit",elementType:"geometry",stylers:[{color:"#f2f2f2"},{lightness:19}]},{featureType:"administrative",elementType:"geometry.fill",stylers:[{color:"#fefefe"},{lightness:20}]},{featureType:"administrative",elementType:"geometry.stroke",stylers:[{color:"#fefefe"},{lightness:17},{weight:1.2}]}];export{$ as Circle,M as CustomControl,P as GoogleMap,I as Marker,B as Polygon,z as Polyline,E as Rectangle,N as aubergine,L as dark,V as grey,Z as minimal,A as retro,R as roadways,q as roadwaysMinimal,D as ultraLight};
    | 
    
     @ ./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader-v16/dist??ref--0-1!./src/App.vue?vue&type=script&lang=js 3:0-44 6:15-24
     @ ./src/App.vue?vue&type=script&lang=js
     @ ./src/App.vue
     @ ./src/main.js
     @ multi ./src/main.js
    
     ERROR  Build failed with errors.
    

    The latest vue3-google-map version that works for me is 0.7.1.

    With version 0.7.2 my app also fails to build, but the reported error is different:

     ERROR  Failed to compile with 1 error                                                            4:04:32 PM
    
     error  in ./node_modules/vue3-google-map/dist/es/index.js
    
    Module parse failed: Unexpected token (416:44)
    You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
    |                 fullscreenControlOptions: props.fullscreenControlPosition
    |                     ? {
    >                         position: api.value?.ControlPosition[props.fullscreenControlPosition],
    |                     }
    |                     : {},
    
     @ ./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader-v16/dist??ref--0-1!./src/App.vue?vue&type=script&lang=js 3:0-44 6:15-24
     @ ./src/App.vue?vue&type=script&lang=js
     @ ./src/App.vue
     @ ./src/main.js
     @ multi ./src/main.js
    
     ERROR  Build failed with errors.
    
    Reviewed by JoseGoncalves at 2021-04-29 15:06
  • 2. Feature: HtmlMarkers

    Using a CustomOverlay it's possible to mimic the behaviour of a marker while adding the ability to render html using a slot.

    The eregnier/vue2-gmap-custom-marker plugin for diegoazh/gmap-vue has this working (demo). Unfortunately this is all written in Vue2 and not usable in Vue3.

    I've tried porting the plugin myself but my JS skills are very limited.

    Is there any chance this wonderful package will support this feature in the future?

    Reviewed by daniel-de-wit at 2021-03-05 07:27
  • 3. Uncaught (in promise) Object { message: "Map: Expected mapDiv of type HTMLElement but was passed null.",

    Hi, I am trying to use this library but I get this error:

    Uncaught (in promise) 
    Object { message: "Map: Expected mapDiv of type HTMLElement but was passed null.",....
    

    This is how I used it in my content component

    ....
    // script.js
    import { GoogleMap, Marker } from "vue3-google-map";
    
    export default {
    	name: "Content",
    	props: {
    		data: Object,
    	},
    	components: { GoogleMap, Marker },
    	data() {
    		return {
                        center: { lat: 40.689247, lng: -74.044502 },
    .....
    
    //component.vue
    ....
    <GoogleMap
    	:api-key="API_KEY"
    	style="width: 100%; height: 500px"
    	:center="center"
    	:zoom="15"
    >
    <Marker :options="{ position: center }" />
    </GoogleMap>
    ...
    

    am I doing this properly?

    Reviewed by web-programmer-here at 2022-02-11 18:53
  • 4. Add Info Window

    Adding the Info Windows feature to this repo is a feature-request for this library. We are happy to accept PR's for this work as there is not immediate demand internally for this feature but we do understand why it might be useful and it will eventually be incorporated.

    Reviewed by yankeeinlondon at 2021-05-15 18:10
  • 5. Examples of using v-for with

    I've tried a few ways to iterate my array to create several markers without success. Have you had to do this?

    I'm looking to confirm this would work and an example. I've tried the below ways and get some awful Vue errors, i.e. Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://n

    Option 1:

    Option 2: <Marker v-for="location in state.locations" :key="location.id" :options="{ position: { lat: location.lat, lng: location.lng } }" @click="markerClicked"/>

    Reviewed by jasonandress at 2020-12-04 22:12
  • 6. Where to register google maps API events listener?

    Where should I register maps API event listeners? I tried to add a listener to click event in mounted hooks but the component's map` property is evaluated to null at this point in a component's lifecycle. Thank you.

    Reviewed by prasetyaputraa at 2021-06-18 17:05
  • 7. Handling Language and Region Change

    I am having an issue with the language and region values, when I need to update them to present a different language or region.

    Seems like it doesn't like to be loaded again.

    Selection_735

    I understand the problem is loading the script again, not sure the best way to solve it.

    Reviewed by scottix at 2021-02-05 20:42
  • 8. Updating `` prop doesn't render markers

    Hello folks,

    Is this a bug or am I using the composition API wrong here (I'm new with it)?

    I have a BaseMap.vue component which is initially rendered the [] as the value of the prop markers and when the backend responds that prop is value is updated with a (new) populated array.

    Here's my BaseMap.vue:

    <template>
      <GoogleMap
        id="map"
        ref="googleMapCmp"
        class="base-map"
        map-type-id="hybrid"
        :api-key="GOOGLE_MAPS_CONFIG.key"
        :region="GOOGLE_MAPS_CONFIG.region"
        :language="GOOGLE_MAPS_CONFIG.language"
        :center="center"
        :zoom="zoom"
        :map-type-control="false"
        :clickable-icons="false"
      >
        <Marker
          v-for="marker in markers"
          :key="marker.title"
          :options="{
            title: marker.name,
            position: marker.position,
            clickable: true,
            icon: {
              url: require('../assets/icons/pin.svg'),
              scaledSize: { width: 50, height: 50 },
            },
          }"
          @click="() => centerMapToPosition(marker.position)"
        />
      </GoogleMap>
    </template>
    
    <script lang="ts">
    import {
      ComponentPublicInstance,
      PropType,
      Ref,
      computed,
      defineComponent,
      ref,
      watch,
    } from 'vue'
    import { GoogleMap, Marker } from 'vue3-google-map'
    
    import { GOOGLE_MAPS_CONFIG } from '../config'
    import { GMapsMarker, GMapsPosition, GPosition } from '../types'
    import { getBoundsForMarkers, getOptimalMapZoom } from '../utils'
    
    type MapComponent = ComponentPublicInstance & {
      map: { panTo: (pos: GPosition) => void }
    }
    
    export default defineComponent({
      props: {
        center: { type: Object as PropType<GMapsPosition>, required: true },
        markers: { type: Array as PropType<GMapsMarker[]>, default: [] },
        maxZoom: { type: Number, default: 16 },
      },
      components: {
        GoogleMap,
        Marker,
      },
      setup(props) {
        const googleMapCmp = ref(undefined) as Ref<ComponentPublicInstance | undefined>
        const boundedZoom = ref(7)
        const zoom = computed(() => Math.min(props.maxZoom, boundedZoom.value))
    
        function centerMapToPosition(newCenter: GPosition): void {
          ;(googleMapCmp.value as MapComponent)?.map.panTo(newCenter)
        }
    
        watch(
          () => props.markers,
          (newMarkers: GMapsMarker[]): void => {
            setTimeout(() => {
              const bounds = getBoundsForMarkers(newMarkers)
    
              if (bounds) {
                centerMapToPosition(bounds.getCenter())
    
                const googleMapEl = googleMapCmp.value?.$el as HTMLElement | undefined
    
                if (googleMapEl) {
                  boundedZoom.value = getOptimalMapZoom(
                    bounds,
                    googleMapEl.getBoundingClientRect(),
                  )
                }
              }
            }, 0)
          },
          { immediate: true },
        )
    
        return {
          googleMapCmp,
          GOOGLE_MAPS_CONFIG,
          center: props.center,
          markers: props.markers,
          zoom,
          centerMapToPosition,
        }
      },
    })
    </script>
    
    <style lang="scss" scoped>
    @import '../styles/constants/_colors.scss';
    @import '../styles/utils/_material-shadow';
    
    .base-map {
      @include material-shadow($level: 1, $background: $color-platinum);
      height: 100%;
      border-radius: 4px;
    
      :deep(.vue-map) {
        border-radius: 4px;
      }
    }
    
    #map {
      width: 100%;
      height: 100%;
    }
    </style>
    

    and the wrapping component StationsMap.vue:

    <template>
      <div class="stations-map">
        <BaseMap :center="center" :markers="markers" />
      </div>
    </template>
    
    <script lang="ts">
    import denormalize from '@weareredlight/denormalize_json_api'
    import { TYPE, useToast } from 'vue-toastification'
    import { defineComponent } from 'vue'
    
    import BaseMap from '../../../components/BaseMap.vue'
    import { fetchFromBackend } from '../../../utils'
    import { CurrentLocation, GMapsMarker, GMapsPosition, RESOURCE, Station } from '../../../types'
    
    interface StationWithCurrentLocation extends Station {
      'current-location'?: CurrentLocation
    }
    
    interface StationsMapData {
      center: GMapsPosition
      markers: GMapsMarker[]
    }
    
    const AUSTRIA_POSITION: GMapsPosition = { lat: 47.6, lng: 14 } as const
    
    export default defineComponent({
      components: { BaseMap },
      data(): StationsMapData {
        return {
          center: AUSTRIA_POSITION,
          markers: [],
        }
      },
      async created(): Promise<void> {
        try {
          const stations: StationWithCurrentLocation[] = denormalize(
            await fetchFromBackend({
              resource: RESOURCE.STATIONS,
              includedResources: [RESOURCE.CURRENT_LOCATION],
            }),
          ).data
    
          const markers: GMapsMarker[] = []
    
          stations.forEach((station) => {
            const currentLocation = station['current-location']
    
            if (currentLocation) {
              markers.push({
                name: station.name,
                position: {
                  lat: parseFloat(currentLocation.latitude),
                  lng: parseFloat(currentLocation.longitude),
                },
              })
            }
          })
    
          this.markers = markers
        } catch (error) {
          useToast()(error.message, { type: TYPE.ERROR })
        }
      },
    })
    </script>
    
    <style lang="scss" scoped>
    .stations-map {
      height: 100%;
    }
    </style>
    
    

    Expectation:

    • Changing the options prop in <Marker> renders new markers

    Actual:

    • No markers are rendered

    Additional info:

    • I know for a fact the Markers are updated inside BaseMap.vue because the map will zoom in and center around where the markers are expected to render (so the watcher triggers)
    • If I hardcode the array of markers inside the setup function instead of getting if from the props asynchronsouly the markers render as expected
    • Keeping the prop changing async, if I force a re-render and HMR of the BaseMap.vue component (note the prop already has value at this point since the wrapping component StationsMap.vue has already received the BE response) by making any irrelevant code change the markers will render as expected. Effectively this emulates the hardcoded case.
    • there's no output in console
    • I'm using [email protected] because of https://github.com/inocan-group/vue3-google-map/issues/13

    Thanks for all the help!!

    Reviewed by maninak at 2021-01-12 18:01
  • 9. No map controls with v0.8.1

    Upgrading to v0.8.1 I don't have the map controls overlayed on my map and I'm unable to zoom in/out or drag the map. You can check my usage demo at https://github.com/JoseGoncalves/vue-google-maps . This demo works fine with v0.8.0, but it does not work with v0.8.1.

    Reviewed by JoseGoncalves at 2021-08-09 10:13
  • 10. Changing markers cause flickering

    Maybe this is more like a howto than a bug. If I change the data basis for the markers (the 'stations' Array), all markers shortly disappear before they appear again. This happens even when there is no actual change for the active markers.

    At the corresponding list view (with the exact same data basis), Vue does the mutation/merging smoothly (even animated). But on the map, the markers get deleted and re-created (so it seems.) Here ist some code from the Component:

    <GoogleMap
      id="map"
      ref="mapRef"
      :api-key="apikey"
      style="width: 100%; height: 60vh; max-height: 120vw"
      :center="center"
      :zoom="zoom"
    >
      <Marker
        v-for="station in stations"
        :key="station.id"
        :options="{
          title: station.name,
          position: { lat: station.geoPos.lat, lng: station.geoPos.lng },
          icon: markerIcon(station)
        }"
      />
    </GoogleMap>
    
    computed: {
      apikey () {
        return this.$store.state.googlemapsapikey;
      },
      zoom () {
        return this.$store.state.map.zoom;
      },
      center () {
        return this.$store.state.map.center;
      },
      stations () {
        return this.$store.state.stations;
      }
    }
    

    And here is some code from the global Vuex store:

    state () {
      return {
        googlemapsapikey: 'xxxxxxxxxxx',
        map: {
          radius: 10000,
          zoom: 14,
          center: {
            lat: 49.7971716, 
            lng: 9.9269455
          }
        },
        stations: []
      };
    },
    mutations: {
      fetchStations (state) {
        Axios.get('/station/geo', {
          params: {
            lat: state.map.center.lat,
            lng: state.map.center.lng,
            radius: state.map.radius || 10000
          } 
        }).then(response => {
          if (response.status === 200) {
    
            // change the reactive station data
            state.stations = response.data;
          }
        });
      }
    

    Is there a way to enable smart/lazy marker changing behaviour?

    Reviewed by gerritvanaaken at 2021-02-01 22:19
  • 11. How to listen for map events?

    Hi, thanks for providing components to interact with Google Maps using Vue 3!

    Can you please provide an example on how to intercept/handle map events like zoom_changed and center_changed ?

    Reviewed by JoseGoncalves at 2020-10-25 12:32
  • 12. Pe {message: 'Map: Expected mapDiv of type HTMLElement but was passed null.'

    Hi,

    please can anyone advise on this error.

    Pe {message: 'Map: Expected mapDiv of type HTMLElement but was passed null.'}

    Is there anything I should be doing to init the map?

    Reviewed by metrix-gs at 2022-04-11 14:40
  • 13. GoogleMap doesn't render when embedded in custom element

    I'm turning a Vue component into a custom element (tag). The GoogleMap component stops working when I embed I use it inside a custom element. Why is that?

    mount Version (works)

    When App users GoogleMap, this works:

    main.js

    import SiteMap from "@/elements/App";
    import {createApp} from "vue";
    createApp(App).mount('#app');
    
    

    index.html

    ...
    <div id="app"></div>
    ...
    

    custom'Element Version (breaks) But when embedding it as a custom element, the map doesn't render:

    main.js

    import App from "@/elements/App";
    import {defineCustomElement} from "vue";
    customElements.define('my-map-component', defineCustomElement(App));
    

    index.html

    ...
    <my-map-component/>
    ...
    
    Reviewed by kerekes at 2022-03-11 10:50
  • 14. cant use local image for marker

    Hi, thanks for making this Lib

    I try to change the marker with the png / svg in my local repo but not work. is my code wrong?

    <Marker 
      v-for="location in locations" :key="location.lat"
      :options="{ 
        position: { ...location }, 
        animation: true,
        icon: {
          url: require('../../assets/img/markers/home.png'),
          scaledSize: { width: 20, height: 20 },
        }
      }" 
    />
    

    any reference to use fontawesome icons as marker?

    Thanks & have a good day

    Reviewed by pisangGoreng at 2021-11-26 10:56
  • 15. googlemap api

    i found provide https://github.com/inocan-group/vue3-google-map/blob/9ef51c45b97ac3c2d9bd72f91aed90f170a1505a/src/components/GoogleMap.vue#L79

    how to inject api on my components googlemaps api aka window.google GmapApi alternate https://github.com/xkjyeah/vue-google-maps/blob/vue2/src/main.js

    or only use ref solution https://vue3-google-map.netlify.app/advanced-usage/ ?

    Reviewed by reslear at 2021-03-23 18:33
Related tags
Reactive Vue 3 components for Google maps

Vue 3 Google maps Components Set of mostly used Google Maps components for Vue.js. Why this library exists? We heavily use Google Maps in our projects

May 23, 2022
Integrate Google Maps in your Vue application
Integrate Google Maps in your Vue application

vue-googlemaps Integrate Google Maps in your Vue application in a "Vue-way". This library is Work In Progress. More components will be available in th

Apr 10, 2022
Google maps component for vue with 2-way data binding

CONTRIBUTORS NEEDED! It's been increasingly difficult for me to make time to maintain this project. My projects at work have also gradually migrated a

May 24, 2022
A lightweight Google Maps plugin for Vue
A lightweight Google Maps plugin for Vue

x5-gmaps (Live Demo) This is a lightweight Google Maps plugin for Vue. Samples/examples/tutorials Tutorial creating a COVID Heatmap Address Autocomple

Apr 22, 2022
Google maps component for vue with 2-way data binding

vue-google-maps Demo: Demo in production Showcase with a lot of features Presentation If you want to write google map this way : <map :center="{lat:

May 10, 2022
Using Google Maps with Vue.js

stores-map Project setup npm install Compiles and hot-reloads for development npm run serve Compiles and minifies for production npm run build Run

Apr 22, 2022
A wrapper component for consuming Google Maps API built on top of VueJs v2.

A wrapper component for consuming Google Maps API built on top of VueJs v2. Fork of the popular vue-google-maps plugin.

May 14, 2022
Vue 2 components for Leaflet maps
Vue 2 components for Leaflet maps

Vue2Leaflet Vue2Leaflet is a JavaScript library for the Vue framework that wraps Leaflet making it easy to create reactive maps. How to install npm in

May 24, 2022
Integrate Azure Maps in your Vue application

Vue Azure Maps Vue Azure Maps is a library for Vue.js that integrates Azure Maps. It offers several Vue components out of the box and supports custom

May 19, 2022
A set of Vue.js components to display an interactive SVG map

vue-svg-map A set of Vue.js components to display an interactive SVG map. Demo Take a look at the live demo! Installation npm npm install --save vue-s

May 26, 2022
Yandex Maps Component for VueJS
Yandex Maps Component for VueJS

vue-yandex-maps Documentation: RU, EN Contributors ✨ Thanks goes to these wonderful people (emoji key): Wormaster ?? Nikitenko Andrey ?? Kamil ?? Alex

May 21, 2022
Annotate maps and generate GeoJSON in Kirby by drawing markers, paths and shapes
Annotate maps and generate GeoJSON in Kirby by drawing markers, paths and shapes

Kirby Mapnotator Annotate maps and generate GeoJSON in Kirby by drawing markers, paths and shapes. Overview This plugin is completely free and publish

Apr 27, 2022
Traveliko - Travel with Friends ~ Web application prepared using Google Street View API that allows you to real-time travel with your friends in the same place in street view mode.
Traveliko - Travel with Friends ~ Web application prepared using Google Street View API that allows you to real-time travel with your friends in the same place in street view mode.

Traveliko - Travel with Friends! You can create a room with your friends and travel in street view mode and navigate the map. The application basicall

Mar 3, 2022
🗺 Vue Mapbox GL - A small components library to use Mapbox GL in Vue

?? Vue Mapbox GL A small components library to use Mapbox GL in Vue. Installation & usage Have a look at the small guide for information on how to set

Mar 21, 2022
vue google map custom marker component
vue google map custom marker component

vue2-gmap-custom-marker This component allows you to display custom HTML content on the map using Overlay. This component is an adaptation of the Goog

May 17, 2022
a simple component to generate an static google map
a simple component to generate an static google map

vue-static-map a simple component to generate an static google map Google Documentation Demo SandBox JSBin example Requirements Vue 2.X.X Usage Instal

May 17, 2022
🔍 Google Place Autocomplete Search - Renderless component + Wrappers for Bulma, Bootstrap and more...
🔍 Google Place Autocomplete Search - Renderless component + Wrappers for Bulma, Bootstrap and more...

vue-custom-google-autocomplete Installation You need Vue.js version 2.0+ and an Google PLACE API key. This plugin is a renderless component. It comes

Apr 11, 2022
Cografya-3d - 3D models of some geographic shapes and their use with Three.js

??️ Coğrafya 3D Bazı coğrafi şekillerin 3D modelleri ve Three.js ile kullanılmas

Apr 27, 2022
Baidu Map components for Vue 2.x
Baidu Map components for Vue 2.x

VUE BAIDU MAP Baidu Map components for Vue 2.x Languages 中文 English Documentation https://dafrok.github.io/vue-baidu-map Get Start Installation npm i

May 24, 2022