import { DivIcon, Icon, Layer, type CRS } from 'leaflet'; // Custom event type for the bubbling registration protocol. Carries the // Leaflet object and the originating element so the nearest parent can // add it as a child layer, popup, or tooltip. export type LeafletRegisterEvent = CustomEvent<{ leafletObject: Layer; element: HTMLElement; }>; export type LeafletLayerEvent = CustomEvent<{ layer: Layer }>; export type LeafletIconChangedEvent = CustomEvent<{ icon: Icon | null }>; // Fired by on itself -- on connect and on every lat/lng // change -- carrying its own current position. polygon/polyline listen for // this (bubbling) to track vertices without ever reading a child's state // directly. export type LeafletLineSyncEvent = CustomEvent<{ element: HTMLElement; latlng: [number, number] }>; // Fired by on disconnect, so a listening parent can drop it. export type LeafletLineRemoveEvent = CustomEvent<{ element: HTMLElement }>; // A CRS is a plain value (methods + a couple of properties, see Leaflet's // own `CRS` interface), not a Layer -- it doesn't fit the leaflet-register // protocol above. Any custom element nested inside a component that accepts // a `crs` (currently just leaflet-tile-layer-wms) can provide one by firing // this itself, bubbling, on connect -- no base class required, just this // event shape. `crs: null` (e.g. on disconnect) reverts to that component's // own default, mirroring icon-changed's `icon: null`. export type LeafletCRSChangedEvent = CustomEvent<{ crs: CRS | null }>; // The `HTMLElementEventMap` augmentation for these event names lives in // `./globals.ts` (npm-only), kept out of this module so JSR -- which rejects // `declare global` in its published graph -- can still publish it. export function emitIconChanged(el: HTMLElement, icon: Icon | DivIcon | null | undefined) { el.dispatchEvent( new CustomEvent('icon-changed', { bubbles: true, detail: { icon: icon ?? null }, }), ); } export function emitLineSync(el: HTMLElement, latlng: [number, number]): void { el.dispatchEvent( new CustomEvent('leaflet-line-sync', { bubbles: true, detail: { element: el, latlng } }), ); } // Dispatched on `from`, not `el`: by the time disconnectedCallback runs, `el` // has already been detached from its parent, so a bubbling dispatch from // `el` itself would have nowhere to bubble to. Callers pass the parent they // cached while still connected. export function emitLineRemove(from: EventTarget, el: HTMLElement): void { from.dispatchEvent( new CustomEvent('leaflet-line-remove', { bubbles: true, detail: { element: el } }), ); } // Dispatches a custom `leaflet-register` event upward through the DOM // tree, carrying a Leaflet object and its host element. Parent components // (map, circles, groups, etc.) intercept this event and add the layer, // bind the popup, or bind the tooltip. This is the core wiring mechanism // that replaces the parent-child relationship that Leaflet normally // manages via imperative code. export function registerWithParent(el: HTMLElement, obj: unknown) { el.dispatchEvent( new CustomEvent('leaflet-register', { detail: { leafletObject: obj, element: el }, bubbles: true, composed: true, }), ); }