import type { LatLngBoundsExpression } from 'leaflet'; import { Path } from 'leaflet'; import type { PropDef } from './props.ts'; // Reads a numeric attribute from an element, falling back to the default // value declared in the PROPS table. Handles the common pattern of // reading lat/lng/radius/opacity with a guaranteed number return. export function numAttr(el: HTMLElement, props: Record, name: string): number { const v = el.getAttribute(name); if (v !== null) return +v; return (props[name] as { default: number }).default; } // Reads the `bounds` attribute from an element and parses it as JSON // into a LatLngBoundsExpression. Returns an empty array when no // attribute is set. Shared by rectangle, image-overlay, video-overlay, // and svg-overlay, all of which accept a `bounds` attribute. export function parseBoundsAttr(el: HTMLElement): LatLngBoundsExpression { const raw = el.getAttribute('bounds'); return raw ? (JSON.parse(raw) as LatLngBoundsExpression) : []; } // Builds a reverse lookup map from HTML attribute name → PROP key. // For example, { fillColor: { attr: 'fill-color' } } becomes // { 'fill-color' → 'fillColor' }. Used by setLayerAttr to find the // Leaflet setter name when an attribute changes at runtime. export function buildAttrMap>(props: T): Map { return new Map(Object.entries(props).map(([name, spec]) => [spec.attr, name])); } // Generic dispatcher for runtime attribute changes on Leaflet objects. // Given the attribute name, it looks up the PROP key, constructs the // matching Leaflet setter name (e.g. "opacity" → "setOpacity"), and // calls it with the parsed value. Returns true if a setter was found // and called, false otherwise (caller can fall back, as WMS does with // setParams). Handles bool-on props by passing the presence/absence of // the attribute rather than its string value. export function setLayerAttr( obj: object, props: Record, attrMap: Map, name: string, val: string | null, ): boolean { const propName = attrMap.get(name); if (!propName) return false; const spec = props[propName]; const setter = `set${propName.charAt(0).toUpperCase()}${propName.slice(1)}`; const fn = (obj as Record)[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; } // Parses an HTML attribute string into a typed JS value: // null → null, "true"/"false" → boolean, numeric strings → number, // valid JSON → parsed object/array, otherwise the raw string. // This is the bridge between HTML attribute strings and Leaflet options. export function parseAttributeValue(value: string | null): unknown { if (value === null) return null; if (value === 'true') return true; if (value === 'false') return false; const num = +value; if (!isNaN(num) && value !== '') return num; try { return JSON.parse(value); } catch { return value; } } const PATH_STYLE_ATTRS = new Set([ 'color', 'weight', 'opacity', 'fill', 'fill-color', 'fill-opacity', 'stroke', 'dash-array', 'dash-offset', 'line-cap', 'line-join', 'fill-rule', ]); export function isPathStyleAttr(name: string): boolean { return PATH_STYLE_ATTRS.has(name); } export function updatePathStyle(obj: Path, name: string, value: string | null) { const key = name.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); obj.setStyle({ [key]: parseAttributeValue(value) }); }