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/docs/02-props-and-attributes.md

8.0 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 (radiusobj.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?) NumberString
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.parseJSON.stringify — bounds, icon sizes/anchors, GeoJSON data

positional(def) wraps any of the above to set option: false.

Shared fragments

src/core/shared-props.ts — spread these into a PROPS table:

  • latLngProps (lat, lng) — the coordinate pair, shared by marker, circle, circle-marker, popup, tooltip. Both are positional (passed to the constructor). Because they travel together, each one's set re-issues obj.setLatLng([...]) using the other axis read off the host element (el.lat / el.lng). Both name event: 'move' for write-back, which is what keeps the attributes live while a marker is dragged.

  • pathProps — every SVG style option a Leaflet Path accepts. The mutable ones (color, weight, opacity, fill*, …) use a style(key) helper whose set calls obj.setStyle({ [key]: value }), because Leaflet exposes these only through setStyle. The constructor-only tail (className, interactive, pane, …) has no set and so no post-create effect.

  • tileLayerProps — the GridLayer/TileLayer options common to leaflet-tile-layer and leaflet-tile-layer-wms (WMS options extend tile layer options). Almost all constructor-only. referrerPolicy is a hand-written PropDef rather than choice() because Leaflet's ReferrerPolicy type has no "unset" member — its absent-attribute fallback is undefined.

  • urlProp — the positional source URL for TileLayer / ImageOverlay / VideoOverlay. Its set ignores a blank value so clearing the attribute can't request an empty tile URL. No get — none of those classes expose getUrl().

  • getBounds(obj) — a shared get returning [[s,w],[n,e]] (matching the JSON attribute shape) rather than a LatLngBounds instance.

Two-way sync and cycle prevention

  1. User sets a property → setter encodes it onto the attribute.
  2. attributeChangedCallback fires → prop's set (or the convention setter) pushes it into the live object.
  3. Leaflet mutates and fires an event (e.g. move).
  4. 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 #syncing flag set, so the attributeChangedCallback this 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.