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

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

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

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

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

@ -3,23 +3,23 @@ import { buildOptions } from '../core/utils.ts';
import { withProps } from '../core/with-props.ts'; import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren, getChildren } from '../core/register.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 = { const PROPS = defineProps({
data: { kind: 'str', attr: 'data', default: '' }, data: str(),
stroke: { kind: 'str', attr: 'stroke', default: '' }, stroke: str(),
color: { kind: 'str', attr: 'color', default: '#3388ff' }, color: str('#3388ff'),
weight: { kind: 'num', attr: 'weight', default: 3 }, weight: num(3),
opacity: { kind: 'num', attr: 'opacity', default: 1.0 }, opacity: num(1.0),
lineCap: { kind: 'str', attr: 'line-cap', default: 'round' }, lineCap: str('round'),
lineJoin: { kind: 'str', attr: 'line-join', default: 'round' }, lineJoin: str('round'),
dashArray: { kind: 'str', attr: 'dash-array', default: '' }, dashArray: str(),
dashOffset: { kind: 'str', attr: 'dash-offset', default: '' }, dashOffset: str(),
fill: { kind: 'bool-on', attr: 'fill' }, fill: on(),
fillColor: { kind: 'str', attr: 'fill-color', default: '#3388ff' }, fillColor: str('#3388ff'),
fillOpacity: { kind: 'num', attr: 'fill-opacity', default: 0.2 }, fillOpacity: num(0.2),
fillRule: { kind: 'str', attr: 'fill-rule', default: 'evenodd' }, fillRule: str('evenodd'),
} satisfies Record<string, PropDef>; });
const PROP_BY_ATTR = new Map<string, string>( const PROP_BY_ATTR = new Map<string, string>(
Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]), 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 { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.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 = { const PROPS = defineProps({
url: { kind: 'str', attr: 'url', default: '' }, url: str(),
bounds: { kind: 'str', attr: 'bounds', default: '' }, bounds: str(),
opacity: { kind: 'num', attr: 'opacity', default: 1.0 }, opacity: num(1.0),
alt: { kind: 'str', attr: 'alt', default: '' }, alt: str(),
interactive: { kind: 'bool-on', attr: 'interactive' }, interactive: on(),
crossOrigin: { kind: 'str', attr: 'cross-origin', default: '' }, crossOrigin: str(),
errorOverlayUrl: { kind: 'str', attr: 'error-overlay-url', default: '' }, errorOverlayUrl: str(),
zIndex: { kind: 'num', attr: 'z-index', default: 0 }, zIndex: num(),
className: { kind: 'str', attr: 'class-name', default: '' }, className: str(),
} satisfies Record<string, PropDef>; });
const PROP_BY_ATTR = buildAttrMap(PROPS); 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 { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.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 = { const PROPS = defineProps({
lat: { kind: 'num', attr: 'lat', default: 0 }, lat: num(),
lng: { kind: 'num', attr: 'lng', default: 0 }, lng: num(),
title: { kind: 'str', attr: 'title', default: '' }, title: str(),
alt: { kind: 'str', attr: 'alt', default: '' }, alt: str(),
draggable: { kind: 'bool-on', attr: 'draggable' }, draggable: on(),
opacity: { kind: 'num', attr: 'opacity', default: 1.0 }, opacity: num(1.0),
zIndexOffset: { kind: 'num', attr: 'z-index-offset', default: 0 }, zIndexOffset: num(),
} satisfies Record<string, PropDef>; });
const PROP_BY_ATTR = buildAttrMap(PROPS); 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 { registerChildren, unregisterChildren } from '../core/register.ts';
import { isPathStyleAttr, updatePathStyle } from '../core/path-style.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'; import type LeafletLine from './leaflet-line.ts';
const PROPS = { const PROPS = defineProps({
color: { kind: 'str', attr: 'color', default: '#3388ff' }, color: str('#3388ff'),
weight: { kind: 'num', attr: 'weight', default: 3 }, weight: num(3),
opacity: { kind: 'num', attr: 'opacity', default: 1.0 }, opacity: num(1.0),
fill: { kind: 'bool-on', attr: 'fill' }, fill: on(),
fillColor: { kind: 'str', attr: 'fill-color', default: '#3388ff' }, fillColor: str('#3388ff'),
fillOpacity: { kind: 'num', attr: 'fill-opacity', default: 0.2 }, fillOpacity: num(0.2),
} satisfies Record<string, PropDef>; });
export default class LeafletPolygon extends withProps(HTMLElement, PROPS) { export default class LeafletPolygon extends withProps(HTMLElement, PROPS) {
#obj?: Polygon; #obj?: Polygon;

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

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

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

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

@ -3,17 +3,17 @@ import { buildOptions, buildAttrMap, setLayerAttr, parseAttributeValue } from '.
import { withProps } from '../core/with-props.ts'; import { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.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 = { const PROPS = defineProps({
url: { kind: 'str', attr: 'url', default: '' }, url: str(),
layers: { kind: 'str', attr: 'layers', default: '' }, layers: str(),
styles: { kind: 'str', attr: 'styles', default: '' }, styles: str(),
format: { kind: 'str', attr: 'format', default: 'image/jpeg' }, format: str('image/jpeg'),
transparent: { kind: 'bool-on', attr: 'transparent' }, transparent: on(),
version: { kind: 'str', attr: 'version', default: '1.1.1' }, version: str('1.1.1'),
uppercase: { kind: 'bool-on', attr: 'uppercase' }, uppercase: on(),
} satisfies Record<string, PropDef>; });
const PROP_BY_ATTR = buildAttrMap(PROPS); 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 { withProps } from '../core/with-props.ts';
import { registerChildren, unregisterChildren } from '../core/register.ts'; import { registerChildren, unregisterChildren } from '../core/register.ts';
import type { PropDef } from '../core/props.ts'; import { defineProps, num, str } from '../core/props.ts';
const PROPS = { const PROPS = defineProps({
url: { kind: 'str', attr: 'url', default: '' }, url: str(),
attribution: { kind: 'str', attr: 'attribution', default: '' }, attribution: str(),
minZoom: { kind: 'num', attr: 'min-zoom', default: 0 }, minZoom: num(),
maxZoom: { kind: 'num', attr: 'max-zoom', default: 18 }, maxZoom: num(18),
opacity: { kind: 'num', attr: 'opacity', default: 1.0 }, opacity: num(1.0),
zIndex: { kind: 'num', attr: 'z-index', default: 0 }, zIndex: num(),
} satisfies Record<string, PropDef>; });
const PROP_BY_ATTR = buildAttrMap(PROPS); const PROP_BY_ATTR = buildAttrMap(PROPS);

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

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