feat: add leafletObject escape hatch + move event wiring

Every component now exposes a uniform get leafletObject() returning
the underlying Leaflet instance (undefined before connected).

Event wiring:
- Marker: listen for 'dragend move' via onChange, syncing lat/lng
  back to attributes. Converted from arrow property to regular
  method using Leaflet's 3rd context arg pattern.
- Circle, CircleMarker: listen for 'move' via onMove with syncing
  guard (same pattern).
- Popup, Tooltip: listen for 'move' via onChange with syncing
  guard and null-check on getLatLng().

Also:
- Update README with escape hatch section
- Change map getter from get map() to get leafletObject() for
  consistency
- index.html: add importmap for leaflet
- All component exports changed from default to named
- index.ts re-exports updated accordingly
main
Buddy 3 months ago
parent 11cae8c89f
commit c283632262

@ -161,13 +161,27 @@ Boolean options that default to `false` are enabled by adding the attribute:
```js ```js
const el = document.querySelector('leaflet-map'); const el = document.querySelector('leaflet-map');
el.map; // L.Map instance (undefined before connected) el.leafletObject; // L.Map instance (undefined before connected)
el.zoom; // current zoom (reads live from map, falls back to attribute) el.zoom; // current zoom (reads live from map, falls back to attribute)
el.scrollWheelZoom = false; // disable at runtime el.scrollWheelZoom = false; // disable at runtime
``` ```
All `leaflet-map` properties are two-way: reading returns the live map value; writing updates both the attribute and the map. All `leaflet-map` properties are two-way: reading returns the live map value; writing updates both the attribute and the map.
## Escape hatch
Every component exposes a `leafletObject` getter that returns the underlying Leaflet instance. This is useful when you need to call Leaflet API methods directly:
```js
const marker = document.querySelector('leaflet-marker');
marker.leafletObject?.setLatLng([51.5, -0.09]);
const map = document.querySelector('leaflet-map');
map.leafletObject?.flyTo([48.86, 2.35], 13);
```
Returns `undefined` while the element isn't connected to the DOM.
### CSS ### CSS
Leaflet's stylesheet is loaded from a CDN via a `<link>` in the shadow DOM. Marker icon paths are derived from the CSS URL automatically. Leaflet's stylesheet is loaded from a CDN via a `<link>` in the shadow DOM. Marker icon paths are derived from the CSS URL automatically.

@ -320,6 +320,13 @@
<leaflet-control-scale position="bottomleft"></leaflet-control-scale> <leaflet-control-scale position="bottomleft"></leaflet-control-scale>
</leaflet-map> </leaflet-map>
<script type="importmap">
{
"imports": {
"leaflet": "/node_modules/leaflet/dist/leaflet-src.esm.js"
}
}
</script>
<script type="module" src="/dist/index.js"></script> <script type="module" src="/dist/index.js"></script>
<script type="module"> <script type="module">
// ── Component metadata ────────────────────────────────────────────────── // ── Component metadata ──────────────────────────────────────────────────

@ -21,25 +21,32 @@ const PROPS = defineProps({
fillOpacity: num(0.2), fillOpacity: num(0.2),
}); });
export default class LeafletCircleMarker extends WithProps(HTMLElement, PROPS) { export class LeafletCircleMarker extends WithProps(HTMLElement, PROPS) {
#obj?: CircleMarker; #obj?: CircleMarker;
#syncing = false;
connectedCallback() { connectedCallback() {
this.#obj = new CircleMarker( this.#obj = new CircleMarker(
[numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')], [numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')],
buildOptions(this, PROPS, ['lat', 'lng']), buildOptions(this, PROPS, ['lat', 'lng']),
); );
this.#obj.on('move', this.#onMove, this);
registerChildren(this, this.#obj); registerChildren(this, this.#obj);
} }
disconnectedCallback() { disconnectedCallback() {
this.#obj?.off('move', this.#onMove, this);
unregisterChildren(this); unregisterChildren(this);
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj || this.#syncing) return;
if (name === 'lat' || name === 'lng') { if (name === 'lat' || name === 'lng') {
this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]); this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]);
} else if (name === 'radius') { } else if (name === 'radius') {
@ -48,6 +55,15 @@ export default class LeafletCircleMarker extends WithProps(HTMLElement, PROPS) {
updatePathStyle(this.#obj, name, val); updatePathStyle(this.#obj, name, val);
} }
} }
#onMove() {
if (!this.#obj || this.#syncing) return;
this.#syncing = true;
const pos = this.#obj.getLatLng();
this.setAttribute('lat', `${pos.lat}`);
this.setAttribute('lng', `${pos.lng}`);
this.#syncing = false;
}
} }
customElements.define('leaflet-circle-marker', LeafletCircleMarker); customElements.define('leaflet-circle-marker', LeafletCircleMarker);

