refactor: extract PROP_BY_ATTR + dynamic setter dispatch into helpers

Adds buildAttrMap(props) and setLayerAttr(obj, props, attrMap,
name, val) to utils.ts. The former replaces the PROP_BY_ATTR Map
construction (2 fewer lines per component), the latter replaces
the 6-line dynamic setter dispatch pattern used as the fallback
else block in 6 components.

For WMS, setLayerAttr returns false when no setter exists, so the
caller can fall back to setParams for non-method props.
main
Buddy 3 months ago
parent 96b48a6ae2
commit c69f9c8503

@ -1,5 +1,5 @@
import { ImageOverlay, LatLngBounds, LatLngExpression } from 'leaflet'; import { ImageOverlay, LatLngBounds, LatLngExpression } from 'leaflet';
import { buildOptions, parseAttributeValue, parseBoundsAttr } from '../core/utils.js'; import { buildOptions, buildAttrMap, setLayerAttr, parseBoundsAttr } from '../core/utils.js';
import { withProps } from '../core/with-props.js'; import { withProps } from '../core/with-props.js';
import { registerChildren, unregisterChildren } from '../core/register.js'; import { registerChildren, unregisterChildren } from '../core/register.js';
@ -17,9 +17,7 @@ const PROPS = {
className: { kind: 'str', attr: 'class-name', default: '' }, className: { kind: 'str', attr: 'class-name', default: '' },
} satisfies Record<string, PropDef>; } satisfies Record<string, PropDef>;
const PROP_BY_ATTR = new Map<string, string>( const PROP_BY_ATTR = buildAttrMap(PROPS);
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]),
);
export class LeafletImageOverlay extends withProps(HTMLElement, PROPS) { export class LeafletImageOverlay extends withProps(HTMLElement, PROPS) {
#obj?: ImageOverlay; #obj?: ImageOverlay;
@ -50,15 +48,7 @@ export class LeafletImageOverlay extends withProps(HTMLElement, PROPS) {
const el = this.#obj.getElement(); const el = this.#obj.getElement();
if (el) (el as HTMLImageElement).alt = val ?? ''; if (el) (el as HTMLImageElement).alt = val ?? '';
} else { } else {
const propName = PROP_BY_ATTR.get(name); setLayerAttr(this.#obj!, PROPS, PROP_BY_ATTR, name, val);
if (!propName) return;
const setter =
`set${propName.charAt(0).toUpperCase()}${propName.slice(1)}` as keyof ImageOverlay;
if (typeof this.#obj[setter] === 'function') {
const spec = PROPS[propName as keyof typeof PROPS];
const value = spec.kind === 'bool-on' ? val !== null : parseAttributeValue(val);
(this.#obj[setter] as (v: unknown) => void)(value);
}
} }
} }
} }

