8.5 KiB
02 — Props & attributes
src/core/props.ts defines the model; src/core/shared-props.ts bundles the
fragments components reuse. WithProps (src/core/with-props.ts) is the only
consumer — components declare a table and never touch the plumbing.
The PropDef
Every element property is one PropDef:
| Field | Meaning |
|---|---|
default |
Value when the attribute is absent. Must equal Leaflet's own default — an absent attribute is left out of the options object entirely, so it's Leaflet's default that actually takes effect. default only feeds the property getter's fallback. |
decode(raw) |
attribute string → value |
encode(value) |
value → attribute string, or null to remove the attribute (which restores Leaflet's default) |
attribute? |
Override the derived kebab-case name. A function form receives the property name (used by disabled() to prefix disable-). |
option?: false |
Set only through positional(). Marks a value the Leaflet constructor takes as an argument (coordinates, url, bounds, GeoJSON data), so it's excluded from the options object and from PropOptionValues. |
set?(obj, value, el) |
Push a new value into the live object. Omitted ⇒ the mixin calls the naming-convention setter (radius → obj.setRadius) if it exists, else no-op. |
get?(obj) |
Read the live value back out. Used by the property getter and by two-way sync. |
event? |
Leaflet event after which get is re-read and written to the attribute (move keeps lat/lng current during a drag). Only meaningful together with get. |
Codec factories
Instead of writing decode/encode by hand, components call a factory:
| Factory | Attribute semantics |
|---|---|
num(default?, opts?) |
Number ↔ String |
str(default?, opts?) |
identity both ways |
choice<T>(default, opts?) |
like str, but typed as a string union (ControlPosition, CrossOrigin, tooltip Direction); no runtime validation — the point is that the options object comes out with the type Leaflet's constructor expects |
bool(default?, opts?) |
present ⇒ true, ="false" ⇒ false, absent ⇒ default. encode returns null when the value equals the default, so the attribute only appears when it's doing something. Use bool(true) for options Leaflet defaults on, so <leaflet-popup auto-pan="false"> can turn them off. |
disabled(opts?) |
the inverse of bool(true): attribute named disable-<kebab>, <leaflet-map disable-dragging> reads as dragging === false |
json<T>(default, opts?) |
JSON.parse ↔ JSON.stringify — bounds, icon sizes/anchors, GeoJSON data |
positional(def) wraps any of the above to set option: false. Its result
type is aliased as Positional<T, Obj> (= PropDef<T, Obj> & { option: false }) in props.ts, for use in the explicit const PROPS annotations
each element file needs (see 01 and
07).
Shared fragments
src/core/shared-props.ts — spread these into a PROPS table. Each is
exported with an explicit object type (not just as const), so a
PROPS annotation can pull it in via typeof pathProps / typeof latLngProps / typeof tileLayerProps without tripping JSR's slow-type
check:
-
latLngProps(lat,lng) — the coordinate pair, shared by marker, circle, circle-marker, popup, tooltip. Both arepositional(passed to the constructor). Because they travel together, each one'ssetre-issuesobj.setLatLng([...])using the other axis read off the host element (el.lat/el.lng). Both nameevent: 'move'for write-back, which is what keeps the attributes live while a marker is dragged. -
pathProps— every SVG style option a LeafletPathaccepts. The mutable ones (color,weight,opacity,fill*, …) use astyle(key)helper whosesetcallsobj.setStyle({ [key]: value }), because Leaflet exposes these only throughsetStyle. The constructor-only tail (className,interactive,pane, …) has nosetand so no post-create effect. -
tileLayerProps— theGridLayer/TileLayeroptions common toleaflet-tile-layerandleaflet-tile-layer-wms(WMS options extend tile layer options). Almost all constructor-only.referrerPolicyis a hand-writtenPropDefrather thanchoice()because Leaflet'sReferrerPolicytype has no "unset" member — its absent-attribute fallback isundefined. -
urlProp— the positional source URL forTileLayer/ImageOverlay/VideoOverlay. Itssetignores a blank value so clearing the attribute can't request an empty tile URL. Noget— none of those classes exposegetUrl(). -
getBounds(obj)— a sharedgetreturning[[s,w],[n,e]](matching the JSON attribute shape) rather than aLatLngBoundsinstance.
Two-way sync and cycle prevention
- User sets a property → setter encodes it onto the attribute.
attributeChangedCallbackfires → prop'sset(or the convention setter) pushes it into the live object.- Leaflet mutates and fires an event (e.g.
move). - The mixin's per-event listener reads
get(obj)for every prop naming that event and writes it back to the attribute — but does so with the private#syncingflag set, so theattributeChangedCallbackthis write triggers returns immediately instead of looping back to step 2.
#syncing is the single guard. attributeChangedCallback also bails when
oldValue === newValue, and when the element isn't connected yet.