diff --git a/demos/layers-control.html b/demos/layers-control.html new file mode 100644 index 0000000..a1f37ed --- /dev/null +++ b/demos/layers-control.html @@ -0,0 +1,53 @@ + + + + + + Layers Control Demo + + + +

Leaflet Layers Control

+ + + + + + + + + + Marker inside a layers control overlay + + + + + + + diff --git a/src/components/leaflet-control-layers.ts b/src/components/leaflet-control-layers.ts new file mode 100644 index 0000000..2b4da58 --- /dev/null +++ b/src/components/leaflet-control-layers.ts @@ -0,0 +1,92 @@ +import { Control, ControlPosition, Layer } from 'leaflet'; +import { defineProps, str } from '../core/props.ts'; +import { WithProps, registerWithParent } from '../core/utils.ts'; +import { LeafletRegisterEvent } from '../core/register.ts'; + +const PROPS = defineProps({ + position: str('topright'), +}); + +export class LeafletControlLayers extends WithProps(HTMLElement, PROPS) { + #obj?: Control.Layers; + + connectedCallback() { + const baseLayers: Record = {}; + const overlays: Record = {}; + const uncheckedLayers: Layer[] = []; + + for (const child of this.querySelectorAll(':scope > *')) { + const layer = (child as unknown as { leafletObject?: Layer }).leafletObject; + const name = child.getAttribute('name'); + if (!name || !layer) continue; + const base = child.getAttribute('slot') === 'base'; + const checked = child.hasAttribute('checked'); + (base ? baseLayers : overlays)[name] = layer; + if (!checked) uncheckedLayers.push(layer); + } + + const map = this.#map; + for (const layer of uncheckedLayers) map?.removeLayer(layer); + + this.#obj = new Control.Layers(baseLayers, overlays, { + position: this.getAttribute('position') as ControlPosition | undefined, + collapsed: !this.hasAttribute('collapsed') || this.getAttribute('collapsed') !== 'false', + autoZIndex: + !this.hasAttribute('auto-z-index') || this.getAttribute('auto-z-index') !== 'false', + hideSingleBase: this.hasAttribute('hide-single-base'), + sortLayers: this.hasAttribute('sort-layers'), + }); + + registerWithParent(this, this.#obj); + this.addEventListener('leaflet-register', this.#onChildRegister); + } + + disconnectedCallback() { + this.removeEventListener('leaflet-register', this.#onChildRegister); + this.#obj?.remove(); + this.#obj = undefined; + } + + attributeChangedCallback(name: string) { + if (name === 'position' && this.#obj) { + this.#obj.setPosition(this.getAttribute('position') as ControlPosition); + } + } + + get leafletObject() { + return this.#obj; + } + + get #map(): + | { + removeLayer(l: Layer): unknown; + addLayer(l: Layer): unknown; + } + | undefined { + const mapEl = this.closest('leaflet-map'); + if (!mapEl) return undefined; + return ( + mapEl as unknown as { + leafletObject?: { + removeLayer(l: Layer): unknown; + addLayer(l: Layer): unknown; + }; + } + ).leafletObject; + } + + #onChildRegister = (e: LeafletRegisterEvent) => { + e.stopPropagation(); + const el = e.detail.element; + const layer = e.detail.leafletObject; + const name = el.getAttribute('name'); + if (!name || !layer) return; + const base = el.getAttribute('slot') === 'base'; + const checked = el.hasAttribute('checked'); + if (base) this.#obj!.addBaseLayer(layer, name); + else this.#obj!.addOverlay(layer, name); + if (checked) this.#map?.addLayer(layer); + }; +} + +customElements.define('leaflet-control-layers', LeafletControlLayers); diff --git a/src/index.ts b/src/index.ts index 4dab2d5..ba8b88b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ export { LeafletSVGOverlay } from './components/leaflet-svg-overlay.ts'; export { LeafletLayerGroup } from './components/leaflet-layer-group.ts'; export { LeafletFeatureGroup } from './components/leaflet-feature-group.ts'; export { LeafletGeoJSON } from './components/leaflet-geojson.ts'; +export { LeafletControlLayers } from './components/leaflet-control-layers.ts'; export { LeafletControlZoom } from './components/leaflet-control-zoom.ts'; export { LeafletControlAttribution } from './components/leaflet-control-attribution.ts'; export { LeafletControlScale } from './components/leaflet-control-scale.ts';