6.5 KiB
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 viacreateLeafletObject): a<div>at100% × 100%for Leaflet to render into, a<style>that gives:hostablockdisplay and a default400pxheight, and a<link>to Leaflet's stylesheet. css-url/css-integrity/css-crossoriginare not Leaflet options — they describe that<link>. Theirsetjust callsapplyCss(), which re-links the stylesheet and re-derivesIcon.Default.imagePathfrom the same URL directory.applyCss()reads the attributes directly (not the properties) because absent and empty mean different things here. Defaults point atunpkg.com/leaflet@1.9.4with a matching SRI hash.lat/lng/zoomarepositionalbut not constructor args in the usual sense — the map is positioned bysetView()right after construction, and they're written back onmoveend/zoomend.ResizeObserveron the host callsmap.invalidateSize(). jsdom lacksResizeObserver, so the test setup stubs it (see 07).- As tree root it also listens for
leaflet-add-layer/leaflet-remove-layer(used by group internals) alongsideleaflet-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 plainHTMLElement(no mixin). On connect and on everylat/lngchange it firesleaflet-line-syncon itself, carrying its own[lat, lng]. On disconnect it firesleaflet-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 viacompareDocumentPosition— 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 thecontentoption, not an attribute. - Both run a
MutationObserver(childList+characterData+subtree) and callsetContent(this.innerHTML)on any change. - A popup/tooltip only carries a position of its own when it's not bound to
a parent layer —
createLeafletObjectsetssetLatLngonly if bothlatandlngattributes 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
crsattribute only covers the four CRSes Leaflet ships by name (EPSG3857,EPSG4326,EPSG3395,Simple) via aNAMED_CRSlookup. - A CRS Leaflet doesn't ship comes from a nested child element that fires
leaflet-crs-changedwith aCRSinstance — no base class, no registry, just the event shape (see 03). It takes priority over the attribute.crs: nullreverts to the attribute's named lookup. crsis constructor-only, so picking up a child CRS meansrecreateLeafletObject()— a full rebuild of the layer.- WMS request params (
layers,styles,format,transparent,version) have no individual setters; each prop'ssetmerges throughobj.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>.