|
|
# 05 — Per-component special cases
|
|
|
|
|
|
Where a component does something the `WithProps` mixin can't express from a
|
|
|
prop table alone.
|
|
|
|
|
|
## `leaflet-map` — shadow DOM, CSS, resize
|
|
|
|
|
|
`src/elements/leaflet-map.ts`.
|
|
|
|
|
|
- **Shadow root** built in `connectedCallback` (not via
|
|
|
`createLeafletObject`): a `<div>` at `100% × 100%` for Leaflet to render
|
|
|
into, a `<style>` that gives `:host` a `block` display and a default
|
|
|
`400px` height, and a `<link>` to Leaflet's stylesheet.
|
|
|
- **`css-url` / `css-integrity` / `css-crossorigin`** are _not_ Leaflet
|
|
|
options — they describe that `<link>`. Their `set` just calls `applyCss()`,
|
|
|
which re-links the stylesheet and re-derives `Icon.Default.imagePath` from
|
|
|
the same URL directory. `applyCss()` reads the _attributes_ directly
|
|
|
(not the properties) because absent and empty mean different things here.
|
|
|
Defaults point at `unpkg.com/leaflet@1.9.4` with a matching SRI hash.
|
|
|
- **`lat` / `lng` / `zoom`** are `positional` but not constructor args in the
|
|
|
usual sense — the map is positioned by `setView()` right after
|
|
|
construction, and they're written back on `moveend` / `zoomend`.
|
|
|
- **`ResizeObserver`** on the host calls `map.invalidateSize()`. jsdom lacks
|
|
|
`ResizeObserver`, so the test setup stubs it (see
|
|
|
[07](./07-tooling-and-build.md)).
|
|
|
- As tree root it also listens for `leaflet-add-layer` / `leaflet-remove-layer`
|
|
|
(used by group internals) alongside `leaflet-register`.
|
|
|
|
|
|
## `leaflet-polygon` / `leaflet-polyline` — vertices from children
|
|
|
|
|
|
`src/elements/leaflet-polygon.ts`, `leaflet-polyline.ts`,
|
|
|
`src/core/vertex-tracker.ts`.
|
|
|
|
|
|
Vertices come from `<leaflet-line>` children, **event-driven, never a
|
|
|
lookup**:
|
|
|
|
|
|
- `<leaflet-line>` is a plain `HTMLElement` (no mixin). On connect and on
|
|
|
every `lat`/`lng` change it fires `leaflet-line-sync` on itself, carrying
|
|
|
its own `[lat, lng]`. On disconnect it fires `leaflet-line-remove`.
|
|
|
- The polygon/polyline feeds those events into a shared `VertexTracker`,
|
|
|
which keeps an ordered coordinate list. A newly-registered vertex is
|
|
|
inserted at its **actual document position** via `compareDocumentPosition`
|
|
|
— registration order is not assumed to match DOM order.
|
|
|
- After every sync/remove the component calls
|
|
|
`obj.setLatLngs(tracker.coords())`.
|
|
|
|
|
|
**The `leaflet-line-remove` wrinkle:** `disconnectedCallback` fires _after_
|
|
|
the node is already detached from its parent, so a bubbling dispatch from the
|
|
|
node has nowhere to go. `<leaflet-line>` caches `parentNode` in
|
|
|
`connectedCallback` and dispatches the remove event from that cached
|
|
|
reference instead (`emitLineRemove(from, el)` in `register.ts`).
|
|
|
|
|
|
## `leaflet-popup` / `leaflet-tooltip` — content is markup
|
|
|
|
|
|
`src/elements/leaflet-popup.ts`, `leaflet-tooltip.ts`.
|
|
|
|
|
|
- Content is `this.innerHTML`, passed as the `content` option, not an
|
|
|
attribute.
|
|
|
- Both run a `MutationObserver` (`childList` + `characterData` + `subtree`)
|
|
|
and call `setContent(this.innerHTML)` on any change.
|
|
|
- A popup/tooltip only carries a position of its own when it's _not_ bound to
|
|
|
a parent layer — `createLeafletObject` sets `setLatLng` only if both `lat`
|
|
|
and `lng` attributes are present.
|
|
|
- `attach: 'self'` — they register with a parent but adopt no children.
|
|
|
|
|
|
## `leaflet-layer-group` / `leaflet-feature-group` — passthrough
|
|
|
|
|
|
`const PROPS: Record<never, never> = {}` — an empty prop table
|
|
|
(`new LayerGroup([])` / `new FeatureGroup([])`). `Record<never, never>`, not
|
|
|
`Record<string, never>`: the latter would put a `never` string-index
|
|
|
signature on the generated instance type that the subclass's own members
|
|
|
can't satisfy. No options of their own, but they still get the full
|
|
|
lifecycle, child registration (`attach: 'children'`), and `leaflet:` event
|
|
|
forwarding. A descendant layer's `leaflet-register` is claimed by the mixin's
|
|
|
own `#onChildRegister` (`Layer` + parent has `addLayer` → `obj.addLayer`),
|
|
|
stopping there instead of bubbling on to the map. The group itself then
|
|
|
registers with _its_ parent, so a group can nest in a group.
|
|
|
|
|
|
## `leaflet-control-layers` — dual role
|
|
|
|
|
|
`src/elements/leaflet-control-layers.ts`. `attach: 'self'` (it's a control —
|
|
|
registers with the map, no children through the standard path), but it _also_
|
|
|
attaches its own `leaflet-register` listener to intercept its child layer
|
|
|
entries (`<… name="OSM" type="base" active>`): `addBaseLayer` when
|
|
|
`type="base"`, else `addOverlay`, keyed by the child's `name` attribute. A
|
|
|
child marked `active` also gets a `leaflet-add-layer` event dispatched
|
|
|
upward, which `<leaflet-map>` handles with `map.addLayer(layer)` so that
|
|
|
layer is shown initially. (The map listens for `leaflet-add-layer` /
|
|
|
`leaflet-remove-layer` for exactly this; `leaflet-control-layers` is their
|
|
|
only dispatcher.)
|
|
|
|
|
|
## `leaflet-geojson` — style nesting
|
|
|
|
|
|
`src/elements/leaflet-geojson.ts`. Extends `pathProps`, but GeoJSON takes
|
|
|
style options nested under a `style` key so they apply to each generated
|
|
|
feature: `new GeoJSON(data, { style: options })`. `data` is `positional` +
|
|
|
`json`; its `set` does `clearLayers()` then `addData(value)`.
|
|
|
|
|
|
## `leaflet-tile-layer-wms` — extensible CRS
|
|
|
|
|
|
`src/elements/leaflet-tile-layer-wms.ts`.
|
|
|
|
|
|
- The `crs` **attribute** only covers the four CRSes Leaflet ships by name
|
|
|
(`EPSG3857`, `EPSG4326`, `EPSG3395`, `Simple`) via a `NAMED_CRS` lookup.
|
|
|
- A CRS Leaflet doesn't ship comes from a **nested child element** that fires
|
|
|
`leaflet-crs-changed` with a `CRS` instance — no base class, no registry,
|
|
|
just the event shape (see [03](./03-component-tree.md)). It takes priority
|
|
|
over the attribute. `crs: null` reverts to the attribute's named lookup.
|
|
|
- `crs` is constructor-only, so picking up a child CRS means
|
|
|
`recreateLeafletObject()` — a full rebuild of the layer.
|
|
|
- WMS request params (`layers`, `styles`, `format`, `transparent`,
|
|
|
`version`) have no individual setters; each prop's `set` merges through
|
|
|
`obj.setParams({ [key]: value })`.
|
|
|
|
|
|
## `leaflet-icon` / `leaflet-div-icon` — recreate on change
|
|
|
|
|
|
Icons are created with `WithProps(PROPS, { recreate: true })`: Leaflet gives
|
|
|
no way to mutate an `Icon` in place, so any attribute change throws the icon
|
|
|
away and rebuilds it. They're `attach: 'none'` — not tree members — and
|
|
|
announce themselves to a parent `leaflet-marker` via `icon-changed`.
|
|
|
|
|
|
## `leaflet-marker` — live `<img>` attributes
|
|
|
|
|
|
`title` and `alt` end up on the `<img>` Leaflet renders, so their `set`
|
|
|
reaches `obj.getElement()` and assigns the DOM property directly.
|
|
|
`draggable`'s `set` toggles `obj.dragging.enable()/disable()`. Note that
|
|
|
`getElement()` only exists after the marker is added to a real map — tests
|
|
|
touching it append through a `<leaflet-map>`.
|