From b9290a4c967260f6ae6c93d82ecd2d7932882f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Mon, 22 Apr 2024 20:26:27 +0200 Subject: [PATCH 1/2] Add popup support. --- lib/components/index.ts | 1 + lib/components/marker.component.ts | 15 +++-- lib/components/popup.component.ts | 97 ++++++++++++++++++++++++++++++ lib/lib/map.lib.ts | 6 +- lib/types.ts | 5 +- 5 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 lib/components/popup.component.ts diff --git a/lib/components/index.ts b/lib/components/index.ts index df391b1..9b9de6d 100644 --- a/lib/components/index.ts +++ b/lib/components/index.ts @@ -10,6 +10,7 @@ export { default as MglScaleControl } from './controls/scale.control'; export { default as MglStyleSwitchControl } from './controls/styleSwitch.control'; export { default as MglButton } from './button.component'; export { default as MglMarker } from './marker.component'; +export { default as MglPopup } from './popup.component'; export { default as MglCanvasSource } from './sources/canvas.source'; export { default as MglGeoJsonSource } from './sources/geojson.source'; export { default as MglImageSource } from './sources/image.source'; diff --git a/lib/components/marker.component.ts b/lib/components/marker.component.ts index 31e08f4..b3db292 100644 --- a/lib/components/marker.component.ts +++ b/lib/components/marker.component.ts @@ -1,7 +1,7 @@ -import { defineComponent, inject, onBeforeUnmount, type PropType, unref, watch } from 'vue'; +import { defineComponent, inject, provide, onBeforeUnmount, type PropType, unref, watch, shallowRef, h } from 'vue'; import { type LngLatLike, Marker, type MarkerOptions, type PointLike, type PositionAnchor } from 'maplibre-gl'; import { MapLib } from '@/lib/lib/map.lib'; -import { mapSymbol } from '@/lib/types'; +import { mapSymbol, markerSymbol } from '@/lib/types'; export default /*#__PURE__*/ defineComponent({ name : 'MglMarker', @@ -20,7 +20,7 @@ export default /*#__PURE__*/ defineComponent({ pitchAlignment : String as PropType<'map' | 'viewport' | 'auto'>, scale : Number as PropType }, - setup(props) { + setup(props, { slots }) { const map = inject(mapSymbol)!, opts: MarkerOptions = Object.keys(props) @@ -32,6 +32,7 @@ export default /*#__PURE__*/ defineComponent({ const marker = new Marker(opts); marker.setLngLat(props.coordinates).addTo(map.value!); + provide(markerSymbol, shallowRef(marker)); watch(() => props.coordinates, v => marker.setLngLat(v)); // watch(() => props.draggable, v => marker.setDraggable(v || false)); @@ -41,10 +42,8 @@ export default /*#__PURE__*/ defineComponent({ onBeforeUnmount(marker.remove.bind(marker)); - return { marker }; - - }, - render() { - // nothing + return () => [ + h('div', slots.default ? slots.default({}) : undefined) + ]; } }); diff --git a/lib/components/popup.component.ts b/lib/components/popup.component.ts new file mode 100644 index 0000000..8305a26 --- /dev/null +++ b/lib/components/popup.component.ts @@ -0,0 +1,97 @@ +import { defineComponent, inject, onMounted, onBeforeUnmount, PropType, unref, watch, ref, h } from 'vue'; +import { LngLatLike, Popup, Offset, PositionAnchor, PopupOptions, PointLike } from 'maplibre-gl'; +import { MapLib } from '@/lib/lib/map.lib'; +import { mapSymbol, markerSymbol } from '@/lib/types'; + +export default /*#__PURE__*/ defineComponent({ + name : 'MglPopup', + emits: ['open', 'close'], + props: { + coordinates: { + type: [ Object, Array ] as unknown as PropType, + required: false + }, + closeButton: { + type: Boolean, + required: false, + default: true, + }, + closeOnClick: { + type: Boolean, + required: false, + default: true, + }, + closeOnMove: { + type: Boolean, + required: false, + default: false, + }, + focusAfterOpen: { + type: Boolean, + required: false, + default: true, + }, + anchor: { + type: String as PropType, + required: false + }, + offset: { + type: [Number, Object, Array] as PropType, + required: false, + }, + className: { + type: String, + required: false, + }, + maxWidth: { + type: String, + default: '240px', + }, + text: { + type: String, + required: false + } + }, + setup(props, { slots, emit }) { + const map = inject(mapSymbol); + const marker = inject(markerSymbol); + const root = ref(); + + const opts: PopupOptions = Object.keys(props) + .filter(opt => (props as any)[ opt ] !== undefined && MapLib.POPUP_OPTION_KEYS.indexOf(opt as keyof PopupOptions) !== -1) + .reduce((obj, opt) => { + (obj as any)[ opt ] = unref((props as any)[ opt ]); + return obj; + }, {}); + + const popup = new Popup(opts); + + if (marker && marker.value) { + marker.value.setPopup(popup); + } else if (props.coordinates && map) { + popup.setLngLat(props.coordinates).addTo(map.value!); + } + + if (props.text) { + popup.setText(props.text); + } + + popup.on('open', () => emit('open')); + popup.on('close', () => emit('close')); + + watch(() => props.coordinates, (v) => { if (v) { popup.setLngLat(v) } }); + watch(() => props.text, v => popup.setText(v || '')); + watch(() => props.offset, v => popup.setOffset(v)); + watch(() => props.maxWidth, v => popup.setMaxWidth(v)); + + onMounted(() => { + if (root.value && !props.text) { + popup.setDOMContent(root.value!); + } + }); + + return () => [ + h('div', {ref: root}, slots.default ? slots.default() : undefined) + ]; + }, +}); diff --git a/lib/lib/map.lib.ts b/lib/lib/map.lib.ts index 3cb3804..ddc8dc2 100644 --- a/lib/lib/map.lib.ts +++ b/lib/lib/map.lib.ts @@ -1,4 +1,4 @@ -import type { Map, MapOptions, MarkerOptions } from 'maplibre-gl'; +import type { Map, MapOptions, MarkerOptions, PopupOptions } from 'maplibre-gl'; import type { MglMap } from '@/lib/components'; import type { MglEvent } from '@/lib/types'; @@ -19,6 +19,10 @@ export class MapLib { 'element', 'offset', 'anchor', 'color', 'draggable', 'clickTolerance', 'rotation', 'rotationAlignment', 'pitchAlignment', 'scale' ]; + static readonly POPUP_OPTION_KEYS: Array = [ + 'closeButton', 'closeOnClick', 'closeOnMove', 'focusAfterOpen', 'anchor', 'offset', 'className', 'maxWidth' + ]; + static readonly MAP_EVENT_TYPES = [ 'error', 'load', 'idle', 'remove', 'render', 'resize', 'webglcontextlost', 'webglcontextrestored', 'dataloading', 'data', 'tiledataloading', 'sourcedataloading', 'styledataloading', 'sourcedata', 'styledata', 'boxzoomcancel', 'boxzoomstart', 'boxzoomend', 'touchcancel', 'touchmove', diff --git a/lib/types.ts b/lib/types.ts index d8d3bef..763c107 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,5 +1,5 @@ import type { InjectionKey, Ref, ShallowRef } from 'vue'; -import type { Map, SourceSpecification, StyleSpecification } from 'maplibre-gl'; +import type { Map, Marker, SourceSpecification, StyleSpecification } from 'maplibre-gl'; import type { MglMap } from '@/lib/components'; import type { Emitter } from 'mitt'; import type { SourceLayerRegistry } from '@/lib/lib/sourceLayer.registry'; @@ -10,7 +10,8 @@ export const mapSymbol = Symbol('map') as InjectionKey, sourceIdSymbol = Symbol('sourceId') as InjectionKey, sourceLayerRegistry = Symbol('sourceLayerRegistry') as InjectionKey, - emitterSymbol = Symbol('emitter') as InjectionKey>; + emitterSymbol = Symbol('emitter') as InjectionKey>, + markerSymbol = Symbol('marker') as InjectionKey>; export interface MglEvent { type: string; From 2da86dd5d1bf42c034e5f9d3c5e9c7b41442879c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Tue, 23 Apr 2024 09:53:59 +0200 Subject: [PATCH 2/2] Add popup example. --- docs/examples/popup.vue | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/examples/popup.vue diff --git a/docs/examples/popup.vue b/docs/examples/popup.vue new file mode 100644 index 0000000..c1ceef5 --- /dev/null +++ b/docs/examples/popup.vue @@ -0,0 +1,42 @@ +// Popup +// +// A map with a marker and a popup + + + + +