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';