You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
88 lines
4.2 KiB
Markdown
88 lines
4.2 KiB
Markdown
# 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):
|
|
|
|
```ts
|
|
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.
|
|
- **`detail` mirrors Leaflet's own `Evented#fire` merge** — original event
|
|
data plus `type` / `target` / `sourceTarget` — so a `leaflet:click`
|
|
listener's `event.detail` is exactly what a real `map.on('click', …)`
|
|
handler would receive. This also keeps Leaflet's typed event interfaces
|
|
(`LeafletMouseEvent`, etc.) honest as the `detail` type.
|
|
- **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`:
|
|
|
|
```ts
|
|
class LeafletCircle 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.
|