refactor: replace PROPS literal tables with factory helpers

Add num(), str(), on() factory functions and defineProps() wrapper
to core/props.ts. Each helper returns a PropDef fragment without
'attr'; defineProps fills it in via camelToKebab(key), with an
optional override for edge cases like playsInline->playsinline.

Input types are derived from their canonical counterparts via
OptionalAttr<T> = Omit<T, 'attr'> & { attr?: string }, keeping
definitions in sync automatically.

Net effect: ~3800 chars removed across 18 component files, no
behavior change, full type inference preserved.
main
Buddy 3 months ago
parent 96037f22b6
commit f615dceea3

@ -4,19 +4,19 @@ import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = {
lat: { kind: 'num', attr: 'lat', default: 0 },
lng: { kind: 'num', attr: 'lng', default: 0 },
radius: { kind: 'num', attr: 'radius', default: 10 },
color: { kind: 'str', attr: 'color', default: '#3388ff' },
weight: { kind: 'num', attr: 'weight', default: 3 },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
fill: { kind: 'bool-on', attr: 'fill' },
fillColor: { kind: 'str', attr: 'fill-color', default: '#3388ff' },
fillOpacity: { kind: 'num', attr: 'fill-opacity', default: 0.2 },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
lat: num(),
lng: num(),
radius: num(10),
color: str('#3388ff'),
weight: num(3),
opacity: num(1.0),
fill: on(),
fillColor: str('#3388ff'),
fillOpacity: num(0.2),
});
export default class LeafletCircleMarker extends withProps(HTMLElement, PROPS) {
#obj?: CircleMarker;

@ -4,19 +4,19 @@ import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = {
lat: { kind: 'num', attr: 'lat', default: 0 },
lng: { kind: 'num', attr: 'lng', default: 0 },
radius: { kind: 'num', attr: 'radius', default: 1000 },
color: { kind: 'str', attr: 'color', default: '#3388ff' },
weight: { kind: 'num', attr: 'weight', default: 3 },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
fill: { kind: 'bool-on', attr: 'fill' },
fillColor: { kind: 'str', attr: 'fill-color', default: '#3388ff' },
fillOpacity: { kind: 'num', attr: 'fill-opacity', default: 0.2 },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
lat: num(),
lng: num(),
radius: num(1000),
color: str('#3388ff'),
weight: num(3),
opacity: num(1.0),
fill: on(),
fillColor: str('#3388ff'),
fillOpacity: num(0.2),
});
export default class LeafletCircle extends withProps(HTMLElement, PROPS) {
#obj?: Circle;

@ -2,12 +2,12 @@ import { Control, ControlPosition } from 'leaflet';
import { registerWithParent } from '../core/utils.ts';
import { withProps } from '../core/with-props.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, str } from '../core/props.ts';
const PROPS = {
position: { kind: 'str', attr: 'position', default: 'bottomright' },
prefix: { kind: 'str', attr: 'prefix', default: '' },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
position: str('bottomright'),
prefix: str(),
});
export default class LeafletControlAttribution extends withProps(HTMLElement, PROPS) {
#obj?: Control.Attribution;

@ -2,15 +2,15 @@ import { Control, ControlPosition } from 'leaflet';
import { registerWithParent } from '../core/utils.ts';
import { withProps } from '../core/with-props.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = {
position: { kind: 'str', attr: 'position', default: 'bottomleft' },
maxWidth: { kind: 'num', attr: 'max-width', default: 100 },
metric: { kind: 'bool-on', attr: 'metric' },
imperial: { kind: 'bool-on', attr: 'imperial' },
updateWhenIdle: { kind: 'bool-on', attr: 'update-when-idle' },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
position: str('bottomleft'),
maxWidth: num(100),
metric: on(),
imperial: on(),
updateWhenIdle: on(),
});
export default class LeafletControlScale extends withProps(HTMLElement, PROPS) {
#obj?: Control.Scale;

@ -2,15 +2,15 @@ import { Control, ControlPosition } from 'leaflet';
import { registerWithParent } from '../core/utils.ts';
import { withProps } from '../core/with-props.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, str } from '../core/props.ts';
const PROPS = {
position: { kind: 'str', attr: 'position', default: 'topleft' },
zoomInText: { kind: 'str', attr: 'zoom-in-text', default: '+' },
zoomInTitle: { kind: 'str', attr: 'zoom-in-title', default: 'Zoom in' },
zoomOutText: { kind: 'str', attr: 'zoom-out-text', default: '-' },
zoomOutTitle: { kind: 'str', attr: 'zoom-out-title', default: 'Zoom out' },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
position: str('topleft'),
zoomInText: str('+'),
zoomInTitle: str('Zoom in'),
zoomOutText: str('-'),
zoomOutTitle: str('Zoom out'),
});
export default class LeafletControlZoom extends withProps(HTMLElement, PROPS) {
#obj?: Control.Zoom;

@ -3,23 +3,23 @@ import { buildOptions } from '../core/utils.ts';
import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren, getChildren } from '../core/register.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = {
data: { kind: 'str', attr: 'data', default: '' },
stroke: { kind: 'str', attr: 'stroke', default: '' },
color: { kind: 'str', attr: 'color', default: '#3388ff' },
weight: { kind: 'num', attr: 'weight', default: 3 },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
lineCap: { kind: 'str', attr: 'line-cap', default: 'round' },
lineJoin: { kind: 'str', attr: 'line-join', default: 'round' },
dashArray: { kind: 'str', attr: 'dash-array', default: '' },
dashOffset: { kind: 'str', attr: 'dash-offset', default: '' },
fill: { kind: 'bool-on', attr: 'fill' },
fillColor: { kind: 'str', attr: 'fill-color', default: '#3388ff' },
fillOpacity: { kind: 'num', attr: 'fill-opacity', default: 0.2 },
fillRule: { kind: 'str', attr: 'fill-rule', default: 'evenodd' },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
data: str(),
stroke: str(),
color: str('#3388ff'),
weight: num(3),
opacity: num(1.0),
lineCap: str('round'),
lineJoin: str('round'),
dashArray: str(),
dashOffset: str(),
fill: on(),
fillColor: str('#3388ff'),
fillOpacity: num(0.2),
fillRule: str('evenodd'),
});
const PROP_BY_ATTR = new Map<string, string>(
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]),

