import { Marker } from 'leaflet'; import { registerWithParent, buildOptions, parseAttributeValue } from '../core/utils.js'; import { withProps } from '../core/with-props.js'; import { createChildRegisterHandler, type ChildEntry, LeafletRegisterEvent, } from '../core/register.js'; import type { PropDef } from '../types/props.js'; const PROPS = { lat: { kind: 'num', attr: 'lat', default: 0 }, lng: { kind: 'num', attr: 'lng', default: 0 }, title: { kind: 'str', attr: 'title', default: '' }, alt: { kind: 'str', attr: 'alt', default: '' }, draggable: { kind: 'bool-on', attr: 'draggable' }, opacity: { kind: 'num', attr: 'opacity', default: 1.0 }, zIndexOffset: { kind: 'num', attr: 'z-index-offset', default: 0 }, } satisfies Record; const PROP_BY_ATTR = new Map( Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]), ); export class LeafletMarker extends withProps(HTMLElement, PROPS) { #obj?: Marker; #syncing = false; #children = new Map(); #childHandler?: (e: LeafletRegisterEvent) => void; connectedCallback() { this.#obj = new Marker( [this.#num('lat'), this.#num('lng')], buildOptions(this, PROPS, ['lat', 'lng']), ); this.#obj.on('dragend', this.#onDragEnd); this.#childHandler = createChildRegisterHandler(this.#obj, this.#children); this.addEventListener('leaflet-register', this.#childHandler); registerWithParent(this, this.#obj); } disconnectedCallback() { if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler); this.#obj?.off('dragend', this.#onDragEnd); this.#children.clear(); this.#obj?.remove(); this.#obj = undefined; } attributeChangedCallback(name: string, _old: string | null, val: string | null) { if (!this.#obj || this.#syncing) return; if (name === 'lat' || name === 'lng') { this.#obj.setLatLng([this.#num('lat'), this.#num('lng')]); } else if (name === 'draggable') { if (val !== null) this.#obj.dragging?.enable(); else this.#obj.dragging?.disable(); } else if (name === 'title') { const el = this.#obj.getElement(); if (el) (el as HTMLImageElement).title = val ?? ''; } else if (name === 'alt') { const el = this.#obj.getElement(); if (el) (el as HTMLImageElement).alt = val ?? ''; } else { const propName = PROP_BY_ATTR.get(name); if (!propName) return; const setter = `set${propName.charAt(0).toUpperCase()}${propName.slice(1)}` as keyof Marker; if (typeof this.#obj[setter] === 'function') { (this.#obj[setter] as (v: unknown) => void)(parseAttributeValue(val)); } } } #onDragEnd = () => { if (!this.#obj || this.#syncing) return; this.#syncing = true; const pos = this.#obj.getLatLng(); this.setAttribute('lat', String(pos.lat)); this.setAttribute('lng', String(pos.lng)); this.#syncing = false; }; #num(name: string): number { const v = this.getAttribute(name); return v !== null ? Number(v) : (PROPS[name as keyof typeof PROPS] as { default: number }).default; } } customElements.define('leaflet-marker', LeafletMarker);