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.
95 lines
5.5 KiB
Markdown
95 lines
5.5 KiB
Markdown
# 03 — Component tree & registration
|
|
|
|
Components never read each other's properties and never query the DOM for
|
|
their relatives. They join the tree by **dispatching DOM events that bubble**,
|
|
and a parent claims a child by handling the event. This replaces the
|
|
imperative parent/child wiring Leaflet normally needs.
|
|
|
|
## The `leaflet-register` protocol
|
|
|
|
`src/core/register.ts`. On connect, unless `attach: 'none'`, a component
|
|
calls:
|
|
|
|
```ts
|
|
registerWithParent(el, leafletObject);
|
|
// dispatches CustomEvent('leaflet-register', {
|
|
// detail: { leafletObject, element: el }, bubbles: true, composed: true })
|
|
```
|
|
|
|
The event bubbles up the DOM. The nearest ancestor component that is in
|
|
`attach: 'children'` mode has a `leaflet-register` listener; its
|
|
`#onChildRegister` handler (`with-props.ts`) inspects
|
|
`detail.leafletObject` by `instanceof` and:
|
|
|
|
- `Popup` → `obj.bindPopup(child)`, `stopPropagation()`
|
|
- `Tooltip` → `obj.bindTooltip(child)`, `stopPropagation()`
|
|
- `Layer` **and** the parent has `addLayer` → `obj.addLayer(child)`,
|
|
`stopPropagation()`
|
|
- otherwise: let it keep bubbling
|
|
|
|
Discrimination is by `instanceof`, not duck typing — which is why the
|
|
child-registration tests need real `Popup`/`Tooltip`/`Layer` instances.
|
|
|
|
If nothing claims it, the event reaches `<leaflet-map>`, whose `#onRegister`
|
|
calls `e.detail.leafletObject.addTo(this.map)` and stops it. The map is the
|
|
terminus; nothing can register before it exists, which is why it's exported
|
|
first (see [06](./06-load-order.md)).
|
|
|
|
`composed: true` lets the event cross the map's shadow boundary.
|
|
|
|
### Un-registration
|
|
|
|
When a child disconnects, the mixin's `#destroyObject()` calls
|
|
`obj.remove()`, which detaches it from whatever it was added to. When a
|
|
_parent_ disconnects, `#releaseChildren()` walks the entries it adopted and
|
|
calls `unbindPopup` / `unbindTooltip` / `removeLayer` for each.
|
|
|
|
`recreateLeafletObject()` re-runs `registerWithParent` — that call otherwise
|
|
happens only once, from `connectedCallback` — so a recreated layer (e.g. a
|
|
WMS layer that picked up a child CRS) still gets re-added to its parent.
|
|
|
|
## `attach` modes
|
|
|
|
Passed as `WithProps(PROPS, { attach })`. Default `'children'`.
|
|
|
|
| Mode | Registers with parent | Adopts registering descendants | Used by |
|
|
| ------------ | --------------------- | ------------------------------ | -------------------------------------------------- |
|
|
| `'children'` | yes | yes (layers, popups, tooltips) | every layer type, groups |
|
|
| `'self'` | yes | no | `leaflet-popup`, `leaflet-tooltip`, controls |
|
|
| `'none'` | no | no | `leaflet-map` (the root), icons (not tree members) |
|
|
|
|
## The other announcement events
|
|
|
|
The `leaflet-register` protocol only carries `Layer` subclasses. Three other
|
|
relationships use the same bubble-and-claim shape with their own event types,
|
|
so a **plain custom element** (no base class) can participate just by firing
|
|
the right event:
|
|
|
|
| Event | Fired by | Claimed by | Purpose |
|
|
| ---------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
| `icon-changed` (`{ icon: Icon \| null }`) | `leaflet-icon`, `leaflet-div-icon`, on connect / disconnect | `leaflet-marker` | `marker.setIcon(icon ?? new Icon.Default())` |
|
|
| `leaflet-line-sync` (`{ element, latlng }`) | `leaflet-line`, on connect and every `lat`/`lng` change | `leaflet-polygon`, `leaflet-polyline` | feed the `VertexTracker` |
|
|
| `leaflet-line-remove` (`{ element }`) | `leaflet-line`, on disconnect (from its _cached_ parent — see [05](./05-special-cases.md)) | same | drop the vertex |
|
|
| `leaflet-crs-changed` (`{ crs: CRS \| null }`) | any element nested in a CRS-accepting component, on connect | `leaflet-tile-layer-wms` | supply a `CRS` Leaflet doesn't ship by name; `null` reverts to the component's default |
|
|
|
|
`null` in a payload consistently means "revert to the host component's own
|
|
default", mirroring `icon-changed`.
|
|
|
|
One more internal event rides the same bubble: `leaflet-add-layer` /
|
|
`leaflet-remove-layer` (`{ layer }`), dispatched only by
|
|
`leaflet-control-layers` for an `active` entry and handled only by
|
|
`leaflet-map` (`map.addLayer` / `removeLayer`). See [05](./05-special-cases.md).
|
|
|
|
## Why events, not lookups
|
|
|
|
- No load-order coupling between a parent and the DOM query it would
|
|
otherwise run (a child may upgrade before or after its parent).
|
|
- A child at any nesting depth works without the parent knowing the shape of
|
|
the subtree.
|
|
- Third-party elements can nest into the tree without importing anything —
|
|
just dispatch the documented event.
|
|
|
|
The one cost is the load-order constraint in [06](./06-load-order.md): a
|
|
one-shot connect-time announcement is lost if the listening parent isn't
|
|
defined yet.
|