refactor: extract static PROP accessor blocks into definePropAccessors helper

All 17 TypedBase components now share a single definePropAccessors(proto, PROPS)
call in their static block instead of duplicating the getter/setter loop.

Also includes pre-existing cleanup:
- Refine LeafletRegisterEvent type (interface -> generic CustomEvent)
- Drop unused attrToPropName helper
- Fix createChildRegisterHandler return type in layer-group/feature-group
main
Buddy 3 months ago
parent 4d27c8eaac
commit 4e1abdcc3d

@ -1,6 +1,11 @@
import { CircleMarker } from 'leaflet';
import { registerWithParent, buildOptions } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import { registerWithParent, buildOptions, definePropAccessors } from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
@ -22,29 +27,14 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletCircleMarker extends TypedBase {
#obj?: CircleMarker;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletCircleMarker.prototype, name, {
get(this: LeafletCircleMarker) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletCircleMarker, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletCircleMarker.prototype, PROPS);
}
connectedCallback() {
@ -52,13 +42,14 @@ export class LeafletCircleMarker extends TypedBase {
[this.#num('lat'), this.#num('lng')],
buildOptions(this, PROPS, ['lat', 'lng']),
);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,6 +1,11 @@
import { Circle } from 'leaflet';
import { registerWithParent, buildOptions } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import { registerWithParent, buildOptions, definePropAccessors } from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
type LeafletRegisterEvent,
} from '../core/register.js';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
@ -22,29 +27,14 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletCircle extends TypedBase {
#obj?: Circle;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletCircle.prototype, name, {
get(this: LeafletCircle) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletCircle, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletCircle.prototype, PROPS);
}
connectedCallback() {
@ -52,13 +42,14 @@ export class LeafletCircle extends TypedBase {
[this.#num('lat'), this.#num('lng')],
buildOptions(this, PROPS, ['lat', 'lng']),
);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,5 +1,6 @@
import { Control, ControlPosition } from 'leaflet';
import { registerWithParent } from '../core/utils.js';
import { registerWithParent, definePropAccessors } from '../core/utils.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -18,18 +19,7 @@ export class LeafletControlAttribution extends TypedBase {
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletControlAttribution.prototype, name, {
get(this: LeafletControlAttribution) {
return this.getAttribute(spec.attr) ?? spec.default;
},
set(this: LeafletControlAttribution, v: string) {
this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletControlAttribution.prototype, PROPS);
}
connectedCallback() {

@ -1,5 +1,6 @@
import { Control, ControlPosition } from 'leaflet';
import { registerWithParent } from '../core/utils.js';
import { registerWithParent, definePropAccessors } from '../core/utils.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -21,22 +22,7 @@ export class LeafletControlScale extends TypedBase {
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletControlScale.prototype, name, {
get(this: LeafletControlScale) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletControlScale, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletControlScale.prototype, PROPS);
}
connectedCallback() {

@ -1,5 +1,6 @@
import { Control, ControlPosition } from 'leaflet';
import { registerWithParent } from '../core/utils.js';
import { registerWithParent, definePropAccessors } from '../core/utils.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -21,19 +22,7 @@ export class LeafletControlZoom extends TypedBase {
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletControlZoom.prototype, name, {
get(this: LeafletControlZoom) {
const val = this.getAttribute(spec.attr);
return val ?? (spec as { default: string }).default;
},
set(this: LeafletControlZoom, v: number | string) {
this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletControlZoom.prototype, PROPS);
}
connectedCallback() {

@ -1,11 +1,15 @@
import { FeatureGroup, Layer } from 'leaflet';
import { buildOptions, registerWithParent } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import {
createChildRegisterHandler,
type ChildEntry,
type LeafletRegisterEvent,
} from '../core/register.js';
export class LeafletFeatureGroup extends HTMLElement {
#obj?: FeatureGroup;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: (e: Event) => void;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return [];
@ -13,13 +17,14 @@ export class LeafletFeatureGroup extends HTMLElement {
connectedCallback() {
this.#obj = new FeatureGroup([], buildOptions(this, {}));
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
for (const [el, entry] of this.#children) {
if (entry.type === 'popup') this.#obj?.unbindPopup();
else if (entry.type === 'tooltip') this.#obj?.unbindTooltip();

@ -1,6 +1,11 @@
import { GeoJSON, PathOptions, Layer } from 'leaflet';
import { buildOptions, registerWithParent } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import { buildOptions, registerWithParent, definePropAccessors } from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -29,28 +34,14 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletGeoJSON extends TypedBase {
#obj?: GeoJSON;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: (e: Event) => void;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletGeoJSON.prototype, name, {
get(this: LeafletGeoJSON) {
if (spec.kind === 'num') return Number(this.getAttribute(spec.attr) ?? spec.default);
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return this.getAttribute(spec.attr) ?? spec.default;
},
set(this: LeafletGeoJSON, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletGeoJSON.prototype, PROPS);
}
connectedCallback() {
@ -60,13 +51,14 @@ export class LeafletGeoJSON extends TypedBase {
Object.entries(buildOptions(this, PROPS, ['data'])).filter(([, v]) => v !== ''),
);
this.#obj = new GeoJSON(data, { style: styleOpts as PathOptions });
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
for (const [el, entry] of this.#children) {
if (entry.type === 'popup') this.#obj?.unbindPopup();
else if (entry.type === 'tooltip') this.#obj?.unbindTooltip();

@ -1,6 +1,16 @@
import { ImageOverlay, LatLngBounds, LatLngBoundsExpression, LatLngExpression } from 'leaflet';
import { registerWithParent, buildOptions, parseAttributeValue } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import {
registerWithParent,
buildOptions,
parseAttributeValue,
definePropAccessors,
} from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -25,29 +35,14 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletImageOverlay extends TypedBase {
#obj?: ImageOverlay;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletImageOverlay.prototype, name, {
get(this: LeafletImageOverlay) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletImageOverlay, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletImageOverlay.prototype, PROPS);
}
connectedCallback() {
@ -57,13 +52,14 @@ export class LeafletImageOverlay extends TypedBase {
this.#parsedBounds(),
buildOptions(this, PROPS, ['url', 'bounds']),
);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,11 +1,15 @@
import { LayerGroup, Layer } from 'leaflet';
import { buildOptions, registerWithParent } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
export class LeafletLayerGroup extends HTMLElement {
#obj?: LayerGroup;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: (e: Event) => void;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return [];
@ -13,13 +17,14 @@ export class LeafletLayerGroup extends HTMLElement {
connectedCallback() {
this.#obj = new LayerGroup([], buildOptions(this, {}));
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
for (const [el, entry] of this.#children) {
if (entry.type === 'popup') this.#obj?.unbindPopup();
else if (entry.type === 'tooltip') this.#obj?.unbindTooltip();

@ -1,6 +1,16 @@
import { Marker } from 'leaflet';
import { registerWithParent, buildOptions, parseAttributeValue } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import {
registerWithParent,
buildOptions,
parseAttributeValue,
definePropAccessors,
} from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -23,29 +33,14 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletMarker extends TypedBase {
#obj?: Marker;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletMarker.prototype, name, {
get(this: LeafletMarker) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletMarker, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletMarker.prototype, PROPS);
}
connectedCallback() {
@ -53,13 +48,14 @@ export class LeafletMarker extends TypedBase {
[this.#num('lat'), this.#num('lng')],
buildOptions(this, PROPS, ['lat', 'lng']),
);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,6 +1,11 @@
import { Polygon } from 'leaflet';
import { registerWithParent, buildOptions } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import { registerWithParent, buildOptions, definePropAccessors } from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
import type { LeafletLine } from './leaflet-line.js';
@ -21,35 +26,20 @@ export class LeafletPolygon extends TypedBase {
#obj?: Polygon;
#observer?: MutationObserver;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletPolygon.prototype, name, {
get(this: LeafletPolygon) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletPolygon, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletPolygon.prototype, PROPS);
}
connectedCallback() {
this.#obj = new Polygon(this.#getCoords(), buildOptions(this, PROPS));
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
this.addEventListener('line-updated', this.#syncCoords);
@ -61,7 +51,8 @@ export class LeafletPolygon extends TypedBase {
this.#observer?.disconnect();
this.#observer = undefined;
this.removeEventListener('line-updated', this.#syncCoords);
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,6 +1,11 @@
import { Polyline } from 'leaflet';
import { registerWithParent, buildOptions } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import { registerWithParent, buildOptions, definePropAccessors } from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
import type { LeafletLine } from './leaflet-line.js';
@ -21,35 +26,20 @@ export class LeafletPolyline extends TypedBase {
#obj?: Polyline;
#observer?: MutationObserver;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletPolyline.prototype, name, {
get(this: LeafletPolyline) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletPolyline, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletPolyline.prototype, PROPS);
}
connectedCallback() {
this.#obj = new Polyline(this.#getCoords(), buildOptions(this, PROPS));
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
this.addEventListener('line-updated', this.#syncCoords);
@ -61,7 +51,8 @@ export class LeafletPolyline extends TypedBase {
this.#observer?.disconnect();
this.#observer = undefined;
this.removeEventListener('line-updated', this.#syncCoords);
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,5 +1,6 @@
import { Popup } from 'leaflet';
import { registerWithParent, buildOptions } from '../core/utils.js';
import { registerWithParent, buildOptions, definePropAccessors } from '../core/utils.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -25,23 +26,7 @@ export class LeafletPopup extends TypedBase {
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletPopup.prototype, name, {
get(this: LeafletPopup) {
const val = this.getAttribute(spec.attr);
if ('default' in spec && spec.kind === 'num')
return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? ('default' in spec ? (spec as { default: string }).default : '');
},
set(this: LeafletPopup, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletPopup.prototype, PROPS);
}
connectedCallback() {

@ -1,6 +1,11 @@
import { Rectangle, LatLngBoundsExpression } from 'leaflet';
import { registerWithParent, buildOptions } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import { registerWithParent, buildOptions, definePropAccessors } from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
@ -20,40 +25,26 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletRectangle extends TypedBase {
#obj?: Rectangle;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletRectangle.prototype, name, {
get(this: LeafletRectangle) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletRectangle, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletRectangle.prototype, PROPS);
}
connectedCallback() {
this.#obj = new Rectangle(this.#parsedBounds(), buildOptions(this, PROPS, ['bounds']));
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,6 +1,16 @@
import { SVGOverlay, LatLngBounds, LatLngBoundsExpression, LatLngExpression } from 'leaflet';
import { registerWithParent, buildOptions, parseAttributeValue } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import {
registerWithParent,
buildOptions,
parseAttributeValue,
definePropAccessors,
} from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -22,29 +32,14 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletSVGOverlay extends TypedBase {
#obj?: SVGOverlay;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletSVGOverlay.prototype, name, {
get(this: LeafletSVGOverlay) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletSVGOverlay, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletSVGOverlay.prototype, PROPS);
}
connectedCallback() {
@ -55,13 +50,14 @@ export class LeafletSVGOverlay extends TypedBase {
this.#parsedBounds(),
buildOptions(this, PROPS, ['bounds']),
);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,6 +1,16 @@
import { TileLayer } from 'leaflet';
import { registerWithParent, buildOptions, parseAttributeValue } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import {
registerWithParent,
buildOptions,
parseAttributeValue,
definePropAccessors,
} from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -23,31 +33,14 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletTileLayerWMS extends TypedBase {
#obj?: TileLayer.WMS;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletTileLayerWMS.prototype, name, {
get(this: LeafletTileLayerWMS) {
const val = this.getAttribute(spec.attr);
const s = spec as { kind: string; default: unknown };
if (s.kind === 'num') return val !== null ? Number(val) : (s.default as number);
if (s.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? (s.default as string);
},
set(this: LeafletTileLayerWMS, v: number | string | boolean) {
const s = spec as { kind: string };
if (s.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletTileLayerWMS.prototype, PROPS);
}
connectedCallback() {
@ -56,13 +49,14 @@ export class LeafletTileLayerWMS extends TypedBase {
url,
buildOptions(this, PROPS, ['url']) as Record<string, string>,
);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,6 +1,16 @@
import { TileLayer } from 'leaflet';
import { registerWithParent, buildOptions, parseAttributeValue } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import {
registerWithParent,
buildOptions,
parseAttributeValue,
definePropAccessors,
} from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -22,39 +32,27 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletTileLayer extends TypedBase {
#obj?: TileLayer;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletTileLayer.prototype, name, {
get(this: LeafletTileLayer) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
return val ?? spec.default;
},
set(this: LeafletTileLayer, v: number | string) {
this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletTileLayer.prototype, PROPS);
}
connectedCallback() {
const url = this.getAttribute('url') || '';
this.#obj = new TileLayer(url, buildOptions(this, PROPS, ['url']));
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,5 +1,6 @@
import { Tooltip } from 'leaflet';
import { registerWithParent, buildOptions } from '../core/utils.js';
import { registerWithParent, buildOptions, definePropAccessors } from '../core/utils.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -25,22 +26,7 @@ export class LeafletTooltip extends TypedBase {
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletTooltip.prototype, name, {
get(this: LeafletTooltip) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletTooltip, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletTooltip.prototype, PROPS);
}
connectedCallback() {

@ -1,6 +1,16 @@
import { VideoOverlay, LatLngBounds, LatLngBoundsExpression, LatLngExpression } from 'leaflet';
import { registerWithParent, buildOptions, parseAttributeValue } from '../core/utils.js';
import { createChildRegisterHandler, type ChildEntry } from '../core/register.js';
import {
registerWithParent,
buildOptions,
parseAttributeValue,
definePropAccessors,
} from '../core/utils.js';
import {
createChildRegisterHandler,
type ChildEntry,
LeafletRegisterEvent,
} from '../core/register.js';
import type { PropDef, PropTypesFromTable } from '../types/props.js';
const PROPS = {
@ -26,29 +36,14 @@ const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes;
export class LeafletVideoOverlay extends TypedBase {
#obj?: VideoOverlay;
#children = new Map<HTMLElement, ChildEntry>();
#childHandler?: EventListener;
#childHandler?: (e: LeafletRegisterEvent) => void;
static get observedAttributes() {
return Object.values(PROPS).map((s) => s.attr);
}
static {
for (const [name, spec] of Object.entries(PROPS)) {
Object.defineProperty(LeafletVideoOverlay.prototype, name, {
get(this: LeafletVideoOverlay) {
const val = this.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return this.hasAttribute(spec.attr);
return val ?? spec.default;
},
set(this: LeafletVideoOverlay, v: number | string | boolean) {
if (spec.kind === 'bool-on') this.toggleAttribute(spec.attr, !!v);
else this.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
definePropAccessors(LeafletVideoOverlay.prototype, PROPS);
}
connectedCallback() {
@ -58,13 +53,14 @@ export class LeafletVideoOverlay extends TypedBase {
this.#parsedBounds(),
buildOptions(this, PROPS, ['url', 'bounds']),
);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children) as EventListener;
this.addEventListener('leaflet-register', this.#childHandler);
this.#childHandler = createChildRegisterHandler(this.#obj, this.#children);
this.addEventListener('leaflet-register', this.#childHandler as EventListener);
registerWithParent(this, this.#obj);
}
disconnectedCallback() {
if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler);
if (this.#childHandler)
this.removeEventListener('leaflet-register', this.#childHandler as EventListener);
this.#children.clear();
this.#obj?.remove();
this.#obj = undefined;

@ -1,21 +1,16 @@
import { Layer, LayerGroup, Popup, Tooltip } from 'leaflet';
export interface LeafletRegisterEvent extends CustomEvent {
detail: {
export type LeafletRegisterEvent = CustomEvent<{
leafletObject: Layer;
element: HTMLElement;
};
}
}>;
export type ChildEntry = {
type: 'layer' | 'popup' | 'tooltip';
};
export function createChildRegisterHandler(
layer: Layer,
children: globalThis.Map<HTMLElement, ChildEntry>,
) {
return (e: LeafletRegisterEvent) => {
export function createChildRegisterHandler(layer: Layer, children: Map<HTMLElement, ChildEntry>) {
return function (e: LeafletRegisterEvent) {
const obj = e.detail.leafletObject;
const el = e.detail.element;
if (obj instanceof Popup) {

@ -17,6 +17,27 @@ export function parseAttributeValue(value: string | null): unknown {
}
}
export function definePropAccessors(proto: object, props: Record<string, PropDef>) {
for (const [name, spec] of Object.entries(props)) {
Object.defineProperty(proto, name, {
get() {
const el = this as HTMLElement;
const val = el.getAttribute(spec.attr);
if (spec.kind === 'num') return val !== null ? Number(val) : spec.default;
if (spec.kind === 'bool-on') return el.hasAttribute(spec.attr);
return val ?? (spec as { default: string }).default;
},
set(v: unknown) {
const el = this as HTMLElement;
if (spec.kind === 'bool-on') el.toggleAttribute(spec.attr, !!v);
else el.setAttribute(spec.attr, String(v));
},
configurable: true,
enumerable: true,
});
}
}
export function buildOptions(
el: HTMLElement,
props: Record<string, PropDef>,

@ -31,13 +31,3 @@ export type PropTypeOf<T extends PropDef> = T extends NumProp
export type PropTypesFromTable<T extends Record<string, PropDef>> = {
[K in keyof T]: PropTypeOf<T[K]>;
};
export function attrToPropName<P extends Record<string, PropDef>>(
props: P,
attr: string,
): keyof P | undefined {
for (const [name, spec] of Object.entries(props) as [keyof P, PropDef][]) {
if (spec.attr === attr) return name;
}
return undefined;
}

Loading…
Cancel
Save