diff --git a/src/core/event-types.ts b/src/core/event-types.ts index 4fe24ec..16cb6b0 100644 --- a/src/core/event-types.ts +++ b/src/core/event-types.ts @@ -27,17 +27,20 @@ import type { ZoomAnimEvent, } from 'leaflet'; +/** `movestart` / `move` / `moveend`. */ export interface MoveEvents { movestart: LeafletEvent; move: LeafletEvent; moveend: LeafletEvent; } +/** A layer's own `add` / `remove` (added to / removed from a map). */ export interface LayerAddRemoveEvents { add: LeafletEvent; remove: LeafletEvent; } +/** `click` / `dblclick` / `mousedown` / `mouseup` / `mouseover` / `mouseout` / `contextmenu`. */ export interface MouseEvents { click: LeafletMouseEvent; dblclick: LeafletMouseEvent; @@ -48,22 +51,26 @@ export interface MouseEvents { contextmenu: LeafletMouseEvent; } +/** `popupopen` / `popupclose`, fired on the layer a popup is bound to. */ export interface PopupBindEvents { popupopen: PopupEvent; popupclose: PopupEvent; } +/** `tooltipopen` / `tooltipclose`, fired on the layer a tooltip is bound to. */ export interface TooltipBindEvents { tooltipopen: TooltipEvent; tooltipclose: TooltipEvent; } +/** `dragstart` / `drag` / `dragend` (marker dragging). */ export interface DragEvents { dragstart: LeafletEvent; drag: LeafletEvent; dragend: DragEndEvent; } +/** Tile-loading lifecycle: `loading` / `load` / `tileloadstart` / `tileload` / `tileunload` / `tileerror`. */ export interface TileEvents { loading: LeafletEvent; load: LeafletEvent; @@ -73,34 +80,40 @@ export interface TileEvents { tileerror: TileErrorEvent; } +/** `contentupdate`, fired by a popup/tooltip when its content changes. */ export interface DivOverlayEvents { contentupdate: LeafletEvent; } +/** `layeradd` / `layerremove`, fired by a group about its children. */ export interface LayerGroupEvents { layeradd: LayerEvent; layerremove: LayerEvent; } -// Every non-group layer (marker, path, overlay, tile layer...) can have a -// popup/tooltip bound to it regardless of its more specific family. +/** Events every non-group layer has: its own `add`/`remove` plus popup/tooltip bind events. */ export type BaseLayerEvents = LayerAddRemoveEvents & PopupBindEvents & TooltipBindEvents; +/** Event map for ``. */ export type PathEvents = BaseLayerEvents & MouseEvents; +/** Event map for ``. */ export type MarkerEvents = BaseLayerEvents & MouseEvents & MoveEvents & DragEvents; +/** Event map for `` / ``. */ export type TileLayerEvents = BaseLayerEvents & TileEvents; -// Popup/Tooltip themselves don't fire popupopen/tooltipopen about -// themselves -- that fires on whatever they're bound to -- so this is -// LayerAddRemoveEvents rather than the fuller BaseLayerEvents. +/** + * Event map for `` / ``. Uses + * {@link LayerAddRemoveEvents} rather than {@link BaseLayerEvents} — a + * popup/tooltip doesn't fire `popupopen` about *itself*. + */ export type DivOverlayLayerEvents = LayerAddRemoveEvents & MouseEvents & DivOverlayEvents; -// LayerGroup, FeatureGroup and GeoJSON (itself a FeatureGroup) all get both -// their own add/remove and their children's layeradd/layerremove. +/** Event map for `` / `` / `` — own add/remove plus children's `layeradd`/`layerremove`. */ export type GroupEvents = LayerAddRemoveEvents & LayerGroupEvents; +/** Event map for `` — move, mouse, zoom, resize, location, keyboard, layer-control and popup/tooltip events. */ export interface MapEvents extends MoveEvents, MouseEvents, PopupBindEvents, TooltipBindEvents, LayerGroupEvents { zoomstart: LeafletEvent; diff --git a/src/core/props.ts b/src/core/props.ts index 3d24bf3..8c1883a 100644 --- a/src/core/props.ts +++ b/src/core/props.ts @@ -1,74 +1,88 @@ -// Every element property is described by a PropDef: how its value encodes to -// and decodes from an HTML attribute, and how it is pushed into (and read back -// out of) the Leaflet object. `WithProps` in with-props.ts is the only consumer -// -- components just declare a table of these and never touch the plumbing. +/** + * Describes one element property: how its value encodes to and decodes from an + * HTML attribute, and how it is pushed into (and read back out of) the Leaflet + * object. {@link WithProps} is the only consumer — a component just declares a + * table of these and never touches the plumbing. + * + * @typeParam T - the decoded property value type + * @typeParam TObj - the Leaflet object type `set`/`get` operate on + */ export interface PropDef { - // Attribute name. Defaults to the kebab-cased property name. A function - // receives the property name and returns the attribute (see `disabled`). + /** Attribute name; defaults to the kebab-cased property name. A function receives the property name and returns the attribute (see {@link disabled}). */ attribute?: string | ((name: string) => string); - // The property value when the attribute is absent. Keep this equal to - // Leaflet's own default: an absent attribute is left out of the options - // object entirely, so it is Leaflet's default that actually takes effect. + /** The value when the attribute is absent. Keep it equal to Leaflet's own default — an absent attribute is omitted from the options object entirely. */ default: T; - // Set through `positional()` for values the Leaflet constructor takes as an - // argument (coordinates, urls, bounds) rather than as an option. + /** Set (to `false`) by {@link positional} for values the Leaflet constructor takes as an argument rather than an option. */ option?: false; + /** Parse the raw attribute string into the value. */ decode(raw: string): T; - // Returning null removes the attribute, which restores Leaflet's default. + /** Serialise the value back to an attribute string; return `null` to remove the attribute (restoring Leaflet's default). */ encode(value: T): string | null; - // Pushes a new value into the Leaflet object. Defaults to calling the - // matching setter when the object has one (`opacity` -> `setOpacity`). + /** Push a new value into the live Leaflet object. Defaults to the matching setter (`opacity` → `setOpacity`) when the object has one. */ set?(obj: TObj, value: T, el: HTMLElement): void; - // Reads the live value back out of the Leaflet object. Used by the property - // getter, and by `event` below to write the value back to the attribute. + /** Read the live value back out of the object — backs the property getter and the `event` write-back. */ get?(obj: TObj): T | undefined; - // Leaflet event after which `get` is re-read and synced to the attribute, - // e.g. `move` keeps lat/lng current while a marker is dragged. + /** Leaflet event after which `get` is re-read and synced to the attribute (`move` keeps lat/lng current during a drag). */ event?: string; } +/** A record of {@link PropDef}s keyed by property name — an element's `PROPS` table. */ export type PropTable = Record>; -// Everything a codec factory doesn't fill in for you. `option` is not here: -// it has to come from `positional()` to be visible in PropOptionValues. +/** + * The optional half of a {@link PropDef} — everything a codec factory doesn't + * fill in for you. `option` is excluded: it must come from {@link positional} + * to stay visible to {@link PropOptionValues}. + */ export type PropOptions = Partial< Omit, 'default' | 'decode' | 'encode' | 'option'> >; -// The value type of a single prop, and of a whole table. +/** The decoded value type of a single prop, read off its `default`. */ export type PropValue

= P extends { default: infer T } ? T : never; +/** {@link PropValue} mapped over a whole {@link PropTable} — the element's property shape. */ export type PropValues = { [K in keyof T]: PropValue }; -// The options object handed to `createLeafletObject`. Partial because a prop -// only appears when its attribute is present; `option: false` props never do. +/** + * The options object handed to `createLeafletObject`. `Partial` because a prop + * only appears when its attribute is present, and `positional()` props + * (`option: false`) are dropped entirely. + */ export type PropOptionValues = Partial<{ [K in keyof T as T[K] extends { option: false } ? never : K]: PropValue; }>; -// The type `positional()` produces: a PropDef flagged so #buildOptions skips -// it. Spelled out as an alias so element PROPS tables can be given the explicit -// type annotations JSR's "no slow types" check requires without repeating the -// intersection everywhere. +/** + * The type {@link positional} produces: a {@link PropDef} flagged so + * `#buildOptions` skips it. Spelled out as an alias so element `PROPS` tables + * can carry the explicit annotations JSR's "no slow types" check requires + * without repeating the intersection everywhere. + */ export type Positional = PropDef & { option: false }; -// Marks a prop the Leaflet constructor takes as an argument, so it is left out -// of the options object handed to createLeafletObject(). +/** + * Marks a prop the Leaflet constructor takes as a positional argument + * (coordinates, url, bounds), so it is left out of the options object handed to + * `createLeafletObject`. + */ export function positional(def: T): T & { option: false } { return { ...def, option: false }; } +/** `fooBar` → `foo-bar`. The default attribute name for a property. */ export function kebab(name: string): string { return name.replaceAll(/[A-Z]/gu, (c) => `-${c.toLowerCase()}`); } +/** A numeric attribute: `Number` on the way in, `String` on the way out. */ export function num( def = 0, opts?: PropOptions, @@ -76,6 +90,7 @@ export function num( return { default: def, decode: Number, encode: String, ...opts }; } +/** A string attribute — identity codec both ways. */ export function str( def = '', opts?: PropOptions, @@ -83,9 +98,11 @@ export function str( return { default: def, decode: (raw) => raw, encode: (value) => value, ...opts }; } -// A string attribute whose values Leaflet types as a union -- ControlPosition, -// CrossOrigin, tooltip Direction. Nothing is validated at runtime; this is how -// the options object comes out with the type Leaflet's constructor expects. +/** + * A string attribute whose values Leaflet types as a union (`ControlPosition`, + * `CrossOrigin`, tooltip `Direction`). Not validated at runtime — this just + * makes the options object come out with the type Leaflet's constructor wants. + */ export function choice( def: T, opts?: PropOptions, @@ -93,9 +110,11 @@ export function choice( return { default: def, decode: (raw) => raw as T, encode: (value) => value, ...opts }; } -// A boolean attribute: present is true, `="false"` is false, absent is `def`. -// Use `bool(true)` for options Leaflet already defaults to true, so that -// `` can turn them off. +/** + * A boolean attribute: present is `true`, `="false"` is `false`, absent is + * `def`. Use `bool(true)` for options Leaflet already defaults on, so + * `` can turn them off. + */ export function bool( def = false, opts?: PropOptions, @@ -108,8 +127,11 @@ export function bool( }; } -// The inverse of `bool(true)`: `` reads as -// `dragging === false`. Named `disable-` unless `attribute` says else. +/** + * The inverse of `bool(true)`: `` reads as + * `dragging === false`. Attribute is named `disable-` unless `attribute` + * overrides it. + */ export function disabled( opts?: PropOptions, ): PropDef { @@ -122,7 +144,7 @@ export function disabled( }; } -// For attributes holding JSON: bounds, icon sizes and anchors, GeoJSON data. +/** A JSON attribute (`JSON.parse` / `JSON.stringify`): bounds, icon sizes and anchors, GeoJSON data. */ export function json(def: T, opts?: PropOptions): PropDef { return { default: def, diff --git a/src/core/register.ts b/src/core/register.ts index 83af608..2fe251d 100644 --- a/src/core/register.ts +++ b/src/core/register.ts @@ -1,39 +1,46 @@ 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. +/** + * The bubbling `leaflet-register` event. Carries the Leaflet object and the + * originating element so the nearest ancestor component can add it as a child + * layer, bind it as a popup, or bind it as a tooltip. + */ export type LeafletRegisterEvent = CustomEvent<{ leafletObject: Layer; element: HTMLElement; }>; +/** `leaflet-add-layer` / `leaflet-remove-layer` — dispatched by `` to toggle a layer on the map. */ export type LeafletLayerEvent = CustomEvent<{ layer: Layer }>; +/** `icon-changed` — a `` / `` child announcing its icon (or `null` on disconnect) to its parent marker. */ 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. +/** + * `leaflet-line-sync` — fired by `` on itself, on connect and on + * every lat/lng change, carrying its own position. `` / + * `` listen for it (bubbling) to track vertices without ever + * reading a child's state. + */ export type LeafletLineSyncEvent = CustomEvent<{ element: HTMLElement; latlng: [number, number] }>; -// Fired by on disconnect, so a listening parent can drop it. +/** `leaflet-line-remove` — fired by `` on disconnect so a listening parent can drop that vertex. */ 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`. +/** + * `leaflet-crs-changed` — a CRS is a plain value, not a `Layer`, so it doesn't + * fit the `leaflet-register` protocol. A custom element nested inside a + * component that accepts a `crs` (currently ``) + * provides one by firing this itself, bubbling, on connect. `crs: null` + * reverts to the component's own default. + */ 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. +/** Fire {@link LeafletIconChangedEvent} (`icon-changed`, bubbling) from `el`. `null` clears the icon. */ export function emitIconChanged(el: HTMLElement, icon: Icon | DivIcon | null | undefined) { el.dispatchEvent( new CustomEvent('icon-changed', { @@ -43,28 +50,31 @@ export function emitIconChanged(el: HTMLElement, icon: Icon | DivIcon | null | u ); } +/** Fire {@link LeafletLineSyncEvent} (`leaflet-line-sync`, bubbling) from `el` with its current position. */ 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. +/** + * Fire {@link LeafletLineRemoveEvent} (`leaflet-line-remove`, bubbling). + * Dispatched on `from`, not `el`: by `disconnectedCallback` time `el` is + * already detached, so a dispatch from it has nowhere to bubble — 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. +/** + * Fire {@link LeafletRegisterEvent} (`leaflet-register`, bubbling and + * `composed`) from `el`. The core wiring mechanism: an ancestor component + * intercepts it and adds the layer / binds the popup / binds the tooltip, + * replacing the imperative parent-child calls Leaflet normally needs. + */ export function registerWithParent(el: HTMLElement, obj: unknown) { el.dispatchEvent( new CustomEvent('leaflet-register', { diff --git a/src/core/shared-props.ts b/src/core/shared-props.ts index 6b60dfb..4cdec81 100644 --- a/src/core/shared-props.ts +++ b/src/core/shared-props.ts @@ -13,26 +13,33 @@ import { bool, choice, json, num, positional, str, type PropDef } from './props. // Leaflet classes share no common interface, so the prop fragments below // describe structurally what they need from the object they update. + +/** Structural type for anything a `latLngProps` prop updates — a marker, circle, popup, tooltip. */ export interface Positioned { getLatLng(): LatLng | undefined; setLatLng(latlng: [number, number]): unknown; } +/** Structural type for anything a `pathProps` style prop updates — any Leaflet `Path`. */ export interface Styleable { setStyle(style: PathOptions): unknown; } +/** Structural type for anything {@link urlProp} updates — `TileLayer`, `ImageOverlay`, `VideoOverlay`. */ export interface Sourced { setUrl(url: string): unknown; } +/** Structural type for anything {@link getBounds} reads — `ImageOverlay`, `Rectangle`, etc. */ export interface Bounded { getBounds(): LatLngBounds; } -// Shared `get` for any `bounds` prop backed by Leaflet's getBounds(). Returns -// a plain [[south, west], [north, east]] pair rather than the LatLngBounds -// instance, matching the JSON-encoded shape the attribute round-trips through. +/** + * Shared `get` for any `bounds` prop, backed by Leaflet's `getBounds()`. + * Returns a plain `[[south, west], [north, east]]` pair (not the `LatLngBounds` + * instance), matching the JSON shape the attribute round-trips through. + */ export function getBounds(obj: Bounded): LatLngBoundsExpression { const b = obj.getBounds(); return [ @@ -46,16 +53,18 @@ interface PositionedHost extends HTMLElement { lng: number; } -// A `set` for any option Leaflet only exposes through setStyle(). +/** Builds a `set` for a style option Leaflet only exposes through `setStyle()`. */ export function style(key: keyof PathOptions): (obj: Styleable, value: T) => void { return (obj, value) => { obj.setStyle({ [key]: value } as PathOptions); }; } -// The source url. Passed positionally by every Leaflet constructor that takes -// one, and ignored when blank so clearing the attribute can't request nothing. -// No `get`: none of TileLayer/ImageOverlay/VideoOverlay expose a getUrl(). +/** + * The `url` prop — the positional source URL for `TileLayer` / `ImageOverlay` / + * `VideoOverlay`. Ignored when blank so clearing the attribute can't request an + * empty URL. No `get` (none of those classes expose `getUrl()`). + */ export const urlProp: PropDef & { option: false } = positional( str('', { set(obj, value) { @@ -64,11 +73,13 @@ export const urlProp: PropDef & { option: false } = positional( }), ); -// lat/lng travel together: both are passed positionally to the Leaflet -// constructor rather than as options, setting either re-issues setLatLng with -// the other's current value, and both are written back whenever the object -// moves -- which is what keeps the attributes current while a marker is -// dragged. Shared by marker, circle, circle-marker, popup and tooltip. +/** + * The `lat` / `lng` prop pair. Both are positional (passed to the Leaflet + * constructor); setting either re-issues `setLatLng` with the other axis read + * off the host element, and both write back on `move` — which keeps the + * attributes live while a marker is dragged. Shared by marker, circle, + * circle-marker, popup and tooltip. + */ export const latLngProps: { lat: PropDef & { option: false }; lng: PropDef & { option: false }; @@ -93,8 +104,12 @@ export const latLngProps: { ), }; -// The style options every Path accepts. Defaults match Leaflet's own, so an -// absent attribute and an unset option mean the same thing. +/** + * Every SVG style option a Leaflet `Path` accepts. The mutable ones (`color`, + * `weight`, `opacity`, `fill*`, …) route through `setStyle()` via {@link style}; + * the constructor-only tail (`className`, `interactive`, `pane`, …) has no + * setter. Defaults match Leaflet's own. + */ export const pathProps: { stroke: PropDef; color: PropDef; @@ -133,11 +148,13 @@ export const pathProps: { pane: str('overlay'), }; -// The GridLayer/TileLayer options every tile source accepts, shared by -// leaflet-tile-layer and leaflet-tile-layer-wms (WMSOptions extends -// TileLayerOptions). Most are constructor-only -- Leaflet exposes no setter -// for them -- so changing the attribute after creation has no effect, same as -// leaflet-map's zoomSnap. +/** + * The `GridLayer` / `TileLayer` options common to `` and + * `` (WMS options extend tile-layer options). Almost + * all constructor-only. `referrerPolicy` is a hand-written {@link PropDef} + * rather than {@link choice} because Leaflet's `ReferrerPolicy` type has no + * "unset" member. + */ export const tileLayerProps: { attribution: PropDef; minZoom: PropDef; diff --git a/src/core/with-props.ts b/src/core/with-props.ts index e135c20..307150c 100644 --- a/src/core/with-props.ts +++ b/src/core/with-props.ts @@ -8,56 +8,78 @@ import { } from './props.ts'; import { registerWithParent, type LeafletRegisterEvent } from './register.ts'; -// How an element joins the component tree: -// children -- register with the nearest parent and adopt registering -// descendants as layers, popups and tooltips (every layer) -// self -- register with the nearest parent only (popups, tooltips, -// controls: they have a parent but manage no children here) -// none -- neither (the map is the root; icons aren't part of the tree) +/** + * How an element joins the component tree: + * + * - `children` — register with the nearest parent and adopt registering + * descendants as layers, popups and tooltips (every layer type). + * - `self` — register with the nearest parent only (popups, tooltips, controls: + * they have a parent but manage no children here). + * - `none` — neither (the map is the tree root; icons aren't tree members). + */ export type Attach = 'children' | 'self' | 'none'; +/** Second argument to {@link WithProps}. */ export interface ElementOptions { + /** Tree-membership mode; defaults to `'children'`. See {@link Attach}. */ attach?: Attach; - // Rebuild the Leaflet object on every attribute change instead of calling - // setters, for objects Leaflet gives us no way to mutate in place (icons). + /** + * Rebuild the Leaflet object on every attribute change instead of calling + * setters — for objects Leaflet gives no way to mutate in place (icons). + */ recreate?: boolean; } -// The members WithProps contributes on top of the property accessors. +/** The members {@link WithProps} contributes on top of the generated property accessors. */ export interface LeafletElement { + /** The wrapped Leaflet object, once created (undefined before connect / after disconnect). */ readonly leafletObject?: TObj; - // The one method every component must implement. `options` holds the decoded - // value of every prop whose attribute is present, keyed by property name, so - // it can be handed straight to the Leaflet constructor. + /** + * The one method every component must implement. `options` holds the decoded + * value of every prop whose attribute is present, keyed by property name, + * ready to hand to the Leaflet constructor. + */ createLeafletObject(options: PropOptionValues): TObj | undefined; - // Called after the object is created and after every recreate. + /** Hook called after the object is created and after every recreate; override to react. */ leafletObjectCreated(): void; + /** Tear the object down and rebuild it from current attributes (used by `recreate` and by WMS CRS children). */ recreateLeafletObject(): void; + /** Custom-element lifecycle: builds the object and joins the tree. Call `super.connectedCallback()` if you override. */ connectedCallback(): void; + /** Custom-element lifecycle: leaves the tree and destroys the object. Call `super.disconnectedCallback()` if you override. */ disconnectedCallback(): void; + /** Custom-element lifecycle: pushes an attribute change into the live object. */ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void; } +/** + * The constructor type {@link WithProps} returns: an `HTMLElement` subclass with + * a two-way accessor per prop ({@link PropValues}) plus the + * {@link LeafletElement} lifecycle members. Element files annotate their + * `const Base` with this. + */ export type LeafletElementConstructor = new () => HTMLElement & PropValues & LeafletElement; -// Type-only narrowing for addEventListener/removeEventListener so a -// component's `leaflet:` events (see #forwardEvents below) type-check -// against the right Leaflet event payload. Like `declare readonly -// leafletObject?: Marker`, a component applies this with a `declare` field -- -// TObj isn't reliably inferred from the PROPS table alone, so this can't live -// in WithProps()'s own generics; it costs nothing at runtime either way. -// -// Deliberately has no generic `(type: string, ...)` fallback overload (unlike -// HTMLElement's real addEventListener): a fallback would silently accept any -// unrecognized `leaflet:*` name too, which defeats the point. The cost is -// that a genuinely dynamic (non-literal) event name string needs a cast. +/** + * Type-only narrowing for `addEventListener` so a component's `leaflet:` + * events (see `#forwardEvents`) type-check against the right Leaflet payload. A + * component applies it with a `declare addEventListener: LeafletAddEventListener` + * field — the same zero-runtime idiom as `declare readonly leafletObject?: X`. + * + * Deliberately has **no** generic `(type: string, …)` fallback overload (unlike + * the real DOM API): a fallback would silently accept any misspelled + * `leaflet:*` name. The cost is that a genuinely dynamic event-name string + * needs a cast. + * + * @typeParam TEvents - an event-name → payload map from `event-types.ts` + */ export type LeafletAddEventListener = (( type: `leaflet:${K}`, listener: (ev: CustomEvent) => void, @@ -69,6 +91,7 @@ export type LeafletAddEventListener = ( void); +/** The `removeEventListener` counterpart of {@link LeafletAddEventListener}. */ export type LeafletRemoveEventListener = (( type: `leaflet:${K}`, listener: (ev: CustomEvent) => void, @@ -113,13 +136,26 @@ function resolve(props: PropTable): ResolvedProp })); } -// Builds a custom element base class from a table of property definitions. -// -// The generated class owns the whole lifecycle: it derives observedAttributes, -// defines two-way property accessors, builds the Leaflet options object, wires -// the object into the component tree, keeps attributes in sync with the object -// (in both directions, without cycles), and re-fires every Leaflet event on the -// element as `leaflet:`. Subclasses implement createLeafletObject(). +/** + * Mixin factory: builds a custom-element base class from a table of + * {@link PropDef}s. Always extends `HTMLElement` internally (there is no + * base-class parameter). + * + * The generated class owns the whole lifecycle — it derives + * `observedAttributes`, defines a two-way property accessor per prop, builds + * the Leaflet options object on connect, wires the object into the component + * tree, keeps attributes and the live object in sync (both directions, no + * cycles), and re-fires every Leaflet event on the element as `leaflet:`. + * A subclass only implements `createLeafletObject()`. + * + * Element files must not write `class Foo extends WithProps({…})` — JSR's + * type checker rejects a call expression as a superclass. Use a + * `const Base: LeafletElementConstructor = WithProps(PROPS)` + * and `extends Base` instead. + * + * @param props - the `PROPS` table (needs an explicit type annotation) + * @param options - tree-attach mode and `recreate` behaviour; see {@link ElementOptions} + */ export function WithProps>( props: TProps, options: ElementOptions = {}, diff --git a/src/elements/index.ts b/src/elements/index.ts index ea0a73c..f86642d 100644 --- a/src/elements/index.ts +++ b/src/elements/index.ts @@ -1,11 +1,20 @@ -// The element classes on their own -- no `customElements.define()` runs from -// anything in this file or the modules it re-exports. Import a `components/*` -// module (or `src/index.ts`) instead when you want the tags actually -// registered; reach for these when you want to subclass an element, register -// it under a different tag name, or otherwise customise before defining. -// -// Every class is the `default` export of its own module, surfaced here under -// a name. Order is irrelevant -- nothing here has a load-time side effect. +/** + * Every `leaflet-*` element class, by name, with **no** `customElements.define()` + * side effect — nothing in this module or the modules it re-exports registers a + * tag. + * + * Reach for this entrypoint (`leaflet-web-components/elements`, or + * `jsr:@buddy/leaflet-components/elements`) when you want to subclass an + * element, register it under a different tag name, or otherwise customise + * before defining. To get the tags registered instead, import the package root + * or an individual `components/*` module. + * + * Each class is the `default` export of its own `leaflet-foo.ts` module, + * surfaced here under a `LeafletFooElement` name. Order is irrelevant — none of + * these modules has a load-time side effect. + * + * @module + */ export { default as LeafletMapElement } from './leaflet-map.ts'; export { default as LeafletControlLayersElement } from './leaflet-control-layers.ts'; export { default as LeafletLayerGroupElement } from './leaflet-layer-group.ts'; diff --git a/src/elements/leaflet-circle-marker.ts b/src/elements/leaflet-circle-marker.ts index 17fda64..f29e49e 100644 --- a/src/elements/leaflet-circle-marker.ts +++ b/src/elements/leaflet-circle-marker.ts @@ -19,6 +19,7 @@ const PROPS: typeof latLngProps & }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `CircleMarker` (radius in pixels, fixed screen size). */ export default class LeafletCircleMarkerElement extends Base { declare readonly leafletObject?: CircleMarker; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-circle.ts b/src/elements/leaflet-circle.ts index 8b2178f..0bca79a 100644 --- a/src/elements/leaflet-circle.ts +++ b/src/elements/leaflet-circle.ts @@ -21,6 +21,7 @@ const PROPS = { } as const; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `Circle` (radius in metres). Takes `pathProps` styling and a `radius`. */ export default class LeafletCircleElement extends Base { declare readonly leafletObject?: Circle; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-control-attribution.ts b/src/elements/leaflet-control-attribution.ts index d359475..b38df8f 100644 --- a/src/elements/leaflet-control-attribution.ts +++ b/src/elements/leaflet-control-attribution.ts @@ -13,6 +13,7 @@ const Base: LeafletElementConstructor = WithP attach: 'self', }); +/** `` — the map's attribution control (`Control.Attribution`). */ export default class LeafletControlAttributionElement extends Base { declare readonly leafletObject?: Control.Attribution; diff --git a/src/elements/leaflet-control-layers.ts b/src/elements/leaflet-control-layers.ts index df4dc8b..00a771a 100644 --- a/src/elements/leaflet-control-layers.ts +++ b/src/elements/leaflet-control-layers.ts @@ -20,6 +20,11 @@ const Base: LeafletElementConstructor = WithProps( attach: 'self', }); +/** + * `` — a layer switcher (`Control.Layers`). Layer + * children register as base layers or overlays (`type="base"`, `active` to + * start visible) instead of being added to the map directly. + */ export default class LeafletControlLayersElement extends Base { declare readonly leafletObject?: Control.Layers; diff --git a/src/elements/leaflet-control-scale.ts b/src/elements/leaflet-control-scale.ts index 2a790dd..b899114 100644 --- a/src/elements/leaflet-control-scale.ts +++ b/src/elements/leaflet-control-scale.ts @@ -19,6 +19,7 @@ const Base: LeafletElementConstructor = WithProps(P attach: 'self', }); +/** `` — the metric/imperial scale bar (`Control.Scale`). */ export default class LeafletControlScaleElement extends Base { declare readonly leafletObject?: Control.Scale; diff --git a/src/elements/leaflet-control-zoom.ts b/src/elements/leaflet-control-zoom.ts index 89f6272..06d1626 100644 --- a/src/elements/leaflet-control-zoom.ts +++ b/src/elements/leaflet-control-zoom.ts @@ -21,6 +21,7 @@ const Base: LeafletElementConstructor = WithProps(PR attach: 'self', }); +/** `` — the +/− zoom buttons (`Control.Zoom`). */ export default class LeafletControlZoomElement extends Base { declare readonly leafletObject?: Control.Zoom; diff --git a/src/elements/leaflet-div-icon.ts b/src/elements/leaflet-div-icon.ts index cbe9f78..18c5398 100644 --- a/src/elements/leaflet-div-icon.ts +++ b/src/elements/leaflet-div-icon.ts @@ -27,6 +27,11 @@ const Base: LeafletElementConstructor = WithProps(PROPS, recreate: true, }); +/** + * `` — a Leaflet `DivIcon` (a CSS-styled marker icon). Child + * of ``; renders its own markup when `html` isn't set, and is + * rebuilt on every change. + */ export default class LeafletDivIconElement extends Base { declare readonly leafletObject?: DivIcon; diff --git a/src/elements/leaflet-feature-group.ts b/src/elements/leaflet-feature-group.ts index 8a0c35d..cbdfbe7 100644 --- a/src/elements/leaflet-feature-group.ts +++ b/src/elements/leaflet-feature-group.ts @@ -10,7 +10,7 @@ import type { GroupEvents } from '../core/event-types.ts'; const PROPS: Record = {}; const Base: LeafletElementConstructor = WithProps(PROPS); -// Like leaflet-layer-group, but its children share events and a bounding box. +/** `` — a Leaflet `FeatureGroup`. Like ``, but its children also share events and a bounding box. */ export default class LeafletFeatureGroupElement extends Base { declare readonly leafletObject?: FeatureGroup; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-geojson.ts b/src/elements/leaflet-geojson.ts index 398b7c0..7007a2d 100644 --- a/src/elements/leaflet-geojson.ts +++ b/src/elements/leaflet-geojson.ts @@ -25,6 +25,7 @@ const PROPS: { }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `GeoJSON` layer. `data` holds the GeoJSON; other attributes style every feature. */ export default class LeafletGeoJSONElement extends Base { declare readonly leafletObject?: GeoJSON; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-icon.ts b/src/elements/leaflet-icon.ts index 8bfcd64..3773f52 100644 --- a/src/elements/leaflet-icon.ts +++ b/src/elements/leaflet-icon.ts @@ -36,6 +36,7 @@ const Base: LeafletElementConstructor = WithProps(PROPS, { recreate: true, }); +/** `` — a Leaflet `Icon` (an image marker icon). Child of ``; rebuilt on every attribute change and swapped in via `setIcon()`. */ export default class LeafletIconElement extends Base { declare readonly leafletObject?: Icon; diff --git a/src/elements/leaflet-image-overlay.ts b/src/elements/leaflet-image-overlay.ts index b485f06..9181998 100644 --- a/src/elements/leaflet-image-overlay.ts +++ b/src/elements/leaflet-image-overlay.ts @@ -51,6 +51,7 @@ const PROPS: { }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `ImageOverlay` (an image pinned to geographic `bounds`). */ export default class LeafletImageOverlayElement extends Base { declare readonly leafletObject?: ImageOverlay; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-layer-group.ts b/src/elements/leaflet-layer-group.ts index 020de2d..ee43764 100644 --- a/src/elements/leaflet-layer-group.ts +++ b/src/elements/leaflet-layer-group.ts @@ -10,8 +10,11 @@ import type { GroupEvents } from '../core/event-types.ts'; const PROPS: Record = {}; const Base: LeafletElementConstructor = WithProps(PROPS); -// A passthrough container: it has no options of its own, and children add -// themselves to it through the standard registration bubble. +/** + * `` — a Leaflet `LayerGroup`. A passthrough container + * with no options of its own; child layers add themselves through the standard + * registration bubble, and groups can nest. + */ export default class LeafletLayerGroupElement extends Base { declare readonly leafletObject?: LayerGroup; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-line.ts b/src/elements/leaflet-line.ts index 1b56104..1851031 100644 --- a/src/elements/leaflet-line.ts +++ b/src/elements/leaflet-line.ts @@ -1,5 +1,11 @@ import { emitLineRemove, emitLineSync } from '../core/register.ts'; +/** + * `` — one vertex of a `` / ``, + * given by `lat` / `lng` attributes. Not a Leaflet object and not built on + * {@link WithProps}: it just announces its position to its parent (on connect, + * on change, and on disconnect) via the `leaflet-line-sync` / `-remove` events. + */ export default class LeafletLineElement extends HTMLElement { static get observedAttributes(): string[] { return ['lat', 'lng']; diff --git a/src/elements/leaflet-map.ts b/src/elements/leaflet-map.ts index bc75d2d..bfd66ac 100644 --- a/src/elements/leaflet-map.ts +++ b/src/elements/leaflet-map.ts @@ -171,8 +171,13 @@ const PROPS: { }; const Base: LeafletElementConstructor = WithProps(PROPS, { attach: 'none' }); -// The root of the component tree. Every other component bubbles a -// `leaflet-register` event up to here, which is where it stops. +/** + * `` — a Leaflet `Map`, and the root of the component tree: every + * other component bubbles a `leaflet-register` event up to here, where it stops + * (`layer.addTo(this.map)`). Builds its own shadow root (container + Leaflet + * CSS ``), runs a `ResizeObserver` → `invalidateSize()`, and treats its + * `css-*` attributes as describing the shadow stylesheet. + */ export default class LeafletMapElement extends Base { declare readonly leafletObject?: LMap; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-marker.ts b/src/elements/leaflet-marker.ts index 4497936..49ae0d2 100644 --- a/src/elements/leaflet-marker.ts +++ b/src/elements/leaflet-marker.ts @@ -42,6 +42,7 @@ const PROPS: typeof latLngProps & { }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `Marker`. Accepts ``, ``, and `` / `` children. */ export default class LeafletMarkerElement extends Base { declare readonly leafletObject?: Marker; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-polygon.ts b/src/elements/leaflet-polygon.ts index 40ed974..bcef599 100644 --- a/src/elements/leaflet-polygon.ts +++ b/src/elements/leaflet-polygon.ts @@ -13,6 +13,7 @@ import { VertexTracker } from '../core/vertex-tracker.ts'; const PROPS = { ...pathProps } as const; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `Polygon`. Vertices come from `` children, not an attribute. */ export default class LeafletPolygonElement extends Base { declare readonly leafletObject?: Polygon; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-polyline.ts b/src/elements/leaflet-polyline.ts index c88c7f1..bb0f626 100644 --- a/src/elements/leaflet-polyline.ts +++ b/src/elements/leaflet-polyline.ts @@ -24,6 +24,7 @@ const PROPS: typeof pathProps & { }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `Polyline`. Vertices come from `` children, not an attribute. */ export default class LeafletPolylineElement extends Base { declare readonly leafletObject?: Polyline; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-popup.ts b/src/elements/leaflet-popup.ts index c3befbe..864340b 100644 --- a/src/elements/leaflet-popup.ts +++ b/src/elements/leaflet-popup.ts @@ -27,6 +27,11 @@ const PROPS: typeof latLngProps & { }; const Base: LeafletElementConstructor = WithProps(PROPS, { attach: 'self' }); +/** + * `` — a Leaflet `Popup`. Child of a layer (bound via + * `bindPopup`) or standalone with its own `lat` / `lng`; content is the + * element's markup, kept in sync via a `MutationObserver`. + */ export default class LeafletPopupElement extends Base { declare readonly leafletObject?: Popup; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-rectangle.ts b/src/elements/leaflet-rectangle.ts index 8688899..8297afe 100644 --- a/src/elements/leaflet-rectangle.ts +++ b/src/elements/leaflet-rectangle.ts @@ -17,6 +17,7 @@ const PROPS: { }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `Rectangle` spanning `bounds`, with `pathProps` styling. */ export default class LeafletRectangleElement extends Base { declare readonly leafletObject?: Rectangle; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-svg-overlay.ts b/src/elements/leaflet-svg-overlay.ts index edee352..984efd6 100644 --- a/src/elements/leaflet-svg-overlay.ts +++ b/src/elements/leaflet-svg-overlay.ts @@ -40,6 +40,7 @@ const PROPS: { }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `SVGOverlay` (an inline `` child pinned to geographic `bounds`). */ export default class LeafletSVGOverlayElement extends Base { declare readonly leafletObject?: SVGOverlay; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-tile-layer-wms.ts b/src/elements/leaflet-tile-layer-wms.ts index 7e856b3..ded659e 100644 --- a/src/elements/leaflet-tile-layer-wms.ts +++ b/src/elements/leaflet-tile-layer-wms.ts @@ -63,6 +63,11 @@ const PROPS: { }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** + * `` — a Leaflet `TileLayer.WMS`. WMS request params + * (`layers`, `styles`, `format`, …) merge through `setParams()`; `crs` takes a + * named CRS or a nested child that fires `leaflet-crs-changed`. + */ export default class LeafletTileLayerWMSElement extends Base { declare readonly leafletObject?: TileLayer.WMS; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-tile-layer.ts b/src/elements/leaflet-tile-layer.ts index 9481e64..5a1e864 100644 --- a/src/elements/leaflet-tile-layer.ts +++ b/src/elements/leaflet-tile-layer.ts @@ -14,6 +14,7 @@ const PROPS = { } as const; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `TileLayer` (an XYZ raster tile source), e.g. OpenStreetMap. */ export default class LeafletTileLayerElement extends Base { declare readonly leafletObject?: TileLayer; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-tooltip.ts b/src/elements/leaflet-tooltip.ts index 331c37a..28656e6 100644 --- a/src/elements/leaflet-tooltip.ts +++ b/src/elements/leaflet-tooltip.ts @@ -27,6 +27,7 @@ const PROPS: typeof latLngProps & { }; const Base: LeafletElementConstructor = WithProps(PROPS, { attach: 'self' }); +/** `` — a Leaflet `Tooltip`. Child of a layer (bound via `bindTooltip`) or standalone; content is the element's markup. */ export default class LeafletTooltipElement extends Base { declare readonly leafletObject?: Tooltip; declare addEventListener: LeafletAddEventListener; diff --git a/src/elements/leaflet-video-overlay.ts b/src/elements/leaflet-video-overlay.ts index ab5a68d..443e7a1 100644 --- a/src/elements/leaflet-video-overlay.ts +++ b/src/elements/leaflet-video-overlay.ts @@ -67,6 +67,7 @@ const PROPS: { }; const Base: LeafletElementConstructor = WithProps(PROPS); +/** `` — a Leaflet `VideoOverlay` (a video pinned to geographic `bounds`). `loop` / `autoplay` / `muted` / `playsinline` map to the `