@vielzeug/ore 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/props.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_dev.cjs"),t=require("./errors.cjs"),n=require("./utils/dom.cjs"),r=require("./runtime.cjs");let i=require("@vielzeug/ripple");function a(e){return{default:e,parse:()=>e,reflect:!1}}var o={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return a(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number(t){let n=t===void 0?void 0:t;return{default:n,parse:t=>{if(t==null)return n;let r=Number(t);return Number.isNaN(r)?(e.warn(`prop.number(): attribute value "${t}" is not a valid number, using default (${String(n)})`),n):r},reflect:!0}},oneOf(e,t){return{default:t,parse:n=>n!=null&&e.includes(n)?n:t,reflect:!0}},string(e){let t=e===void 0?void 0:e;return{default:t,parse:e=>e??t,reflect:!0}}},s=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function c(e,r){if(!s(e))throw new t.OreApiError(`Prop "${r}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof e}`);let i=e;if(!i.parse)throw new t.OreApiError(`Prop "${r}" must have a parse function. Use prop.* helpers.`);let a=i.reflect??!1;if(a&&n.isStructuredValue(i.default))throw new t.OreApiError(`Prop "${r}": ${t.ORE_ERRORS.propInvalidReflect}`);return{...i,reflect:a}}function l(e){let t=[];for(let[n,r]of Object.entries(e))try{c(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var u=new WeakMap,d=(e,t)=>u.get(e)?.get(t),f=(e,t,a,o)=>{u.has(e)||u.set(e,new Map);let{default:s,parse:c,reflect:l=!1}=o,d=(0,i.signal)(s),f=Object.hasOwn(e,t),p=f?e[t]:void 0,m={parse:c,reflect:l,signal:d};return f?(delete e[t],d.value=p):e.hasAttribute(a)&&(d.value=c(e.getAttribute(a))),u.get(e).set(a,m),Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:()=>d.value,set:e=>{d.value=e}}),l&&r.effect(()=>{let t=d.value;t==null?e.removeAttribute(a):typeof t==`boolean`?e.toggleAttribute(a,t):n.setAttr(e,a,t)}),d};function p(e,t){let r={};for(let[i,a]of Object.entries(t))r[i]=f(e,i,n.toKebab(i),a);return r}exports.createProps=p,exports.getPropMeta=d,exports.normalizePropDefinition=c,exports.prop=o,exports.validatePropDefs=l;
1
+ const e=require("./_dev.cjs"),t=require("./errors.cjs"),n=require("./utils/dom.cjs"),r=require("./runtime.cjs");let i=require("@vielzeug/ripple");function a(e){return{default:e,parse:()=>e,reflect:!1}}var o={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return a(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number(t){let n=t===void 0?void 0:t;return{default:n,parse:t=>{if(t==null)return n;let r=Number(t);return Number.isNaN(r)?(e.warn(`prop.number(): attribute value "${t}" is not a valid number, using default (${String(n)})`),n):r},reflect:!0}},oneOf(e,t){return{default:t,parse:n=>n!=null&&e.includes(n)?n:t,reflect:!0}},string(e){let t=e===void 0?void 0:e;return{default:t,parse:e=>e??t,reflect:!0}}},s=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function c(e,r){if(!s(e))throw new t.OreApiError(`Prop "${r}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof e}`);let i=e;if(!i.parse)throw new t.OreApiError(`Prop "${r}" must have a parse function. Use prop.* helpers.`);let a=i.reflect??!1;if(a&&n.isStructuredValue(i.default))throw new t.OreApiError(`Prop "${r}": ${t.ORE_ERRORS.propInvalidReflect}`);return{...i,reflect:a}}function l(e){let t=[];for(let[n,r]of Object.entries(e))try{c(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var u=new WeakMap,d=(e,t)=>u.get(e)?.get(t),f=(e,t,a,o)=>{u.has(e)||u.set(e,new Map);let{default:s,parse:c,reflect:l=!1}=o,d=(0,i.signal)(s),f=Object.hasOwn(e,t),p=f?e[t]:void 0,m={parse:c,reflect:l,signal:d};return f?(delete e[t],d.value=typeof p==`string`?c(p):p):e.hasAttribute(a)&&(d.value=c(e.getAttribute(a))),u.get(e).set(a,m),Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:()=>d.value,set:e=>{d.value=typeof e==`string`?c(e):e}}),l&&r.effect(()=>{let t=d.value;t==null?e.removeAttribute(a):typeof t==`boolean`?e.toggleAttribute(a,t):n.setAttr(e,a,t)}),d};function p(e,t){let r={};for(let[i,a]of Object.entries(t))r[i]=f(e,i,n.toKebab(i),a);return r}exports.createProps=p,exports.getPropMeta=d,exports.normalizePropDefinition=c,exports.prop=o,exports.validatePropDefs=l;
2
2
  //# sourceMappingURL=props.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"props.cjs","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { OreApiError, ORE_ERRORS } from './errors';\nimport { effect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\n/**\n * Define a custom prop with explicit parser and reflection behavior.\n * Use when prop.* helpers don't cover your type.\n *\n * @example\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\ntype PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n if (!propRegistry.has(el)) propRegistry.set(el, new Map());\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = preUpgradeValue as T;\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n propRegistry.get(el)!.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = value;\n },\n });\n\n if (reflect) {\n effect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"props.cjs","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { OreApiError, ORE_ERRORS } from './errors';\nimport { effect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\n/**\n * Define a custom prop with explicit parser and reflection behavior.\n * Use when prop.* helpers don't cover your type.\n *\n * @example\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\ntype PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n if (!propRegistry.has(el)) propRegistry.set(el, new Map());\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n // Frameworks (e.g. Vue, when hydrating or patching an already-upgraded custom\n // element) may assign the pre-upgrade value as a plain string rather than calling\n // setAttribute — the same way it would for a real HTML attribute. Route strings\n // through the same parser an attribute value would use so e.g. size=\"16\" doesn't\n // end up stored as the string \"16\" instead of the number 16. Already-typed values\n // (objects, functions, booleans, numbers set deliberately as JS properties) pass\n // through untouched.\n s.value = (typeof preUpgradeValue === 'string' ? parse(preUpgradeValue) : preUpgradeValue) as T;\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n propRegistry.get(el)!.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n // A framework can still reach this setter with a raw attribute-style string after\n // the element has already upgraded — e.g. Vue reconciling server-rendered markup\n // against a custom element that auto-upgraded (from its SSR attribute) before\n // hydration ran: it finds the property already defined and assigns the vnode's\n // plain string prop value directly instead of calling setAttribute. Route strings\n // through the same parser an attribute value would use so it isn't stored raw.\n s.value = (typeof value === 'string' ? parse(value) : value) as T;\n },\n });\n\n if (reflect) {\n effect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"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"}