@ -21,25 +21,32 @@ const PROPS = defineProps({
fillOpacity: num(0.2), fillOpacity: num(0.2),
}); });
export default class LeafletCircle extends WithProps(HTMLElement, PROPS) { export class LeafletCircle extends WithProps(HTMLElement, PROPS) {
#obj?: Circle; #obj?: Circle;
#syncing = false;
connectedCallback() { connectedCallback() {
this.#obj = new Circle( this.#obj = new Circle(
[numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')], [numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')],
buildOptions(this, PROPS, ['lat', 'lng']), buildOptions(this, PROPS, ['lat', 'lng']),
); );
this.#obj.on('move', this.#onMove, this);
registerChildren(this, this.#obj); registerChildren(this, this.#obj);
} }
disconnectedCallback() { disconnectedCallback() {
this.#obj?.off('move', this.#onMove, this);
unregisterChildren(this); unregisterChildren(this);
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj || this.#syncing) return;
if (name === 'lat' || name === 'lng') { if (name === 'lat' || name === 'lng') {
this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]); this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]);
} else if (name === 'radius') { } else if (name === 'radius') {
@ -48,6 +55,15 @@ export default class LeafletCircle extends WithProps(HTMLElement, PROPS) {
updatePathStyle(this.#obj, name, val); updatePathStyle(this.#obj, name, val);
} }
} }
#onMove() {
if (!this.#obj || this.#syncing) return;
this.#syncing = true;
const pos = this.#obj.getLatLng();
this.setAttribute('lat', `${pos.lat}`);
this.setAttribute('lng', `${pos.lng}`);
this.#syncing = false;
}
} }
customElements.define('leaflet-circle', LeafletCircle); customElements.define('leaflet-circle', LeafletCircle);

