feat: split leaflet-icon into image Icon and DivIcon, event-only marker↔icon handshake
- Strip leaflet-icon.ts to image-only (new Icon(), remove div/html/bgPos) - Create leaflet-div-icon.ts with DivIcon, innerHTML content, MutationObserver - Convert marker↔icon from querySelector to event-only protocol: marker iterates children dispatching leaflet-request-icon on each, icons listen on themselves and respond with icon-changed - Extract emitIconChanged() helper into register.ts to eliminate repeated CustomEvent construction across both icon components - Type icon-changed and leaflet-request-icon in HTMLElementEventMap - Update marker's #onIconChanged to use typed event instead of cast - Export LeafletDivIcon from index.ts - Add DivIcon demo markers to custom-icon.html and index.htmlmain
parent
0855851504
commit
35190bacc5
@ -0,0 +1,74 @@
|
|||||||
|
import { DivIcon, DivIconOptions, Icon } from 'leaflet';
|
||||||
|
import { emitIconChanged } from '../core/register.ts';
|
||||||
|
|
||||||
|
const PROPS = {
|
||||||
|
iconSize: { attr: 'icon-size' },
|
||||||
|
iconAnchor: { attr: 'icon-anchor' },
|
||||||
|
popupAnchor: { attr: 'popup-anchor' },
|
||||||
|
tooltipAnchor: { attr: 'tooltip-anchor' },
|
||||||
|
className: { attr: 'class-name' },
|
||||||
|
html: { attr: 'html' },
|
||||||
|
bgPos: { attr: 'bg-pos' },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const JSON_KEYS = new Set(['iconSize', 'iconAnchor', 'popupAnchor', 'tooltipAnchor', 'bgPos']);
|
||||||
|
|
||||||
|
export class LeafletDivIcon extends HTMLElement {
|
||||||
|
#obj?: Icon;
|
||||||
|
#observer?: MutationObserver;
|
||||||
|
#onRequest = () => {
|
||||||
|
if (!this.#obj) return;
|
||||||
|
emitIconChanged(this, this.#obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.#applyIcon();
|
||||||
|
emitIconChanged(this, this.#obj);
|
||||||
|
this.addEventListener('leaflet-request-icon', this.#onRequest);
|
||||||
|
|
||||||
|
this.#observer = new MutationObserver(() => {
|
||||||
|
this.#applyIcon();
|
||||||
|
emitIconChanged(this, this.#obj);
|
||||||
|
});
|
||||||
|
this.#observer.observe(this, {
|
||||||
|
childList: true,
|
||||||
|
characterData: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.removeEventListener('leaflet-request-icon', this.#onRequest);
|
||||||
|
this.#observer?.disconnect();
|
||||||
|
this.#observer = undefined;
|
||||||
|
emitIconChanged(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get observedAttributes() {
|
||||||
|
return Object.values(PROPS).map((s) => s.attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback() {
|
||||||
|
this.#applyIcon();
|
||||||
|
if (!this.#obj) return;
|
||||||
|
emitIconChanged(this, this.#obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
get leafletObject() {
|
||||||
|
return this.#obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
#applyIcon() {
|
||||||
|
const opts: Record<string, unknown> = {};
|
||||||
|
for (const [key, spec] of Object.entries(PROPS)) {
|
||||||
|
const v = this.getAttribute(spec.attr);
|
||||||
|
if (v === null) continue;
|
||||||
|
opts[key] = JSON_KEYS.has(key) ? JSON.parse(v) : v;
|
||||||
|
}
|
||||||
|
const content = this.innerHTML;
|
||||||
|
if (content) opts.html = content;
|
||||||
|
this.#obj = new DivIcon(opts as DivIconOptions) as unknown as Icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('leaflet-div-icon', LeafletDivIcon);
|
||||||
Loading…
Reference in New Issue