package/dist/props.js CHANGED
@@ -1,2 +1,2 @@
1
- import{warn as e}from"./_dev.js";import{ORE_ERRORS as t,OreApiError as n}from"./errors.js";import{isStructuredValue as r,setAttr as i,toKebab as a}from"./utils/dom.js";import{effect as o}from"./runtime.js";import{signal as s}from"@vielzeug/ripple";function c(e){return{default:e,parse:()=>e,reflect:!1}}var l={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return c(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number(t){let n=t===void 0?void 0:t;return{default:n,parse:t=>{if(t==null)return n;let r=Number(t);return Number.isNaN(r)?(e(`prop.number(): attribute value "${t}" is not a valid number, using default (${String(n)})`),n):r},reflect:!0}},oneOf(e,t){return{default:t,parse:n=>n!=null&&e.includes(n)?n:t,reflect:!0}},string(e){let t=e===void 0?void 0:e;return{default:t,parse:e=>e??t,reflect:!0}}},u=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function d(e,i){if(!u(e))throw new n(`Prop "${i}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof e}`);let a=e;if(!a.parse)throw new n(`Prop "${i}" must have a parse function. Use prop.* helpers.`);let o=a.reflect??!1;if(o&&r(a.default))throw new n(`Prop "${i}": ${t.propInvalidReflect}`);return{...a,reflect:o}}function f(e){let t=[];for(let[n,r]of Object.entries(e))try{d(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var p=new WeakMap,m=(e,t)=>p.get(e)?.get(t),h=(e,t,n,r)=>{p.has(e)||p.set(e,new Map);let{default:a,parse:c,reflect:l=!1}=r,u=s(a),d=Object.hasOwn(e,t),f=d?e[t]:void 0,m={parse:c,reflect:l,signal:u};return d?(delete e[t],u.value=f):e.hasAttribute(n)&&(u.value=c(e.getAttribute(n))),p.get(e).set(n,m),Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:()=>u.value,set:e=>{u.value=e}}),l&&o(()=>{let t=u.value;t==null?e.removeAttribute(n):typeof t==`boolean`?e.toggleAttribute(n,t):i(e,n,t)}),u};function g(e,t){let n={};for(let[r,i]of Object.entries(t))n[r]=h(e,r,a(r),i);return n}export{g as createProps,m as getPropMeta,d as normalizePropDefinition,l as prop,f as validatePropDefs};
1
+ import{warn as e}from"./_dev.js";import{ORE_ERRORS as t,OreApiError as n}from"./errors.js";import{isStructuredValue as r,setAttr as i,toKebab as a}from"./utils/dom.js";import{effect as o}from"./runtime.js";import{signal as s}from"@vielzeug/ripple";function c(e){return{default:e,parse:()=>e,reflect:!1}}var l={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return c(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number(t){let n=t===void 0?void 0:t;return{default:n,parse:t=>{if(t==null)return n;let r=Number(t);return Number.isNaN(r)?(e(`prop.number(): attribute value "${t}" is not a valid number, using default (${String(n)})`),n):r},reflect:!0}},oneOf(e,t){return{default:t,parse:n=>n!=null&&e.includes(n)?n:t,reflect:!0}},string(e){let t=e===void 0?void 0:e;return{default:t,parse:e=>e??t,reflect:!0}}},u=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function d(e,i){if(!u(e))throw new n(`Prop "${i}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof e}`);let a=e;if(!a.parse)throw new n(`Prop "${i}" must have a parse function. Use prop.* helpers.`);let o=a.reflect??!1;if(o&&r(a.default))throw new n(`Prop "${i}": ${t.propInvalidReflect}`);return{...a,reflect:o}}function f(e){let t=[];for(let[n,r]of Object.entries(e))try{d(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var p=new WeakMap,m=(e,t)=>p.get(e)?.get(t),h=(e,t,n,r)=>{p.has(e)||p.set(e,new Map);let{default:a,parse:c,reflect:l=!1}=r,u=s(a),d=Object.hasOwn(e,t),f=d?e[t]:void 0,m={parse:c,reflect:l,signal:u};return d?(delete e[t],u.value=typeof f==`string`?c(f):f):e.hasAttribute(n)&&(u.value=c(e.getAttribute(n))),p.get(e).set(n,m),Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:()=>u.value,set:e=>{u.value=typeof e==`string`?c(e):e}}),l&&o(()=>{let t=u.value;t==null?e.removeAttribute(n):typeof t==`boolean`?e.toggleAttribute(n,t):i(e,n,t)}),u};function g(e,t){let n={};for(let[r,i]of Object.entries(t))n[r]=h(e,r,a(r),i);return n}export{g as createProps,m as getPropMeta,d as normalizePropDefinition,l as prop,f as validatePropDefs};
2
2
  //# sourceMappingURL=props.js.map
package/dist/props.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"props.js","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { OreApiError, ORE_ERRORS } from './errors';\nimport { effect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\n/**\n * Define a custom prop with explicit parser and reflection behavior.\n * Use when prop.* helpers don't cover your type.\n *\n * @example\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\ntype PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n if (!propRegistry.has(el)) propRegistry.set(el, new Map());\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = preUpgradeValue as T;\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n propRegistry.get(el)!.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = value;\n },\n });\n\n if (reflect) {\n effect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"props.js","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { OreApiError, ORE_ERRORS } from './errors';\nimport { effect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\n/**\n * Define a custom prop with explicit parser and reflection behavior.\n * Use when prop.* helpers don't cover your type.\n *\n * @example\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\ntype PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n if (!propRegistry.has(el)) propRegistry.set(el, new Map());\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n // Frameworks (e.g. Vue, when hydrating or patching an already-upgraded custom\n // element) may assign the pre-upgrade value as a plain string rather than calling\n // setAttribute — the same way it would for a real HTML attribute. Route strings\n // through the same parser an attribute value would use so e.g. size=\"16\" doesn't\n // end up stored as the string \"16\" instead of the number 16. Already-typed values\n // (objects, functions, booleans, numbers set deliberately as JS properties) pass\n // through untouched.\n s.value = (typeof preUpgradeValue === 'string' ? parse(preUpgradeValue) : preUpgradeValue) as T;\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n propRegistry.get(el)!.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n // A framework can still reach this setter with a raw attribute-style string after\n // the element has already upgraded — e.g. Vue reconciling server-rendered markup\n // against a custom element that auto-upgraded (from its SSR attribute) before\n // hydration ran: it finds the property already defined and assigns the vnode's\n // plain string prop value directly instead of calling setAttribute. Route strings\n // through the same parser an attribute value would use so it isn't stored raw.\n s.value = (typeof value === 'string' ? parse(value) : value) as T;\n },\n });\n\n if (reflect) {\n effect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/ore",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Custom element authoring primitives — reactive templates, signals, slots, and automatic lifecycle management",
5
5
  "type": "module",
6
6
  "files": [