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.
83 lines
8.0 KiB
Markdown
83 lines
8.0 KiB
Markdown
# 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`.
|
|
|
|
## 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.
|