|
|
|
|
@ -1,10 +1,16 @@
|
|
|
|
|
import type { PropDef } from '../types/props.js';
|
|
|
|
|
import type { LatLngBoundsExpression } from 'leaflet';
|
|
|
|
|
|
|
|
|
|
// Converts kebab-case to camelCase (e.g. "fill-color" → "fillColor").
|
|
|
|
|
// Used when component PROPS tables map attribute names to Leaflet option keys.
|
|
|
|
|
export function camelCase(str: string): string {
|
|
|
|
|
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
@ -18,6 +24,10 @@ export function parseAttributeValue(value: string | null): unknown {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Installs reactive getter/setter pairs on a prototype for every entry in
|
|
|
|
|
// a PROPS table. Each getter reads from the attribute (coerced to the
|
|
|
|
|
// correct type), each setter writes via setAttribute/toggleAttribute.
|
|
|
|
|
// Used by the withProps() mixin so components have `el.lat = 51.5` sugar.
|
|
|
|
|
export function definePropAccessors(proto: object, props: Record<string, PropDef>) {
|
|
|
|
|
for (const [name, spec] of Object.entries(props)) {
|
|
|
|
|
Object.defineProperty(proto, name, {
|
|
|
|
|
@ -39,21 +49,39 @@ export function definePropAccessors(proto: object, props: Record<string, PropDef
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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<string, PropDef>, name: string): number {
|
|
|
|
|
const v = el.getAttribute(name);
|
|
|
|
|
if (v !== null) return Number(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: Record<string, PropDef>): Map<string, string> {
|
|
|
|
|
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<string, PropDef>,
|
|
|
|
|
@ -74,6 +102,12 @@ export function setLayerAttr(
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Builds the initial Leaflet options object from the PROPS table and the
|
|
|
|
|
// element's current attributes at connection time. Props listed in
|
|
|
|
|
// `exclude` are skipped (they're passed separately to Leaflet
|
|
|
|
|
// constructors, like coordinates or URLs). Null attributes use the
|
|
|
|
|
// default from PROPS if one exists (skipping empty-string defaults to
|
|
|
|
|
// avoid Leaflet rejecting them).
|
|
|
|
|
export function buildOptions(
|
|
|
|
|
el: HTMLElement,
|
|
|
|
|
props: Record<string, PropDef>,
|
|
|
|
|
@ -95,6 +129,12 @@ export function buildOptions(
|
|
|
|
|
return opts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dispatches a custom `leaflet-register` event upward through the DOM
|
|
|
|
|
// tree, carrying a Leaflet object and its host element. Parent components
|
|
|
|
|
// (map, circles, groups, etc.) intercept this event and add the layer,
|
|
|
|
|
// bind the popup, or bind the tooltip. This is the core wiring mechanism
|
|
|
|
|
// that replaces the parent-child relationship that Leaflet normally
|
|
|
|
|
// manages via imperative code.
|
|
|
|
|
export function registerWithParent(el: HTMLElement, obj: unknown) {
|
|
|
|
|
el.dispatchEvent(
|
|
|
|
|
new CustomEvent('leaflet-register', {
|
|
|
|
|
|