import { CRS, TileLayer, type WMSOptions, type WMSParams } from 'leaflet'; import { bool, str, type PropDef, type Positional } from '../core/props.ts'; import { tileLayerProps, urlProp, type Sourced } from '../core/shared-props.ts'; import { WithProps, type LeafletAddEventListener, type LeafletElementConstructor, type LeafletRemoveEventListener, } from '../core/with-props.ts'; import type { TileLayerEvents } from '../core/event-types.ts'; import type { LeafletCRSChangedEvent } from '../core/register.ts'; // WMS request parameters have no individual setters -- they're merged into the // query string through setParams(). function param(key: keyof WMSParams): (obj: TileLayer.WMS, value: T) => void { return (obj, value) => { obj.setParams({ [key]: value } as unknown as WMSParams); }; } // `crs` takes a CRS instance, not a primitive. The attribute only covers the // 4 CRSes Leaflet ships by name -- a CRS Leaflet doesn't ship comes from a // nested child announcing itself via leaflet-crs-changed instead (see // #onCrsChanged), which takes priority over this when present. Constructor- // only, like the rest of tileLayerProps -- Leaflet has no setter for it. const NAMED_CRS = { EPSG3857: CRS.EPSG3857, EPSG4326: CRS.EPSG4326, EPSG3395: CRS.EPSG3395, Simple: CRS.Simple, }; type CrsName = keyof typeof NAMED_CRS; const crsProp: PropDef = { default: CRS.EPSG3857, decode: (raw) => NAMED_CRS[raw as CrsName] ?? CRS.EPSG3857, encode: (value) => value === CRS.EPSG3857 ? null : ((Object.keys(NAMED_CRS) as CrsName[]).find((name) => NAMED_CRS[name] === value) ?? null), }; const PROPS: { url: Positional; } & typeof tileLayerProps & { layers: PropDef; styles: PropDef; format: PropDef; transparent: PropDef; version: PropDef; uppercase: PropDef; crs: PropDef; } = { url: urlProp, ...tileLayerProps, layers: str('', { set: param('layers') }), styles: str('', { set: param('styles') }), format: str('image/jpeg', { set: param('format') }), transparent: bool(false, { set: param('transparent') }), version: str('1.1.1', { set: param('version') }), uppercase: bool(), crs: crsProp, }; const Base: LeafletElementConstructor = WithProps(PROPS); export default class LeafletTileLayerWMSElement extends Base { declare readonly leafletObject?: TileLayer.WMS; declare addEventListener: LeafletAddEventListener; declare removeEventListener: LeafletRemoveEventListener; // Set by a nested CRS-providing child; overrides the `crs` attribute's // named lookup when present. #childCrs?: CRS; createLeafletObject(options: WMSOptions): TileLayer.WMS { return new TileLayer.WMS( this.url, this.#childCrs ? { ...options, crs: this.#childCrs } : options, ); } connectedCallback(): void { super.connectedCallback(); this.addEventListener('leaflet-crs-changed', this.#onCrsChanged); } disconnectedCallback(): void { this.removeEventListener('leaflet-crs-changed', this.#onCrsChanged); super.disconnectedCallback(); } // A CRS Leaflet doesn't ship comes from any custom element nested here // that fires this event -- no base class required, just this event shape // (see register.ts). `crs` is constructor-only, so picking it up means // rebuilding the whole layer. #onCrsChanged = (e: LeafletCRSChangedEvent) => { this.#childCrs = e.detail.crs ?? undefined; this.recreateLeafletObject(); }; }