refactor: move WithProps mixin from utils.ts to props.ts

WithProps is tightly coupled to PropDef types, so it belongs in
props.ts alongside defineProps and the type definitions.

- Move WithProps, definePropAccessors, and Ctor to props.ts
- Import PropTypesFromTable in utils.ts (used by buildOptions)
- Update all 18 component imports to source WithProps from props.ts
main
Buddy 3 months ago
parent d6f7304313
commit 3854c62896

@ -1,8 +1,7 @@
import { CircleMarker } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import {
WithProps,
buildOptions,
isPathStyleAttr,
numAttr,

@ -1,8 +1,7 @@
import { Circle } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import {
WithProps,
buildOptions,
isPathStyleAttr,
numAttr,

@ -1,6 +1,6 @@
import { Control, ControlPosition } from 'leaflet';
import { defineProps, str } from '../core/props.ts';
import { WithProps, registerWithParent } from '../core/utils.ts';
import { WithProps, defineProps, str } from '../core/props.ts';
import { registerWithParent } from '../core/utils.ts';
const PROPS = defineProps({
position: str('bottomright'),

@ -1,6 +1,6 @@
import { Control, ControlPosition, Layer } from 'leaflet';
import { defineProps, str } from '../core/props.ts';
import { WithProps, registerWithParent } from '../core/utils.ts';
import { WithProps, defineProps, str } from '../core/props.ts';
import { registerWithParent } from '../core/utils.ts';
import { LeafletRegisterEvent } from '../core/register.ts';
const PROPS = defineProps({

@ -1,6 +1,6 @@
import { Control, ControlPosition } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, registerWithParent } from '../core/utils.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerWithParent } from '../core/utils.ts';
const PROPS = defineProps({
position: str('bottomleft'),

@ -1,6 +1,6 @@
import { Control, ControlPosition } from 'leaflet';
import { defineProps, str } from '../core/props.ts';
import { WithProps, registerWithParent } from '../core/utils.ts';
import { WithProps, defineProps, str } from '../core/props.ts';
import { registerWithParent } from '../core/utils.ts';
const PROPS = defineProps({
position: str('topleft'),

@ -1,7 +1,7 @@
import { GeoJSON, PathOptions, Layer } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren, getChildren } from '../core/register.ts';
import { WithProps, buildOptions } from '../core/utils.ts';
import { buildOptions } from '../core/utils.ts';
const PROPS = defineProps({
data: str(),

@ -1,9 +1,8 @@
import { ImageOverlay, LatLngBounds, LatLngExpression } from 'leaflet';
import type { ImageOverlayOptions } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import {
WithProps,
buildAttrMap,
buildOptions,
parseBoundsAttr,

@ -1,7 +1,7 @@
import { Icon, Marker } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { LeafletIconChangedEvent, registerChildren, unregisterChildren } from '../core/register.ts';
import { WithProps, buildAttrMap, buildOptions, numAttr, setLayerAttr } from '../core/utils.ts';
import { buildAttrMap, buildOptions, numAttr, setLayerAttr } from '../core/utils.ts';
const PROPS = defineProps({
lat: num(),

@ -1,7 +1,7 @@
import { Polygon } from 'leaflet';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, buildOptions, isPathStyleAttr, updatePathStyle } from '../core/utils.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { buildOptions, isPathStyleAttr, updatePathStyle } from '../core/utils.ts';
import type { LeafletLine } from './leaflet-line.ts';
const PROPS = defineProps({

@ -1,7 +1,7 @@
import { Polyline } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { WithProps, buildOptions, isPathStyleAttr, updatePathStyle } from '../core/utils.ts';
import { buildOptions, isPathStyleAttr, updatePathStyle } from '../core/utils.ts';
import type { LeafletLine } from './leaflet-line.ts';
const PROPS = defineProps({

@ -1,6 +1,6 @@
import { Popup } from 'leaflet';
import { defineProps, num, on } from '../core/props.ts';
import { WithProps, buildOptions, numAttr, registerWithParent } from '../core/utils.ts';
import { WithProps, defineProps, num, on } from '../core/props.ts';
import { buildOptions, numAttr, registerWithParent } from '../core/utils.ts';
const PROPS = defineProps({
lat: num(),

@ -1,8 +1,7 @@
import { Rectangle } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import {
WithProps,
buildOptions,
isPathStyleAttr,
parseBoundsAttr,

@ -1,9 +1,8 @@
import { SVGOverlay, LatLngBounds, LatLngExpression } from 'leaflet';
import type { ImageOverlayOptions } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import {
WithProps,
buildAttrMap,
buildOptions,
parseBoundsAttr,

@ -1,8 +1,7 @@
import { TileLayer } from 'leaflet';
import { defineProps, str, on } from '../core/props.ts';
import { WithProps, defineProps, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import {
WithProps,
buildAttrMap,
buildOptions,
parseAttributeValue,

@ -1,7 +1,7 @@
import { TileLayer } from 'leaflet';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { defineProps, num, str } from '../core/props.ts';
import { WithProps, buildAttrMap, buildOptions, setLayerAttr } from '../core/utils.ts';
import { WithProps, defineProps, num, str } from '../core/props.ts';
import { buildAttrMap, buildOptions, setLayerAttr } from '../core/utils.ts';
const PROPS = defineProps({
url: str(),

@ -1,7 +1,7 @@
import { Tooltip } from 'leaflet';
import type { TooltipOptions } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, buildOptions, numAttr, registerWithParent } from '../core/utils.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { buildOptions, numAttr, registerWithParent } from '../core/utils.ts';
const PROPS = defineProps({
lat: num(),

@ -1,9 +1,8 @@
import { VideoOverlay, LatLngBounds, LatLngExpression } from 'leaflet';
import type { VideoOverlayOptions } from 'leaflet';
import { defineProps, num, str, on } from '../core/props.ts';
import { WithProps, defineProps, num, str, on } from '../core/props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import {
WithProps,
buildAttrMap,
buildOptions,
parseBoundsAttr,

@ -37,8 +37,6 @@ export type PropTypesFromTable<T extends Record<string, PropDef>> = {
[K in keyof T]: PropTypeOf<T[K]>;
};
// --- Factory helpers ---
type OptionalAttr<T> = Omit<T, 'attr'> & { attr?: string };
type NumPropInput<T = unknown> = OptionalAttr<NumProp<T>>;
@ -98,3 +96,40 @@ export function defineProps<T extends Record<string, unknown>>(
}
return output as unknown as { [K in keyof T]: PropDefFromInput<T[K]> };
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Ctor<T = object> = new (...args: any[]) => T;
export function WithProps<TBase extends Ctor<HTMLElement>, TProps extends Record<string, PropDef>>(
Base: TBase,
props: TProps,
): TBase & Ctor<HTMLElement & PropTypesFromTable<TProps>> {
class WithProps extends Base {
static get observedAttributes(): string[] {
return Object.values(props).map((s) => s.attr);
}
}
definePropAccessors(WithProps.prototype, props);
return WithProps as TBase & Ctor<HTMLElement & PropTypesFromTable<TProps>>;
}
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 ? +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, `${v}`);
},
configurable: true,
enumerable: true,
});
}
}

@ -2,50 +2,6 @@ import { Path } from 'leaflet';
import type { PropDef, PropTypesFromTable } from './props.ts';
import type { LatLngBoundsExpression } from 'leaflet';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Ctor<T = object> = new (...args: any[]) => T;
export function WithProps<TBase extends Ctor<HTMLElement>, TProps extends Record<string, PropDef>>(
Base: TBase,
props: TProps,
): TBase & Ctor<HTMLElement & PropTypesFromTable<TProps>> {
class WithProps extends Base {
static {
definePropAccessors(WithProps.prototype, props);
}
static get observedAttributes(): string[] {
return Object.values(props).map((s) => s.attr);
}
}
return WithProps as TBase & Ctor<HTMLElement & PropTypesFromTable<TProps>>;
}
// 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.
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 ? +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, `${v}`);
},
configurable: true,
enumerable: true,
});
}
}
// 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.

Loading…
Cancel
Save