@ -4,19 +4,19 @@ import { buildOptions, buildAttrMap, setLayerAttr, parseBoundsAttr } from '../co
import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import type { PropDef } from '../core/props.ts';
const PROPS = {
url: { kind: 'str', attr: 'url', default: '' },
bounds: { kind: 'str', attr: 'bounds', default: '' },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
alt: { kind: 'str', attr: 'alt', default: '' },
interactive: { kind: 'bool-on', attr: 'interactive' },
crossOrigin: { kind: 'str', attr: 'cross-origin', default: '' },
errorOverlayUrl: { kind: 'str', attr: 'error-overlay-url', default: '' },
zIndex: { kind: 'num', attr: 'z-index', default: 0 },
className: { kind: 'str', attr: 'class-name', default: '' },
} satisfies Record<string, PropDef>;
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = defineProps({
url: str(),
bounds: str(),
opacity: num(1.0),
alt: str(),
interactive: on(),
crossOrigin: str(),
errorOverlayUrl: str(),
zIndex: num(),
className: str(),
});
const PROP_BY_ATTR = buildAttrMap(PROPS);

@ -3,17 +3,17 @@ import { buildOptions, numAttr, buildAttrMap, setLayerAttr } from '../core/utils
import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = {
lat: { kind: 'num', attr: 'lat', default: 0 },
lng: { kind: 'num', attr: 'lng', default: 0 },
title: { kind: 'str', attr: 'title', default: '' },
alt: { kind: 'str', attr: 'alt', default: '' },
draggable: { kind: 'bool-on', attr: 'draggable' },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
zIndexOffset: { kind: 'num', attr: 'z-index-offset', default: 0 },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
lat: num(),
lng: num(),
title: str(),
alt: str(),
draggable: on(),
opacity: num(1.0),
zIndexOffset: num(),
});
const PROP_BY_ATTR = buildAttrMap(PROPS);