@ -1,5 +1,5 @@
import { Marker } from 'leaflet'; import { Marker } from 'leaflet';
import { buildOptions, numAttr, parseAttributeValue } from '../core/utils.js'; import { buildOptions, numAttr, buildAttrMap, setLayerAttr } from '../core/utils.js';
import { withProps } from '../core/with-props.js'; import { withProps } from '../core/with-props.js';
import { registerChildren, unregisterChildren } from '../core/register.js'; import { registerChildren, unregisterChildren } from '../core/register.js';
@ -15,9 +15,7 @@ const PROPS = {
zIndexOffset: { kind: 'num', attr: 'z-index-offset', default: 0 }, zIndexOffset: { kind: 'num', attr: 'z-index-offset', default: 0 },
} satisfies Record<string, PropDef>; } satisfies Record<string, PropDef>;
const PROP_BY_ATTR = new Map<string, string>( const PROP_BY_ATTR = buildAttrMap(PROPS);
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]),
);
export class LeafletMarker extends withProps(HTMLElement, PROPS) { export class LeafletMarker extends withProps(HTMLElement, PROPS) {
#obj?: Marker; #obj?: Marker;
@ -53,12 +51,7 @@ export class LeafletMarker extends withProps(HTMLElement, PROPS) {
const el = this.#obj.getElement(); const el = this.#obj.getElement();
if (el) (el as HTMLImageElement).alt = val ?? ''; if (el) (el as HTMLImageElement).alt = val ?? '';
} else { } else {
const propName = PROP_BY_ATTR.get(name); setLayerAttr(this.#obj!, PROPS, PROP_BY_ATTR, name, val);
if (!propName) return;
const setter = `set${propName.charAt(0).toUpperCase()}${propName.slice(1)}` as keyof Marker;
if (typeof this.#obj[setter] === 'function') {
(this.#obj[setter] as (v: unknown) => void)(parseAttributeValue(val));
}
} }
} }

@ -1,5 +1,5 @@
import { SVGOverlay, LatLngBounds, LatLngExpression } from 'leaflet'; import { SVGOverlay, LatLngBounds, LatLngExpression } from 'leaflet';
import { buildOptions, parseAttributeValue, parseBoundsAttr } from '../core/utils.js'; import { buildOptions, buildAttrMap, setLayerAttr, parseBoundsAttr } from '../core/utils.js';
import { withProps } from '../core/with-props.js'; import { withProps } from '../core/with-props.js';
import { registerChildren, unregisterChildren } from '../core/register.js'; import { registerChildren, unregisterChildren } from '../core/register.js';
@ -14,9 +14,7 @@ const PROPS = {
className: { kind: 'str', attr: 'class-name', default: '' }, className: { kind: 'str', attr: 'class-name', default: '' },
} satisfies Record<string, PropDef>; } satisfies Record<string, PropDef>;
const PROP_BY_ATTR = new Map<string, string>( const PROP_BY_ATTR = buildAttrMap(PROPS);
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]),
);
export class LeafletSVGOverlay extends withProps(HTMLElement, PROPS) { export class LeafletSVGOverlay extends withProps(HTMLElement, PROPS) {
#obj?: SVGOverlay; #obj?: SVGOverlay;
@ -43,15 +41,7 @@ export class LeafletSVGOverlay extends withProps(HTMLElement, PROPS) {
if (name === 'bounds') { if (name === 'bounds') {
this.#obj.setBounds(new LatLngBounds(parseBoundsAttr(this) as LatLngExpression[])); this.#obj.setBounds(new LatLngBounds(parseBoundsAttr(this) as LatLngExpression[]));
} else { } else {
const propName = PROP_BY_ATTR.get(name); setLayerAttr(this.#obj!, PROPS, PROP_BY_ATTR, name, val);
if (!propName) return;
const setter =
`set${propName.charAt(0).toUpperCase()}${propName.slice(1)}` as keyof SVGOverlay;
if (typeof this.#obj[setter] === 'function') {
const spec = PROPS[propName as keyof typeof PROPS];
const value = spec.kind === 'bool-on' ? val !== null : parseAttributeValue(val);
(this.#obj[setter] as (v: unknown) => void)(value);
}
} }
} }
} }

@ -1,5 +1,5 @@
import { TileLayer } from 'leaflet'; import { TileLayer } from 'leaflet';
import { buildOptions, parseAttributeValue } from '../core/utils.js'; import { buildOptions, buildAttrMap, setLayerAttr, parseAttributeValue } from '../core/utils.js';
import { withProps } from '../core/with-props.js'; import { withProps } from '../core/with-props.js';
import { registerChildren, unregisterChildren } from '../core/register.js'; import { registerChildren, unregisterChildren } from '../core/register.js';
@ -15,9 +15,7 @@ const PROPS = {
uppercase: { kind: 'bool-on', attr: 'uppercase' }, uppercase: { kind: 'bool-on', attr: 'uppercase' },
} satisfies Record<string, PropDef>; } satisfies Record<string, PropDef>;
const PROP_BY_ATTR = new Map<string, string>( const PROP_BY_ATTR = buildAttrMap(PROPS);
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]),
);
export class LeafletTileLayerWMS extends withProps(HTMLElement, PROPS) { export class LeafletTileLayerWMS extends withProps(HTMLElement, PROPS) {
#obj?: TileLayer.WMS; #obj?: TileLayer.WMS;
@ -44,13 +42,8 @@ export class LeafletTileLayerWMS extends withProps(HTMLElement, PROPS) {
} else { } else {
const propName = PROP_BY_ATTR.get(name); const propName = PROP_BY_ATTR.get(name);
if (!propName) return; if (!propName) return;
const setter =
`set${propName.charAt(0).toUpperCase()}${propName.slice(1)}` as keyof TileLayer.WMS;
const spec = PROPS[propName as keyof typeof PROPS]; const spec = PROPS[propName as keyof typeof PROPS];
if (typeof this.#obj[setter] === 'function') { if (!setLayerAttr(this.#obj!, PROPS, PROP_BY_ATTR, name, val)) {
const value = spec.kind === 'bool-on' ? val !== null : parseAttributeValue(val);
(this.#obj[setter] as (v: unknown) => void)(value);
} else {
const value = spec.kind === 'bool-on' ? val !== null : parseAttributeValue(val); const value = spec.kind === 'bool-on' ? val !== null : parseAttributeValue(val);
(this.#obj.setParams as unknown as (params: Record<string, unknown>) => void)({ (this.#obj.setParams as unknown as (params: Record<string, unknown>) => void)({
[propName]: value, [propName]: value,

@ -1,5 +1,5 @@
import { TileLayer } from 'leaflet'; import { TileLayer } from 'leaflet';
import { buildOptions, parseAttributeValue } from '../core/utils.js'; import { buildOptions, buildAttrMap, setLayerAttr } from '../core/utils.js';
import { withProps } from '../core/with-props.js'; import { withProps } from '../core/with-props.js';
import { registerChildren, unregisterChildren } from '../core/register.js'; import { registerChildren, unregisterChildren } from '../core/register.js';
@ -14,9 +14,7 @@ const PROPS = {
zIndex: { kind: 'num', attr: 'z-index', default: 0 }, zIndex: { kind: 'num', attr: 'z-index', default: 0 },
} satisfies Record<string, PropDef>; } satisfies Record<string, PropDef>;
const PROP_BY_ATTR = new Map<string, string>( const PROP_BY_ATTR = buildAttrMap(PROPS);
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]),
);
export class LeafletTileLayer extends withProps(HTMLElement, PROPS) { export class LeafletTileLayer extends withProps(HTMLElement, PROPS) {
#obj?: TileLayer; #obj?: TileLayer;
@ -38,13 +36,7 @@ export class LeafletTileLayer extends withProps(HTMLElement, PROPS) {
if (name === 'url') { if (name === 'url') {
if (val) this.#obj.setUrl(val); if (val) this.#obj.setUrl(val);
} else { } else {
const propName = PROP_BY_ATTR.get(name); setLayerAttr(this.#obj!, PROPS, PROP_BY_ATTR, name, val);
if (!propName) return;
const setter =
`set${propName.charAt(0).toUpperCase()}${propName.slice(1)}` as keyof TileLayer;
if (typeof this.#obj[setter] === 'function') {
(this.#obj[setter] as (v: unknown) => void)(parseAttributeValue(val));
}
} }
} }
} }

@ -1,5 +1,5 @@
import { VideoOverlay, LatLngBounds, LatLngExpression } from 'leaflet'; import { VideoOverlay, LatLngBounds, LatLngExpression } from 'leaflet';
import { buildOptions, parseAttributeValue, parseBoundsAttr } from '../core/utils.js'; import { buildOptions, buildAttrMap, setLayerAttr, parseBoundsAttr } from '../core/utils.js';
import { withProps } from '../core/with-props.js'; import { withProps } from '../core/with-props.js';
import { registerChildren, unregisterChildren } from '../core/register.js'; import { registerChildren, unregisterChildren } from '../core/register.js';
@ -18,9 +18,7 @@ const PROPS = {
playsInline: { kind: 'bool-on', attr: 'playsinline' }, playsInline: { kind: 'bool-on', attr: 'playsinline' },
} satisfies Record<string, PropDef>; } satisfies Record<string, PropDef>;
const PROP_BY_ATTR = new Map<string, string>( const PROP_BY_ATTR = buildAttrMap(PROPS);
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]),
);
export class LeafletVideoOverlay extends withProps(HTMLElement, PROPS) { export class LeafletVideoOverlay extends withProps(HTMLElement, PROPS) {
#obj?: VideoOverlay; #obj?: VideoOverlay;
@ -61,15 +59,7 @@ export class LeafletVideoOverlay extends withProps(HTMLElement, PROPS) {
else if (name === 'playsinline') el.playsInline = val !== null; else if (name === 'playsinline') el.playsInline = val !== null;
} }
} else { } else {
const propName = PROP_BY_ATTR.get(name); setLayerAttr(this.#obj!, PROPS, PROP_BY_ATTR, name, val);
if (!propName) return;
const setter =
`set${propName.charAt(0).toUpperCase()}${propName.slice(1)}` as keyof VideoOverlay;
if (typeof this.#obj[setter] === 'function') {
const spec = PROPS[propName as keyof typeof PROPS];
const value = spec.kind === 'bool-on' ? val !== null : parseAttributeValue(val);
(this.#obj[setter] as (v: unknown) => void)(value);
}
} }
} }

@ -50,6 +50,30 @@ export function parseBoundsAttr(el: HTMLElement): LatLngBoundsExpression {
return raw ? (JSON.parse(raw) as LatLngBoundsExpression) : []; return raw ? (JSON.parse(raw) as LatLngBoundsExpression) : [];
} }
export function buildAttrMap(props: Record<string, PropDef>): Map<string, string> {
return new Map(Object.entries(props).map(([name, spec]) => [spec.attr, name]));
}
export function setLayerAttr(
obj: object,
props: Record<string, PropDef>,
attrMap: Map<string, string>,
name: string,
val: string | null,
): boolean {
const propName = attrMap.get(name);
if (!propName) return false;
const spec = props[propName as keyof typeof props];
const setter = `set${propName.charAt(0).toUpperCase()}${propName.slice(1)}`;
const fn = (obj as Record<string, unknown>)[setter];
if (typeof fn === 'function') {
const value = spec.kind === 'bool-on' ? val !== null : parseAttributeValue(val);
(fn as (v: unknown) => void)(value);
return true;
}
return false;
}
export function buildOptions( export function buildOptions(
el: HTMLElement, el: HTMLElement,
props: Record<string, PropDef>, props: Record<string, PropDef>,

Loading…
Cancel
Save