4.3 KiB
04 — Events
Forwarding: every Leaflet event → leaflet:<type>
#forwardEvents() in src/core/with-props.ts. Leaflet has no wildcard
listener, so the mixin wraps the instance's own fire() method (safe — it's
an object we created and hold alone):
target.fire = (type, data, propagate) => {
const result = originalFire.call(obj, type, data, propagate);
const sourceTarget = data?.sourceTarget ?? obj;
const detail = { ...data, type, target: obj, sourceTarget };
this.dispatchEvent(new CustomEvent(`leaflet:${type}`, { detail }));
return result;
};
Properties of this:
- Generic — no per-component or per-event-type registration. Any event Leaflet fires on the object is re-emitted.
detailmirrors Leaflet's ownEvented#firemerge — original event data plustype/target/sourceTarget— so aleaflet:clicklistener'sevent.detailis exactly what a realmap.on('click', …)handler would receive. This also keeps Leaflet's typed event interfaces (LeafletMouseEvent, etc.) honest as thedetailtype.- Dispatched after Leaflet's own handlers run, so any attributes the two-way sync updated in response to the same event are already current when the DOM event fires.
- Not bubbling. Leaflet already propagates layer events up to the map
internally, so a bubbling DOM event would make
<leaflet-map>see each one twice.
Typing: event-types.ts
src/core/event-types.ts holds small reusable event-name → payload-type
fragments, mirroring how shared-props.ts shares prop fragments:
MouseEvents click/dblclick/mousedown/… → LeafletMouseEvent
MoveEvents movestart/move/moveend → LeafletEvent
DragEvents dragstart/drag → LeafletEvent, dragend → DragEndEvent
PopupBindEvents popupopen/popupclose → PopupEvent
TileEvents loading/load/tileload/tileerror/…
LayerGroupEvents layeradd/layerremove → LayerEvent
…
composed into one type per family:
| Family type | = | Applied by |
|---|---|---|
BaseLayerEvents |
LayerAddRemoveEvents & PopupBindEvents & TooltipBindEvents |
(building block) |
PathEvents |
BaseLayerEvents & MouseEvents |
circle, polygon, polyline, rectangle |
MarkerEvents |
BaseLayerEvents & MouseEvents & MoveEvents & DragEvents |
marker |
TileLayerEvents |
BaseLayerEvents & TileEvents |
tile-layer, tile-layer-wms |
DivOverlayLayerEvents |
LayerAddRemoveEvents & MouseEvents & DivOverlayEvents |
popup, tooltip |
GroupEvents |
LayerAddRemoveEvents & LayerGroupEvents |
layer-group, feature-group, geojson |
MapEvents |
move + mouse + popup/tooltip + zoom + resize + location + keyboard + … | map |
A component narrows its listener types with a declare field — the same
zero-runtime idiom as declare readonly leafletObject?: Marker:
class LeafletCircleElement extends WithProps(PROPS) {
declare addEventListener: LeafletAddEventListener<PathEvents>;
declare removeEventListener: LeafletRemoveEventListener<PathEvents>;
}
Components that fire nothing meaningful (controls, icons, leaflet-line)
skip this and keep the default HTMLElementEventMap typing.
Why no string fallback overload
LeafletAddEventListener / LeafletRemoveEventListener (defined in
with-props.ts) deliberately have no generic (type: string, …)
overload, unlike the real DOM API. A fallback would silently accept any
misspelled leaflet:* name, which defeats the purpose of typing this. The
cost: a genuinely dynamic (non-literal) event-name string needs a cast.
Keep detail honest
If you change what a component fires, keep #forwardEvents's merge shape in
mind — don't type an event's detail against a Leaflet interface it wouldn't
actually match at runtime.