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

Related tags

Map vue3-google-map
Overview

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

Issues
  • Issue when generating app with v0.7.2+

    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.
    
    bug 
    opened by JoseGoncalves 33
  • Examples of using v-for with <Marker>

    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"/>

    opened by jasonandress 10
  • Handling Language and Region Change

    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.

    upstream 
    opened by scottix 8
  • Search box

    Search box

    Hello, I am new in VueJs and I would like to ask that I can use the search box for the maps? And if yes, how can I use it? Thanks

    opened by bvnn 8
  • Where to register google maps API events listener?

    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.

    opened by prasetyaputraa 7
  • Updating `<Marker :options>` prop doesn't render markers

    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!!

    opened by maninak 7
  • Changing markers cause flickering

    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?

    enhancement feature request 
    opened by gerritvanaaken 6
  • No map controls with v0.8.1

    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.

    regression 
    opened by JoseGoncalves 6
  • How to listen for map events?

    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 ?

    opened by JoseGoncalves 5
  • Consulting is a feature

    Consulting is a feature

    Excuse me,when will infowindow be implemented? Thanks!!

    feature request 
    opened by yangxi518 4
  • cant use local image for marker

    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

    needs repro 
    opened by pisangGoreng 1
  • Typescript problems

    Typescript problems

    Hello!

    First I want to say awesome lib.

    I using typescript and whren I trying to use map ref by doing ref="mapRef".

    Linting complains that ready doesn't exist on type never.

    const panToAndZoom = (position: LatLng) => {
      if (mapRef.value?.ready) {
        mapRef.value.map.panTo(position);
        mapRef.value.map.setZoom(15);
      }
    };
    

    To get it to work I had to to like this.

    const panToAndZoom = (position: LatLng) => {
      const gmap = mapRef.value as InstanceType<typeof GoogleMap> | null;
    
      if (gmap?.ready) {
        gmap.map?.panTo(position);
        gmap.map?.setZoom(15);
      }
    };
    
    opened by ChristherLenander 1
  • How to make multiple marker

    How to make multiple marker

    can you show some simple example for multiple marker in this package? thankyou

    opened by rplkabir 2
  • How to add

    How to add "MarkerClusterer"?

    How can I add "MarkerClusterer" to map instance?

    feature request 
    opened by asoltani927 2
  • Added marker in streetview is now shown.

    Added marker in streetview is now shown.

    The code below is taken from the Google Maps sample. https://jsfiddle.net/api/post/library/pure/ Maybe someone can help me spot the issue. Thanks

    onMounted(() => { setTimeout(() => { if (mapRef.value.ready) {
    // const panorama = mapRef.value.api.StreetViewPanorama; const panorama = mapRef.value.map.getStreetView();

          const cafeMarker = new mapRef.value.api.Marker({
            position: { lat: 53.48161, lng: -2.24212 },
            panorama,
            icon: "https://chart.apis.google.com/chart?chst=d_map_pin_icon&chld=cafe|FFFF00",
            title: "Cafe",
          });
         
          console.log('cafeMarker', cafeMarker); 
    
          panorama.setPosition(centerPosition.value);
          panorama.setPov(
            /** @type {google.maps.StreetViewPov} */ {
              heading: 265,
              pitch: 0,
            });
        }      
      }, 2000);
    });
    
    opened by jbbulls 1
  • Add Info Window

    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.

    feature request 
    opened by ksnyde 0
  • googlemap api

    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/ ?

    feature request 
    opened by reslear 4
  • Feature: HtmlMarkers

    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?

    feature request 
    opened by daniel-de-wit 1
Owner
Inocan Group
Inocan Group is an accelerator for businesses in the Cannabis space, this is our home for Open Source projects
Inocan Group
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

Guillaume Chau 525 Jan 9, 2022
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

Fawad Mirzad 55 Jan 12, 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

Daniel Sim 1.8k Jan 8, 2022
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

Keagan Chisnall 101 Jan 7, 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:

Guillaume Leclerc 547 Jan 14, 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

Edoardo Gargano 7 Mar 31, 2020
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.

Diego A. Zapata Häntsch 117 Jan 12, 2022
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

Vue Leaflet 1.8k Jan 21, 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

Ricardo Ruiz 21 Dec 27, 2021
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

null 310 Jan 23, 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

Victor Cazanave 64 Dec 21, 2021
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

İlker Güldalı 5 Jun 1, 2021
🗺 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

Studio Meta 36 Nov 9, 2021
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

eric regnier 124 Jan 14, 2022
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

Eduardo P. Rivero 22 Sep 18, 2021
🔍 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

Damien Roche 29 Dec 9, 2021
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

马金花儿 2.2k Jan 16, 2022
Web map Vue components with the power of OpenLayers

VueLayers Web map Vue components with the power of OpenLayers Overview VueLayers is components library that brings the powerful OpenLayers API to the

Vladimir Vershinin 610 Jan 12, 2022
Vue 2.x components for CesiumJS.

VUE CESIUM Vue 2.x components for CesiumJS. Load Cesium built package or other third-party packages which are built on Cesium. Languages 中文 English Li

zouyaoji 631 Jan 15, 2022