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.
leaflet-components/src/core/register.ts

77 lines
3.2 KiB
TypeScript

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 <leaflet-line> 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 <leaflet-line> 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,
}),
);
}