Skip to content

Commit

Permalink
feat!: ability to nest info windows inside markers
Browse files Browse the repository at this point in the history
  • Loading branch information
HusamElbashir committed Mar 22, 2022
1 parent 1059cd2 commit c239cc7
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 40 deletions.
94 changes: 59 additions & 35 deletions src/components/InfoWindow.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<template>
<div ref="infoWindowRef" v-if="hasSlotContent" v-show="mapTilesLoaded">
<div ref="infoWindowRef" v-if="hasSlotContent" class="info-window-content">
<slot />
</div>
</template>

<script lang="ts">
import { defineComponent, PropType, watch, ref, computed, inject, onBeforeUnmount, Comment } from "vue";
import { apiSymbol, mapSymbol, mapTilesLoadedSymbol } from "../shared/index";
import { defineComponent, PropType, watch, ref, computed, inject, onBeforeUnmount, Comment, onMounted } from "vue";
import { apiSymbol, mapSymbol, markerSymbol } from "../shared/index";
const infoWindowEvents = ["closeclick", "content_changed", "domready", "position_changed", "visible", "zindex_changed"];
export default defineComponent({
props: {
options: {
type: Object as PropType<google.maps.InfoWindowOptions>,
required: true,
default: () => ({}),
},
},
Expand All @@ -27,50 +27,74 @@ export default defineComponent({
const map = inject(mapSymbol, ref(null));
const api = inject(apiSymbol, ref(null));
const mapTilesLoaded = inject(mapTilesLoadedSymbol, ref(false));
const anchor = inject(markerSymbol, ref(null));
let anchorClickListener: google.maps.MapsEventListener;
const hasSlotContent = computed(() => slots.default?.().some((vnode) => vnode.type !== Comment));
watch(
[map, () => props.options, mapTilesLoaded],
([_, options, newMapTilesLoaded], [oldMap, oldOptions, oldMapTilesLoaded]) => {
const checkIfChanged = JSON.stringify(options) !== JSON.stringify(oldOptions) || map.value !== oldMap;
if (map.value && api.value && (checkIfChanged || (newMapTilesLoaded && !oldMapTilesLoaded))) {
if (_infoWindow) {
_infoWindow.setOptions({
...options,
content: hasSlotContent.value ? infoWindowRef.value : options.content,
});
if (!hasSlotContent.value || mapTilesLoaded.value) _infoWindow.open({ map: map.value });
} else {
infoWindow.value = _infoWindow = new api.value.InfoWindow({
...options,
content: hasSlotContent.value ? infoWindowRef.value : options.content,
});
if (!hasSlotContent.value || mapTilesLoaded.value) _infoWindow.open({ map: map.value });
infoWindowEvents.forEach((event) => {
_infoWindow?.addListener(event, (e: unknown) => emit(event, e));
});
onMounted(() => {
watch(
[map, () => props.options],
([_, options], [oldMap, oldOptions]) => {
const checkIfChanged = JSON.stringify(options) !== JSON.stringify(oldOptions) || map.value !== oldMap;
if (map.value && api.value && checkIfChanged) {
if (_infoWindow) {
_infoWindow.setOptions({
...options,
content: hasSlotContent.value ? infoWindowRef.value : options.content,
});
if (!anchor.value) _infoWindow.open({ map: map.value });
} else {
infoWindow.value = _infoWindow = new api.value.InfoWindow({
...options,
content: hasSlotContent.value ? infoWindowRef.value : options.content,
});
if (anchor.value) {
anchorClickListener = anchor.value.addListener("click", () => {
_infoWindow.open({
map: map.value,
anchor: anchor.value,
});
});
} else {
_infoWindow.open({ map: map.value });
}
infoWindowEvents.forEach((event) => {
_infoWindow?.addListener(event, (e: unknown) => emit(event, e));
});
}
}
},
{
immediate: true,
}
},
{
immediate: true,
}
);
);
});
onBeforeUnmount(() => {
if (anchorClickListener) anchorClickListener.remove();
if (_infoWindow) {
api.value?.event.clearInstanceListeners(_infoWindow);
_infoWindow.close();
}
});
return { infoWindow, infoWindowRef, hasSlotContent, mapTilesLoaded };
return { infoWindow, infoWindowRef, hasSlotContent, anchor };
},
});
</script>

<style scoped>
.info-window-content {
display: none;
}
.mapdiv .info-window-content {
display: block;
}
</style>
13 changes: 8 additions & 5 deletions src/components/Marker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineComponent, PropType, Ref, toRef } from "vue";
import { defineComponent, PropType, Ref, toRef, provide } from "vue";
import { IComponentOptions, useSetupMapComponent } from "../composables/index";
import { markerSymbol } from "../shared/index";

const markerEvents = [
"animation_changed",
Expand Down Expand Up @@ -35,11 +36,13 @@ export default defineComponent({
},
},
emits: markerEvents,
setup(props, { emit }) {
setup(props, { emit, expose, slots }) {
const options = toRef(props, "options") as Ref<IComponentOptions>;
const marker = useSetupMapComponent("Marker", markerEvents, options, emit);
const marker = useSetupMapComponent("Marker", markerEvents, options, emit) as Ref<google.maps.Marker | null>;
provide(markerSymbol, marker);

return { marker };
expose({ marker });

return () => slots.default?.();
},
render: () => null,
});
1 change: 1 addition & 0 deletions src/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InjectionKey, ref, Ref } from "vue";

export const mapSymbol: InjectionKey<Ref<google.maps.Map | null>> = Symbol("map");
export const apiSymbol: InjectionKey<Ref<typeof google.maps | null>> = Symbol("api");
export const markerSymbol: InjectionKey<Ref<google.maps.Marker | null>> = Symbol("marker");
/**
* Utilitary flag for components that need to know the map
* was fully loaded (including its tiles) to decide their behavior
Expand Down

0 comments on commit c239cc7

Please sign in to comment.