diff --git a/demos/custom-icon.html b/demos/custom-icon.html
new file mode 100644
index 0000000..076e88e
--- /dev/null
+++ b/demos/custom-icon.html
@@ -0,0 +1,317 @@
+
+
+
+
+
+ Leaflet Components Demo
+
+
+
+ Leaflet Web Components
+
+
+
+
+
+
+
+ I am a green leaf.
+
+
+
+
+ I am a red leaf.
+
+
+
+
+ I am a orange leaf.
+
+
+
+
+
+
+
+
+
diff --git a/src/components/leaflet-icon.ts b/src/components/leaflet-icon.ts
new file mode 100644
index 0000000..42023b0
--- /dev/null
+++ b/src/components/leaflet-icon.ts
@@ -0,0 +1,87 @@
+import { Icon, icon, divIcon, IconOptions, DivIconOptions } from 'leaflet';
+import { defineProps, str, on } from '../core/props.ts';
+import { WithProps } from '../core/utils.ts';
+
+const PROPS = defineProps({
+ iconUrl: str(),
+ iconRetinaUrl: str(),
+ iconSize: str(),
+ iconAnchor: str(),
+ popupAnchor: str(),
+ tooltipAnchor: str(),
+ shadowUrl: str(),
+ shadowRetinaUrl: str(),
+ shadowSize: str(),
+ shadowAnchor: str(),
+ className: str(),
+ div: on(),
+ html: str(),
+ bgPos: str(),
+});
+
+const JSON_KEYS = new Set([
+ 'iconSize',
+ 'iconAnchor',
+ 'popupAnchor',
+ 'tooltipAnchor',
+ 'shadowSize',
+ 'shadowAnchor',
+ 'bgPos',
+]);
+
+export class LeafletIcon extends WithProps(HTMLElement, PROPS) {
+ #obj?: Icon;
+
+ connectedCallback() {
+ this.#applyIcon();
+ this.dispatchEvent(
+ new CustomEvent('icon-changed', {
+ bubbles: true,
+ detail: { icon: this.#obj },
+ }),
+ );
+ }
+
+ disconnectedCallback() {
+ this.dispatchEvent(
+ new CustomEvent('icon-changed', {
+ bubbles: true,
+ detail: { icon: null },
+ }),
+ );
+ }
+
+ attributeChangedCallback() {
+ this.#applyIcon();
+ if (!this.#obj) return;
+ this.dispatchEvent(
+ new CustomEvent('icon-changed', {
+ bubbles: true,
+ detail: { icon: this.#obj },
+ }),
+ );
+ }
+
+ get leafletObject() {
+ return this.#obj;
+ }
+
+ #applyIcon() {
+ const opts: Record = {};
+ for (const [key, spec] of Object.entries(PROPS)) {
+ if (key === 'div') continue;
+ const v = this.getAttribute(spec.attr);
+ if (v === null) continue;
+ opts[key] = JSON_KEYS.has(key) ? JSON.parse(v) : v;
+ }
+ if (this.hasAttribute('div')) {
+ this.#obj = divIcon(opts as unknown as DivIconOptions) as unknown as Icon;
+ } else if (opts.iconUrl) {
+ this.#obj = icon(opts as unknown as IconOptions) as unknown as Icon;
+ } else {
+ this.#obj = undefined;
+ }
+ }
+}
+
+customElements.define('leaflet-icon', LeafletIcon);
diff --git a/src/components/leaflet-marker.ts b/src/components/leaflet-marker.ts
index af19b4c..3454555 100644
--- a/src/components/leaflet-marker.ts
+++ b/src/components/leaflet-marker.ts
@@ -1,4 +1,4 @@
-import { Marker } from 'leaflet';
+import { Icon, Marker } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { WithProps, buildAttrMap, buildOptions, numAttr, setLayerAttr } from '../core/utils.ts';
@@ -26,10 +26,16 @@ export class LeafletMarker extends WithProps(HTMLElement, PROPS) {
);
this.#obj.on('dragend move', this.#onChange, this);
registerChildren(this, this.#obj);
+ this.addEventListener('icon-changed', this.#onIconChanged);
+ const iconEl = this.querySelector(':scope > leaflet-icon') as unknown as {
+ leafletObject: Icon;
+ } | null;
+ if (iconEl?.leafletObject) this.#obj.setIcon(iconEl.leafletObject);
}
disconnectedCallback() {
this.#obj?.off('dragend move', this.#onChange, this);
+ this.removeEventListener('icon-changed', this.#onIconChanged);
unregisterChildren(this);
this.#obj?.remove();
this.#obj = undefined;
@@ -65,6 +71,13 @@ export class LeafletMarker extends WithProps(HTMLElement, PROPS) {
this.setAttribute('lng', `${pos.lng}`);
this.#syncing = false;
}
+
+ #onIconChanged(e: Event) {
+ if (!this.#obj) return;
+ const detail = (e as CustomEvent).detail;
+ if (detail?.icon) this.#obj.setIcon(detail.icon);
+ else this.#obj.setIcon(new Icon.Default());
+ }
}
customElements.define('leaflet-marker', LeafletMarker);
diff --git a/src/index.ts b/src/index.ts
index 6e48ada..4dab2d5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -22,3 +22,4 @@ export { LeafletControlAttribution } from './components/leaflet-control-attribut
export { LeafletControlScale } from './components/leaflet-control-scale.ts';
export { LeafletPopup } from './components/leaflet-popup.ts';
export { LeafletTooltip } from './components/leaflet-tooltip.ts';
+export { LeafletIcon } from './components/leaflet-icon.ts';