@ -7,7 +7,7 @@ const PROPS = defineProps({
prefix: str(), prefix: str(),
}); });
export default class LeafletControlAttribution extends WithProps(HTMLElement, PROPS) { export class LeafletControlAttribution extends WithProps(HTMLElement, PROPS) {
#obj?: Control.Attribution; #obj?: Control.Attribution;
connectedCallback() { connectedCallback() {
@ -22,6 +22,10 @@ export default class LeafletControlAttribution extends WithProps(HTMLElement, PR
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
} }
customElements.define('leaflet-control-attribution', LeafletControlAttribution); customElements.define('leaflet-control-attribution', LeafletControlAttribution);

@ -10,7 +10,7 @@ const PROPS = defineProps({
updateWhenIdle: on(), updateWhenIdle: on(),
}); });
export default class LeafletControlScale extends WithProps(HTMLElement, PROPS) { export class LeafletControlScale extends WithProps(HTMLElement, PROPS) {
#obj?: Control.Scale; #obj?: Control.Scale;
connectedCallback() { connectedCallback() {
@ -29,6 +29,10 @@ export default class LeafletControlScale extends WithProps(HTMLElement, PROPS) {
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
} }
customElements.define('leaflet-control-scale', LeafletControlScale); customElements.define('leaflet-control-scale', LeafletControlScale);

@ -10,7 +10,7 @@ const PROPS = defineProps({
zoomOutTitle: str('Zoom out'), zoomOutTitle: str('Zoom out'),
}); });
export default class LeafletControlZoom extends WithProps(HTMLElement, PROPS) { export class LeafletControlZoom extends WithProps(HTMLElement, PROPS) {
#obj?: Control.Zoom; #obj?: Control.Zoom;
connectedCallback() { connectedCallback() {
@ -28,6 +28,10 @@ export default class LeafletControlZoom extends WithProps(HTMLElement, PROPS) {
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
} }
customElements.define('leaflet-control-zoom', LeafletControlZoom); customElements.define('leaflet-control-zoom', LeafletControlZoom);

@ -2,7 +2,7 @@ import { FeatureGroup, Layer } from 'leaflet';
import { registerChildren, unregisterChildren, getChildren } from '../core/register.ts'; import { registerChildren, unregisterChildren, getChildren } from '../core/register.ts';
import { buildOptions } from '../core/utils.ts'; import { buildOptions } from '../core/utils.ts';
export default class LeafletFeatureGroup extends HTMLElement { export class LeafletFeatureGroup extends HTMLElement {
#obj?: FeatureGroup; #obj?: FeatureGroup;
static get observedAttributes() { static get observedAttributes() {
@ -24,6 +24,10 @@ export default class LeafletFeatureGroup extends HTMLElement {
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
} }
customElements.define('leaflet-feature-group', LeafletFeatureGroup); customElements.define('leaflet-feature-group', LeafletFeatureGroup);

@ -23,7 +23,7 @@ const PROP_BY_ATTR = new Map<string, string>(
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]), Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]),
); );
export default class LeafletGeoJSON extends WithProps(HTMLElement, PROPS) { export class LeafletGeoJSON extends WithProps(HTMLElement, PROPS) {
#obj?: GeoJSON; #obj?: GeoJSON;
connectedCallback() { connectedCallback() {
@ -47,6 +47,10 @@ export default class LeafletGeoJSON extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (name === 'data') { if (name === 'data') {

@ -24,7 +24,7 @@ const PROPS = defineProps({
const PROP_BY_ATTR = buildAttrMap(PROPS); const PROP_BY_ATTR = buildAttrMap(PROPS);
export default class LeafletImageOverlay extends WithProps(HTMLElement, PROPS) { export class LeafletImageOverlay extends WithProps(HTMLElement, PROPS) {
#obj?: ImageOverlay; #obj?: ImageOverlay;
connectedCallback() { connectedCallback() {
@ -43,6 +43,10 @@ export default class LeafletImageOverlay extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (name === 'url') { if (name === 'url') {

@ -2,7 +2,7 @@ import { LayerGroup, Layer } from 'leaflet';
import { registerChildren, unregisterChildren, getChildren } from '../core/register.ts'; import { registerChildren, unregisterChildren, getChildren } from '../core/register.ts';
import { buildOptions } from '../core/utils.ts'; import { buildOptions } from '../core/utils.ts';
export default class LeafletLayerGroup extends HTMLElement { export class LeafletLayerGroup extends HTMLElement {
#obj?: LayerGroup; #obj?: LayerGroup;
static get observedAttributes() { static get observedAttributes() {
@ -24,6 +24,10 @@ export default class LeafletLayerGroup extends HTMLElement {
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
} }
customElements.define('leaflet-layer-group', LeafletLayerGroup); customElements.define('leaflet-layer-group', LeafletLayerGroup);

@ -1,4 +1,4 @@
export default class LeafletLine extends HTMLElement { export class LeafletLine extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ['lat', 'lng']; return ['lat', 'lng'];
} }

@ -93,7 +93,7 @@ type PropTypes = {
const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes; const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export default class LeafletMap extends TypedBase { export class LeafletMap extends TypedBase {
#map?: LMap; #map?: LMap;
#container!: HTMLDivElement; #container!: HTMLDivElement;
#cssLink?: HTMLLinkElement; #cssLink?: HTMLLinkElement;
@ -309,7 +309,7 @@ export default class LeafletMap extends TypedBase {
} }
}; };
get map(): LMap | undefined { get leafletObject() {
return this.#map; return this.#map;
} }
} }

@ -15,7 +15,7 @@ const PROPS = defineProps({
const PROP_BY_ATTR = buildAttrMap(PROPS); const PROP_BY_ATTR = buildAttrMap(PROPS);
export default class LeafletMarker extends WithProps(HTMLElement, PROPS) { export class LeafletMarker extends WithProps(HTMLElement, PROPS) {
#obj?: Marker; #obj?: Marker;
#syncing = false; #syncing = false;
@ -24,17 +24,21 @@ export default class LeafletMarker extends WithProps(HTMLElement, PROPS) {
[numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')], [numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')],
buildOptions(this, PROPS, ['lat', 'lng']), buildOptions(this, PROPS, ['lat', 'lng']),
); );
this.#obj.on('dragend', this.#onDragEnd); this.#obj.on('dragend move', this.#onChange, this);
registerChildren(this, this.#obj); registerChildren(this, this.#obj);
} }
disconnectedCallback() { disconnectedCallback() {
this.#obj?.off('dragend', this.#onDragEnd); this.#obj?.off('dragend move', this.#onChange, this);
unregisterChildren(this); unregisterChildren(this);
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj || this.#syncing) return; if (!this.#obj || this.#syncing) return;
if (name === 'lat' || name === 'lng') { if (name === 'lat' || name === 'lng') {
@ -53,14 +57,14 @@ export default class LeafletMarker extends WithProps(HTMLElement, PROPS) {
} }
} }
#onDragEnd = () => { #onChange() {
if (!this.#obj || this.#syncing) return; if (!this.#obj || this.#syncing) return;
this.#syncing = true; this.#syncing = true;
const pos = this.#obj.getLatLng(); const pos = this.#obj.getLatLng();
this.setAttribute('lat', `${pos.lat}`); this.setAttribute('lat', `${pos.lat}`);
this.setAttribute('lng', `${pos.lng}`); this.setAttribute('lng', `${pos.lng}`);
this.#syncing = false; this.#syncing = false;
}; }
} }
customElements.define('leaflet-marker', LeafletMarker); customElements.define('leaflet-marker', LeafletMarker);

@ -2,7 +2,7 @@ import { Polygon } from 'leaflet';
import { registerChildren, unregisterChildren } from '../core/register.ts'; import { registerChildren, unregisterChildren } from '../core/register.ts';
import { defineProps, num, str, on } from '../core/props.ts'; import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, buildOptions, isPathStyleAttr, updatePathStyle } from '../core/utils.ts'; import { WithProps, buildOptions, isPathStyleAttr, updatePathStyle } from '../core/utils.ts';
import type LeafletLine from './leaflet-line.ts'; import type { LeafletLine } from './leaflet-line.ts';
const PROPS = defineProps({ const PROPS = defineProps({
color: str('#3388ff'), color: str('#3388ff'),
@ -13,7 +13,7 @@ const PROPS = defineProps({
fillOpacity: num(0.2), fillOpacity: num(0.2),
}); });
export default class LeafletPolygon extends WithProps(HTMLElement, PROPS) { export class LeafletPolygon extends WithProps(HTMLElement, PROPS) {
#obj?: Polygon; #obj?: Polygon;
#observer?: MutationObserver; #observer?: MutationObserver;
@ -35,6 +35,10 @@ export default class LeafletPolygon extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (isPathStyleAttr(name)) { if (isPathStyleAttr(name)) {

@ -2,7 +2,7 @@ import { Polyline } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts'; import { defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts'; import { registerChildren, unregisterChildren } from '../core/register.ts';
import { WithProps, buildOptions, isPathStyleAttr, updatePathStyle } from '../core/utils.ts'; import { WithProps, buildOptions, isPathStyleAttr, updatePathStyle } from '../core/utils.ts';
import type LeafletLine from './leaflet-line.ts'; import type { LeafletLine } from './leaflet-line.ts';
const PROPS = defineProps({ const PROPS = defineProps({
color: str('#3388ff'), color: str('#3388ff'),
@ -13,7 +13,7 @@ const PROPS = defineProps({
fillOpacity: num(0.2), fillOpacity: num(0.2),
}); });
export default class LeafletPolyline extends WithProps(HTMLElement, PROPS) { export class LeafletPolyline extends WithProps(HTMLElement, PROPS) {
#obj?: Polyline; #obj?: Polyline;
#observer?: MutationObserver; #observer?: MutationObserver;
@ -35,6 +35,10 @@ export default class LeafletPolyline extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (isPathStyleAttr(name)) { if (isPathStyleAttr(name)) {

@ -13,9 +13,10 @@ const PROPS = defineProps({
autoClose: on(), autoClose: on(),
}); });
export default class LeafletPopup extends WithProps(HTMLElement, PROPS) { export class LeafletPopup extends WithProps(HTMLElement, PROPS) {
#obj?: Popup; #obj?: Popup;
#observer?: MutationObserver; #observer?: MutationObserver;
#syncing = false;
connectedCallback() { connectedCallback() {
this.#obj = new Popup({ this.#obj = new Popup({
@ -25,6 +26,7 @@ export default class LeafletPopup extends WithProps(HTMLElement, PROPS) {
if (this.hasAttribute('lat') && this.hasAttribute('lng')) { if (this.hasAttribute('lat') && this.hasAttribute('lng')) {
this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]); this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]);
} }
this.#obj.on('move', this.#onChange, this);
registerWithParent(this, this.#obj); registerWithParent(this, this.#obj);
this.#observer = new MutationObserver(() => { this.#observer = new MutationObserver(() => {
@ -38,18 +40,33 @@ export default class LeafletPopup extends WithProps(HTMLElement, PROPS) {
} }
disconnectedCallback() { disconnectedCallback() {
this.#obj?.off('move', this.#onChange, this);
this.#observer?.disconnect(); this.#observer?.disconnect();
this.#observer = undefined; this.#observer = undefined;
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string) { attributeChangedCallback(name: string) {
if (!this.#obj) return; if (!this.#obj || this.#syncing) return;
if (name === 'lat' || name === 'lng') { if (name === 'lat' || name === 'lng') {
this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]); this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]);
} }
} }
#onChange() {
if (!this.#obj || this.#syncing) return;
const pos = this.#obj.getLatLng();
if (!pos) return;
this.#syncing = true;
this.setAttribute('lat', `${pos.lat}`);
this.setAttribute('lng', `${pos.lng}`);
this.#syncing = false;
}
} }
customElements.define('leaflet-popup', LeafletPopup); customElements.define('leaflet-popup', LeafletPopup);

@ -19,7 +19,7 @@ const PROPS = defineProps({
fillOpacity: num(0.2), fillOpacity: num(0.2),
}); });
export default class LeafletRectangle extends WithProps(HTMLElement, PROPS) { export class LeafletRectangle extends WithProps(HTMLElement, PROPS) {
#obj?: Rectangle; #obj?: Rectangle;
connectedCallback() { connectedCallback() {
@ -33,6 +33,10 @@ export default class LeafletRectangle extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (name === 'bounds') { if (name === 'bounds') {

@ -21,7 +21,7 @@ const PROPS = defineProps({
const PROP_BY_ATTR = buildAttrMap(PROPS); const PROP_BY_ATTR = buildAttrMap(PROPS);
export default class LeafletSVGOverlay extends WithProps(HTMLElement, PROPS) { export class LeafletSVGOverlay extends WithProps(HTMLElement, PROPS) {
#obj?: SVGOverlay; #obj?: SVGOverlay;
connectedCallback() { connectedCallback() {
@ -41,6 +41,10 @@ export default class LeafletSVGOverlay extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (name === 'bounds') { if (name === 'bounds') {

@ -21,7 +21,7 @@ const PROPS = defineProps({
const PROP_BY_ATTR = buildAttrMap(PROPS); const PROP_BY_ATTR = buildAttrMap(PROPS);
export default class LeafletTileLayerWMS extends WithProps(HTMLElement, PROPS) { export class LeafletTileLayerWMS extends WithProps(HTMLElement, PROPS) {
#obj?: TileLayer.WMS; #obj?: TileLayer.WMS;
connectedCallback() { connectedCallback() {
@ -36,6 +36,10 @@ export default class LeafletTileLayerWMS extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (name === 'url') { if (name === 'url') {

@ -14,7 +14,7 @@ const PROPS = defineProps({
const PROP_BY_ATTR = buildAttrMap(PROPS); const PROP_BY_ATTR = buildAttrMap(PROPS);
export default class LeafletTileLayer extends WithProps(HTMLElement, PROPS) { export class LeafletTileLayer extends WithProps(HTMLElement, PROPS) {
#obj?: TileLayer; #obj?: TileLayer;
connectedCallback() { connectedCallback() {
@ -29,6 +29,10 @@ export default class LeafletTileLayer extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (name === 'url') { if (name === 'url') {

@ -14,9 +14,10 @@ const PROPS = defineProps({
opacity: num(1.0), opacity: num(1.0),
}); });
export default class LeafletTooltip extends WithProps(HTMLElement, PROPS) { export class LeafletTooltip extends WithProps(HTMLElement, PROPS) {
#obj?: Tooltip; #obj?: Tooltip;
#observer?: MutationObserver; #observer?: MutationObserver;
#syncing = false;
connectedCallback() { connectedCallback() {
this.#obj = new Tooltip({ this.#obj = new Tooltip({
@ -26,6 +27,7 @@ export default class LeafletTooltip extends WithProps(HTMLElement, PROPS) {
if (this.hasAttribute('lat') && this.hasAttribute('lng')) { if (this.hasAttribute('lat') && this.hasAttribute('lng')) {
this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]); this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]);
} }
this.#obj.on('move', this.#onChange, this);
registerWithParent(this, this.#obj); registerWithParent(this, this.#obj);
this.#observer = new MutationObserver(() => { this.#observer = new MutationObserver(() => {
@ -39,18 +41,33 @@ export default class LeafletTooltip extends WithProps(HTMLElement, PROPS) {
} }
disconnectedCallback() { disconnectedCallback() {
this.#obj?.off('move', this.#onChange, this);
this.#observer?.disconnect(); this.#observer?.disconnect();
this.#observer = undefined; this.#observer = undefined;
this.#obj?.remove(); this.#obj?.remove();
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string) { attributeChangedCallback(name: string) {
if (!this.#obj) return; if (!this.#obj || this.#syncing) return;
if (name === 'lat' || name === 'lng') { if (name === 'lat' || name === 'lng') {
this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]); this.#obj.setLatLng([numAttr(this, PROPS, 'lat'), numAttr(this, PROPS, 'lng')]);
} }
} }
#onChange() {
if (!this.#obj || this.#syncing) return;
const pos = this.#obj.getLatLng();
if (!pos) return;
this.#syncing = true;
this.setAttribute('lat', `${pos.lat}`);
this.setAttribute('lng', `${pos.lng}`);
this.#syncing = false;
}
} }
customElements.define('leaflet-tooltip', LeafletTooltip); customElements.define('leaflet-tooltip', LeafletTooltip);

@ -25,7 +25,7 @@ const PROPS = defineProps({
const PROP_BY_ATTR = buildAttrMap(PROPS); const PROP_BY_ATTR = buildAttrMap(PROPS);
export default class LeafletVideoOverlay extends WithProps(HTMLElement, PROPS) { export class LeafletVideoOverlay extends WithProps(HTMLElement, PROPS) {
#obj?: VideoOverlay; #obj?: VideoOverlay;
connectedCallback() { connectedCallback() {
@ -44,6 +44,10 @@ export default class LeafletVideoOverlay extends WithProps(HTMLElement, PROPS) {
this.#obj = undefined; this.#obj = undefined;
} }
get leafletObject() {
return this.#obj;
}
attributeChangedCallback(name: string, _old: string | null, val: string | null) { attributeChangedCallback(name: string, _old: string | null, val: string | null) {
if (!this.#obj) return; if (!this.#obj) return;
if (name === 'url') { if (name === 'url') {

@ -1,24 +1,24 @@
export * from './core/utils.ts'; export * from './core/utils.ts';
export * from './core/register.ts'; export * from './core/register.ts';
export * from './core/props.ts'; export * from './core/props.ts';
export { default as LeafletMap } from './components/leaflet-map.ts'; export { LeafletMap } from './components/leaflet-map.ts';
export { default as LeafletMarker } from './components/leaflet-marker.ts'; export { LeafletMarker } from './components/leaflet-marker.ts';
export { default as LeafletCircle } from './components/leaflet-circle.ts'; export { LeafletCircle } from './components/leaflet-circle.ts';
export { default as LeafletCircleMarker } from './components/leaflet-circle-marker.ts'; export { LeafletCircleMarker } from './components/leaflet-circle-marker.ts';
export { default as LeafletLine } from './components/leaflet-line.ts'; export { LeafletLine } from './components/leaflet-line.ts';
export { default as LeafletPolygon } from './components/leaflet-polygon.ts'; export { LeafletPolygon } from './components/leaflet-polygon.ts';
export { default as LeafletPolyline } from './components/leaflet-polyline.ts'; export { LeafletPolyline } from './components/leaflet-polyline.ts';
export { default as LeafletRectangle } from './components/leaflet-rectangle.ts'; export { LeafletRectangle } from './components/leaflet-rectangle.ts';
export { default as LeafletTileLayer } from './components/leaflet-tile-layer.ts'; export { LeafletTileLayer } from './components/leaflet-tile-layer.ts';
export { default as LeafletTileLayerWMS } from './components/leaflet-tile-layer-wms.ts'; export { LeafletTileLayerWMS } from './components/leaflet-tile-layer-wms.ts';
export { default as LeafletImageOverlay } from './components/leaflet-image-overlay.ts'; export { LeafletImageOverlay } from './components/leaflet-image-overlay.ts';
export { default as LeafletVideoOverlay } from './components/leaflet-video-overlay.ts'; export { LeafletVideoOverlay } from './components/leaflet-video-overlay.ts';
export { default as LeafletSVGOverlay } from './components/leaflet-svg-overlay.ts'; export { LeafletSVGOverlay } from './components/leaflet-svg-overlay.ts';
export { default as LeafletLayerGroup } from './components/leaflet-layer-group.ts'; export { LeafletLayerGroup } from './components/leaflet-layer-group.ts';
export { default as LeafletFeatureGroup } from './components/leaflet-feature-group.ts'; export { LeafletFeatureGroup } from './components/leaflet-feature-group.ts';
export { default as LeafletGeoJSON } from './components/leaflet-geojson.ts'; export { LeafletGeoJSON } from './components/leaflet-geojson.ts';
export { default as LeafletControlZoom } from './components/leaflet-control-zoom.ts'; export { LeafletControlZoom } from './components/leaflet-control-zoom.ts';
export { default as LeafletControlAttribution } from './components/leaflet-control-attribution.ts'; export { LeafletControlAttribution } from './components/leaflet-control-attribution.ts';
export { default as LeafletControlScale } from './components/leaflet-control-scale.ts'; export { LeafletControlScale } from './components/leaflet-control-scale.ts';
export { default as LeafletPopup } from './components/leaflet-popup.ts'; export { LeafletPopup } from './components/leaflet-popup.ts';
export { default as LeafletTooltip } from './components/leaflet-tooltip.ts'; export { LeafletTooltip } from './components/leaflet-tooltip.ts';

Loading…
Cancel
Save