@ -4,17 +4,17 @@ import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
import type LeafletLine from './leaflet-line.ts';
const PROPS = {
color: { kind: 'str', attr: 'color', default: '#3388ff' },
weight: { kind: 'num', attr: 'weight', default: 3 },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
fill: { kind: 'bool-on', attr: 'fill' },
fillColor: { kind: 'str', attr: 'fill-color', default: '#3388ff' },
fillOpacity: { kind: 'num', attr: 'fill-opacity', default: 0.2 },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
color: str('#3388ff'),
weight: num(3),
opacity: num(1.0),
fill: on(),
fillColor: str('#3388ff'),
fillOpacity: num(0.2),
});
export default class LeafletPolygon extends withProps(HTMLElement, PROPS) {
#obj?: Polygon;

@ -4,17 +4,17 @@ import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
import type LeafletLine from './leaflet-line.ts';
const PROPS = {
color: { kind: 'str', attr: 'color', default: '#3388ff' },
weight: { kind: 'num', attr: 'weight', default: 3 },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
fill: { kind: 'bool-on', attr: 'fill' },
fillColor: { kind: 'str', attr: 'fill-color', default: '#3388ff' },
fillOpacity: { kind: 'num', attr: 'fill-opacity', default: 0.2 },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
color: str('#3388ff'),
weight: num(3),
opacity: num(1.0),
fill: on(),
fillColor: str('#3388ff'),
fillOpacity: num(0.2),
});
export default class LeafletPolyline extends withProps(HTMLElement, PROPS) {
#obj?: Polyline;

@ -2,18 +2,18 @@ import { Popup } from 'leaflet';
import { registerWithParent, buildOptions, numAttr } from '../core/utils.ts';
import { withProps } from '../core/with-props.ts';
import type { PropDef } from '../core/props.ts';
const PROPS = {
lat: { kind: 'num', attr: 'lat', default: 0 },
lng: { kind: 'num', attr: 'lng', default: 0 },
maxWidth: { kind: 'num', attr: 'max-width', default: 300 },
minWidth: { kind: 'num', attr: 'min-width', default: 50 },
maxHeight: { kind: 'num', attr: 'max-height', default: 0 },
autoPan: { kind: 'bool-on', attr: 'auto-pan' },
closeButton: { kind: 'bool-on', attr: 'close-button' },
autoClose: { kind: 'bool-on', attr: 'auto-close' },
} satisfies Record<string, PropDef>;
import { defineProps, num, on } from '../core/props.ts';
const PROPS = defineProps({
lat: num(),
lng: num(),
maxWidth: num(300),
minWidth: num(50),
maxHeight: num(),
autoPan: on(),
closeButton: on(),
autoClose: on(),
});
export default class LeafletPopup extends withProps(HTMLElement, PROPS) {
#obj?: Popup;

@ -4,17 +4,17 @@ import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = {
bounds: { kind: 'str', attr: 'bounds', default: '' },
color: { kind: 'str', attr: 'color', default: '#3388ff' },
weight: { kind: 'num', attr: 'weight', default: 3 },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
fill: { kind: 'bool-on', attr: 'fill' },
fillColor: { kind: 'str', attr: 'fill-color', default: '#3388ff' },
fillOpacity: { kind: 'num', attr: 'fill-opacity', default: 0.2 },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
bounds: str(),
color: str('#3388ff'),
weight: num(3),
opacity: num(1.0),
fill: on(),
fillColor: str('#3388ff'),
fillOpacity: num(0.2),
});
export default class LeafletRectangle extends withProps(HTMLElement, PROPS) {
#obj?: Rectangle;

@ -4,16 +4,16 @@ import { buildOptions, buildAttrMap, setLayerAttr, parseBoundsAttr } from '../co
import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import type { PropDef } from '../core/props.ts';
const PROPS = {
bounds: { kind: 'str', attr: 'bounds', default: '' },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
interactive: { kind: 'bool-on', attr: 'interactive' },
crossOrigin: { kind: 'str', attr: 'cross-origin', default: '' },
zIndex: { kind: 'num', attr: 'z-index', default: 0 },
className: { kind: 'str', attr: 'class-name', default: '' },
} satisfies Record<string, PropDef>;
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = defineProps({
bounds: str(),
opacity: num(1.0),
interactive: on(),
crossOrigin: str(),
zIndex: num(),
className: str(),
});
const PROP_BY_ATTR = buildAttrMap(PROPS);

@ -3,17 +3,17 @@ import { buildOptions, buildAttrMap, setLayerAttr, parseAttributeValue } from '.
import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import type { PropDef } from '../core/props.ts';
const PROPS = {
url: { kind: 'str', attr: 'url', default: '' },
layers: { kind: 'str', attr: 'layers', default: '' },
styles: { kind: 'str', attr: 'styles', default: '' },
format: { kind: 'str', attr: 'format', default: 'image/jpeg' },
transparent: { kind: 'bool-on', attr: 'transparent' },
version: { kind: 'str', attr: 'version', default: '1.1.1' },
uppercase: { kind: 'bool-on', attr: 'uppercase' },
} satisfies Record<string, PropDef>;
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = defineProps({
url: str(),
layers: str(),
styles: str(),
format: str('image/jpeg'),
transparent: on(),
version: str('1.1.1'),
uppercase: on(),
});
const PROP_BY_ATTR = buildAttrMap(PROPS);

@ -3,16 +3,16 @@ import { buildOptions, buildAttrMap, setLayerAttr } from '../core/utils.ts';
import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import type { PropDef } from '../core/props.ts';
const PROPS = {
url: { kind: 'str', attr: 'url', default: '' },
attribution: { kind: 'str', attr: 'attribution', default: '' },
minZoom: { kind: 'num', attr: 'min-zoom', default: 0 },
maxZoom: { kind: 'num', attr: 'max-zoom', default: 18 },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
zIndex: { kind: 'num', attr: 'z-index', default: 0 },
} satisfies Record<string, PropDef>;
import { defineProps, num, str } from '../core/props.ts';
const PROPS = defineProps({
url: str(),
attribution: str(),
minZoom: num(),
maxZoom: num(18),
opacity: num(1.0),
zIndex: num(),
});
const PROP_BY_ATTR = buildAttrMap(PROPS);

@ -3,18 +3,18 @@ import type { TooltipOptions } from 'leaflet';
import { registerWithParent, buildOptions, numAttr } from '../core/utils.ts';
import { withProps } from '../core/with-props.ts';
import type { PropDef } from '../core/props.ts';
const PROPS = {
lat: { kind: 'num', attr: 'lat', default: 0 },
lng: { kind: 'num', attr: 'lng', default: 0 },
pane: { kind: 'str', attr: 'pane', default: '' },
offset: { kind: 'str', attr: 'offset', default: '' },
direction: { kind: 'str', attr: 'direction', default: 'auto' },
permanent: { kind: 'bool-on', attr: 'permanent' },
sticky: { kind: 'bool-on', attr: 'sticky' },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
} satisfies Record<string, PropDef>;
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = defineProps({
lat: num(),
lng: num(),
pane: str(),
offset: str(),
direction: str('auto'),
permanent: on(),
sticky: on(),
opacity: num(1.0),
});
export default class LeafletTooltip extends withProps(HTMLElement, PROPS) {
#obj?: Tooltip;

@ -4,20 +4,20 @@ import { buildOptions, buildAttrMap, setLayerAttr, parseBoundsAttr } from '../co
import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts';
import type { PropDef } from '../core/props.ts';
import { defineProps, num, str, on } from '../core/props.ts';
const PROPS = {
url: { kind: 'str', attr: 'url', default: '' },
bounds: { kind: 'str', attr: 'bounds', default: '' },
opacity: { kind: 'num', attr: 'opacity', default: 1.0 },
alt: { kind: 'str', attr: 'alt', default: '' },
interactive: { kind: 'bool-on', attr: 'interactive' },
crossOrigin: { kind: 'str', attr: 'cross-origin', default: '' },
loop: { kind: 'bool-on', attr: 'loop' },
autoplay: { kind: 'bool-on', attr: 'autoplay' },
muted: { kind: 'bool-on', attr: 'muted' },
playsInline: { kind: 'bool-on', attr: 'playsinline' },
} satisfies Record<string, PropDef>;
const PROPS = defineProps({
url: str(),
bounds: str(),
opacity: num(1.0),
alt: str(),
interactive: on(),
crossOrigin: str(),
loop: on(),
autoplay: on(),
muted: on(),
playsInline: on('playsinline'),
});
const PROP_BY_ATTR = buildAttrMap(PROPS);

@ -31,3 +31,46 @@ export type PropTypeOf<T extends PropDef> = T extends NumProp
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 = OptionalAttr<NumProp>;
type StrPropInput = OptionalAttr<StrProp>;
type BoolOnPropInput = OptionalAttr<BoolOnProp>;
type PropDefInput = NumPropInput | StrPropInput | BoolOnPropInput;
type PropDefFromInput<T> =
T extends { kind: 'num' } ? NumProp
: T extends { kind: 'str' } ? StrProp
: BoolOnProp;
function camelToKebab(s: string): string {
return s.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
}
export function num(def: number = 0, attr?: string): NumPropInput {
return { kind: 'num', default: def, ...(attr ? { attr } : {}) };
}
export function str(def: string = '', attr?: string): StrPropInput {
return { kind: 'str', default: def, ...(attr ? { attr } : {}) };
}
export function on(attr?: string): BoolOnPropInput {
return { kind: 'bool-on', ...(attr ? { attr } : {}) };
}
export function defineProps<T extends Record<string, PropDefInput>>(
input: T,
): { [K in keyof T]: PropDefFromInput<T[K]> } {
const output = {} as Record<string, PropDef>;
for (const key of Object.keys(input)) {
const val = input[key] as PropDefInput;
const { attr: override, ...rest } = val as any;
output[key] = { ...rest, attr: override ?? camelToKebab(key) } as PropDef;
}
return output as any;
}

Loading…
Cancel
Save