@vielzeug/ore 26.10.7 → 26.10.9
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/README.md +7 -0
- package/dist/define.cjs +1 -1
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +1 -1
- package/dist/define.js.map +1 -1
- package/dist/directives/classMap.cjs +1 -1
- package/dist/directives/classMap.cjs.map +1 -1
- package/dist/directives/classMap.d.ts.map +1 -1
- package/dist/directives/classMap.js +1 -1
- package/dist/directives/classMap.js.map +1 -1
- package/dist/directives/each.cjs +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.d.ts.map +1 -1
- package/dist/directives/each.js +1 -1
- package/dist/directives/each.js.map +1 -1
- package/dist/directives/model.cjs +2 -0
- package/dist/directives/model.cjs.map +1 -0
- package/dist/directives/model.d.ts +47 -0
- package/dist/directives/model.d.ts.map +1 -0
- package/dist/directives/model.js +2 -0
- package/dist/directives/model.js.map +1 -0
- package/dist/directives/styleMap.cjs +1 -1
- package/dist/directives/styleMap.cjs.map +1 -1
- package/dist/directives/styleMap.d.ts.map +1 -1
- package/dist/directives/styleMap.js +1 -1
- package/dist/directives/styleMap.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/forms/field.cjs +1 -1
- package/dist/forms/field.cjs.map +1 -1
- package/dist/forms/field.d.ts +18 -5
- package/dist/forms/field.d.ts.map +1 -1
- package/dist/forms/field.js +1 -1
- package/dist/forms/field.js.map +1 -1
- package/dist/host-bind.cjs +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.d.ts.map +1 -1
- package/dist/host-bind.js +1 -1
- package/dist/host-bind.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/ore.cjs +2 -2
- package/dist/ore.cjs.map +1 -1
- package/dist/ore.iife.js +2 -2
- package/dist/ore.iife.js.map +1 -1
- package/dist/ore.js +2 -2
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.d.ts +16 -5
- package/dist/props.d.ts.map +1 -1
- package/dist/props.js +1 -1
- package/dist/props.js.map +1 -1
- package/dist/template/binding-types.d.ts +26 -5
- package/dist/template/binding-types.d.ts.map +1 -1
- package/dist/template/bindings.cjs +1 -1
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.d.ts +3 -3
- package/dist/template/bindings.d.ts.map +1 -1
- package/dist/template/bindings.js +1 -1
- package/dist/template/bindings.js.map +1 -1
- package/dist/template/compiler.cjs +1 -1
- package/dist/template/compiler.cjs.map +1 -1
- package/dist/template/compiler.d.ts +13 -0
- package/dist/template/compiler.d.ts.map +1 -1
- package/dist/template/compiler.js +1 -1
- package/dist/template/compiler.js.map +1 -1
- package/dist/template/instantiator.cjs +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.d.ts.map +1 -1
- package/dist/template/instantiator.js +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/testing/index.d.ts +2 -13
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/utils/brand.cjs.map +1 -1
- package/dist/utils/brand.d.ts +3 -2
- package/dist/utils/brand.d.ts.map +1 -1
- package/dist/utils/brand.js.map +1 -1
- package/dist/utils/dom.cjs +1 -1
- package/dist/utils/dom.cjs.map +1 -1
- package/dist/utils/dom.d.ts +19 -0
- package/dist/utils/dom.d.ts.map +1 -1
- package/dist/utils/dom.js +1 -1
- package/dist/utils/dom.js.map +1 -1
- package/package.json +5 -5
- package/dist/_component-class.cjs +0 -2
- package/dist/_component-class.cjs.map +0 -1
- package/dist/_component-class.d.ts +0 -2
- package/dist/_component-class.d.ts.map +0 -1
- package/dist/_component-class.js +0 -2
- package/dist/_component-class.js.map +0 -1
- package/dist/testing/dom.cjs +0 -2
- package/dist/testing/dom.cjs.map +0 -1
- package/dist/testing/dom.d.ts +0 -22
- package/dist/testing/dom.d.ts.map +0 -1
- package/dist/testing/dom.js +0 -2
- package/dist/testing/dom.js.map +0 -1
- package/dist/testing/form-internals-polyfill.cjs +0 -2
- package/dist/testing/form-internals-polyfill.cjs.map +0 -1
- package/dist/testing/form-internals-polyfill.d.ts +0 -19
- package/dist/testing/form-internals-polyfill.d.ts.map +0 -1
- package/dist/testing/form-internals-polyfill.js +0 -2
- package/dist/testing/form-internals-polyfill.js.map +0 -1
package/dist/props.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("./_dev.cjs");const e=require("./errors.cjs"),t=require("./utils/dom.cjs"),n=require("./runtime.cjs");let r=require("@vielzeug/ripple");function i(e){return{default:e,parse:()=>e,reflect:!1}}
|
|
1
|
+
require("./_dev.cjs");const e=require("./errors.cjs"),t=require("./utils/dom.cjs"),n=require("./runtime.cjs");let r=require("@vielzeug/ripple");function i(e){return{default:e,parse:()=>e,reflect:!1}}function a(e){let t=e;return{default:t,parse:e=>{if(e==null)return t;let n=Number(e);return Number.isNaN(n)?(`${e}${String(t)}`,t):n},reflect:!0}}function o(e){return{default:e,parse:t=>t??e,reflect:!0}}var s={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return i(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:a,oneOf(e,t){return{default:t,parse:n=>{if(n==null)return t;let r=e,i=r.indexOf(n);if(i!==-1)return e[i];let a=Number(n);if(!Number.isNaN(a)){let t=r.indexOf(a);if(t!==-1)return e[t]}return t},reflect:!0}},string:o},c=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function l(n,r){if(!c(n))throw new e.OreApiError(`Prop "${r}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof n}`);let i=n;if(!i.parse)throw new e.OreApiError(`Prop "${r}" must have a parse function. Use prop.* helpers.`);let a=i.reflect??!1;if(a&&t.isStructuredValue(i.default))throw new e.OreApiError(`Prop "${r}": ${e.ORE_ERRORS.propInvalidReflect}`);return{...i,reflect:a}}function u(e){let t=[];for(let[n,r]of Object.entries(e))try{l(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var d=new WeakMap,f=(e,t)=>d.get(e)?.get(t),p=(e,t)=>typeof e==`string`?t(e):e,m=(e,i,a,o)=>{let s=d.get(e);s||(s=new Map,d.set(e,s));let{default:c,parse:l,reflect:u=!1}=o,f=(0,r.signal)(c),m=s.get(a),h=Object.hasOwn(e,i),g=h?e[i]:void 0,_={parse:l,reflect:u,signal:f};return m?f.value=m.signal.peek():h?(delete e[i],f.value=p(g,l)):e.hasAttribute(a)&&(f.value=l(e.getAttribute(a))),s.set(a,_),Object.defineProperty(e,i,{configurable:!0,enumerable:!0,get:()=>f.value,set:e=>{f.value=p(e,l)}}),u&&n.watchEffect(()=>{let n=f.value;n==null?e.removeAttribute(a):typeof n==`boolean`?e.toggleAttribute(a,n):t.setAttr(e,a,n)}),f};function h(e,n){let r={};for(let[i,a]of Object.entries(n))r[i]=m(e,i,t.toKebab(i),a);return r}exports.createProps=h,exports.getPropMeta=f,exports.normalizePropDefinition=l,exports.prop=s,exports.validatePropDefs=u;
|
|
2
2
|
//# sourceMappingURL=props.cjs.map
|
package/dist/props.cjs.map
CHANGED
|
@@ -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 { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } 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\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 *\n * Accepts any object shaped like `PropDef`: not just `prop.*` helper output, so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\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 */\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\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type 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/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded: e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n\n // On reconnection, the prop registry still holds the previous connection's\n // PropMeta. Its signal carries the correct prop value (updated by\n // attributeChangedCallback between connections). We must read from it rather\n // than from `el[propName]`, because setup() may have overwritten the host\n // property with a different getter/setter (e.g. `defineFieldValue()` in\n // refine's form-field components redefines `el.value` to expose the internal\n // field string). Reading that getter would yield the wrong value and lose\n // the prop's actual state across reconnections.\n const existingMeta = registry.get(attrName);\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 (existingMeta) {\n s.value = existingMeta.signal.peek() as T;\n } else if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n registry.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 = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\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 { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } 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(): PropDef<number | undefined>;\n number(defaultValue: number): PropDef<number>;\n /**\n * Prop restricted to a fixed set of allowed values: the only honest way to\n * type a union-valued prop, because the restriction is enforced at runtime\n * (`parse` falls back to the default for anything outside `allowed`), not just\n * in a type parameter a raw attribute value could violate. Numeric members are\n * matched against the attribute value parsed as a number. Omit `defaultValue`\n * for an optional prop (`undefined` when the attribute is absent).\n */\n oneOf<T extends number>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends number>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n oneOf<T extends string>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends string>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n string(): PropDef<string | undefined>;\n string(defaultValue: string): PropDef<string>;\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\n/** @internal Implementation behind the overloaded `prop.number` signature. */\nfunction _numberProp(defaultValue?: number): PropDef<number | undefined> {\n const def = defaultValue;\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;\n },\n reflect: true,\n };\n}\n\n/** @internal Implementation behind the overloaded `prop.string` signature. */\nfunction _stringProp(defaultValue?: string): PropDef<string | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n return {\n default: defaultValue,\n parse: (value: string | null) => (value == null ? defaultValue : value),\n reflect: true,\n };\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: _numberProp as PropFactory['number'],\n oneOf<T extends string | number>(allowed: readonly T[], defaultValue?: T): PropDef<T | undefined> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null) return defaultValue;\n\n // Return the allowed member itself (correct literal type). Numeric unions\n // arrive as attribute strings, so also try the value parsed as a number.\n const members = allowed as readonly unknown[];\n const direct = members.indexOf(value);\n\n if (direct !== -1) return (allowed as readonly T[])[direct];\n\n const asNumber = Number(value);\n\n if (!Number.isNaN(asNumber)) {\n const byNumber = members.indexOf(asNumber);\n\n if (byNumber !== -1) return (allowed as readonly T[])[byNumber];\n }\n\n return defaultValue;\n },\n reflect: true,\n };\n },\n string: _stringProp as PropFactory['string'],\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 *\n * Accepts any object shaped like `PropDef`: not just `prop.*` helper output, so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\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 */\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\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type 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/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded: e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n\n // On reconnection, the prop registry still holds the previous connection's\n // PropMeta. Its signal carries the correct prop value (updated by\n // attributeChangedCallback between connections). We must read from it rather\n // than from `el[propName]`, because setup() may have overwritten the host\n // property with a different getter/setter (e.g. `defineFieldValue()` in\n // refine's form-field components redefines `el.value` to expose the internal\n // field string). Reading that getter would yield the wrong value and lose\n // the prop's actual state across reconnections.\n const existingMeta = registry.get(attrName);\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 (existingMeta) {\n s.value = existingMeta.signal.peek() as T;\n } else if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n registry.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 = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\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/dist/props.d.ts
CHANGED
|
@@ -29,11 +29,22 @@ type PropFactory = {
|
|
|
29
29
|
data<T>(): PropDef<T | undefined>;
|
|
30
30
|
data<T>(defaultValue: T): PropDef<T>;
|
|
31
31
|
json<T>(defaultValue: T): PropDef<T>;
|
|
32
|
-
number
|
|
33
|
-
number
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
number(): PropDef<number | undefined>;
|
|
33
|
+
number(defaultValue: number): PropDef<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Prop restricted to a fixed set of allowed values: the only honest way to
|
|
36
|
+
* type a union-valued prop, because the restriction is enforced at runtime
|
|
37
|
+
* (`parse` falls back to the default for anything outside `allowed`), not just
|
|
38
|
+
* in a type parameter a raw attribute value could violate. Numeric members are
|
|
39
|
+
* matched against the attribute value parsed as a number. Omit `defaultValue`
|
|
40
|
+
* for an optional prop (`undefined` when the attribute is absent).
|
|
41
|
+
*/
|
|
42
|
+
oneOf<T extends number>(allowed: readonly T[]): PropDef<T | undefined>;
|
|
43
|
+
oneOf<T extends number>(allowed: readonly T[], defaultValue: T): PropDef<T>;
|
|
44
|
+
oneOf<T extends string>(allowed: readonly T[]): PropDef<T | undefined>;
|
|
45
|
+
oneOf<T extends string>(allowed: readonly T[], defaultValue: T): PropDef<T>;
|
|
46
|
+
string(): PropDef<string | undefined>;
|
|
47
|
+
string(defaultValue: string): PropDef<string>;
|
|
37
48
|
};
|
|
38
49
|
export declare const prop: PropFactory;
|
|
39
50
|
/**
|
package/dist/props.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,kBAAkB,CAAC;AAOtE,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;KAEvD,CAAC,IAAI,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AACjH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;AAE7D;;;GAGG;AACH,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,
|
|
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"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{isStructuredValue as n,setAttr as r,toKebab as i}from"./utils/dom.js";import{watchEffect as a}from"./runtime.js";import{signal as o}from"@vielzeug/ripple";function s(e){return{default:e,parse:()=>e,reflect:!1}}
|
|
1
|
+
import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{isStructuredValue as n,setAttr as r,toKebab as i}from"./utils/dom.js";import{watchEffect as a}from"./runtime.js";import{signal as o}from"@vielzeug/ripple";function s(e){return{default:e,parse:()=>e,reflect:!1}}function c(e){let t=e;return{default:t,parse:e=>{if(e==null)return t;let n=Number(e);return Number.isNaN(n)?(`${e}${String(t)}`,t):n},reflect:!0}}function l(e){return{default:e,parse:t=>t??e,reflect:!0}}var u={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return s(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:c,oneOf(e,t){return{default:t,parse:n=>{if(n==null)return t;let r=e,i=r.indexOf(n);if(i!==-1)return e[i];let a=Number(n);if(!Number.isNaN(a)){let t=r.indexOf(a);if(t!==-1)return e[t]}return t},reflect:!0}},string:l},d=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function f(r,i){if(!d(r))throw new t(`Prop "${i}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof r}`);let a=r;if(!a.parse)throw new t(`Prop "${i}" must have a parse function. Use prop.* helpers.`);let o=a.reflect??!1;if(o&&n(a.default))throw new t(`Prop "${i}": ${e.propInvalidReflect}`);return{...a,reflect:o}}function p(e){let t=[];for(let[n,r]of Object.entries(e))try{f(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var m=new WeakMap,h=(e,t)=>m.get(e)?.get(t),g=(e,t)=>typeof e==`string`?t(e):e,_=(e,t,n,i)=>{let s=m.get(e);s||(s=new Map,m.set(e,s));let{default:c,parse:l,reflect:u=!1}=i,d=o(c),f=s.get(n),p=Object.hasOwn(e,t),h=p?e[t]:void 0,_={parse:l,reflect:u,signal:d};return f?d.value=f.signal.peek():p?(delete e[t],d.value=g(h,l)):e.hasAttribute(n)&&(d.value=l(e.getAttribute(n))),s.set(n,_),Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:()=>d.value,set:e=>{d.value=g(e,l)}}),u&&a(()=>{let t=d.value;t==null?e.removeAttribute(n):typeof t==`boolean`?e.toggleAttribute(n,t):r(e,n,t)}),d};function v(e,t){let n={};for(let[r,a]of Object.entries(t))n[r]=_(e,r,i(r),a);return n}export{v as createProps,h as getPropMeta,f as normalizePropDefinition,u as prop,p 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 { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } 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\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 *\n * Accepts any object shaped like `PropDef`: not just `prop.*` helper output, so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\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 */\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\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type 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/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded: e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n\n // On reconnection, the prop registry still holds the previous connection's\n // PropMeta. Its signal carries the correct prop value (updated by\n // attributeChangedCallback between connections). We must read from it rather\n // than from `el[propName]`, because setup() may have overwritten the host\n // property with a different getter/setter (e.g. `defineFieldValue()` in\n // refine's form-field components redefines `el.value` to expose the internal\n // field string). Reading that getter would yield the wrong value and lose\n // the prop's actual state across reconnections.\n const existingMeta = registry.get(attrName);\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 (existingMeta) {\n s.value = existingMeta.signal.peek() as T;\n } else if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n registry.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 = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\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 { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } 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(): PropDef<number | undefined>;\n number(defaultValue: number): PropDef<number>;\n /**\n * Prop restricted to a fixed set of allowed values: the only honest way to\n * type a union-valued prop, because the restriction is enforced at runtime\n * (`parse` falls back to the default for anything outside `allowed`), not just\n * in a type parameter a raw attribute value could violate. Numeric members are\n * matched against the attribute value parsed as a number. Omit `defaultValue`\n * for an optional prop (`undefined` when the attribute is absent).\n */\n oneOf<T extends number>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends number>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n oneOf<T extends string>(allowed: readonly T[]): PropDef<T | undefined>;\n oneOf<T extends string>(allowed: readonly T[], defaultValue: T): PropDef<T>;\n string(): PropDef<string | undefined>;\n string(defaultValue: string): PropDef<string>;\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\n/** @internal Implementation behind the overloaded `prop.number` signature. */\nfunction _numberProp(defaultValue?: number): PropDef<number | undefined> {\n const def = defaultValue;\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;\n },\n reflect: true,\n };\n}\n\n/** @internal Implementation behind the overloaded `prop.string` signature. */\nfunction _stringProp(defaultValue?: string): PropDef<string | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n return {\n default: defaultValue,\n parse: (value: string | null) => (value == null ? defaultValue : value),\n reflect: true,\n };\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: _numberProp as PropFactory['number'],\n oneOf<T extends string | number>(allowed: readonly T[], defaultValue?: T): PropDef<T | undefined> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null) return defaultValue;\n\n // Return the allowed member itself (correct literal type). Numeric unions\n // arrive as attribute strings, so also try the value parsed as a number.\n const members = allowed as readonly unknown[];\n const direct = members.indexOf(value);\n\n if (direct !== -1) return (allowed as readonly T[])[direct];\n\n const asNumber = Number(value);\n\n if (!Number.isNaN(asNumber)) {\n const byNumber = members.indexOf(asNumber);\n\n if (byNumber !== -1) return (allowed as readonly T[])[byNumber];\n }\n\n return defaultValue;\n },\n reflect: true,\n };\n },\n string: _stringProp as PropFactory['string'],\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 *\n * Accepts any object shaped like `PropDef`: not just `prop.*` helper output, so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\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 */\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\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type 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/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded: e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n\n // On reconnection, the prop registry still holds the previous connection's\n // PropMeta. Its signal carries the correct prop value (updated by\n // attributeChangedCallback between connections). We must read from it rather\n // than from `el[propName]`, because setup() may have overwritten the host\n // property with a different getter/setter (e.g. `defineFieldValue()` in\n // refine's form-field components redefines `el.value` to expose the internal\n // field string). Reading that getter would yield the wrong value and lose\n // the prop's actual state across reconnections.\n const existingMeta = registry.get(attrName);\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 (existingMeta) {\n s.value = existingMeta.signal.peek() as T;\n } else if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n registry.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 = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\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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"}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* by path navigation on the cloned template (see compiler.ts / instantiator.ts).
|
|
6
6
|
* Runtime factories and brand guards live in `result.ts`: this module is types only.
|
|
7
7
|
*/
|
|
8
|
-
import type { Readable } from '@vielzeug/ripple';
|
|
9
|
-
import type {
|
|
8
|
+
import type { Readable, Signal } from '@vielzeug/ripple';
|
|
9
|
+
import type { EventModifiers } from './compiler';
|
|
10
10
|
import type { DirectiveResult, HTMLResult, Ref, RefCallback } from './result';
|
|
11
11
|
type AttrBaseFields = {
|
|
12
12
|
el: HTMLElement;
|
|
@@ -18,8 +18,6 @@ type AttrBaseFields = {
|
|
|
18
18
|
live?: true;
|
|
19
19
|
mode: 'attr' | 'bool';
|
|
20
20
|
name: string;
|
|
21
|
-
/** Pre-resolved prop metadata (if the target element is an ore component). */
|
|
22
|
-
propMeta?: PropMeta;
|
|
23
21
|
type: 'attr';
|
|
24
22
|
};
|
|
25
23
|
/** Attribute binding with a static value (no signal). */
|
|
@@ -34,9 +32,32 @@ export type AttrBinding = AttrStaticBinding | AttrReactiveBinding;
|
|
|
34
32
|
export type EventBinding = {
|
|
35
33
|
el: HTMLElement;
|
|
36
34
|
handler: (e: Event) => void;
|
|
35
|
+
modifiers?: EventModifiers;
|
|
37
36
|
name: string;
|
|
38
37
|
type: 'event';
|
|
39
38
|
};
|
|
39
|
+
/** Explicit property binding (`.prop=${value}` in a template): always writes the DOM property. */
|
|
40
|
+
export type PropBinding = {
|
|
41
|
+
el: HTMLElement;
|
|
42
|
+
name: string;
|
|
43
|
+
type: 'prop';
|
|
44
|
+
} & ({
|
|
45
|
+
signal: Readable<unknown>;
|
|
46
|
+
} | {
|
|
47
|
+
value: unknown;
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Two-way form-control binding produced by `model()` (see directives/model.ts):
|
|
51
|
+
* writes the source signal to the control's value/checked property and writes
|
|
52
|
+
* user input back through the control's configured event.
|
|
53
|
+
*/
|
|
54
|
+
export type ModelBinding = {
|
|
55
|
+
el: HTMLElement;
|
|
56
|
+
event: string;
|
|
57
|
+
get: (el: HTMLElement) => unknown;
|
|
58
|
+
signal: Signal<unknown>;
|
|
59
|
+
type: 'model';
|
|
60
|
+
};
|
|
40
61
|
export type RefBinding = {
|
|
41
62
|
el: HTMLElement;
|
|
42
63
|
ref: Ref<Element> | RefCallback<Element>;
|
|
@@ -54,6 +75,6 @@ export type DirectiveBinding = {
|
|
|
54
75
|
directive: DirectiveResult;
|
|
55
76
|
type: 'directive';
|
|
56
77
|
};
|
|
57
|
-
export type Binding = AttrBinding |
|
|
78
|
+
export type Binding = AttrBinding | DirectiveBinding | EventBinding | HtmlBinding | ModelBinding | PropBinding | RefBinding;
|
|
58
79
|
export {};
|
|
59
80
|
//# sourceMappingURL=binding-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding-types.d.ts","sourceRoot":"","sources":["../../src/template/binding-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"binding-types.d.ts","sourceRoot":"","sources":["../../src/template/binding-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI9E,KAAK,cAAc,GAAG;IACpB,EAAE,EAAE,WAAW,CAAC;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpE,qDAAqD;AACrD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC;AAEjF,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,WAAW,CAAC;IAChB,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,WAAW,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,WAAW,CAAC;IAChB,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,OAAO,GACf,WAAW,GACX,gBAAgB,GAChB,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,WAAW,GACX,UAAU,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../props.cjs"),r=require("./result.cjs"),i=require("../directives/live.cjs");let a=require("@vielzeug/ripple");var o=(e,t,n)=>{let r=(0,a.effect)(()=>{t(e.value)});n(()=>r.dispose())},s=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,c=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),l=(e,t,n,r={last:void 0})=>{let i=c(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},u=(e,n,r,i)=>{let o=t.isStructuredValue(i)?i:n.parse(r.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>n.signal.value),o)||(n.signal.value=o),!n.reflect){if(t.isStructuredValue(i))return;r.mode===`bool`?e.toggleAttribute(r.name,!!i):t.setAttr(e,r.name,i)}},d=(e,
|
|
1
|
+
const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../props.cjs"),r=require("./result.cjs"),i=require("../directives/live.cjs");let a=require("@vielzeug/ripple");var o=(e,t,n)=>{let r=(0,a.effect)(()=>{t(e.value)});n(()=>r.dispose())},s=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,c=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),l=(e,t,n,r={last:void 0})=>{let i=c(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},u=(e,n,r,i)=>{let o=t.isStructuredValue(i)?i:n.parse(r.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>n.signal.value),o)||(n.signal.value=o),!n.reflect){if(t.isStructuredValue(i))return;r.mode===`bool`?e.toggleAttribute(r.name,!!i):t.setAttr(e,r.name,i)}},d=(e,r)=>{let{el:i,mode:a,name:c}=e,d={last:void 0},f=r=>{let o=n.getPropMeta(i,c);if(o){u(i,o,e,r);return}if(c===`value`&&s(i)||c===`checked`&&i instanceof HTMLInputElement){l(i,r,e.live,d);return}if(c===`indeterminate`&&i instanceof HTMLInputElement){i.indeterminate=!!r;return}a===`bool`?i.toggleAttribute(c,!!r):t.setAttr(i,c,r)};`signal`in e?o(e.signal,f,r):f(e.value)},f=(e,n)=>{let{modifiers:r}=e,i=r?.stop||r?.prevent?t=>{r.stop&&t.stopPropagation(),r.prevent&&t.preventDefault(),e.handler(t)}:e.handler,a=r?.once||r?.capture?{capture:r.capture,once:r.once}:void 0;n(t.listen(e.el,e.name,i,a))},p=(e,t)=>{let n=t=>{e.el[e.name]=t};`signal`in e?o(e.signal,n,t):n(e.value)},m=(e,n)=>{let{el:r,get:i,signal:o}=e,c={last:void 0},u=e=>{s(r)?l(r,e,!0,c):r.value=e},d=(0,a.effect)(()=>{u(o.value)});n(()=>d.dispose()),n(t.listen(r,e.event,()=>{let e=i(r);Object.is((0,a.untrack)(()=>o.value),e)||(o.value=e)}))},h=(e,t)=>{let{el:n,ref:r}=e;if(typeof r==`function`){r(n),t(()=>r(null));return}r.value=n,t(()=>{r.value=null})},g=(t,n,i)=>{let a=[],o=n.parentNode;e.invariant(o,`html binding anchor has no parent node`);for(let e of t)if(r.isHtmlResult(e)){let t=Array.from(e.fragment.childNodes);o.insertBefore(e.fragment,n),e.apply(i),a.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));o.insertBefore(t,n),a.push(t)}return a},_=(e,n)=>{let{anchor:r,signal:i}=e,o=t.createReplaceableSlot(),s=(0,a.effect)(()=>{let e=i.value;o.clear(),e!=null&&e.length!==0&&(0,a.untrack)(()=>{o.setNodes(g(e,r,o.registerCleanup))})});n(()=>{s.dispose(),o.clear()})},v=(e,t)=>{e.directive.mount(e.anchor,t)},y=(e,t)=>{switch(e.type){case`attr`:d(e,t);break;case`directive`:v(e,t);break;case`event`:f(e,t);break;case`html`:_(e,t);break;case`model`:m(e,t);break;case`prop`:p(e,t);break;case`ref`:h(e,t)}},b=(e,t,n,r)=>i.isLiveBinding(r)?{el:e,live:!0,mode:t,name:n,signal:r.source,type:`attr`}:typeof r==`function`?{el:e,mode:t,name:n,signal:(0,a.computed)(r),type:`attr`}:(0,a.isReactive)(r)?{el:e,mode:t,name:n,signal:r,type:`attr`}:{el:e,mode:t,name:n,type:`attr`,value:r},x=e=>e==null?``:String(e);exports.applyAttrBinding=d,exports.applyBinding=y,exports.applyHtmlBinding=_,exports.createAttrBindingFromValue=b,exports.resolveStaticText=x,exports.syncFormControl=l;
|
|
2
2
|
//# sourceMappingURL=bindings.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.cjs","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, ref,\n * directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name, propMeta } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if (!isReactive(value) && isStructuredValue(value)) {\n if (name !== '__proto__' && name !== 'constructor' && name !== 'prototype') {\n (el as unknown as Record<string, unknown>)[name] = value;\n }\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n registerCleanup(listen(binding.el, binding.name, binding.handler));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n const propMeta = getPropMeta(el, name);\n\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, propMeta, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, propMeta, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, propMeta, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bindings.cjs","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* template/bindings.ts: Runtime binding appliers.
|
|
3
3
|
*
|
|
4
4
|
* Responsibilities:
|
|
5
|
-
* - Apply each Binding variant to the live DOM (attr, event, html,
|
|
6
|
-
* directive).
|
|
5
|
+
* - Apply each Binding variant to the live DOM (attr, event, html, model,
|
|
6
|
+
* prop, ref, directive).
|
|
7
7
|
* - Manage reactive effects and cleanup registration.
|
|
8
8
|
* - Expose `applyBinding()` as the single dispatch entry point.
|
|
9
9
|
* - Own the signal-to-form-control write path (`syncFormControl`) for regular
|
|
10
|
-
* attribute bindings.
|
|
10
|
+
* attribute bindings, and the two-way `model()` write-back loop.
|
|
11
11
|
*/
|
|
12
12
|
import type { AttrBinding, Binding, HtmlBinding } from './binding-types';
|
|
13
13
|
export type RegisterCleanup = (fn: () => void) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/template/bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EACV,WAAW,EACX,OAAO,EAGP,WAAW,
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/template/bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EACV,WAAW,EACX,OAAO,EAGP,WAAW,EAKZ,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAyBvD,KAAK,cAAc,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAC1B,IAAI,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,EAC9D,OAAO,OAAO,EACd,SAAS,OAAO,EAChB,QAAO,cAAoC,KAC1C,IAWF,CAAC;AA4BF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IA4CzF,CAAC;AAsIF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IAqBzF,CAAC;AAUF,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,EAAE,iBAAiB,eAAe,KAAG,IAwBjF,CAAC;AAIF,eAAO,MAAM,0BAA0B,GACrC,IAAI,WAAW,EACf,MAAM,MAAM,GAAG,MAAM,EACrB,MAAM,MAAM,EACZ,OAAO,OAAO,KACb,WAcF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,MAIlD,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{invariant as e}from"../errors.js";import{createReplaceableSlot as t,isStructuredValue as n,listen as r,setAttr as i}from"../utils/dom.js";import{getPropMeta as a}from"../props.js";import{isHtmlResult as o}from"./result.js";import{isLiveBinding as s}from"../directives/live.js";import{computed as c,effect as l,isReactive as u,untrack as d}from"@vielzeug/ripple";var f=(e,t,n)=>{let r=l(()=>{t(e.value)});n(()=>r.dispose())},p=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,m=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),h=(e,t,n,r={last:void 0})=>{let i=m(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},g=(e,t,r,a)=>{let o=n(a)?a:t.parse(r.mode===`bool`?a?``:null:a==null||a===!1?null:String(a));if(Object.is(d(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(n(a))return;r.mode===`bool`?e.toggleAttribute(r.name,!!a):i(e,r.name,a)}},_=(e,t)=>{let{el:
|
|
1
|
+
import{invariant as e}from"../errors.js";import{createReplaceableSlot as t,isStructuredValue as n,listen as r,setAttr as i}from"../utils/dom.js";import{getPropMeta as a}from"../props.js";import{isHtmlResult as o}from"./result.js";import{isLiveBinding as s}from"../directives/live.js";import{computed as c,effect as l,isReactive as u,untrack as d}from"@vielzeug/ripple";var f=(e,t,n)=>{let r=l(()=>{t(e.value)});n(()=>r.dispose())},p=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,m=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),h=(e,t,n,r={last:void 0})=>{let i=m(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},g=(e,t,r,a)=>{let o=n(a)?a:t.parse(r.mode===`bool`?a?``:null:a==null||a===!1?null:String(a));if(Object.is(d(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(n(a))return;r.mode===`bool`?e.toggleAttribute(r.name,!!a):i(e,r.name,a)}},_=(e,t)=>{let{el:n,mode:r,name:o}=e,s={last:void 0},c=t=>{let c=a(n,o);if(c){g(n,c,e,t);return}if(o===`value`&&p(n)||o===`checked`&&n instanceof HTMLInputElement){h(n,t,e.live,s);return}if(o===`indeterminate`&&n instanceof HTMLInputElement){n.indeterminate=!!t;return}r===`bool`?n.toggleAttribute(o,!!t):i(n,o,t)};`signal`in e?f(e.signal,c,t):c(e.value)},v=(e,t)=>{let{modifiers:n}=e,i=n?.stop||n?.prevent?t=>{n.stop&&t.stopPropagation(),n.prevent&&t.preventDefault(),e.handler(t)}:e.handler,a=n?.once||n?.capture?{capture:n.capture,once:n.once}:void 0;t(r(e.el,e.name,i,a))},y=(e,t)=>{let n=t=>{e.el[e.name]=t};`signal`in e?f(e.signal,n,t):n(e.value)},b=(e,t)=>{let{el:n,get:i,signal:a}=e,o={last:void 0},s=e=>{p(n)?h(n,e,!0,o):n.value=e},c=l(()=>{s(a.value)});t(()=>c.dispose()),t(r(n,e.event,()=>{let e=i(n);Object.is(d(()=>a.value),e)||(a.value=e)}))},x=(e,t)=>{let{el:n,ref:r}=e;if(typeof r==`function`){r(n),t(()=>r(null));return}r.value=n,t(()=>{r.value=null})},S=(t,n,r)=>{let i=[],a=n.parentNode;e(a,`html binding anchor has no parent node`);for(let e of t)if(o(e)){let t=Array.from(e.fragment.childNodes);a.insertBefore(e.fragment,n),e.apply(r),i.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));a.insertBefore(t,n),i.push(t)}return i},C=(e,n)=>{let{anchor:r,signal:i}=e,a=t(),o=l(()=>{let e=i.value;a.clear(),e!=null&&e.length!==0&&d(()=>{a.setNodes(S(e,r,a.registerCleanup))})});n(()=>{o.dispose(),a.clear()})},w=(e,t)=>{e.directive.mount(e.anchor,t)},T=(e,t)=>{switch(e.type){case`attr`:_(e,t);break;case`directive`:w(e,t);break;case`event`:v(e,t);break;case`html`:C(e,t);break;case`model`:b(e,t);break;case`prop`:y(e,t);break;case`ref`:x(e,t)}},E=(e,t,n,r)=>s(r)?{el:e,live:!0,mode:t,name:n,signal:r.source,type:`attr`}:typeof r==`function`?{el:e,mode:t,name:n,signal:c(r),type:`attr`}:u(r)?{el:e,mode:t,name:n,signal:r,type:`attr`}:{el:e,mode:t,name:n,type:`attr`,value:r},D=e=>e==null?``:String(e);export{_ as applyAttrBinding,T as applyBinding,C as applyHtmlBinding,E as createAttrBindingFromValue,D as resolveStaticText,h as syncFormControl};
|
|
2
2
|
//# sourceMappingURL=bindings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.js","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, ref,\n * directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name, propMeta } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if (!isReactive(value) && isStructuredValue(value)) {\n if (name !== '__proto__' && name !== 'constructor' && name !== 'prototype') {\n (el as unknown as Record<string, unknown>)[name] = value;\n }\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n registerCleanup(listen(binding.el, binding.name, binding.handler));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n const propMeta = getPropMeta(el, name);\n\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, propMeta, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, propMeta, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, propMeta, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bindings.js","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../errors.cjs");var t={ATTR:`attr`,BOOL_ATTR:`boolAttr`,EVENT:`event`,NODE:`node`,REF:`ref`},n=/\s+@([a-zA-Z_][-a-zA-Z0-9_.-]*)\s*=\s*["']?$/,
|
|
1
|
+
const e=require("../errors.cjs");var t={ATTR:`attr`,BOOL_ATTR:`boolAttr`,EVENT:`event`,NODE:`node`,PROP:`prop`,REF:`ref`},n={capture:!0,once:!0,prevent:!0,stop:!0},r=(t,r)=>{if(r.length===0)return;let i={};for(let a of r){if(!(a in n))throw new e.OreApiError(e.ORE_ERRORS.eventModifierUnknown(t,a));i[a]=!0}return i},i=/\s+@([a-zA-Z_][-a-zA-Z0-9_.-]*)\s*=\s*["']?$/,a=/\s+ref\s*=\s*["']?$/,o=/\s+\?([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,s=/\s+\.([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,c=/\s+([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,l=n=>{let l;if(l=i.exec(n)){let e=n.slice(0,-l[0].length),[i,...a]=l[1].split(`.`);return{eventModifiers:r(i,a),kind:t.EVENT,name:i,prefix:e}}if(l=a.exec(n))return{kind:t.REF,prefix:n.slice(0,-l[0].length)};if(l=o.exec(n))return{kind:t.BOOL_ATTR,name:l[1],prefix:n.slice(0,-l[0].length)};if(l=s.exec(n)){let r=l[1];if(r===`__proto__`||r===`constructor`||r===`prototype`)throw new e.OreApiError(e.ORE_ERRORS.propBindingForbiddenName(r));return{kind:t.PROP,name:r,prefix:n.slice(0,-l[0].length)}}if(l=c.exec(n))return{kind:t.ATTR,name:l[1],prefix:n.slice(0,-l[0].length)};let u=n.lastIndexOf(`<`);if(u>n.lastIndexOf(`>`)&&n[u+1]!==`/`)throw new e.OreApiError(e.ORE_ERRORS.templateInterpolationInTag);return{kind:t.NODE,prefix:n}},u=new WeakMap,d=/[@?]?[a-zA-Z_][-a-zA-Z0-9_.]*\s*=\s*$/,f=e=>{let t=Array.from(e),n=!1;for(let e=0;e<t.length-1;e++){let r=t[e],i=r[r.length-1];for(let e of r)e===`<`?n=!0:e===`>`&&(n=!1);if((i===`"`||i===`'`)&&n&&d.test(r.slice(0,-1))){t[e]=r.slice(0,-1);let n=t[e+1];n.startsWith(i)&&(t[e+1]=n.slice(1))}}return t},p=`data-ore-b`,m=/^ore:(\d+)$/,h=(e,t,n,r)=>{if(e.nodeType===Node.ELEMENT_NODE){let r=e,i=r.getAttribute(p);i!==null&&(n.set(Number(i),[...t]),r.removeAttribute(p))}else if(e.nodeType===Node.COMMENT_NODE){let n=e.nodeValue,i=n===null?null:m.exec(n);i&&r.set(Number(i[1]),[...t])}let i=e.childNodes;for(let e=0;e<i.length;e++)h(i[e],[...t,e],n,r)},g=e=>{let n=f(e),r=``,i,a=0,o=0,s=[];for(let e=0;e<n.length-1;e++){let c=n[e],u=l(c);if(u.kind===t.NODE)r+=`${u.prefix}<!--ore:${o}-->`,s.push({commentId:o,kind:t.NODE}),o++,i=void 0;else{i===void 0||u.prefix.lastIndexOf(`<`)>u.prefix.lastIndexOf(`>`)?(i=a++,r+=`${u.prefix} ${p}="${i}"`):r+=u.prefix;let e=u.kind===t.BOOL_ATTR?`bool`:u.kind===t.ATTR?`attr`:void 0;s.push({elementId:i,eventModifiers:u.eventModifiers,kind:u.kind,mode:e,name:u.name})}}r+=n[n.length-1]??``;let c=document.createElement(`template`);c.innerHTML=r;let u=new Map,d=new Map,m=c.content.childNodes;for(let e=0;e<m.length;e++)h(m[e],[e],u,d);return{commentPaths:d,element:c,elementPaths:u,slots:s}},_=e=>{let t=u.get(e);return t||(t=g(e),u.set(e,t)),t},v=(e,t)=>{let n=e;for(let e of t)n=n.childNodes[e];return n};exports.SlotKind=t,exports.followPath=v,exports.getStaticTemplate=_;
|
|
2
2
|
//# sourceMappingURL=compiler.cjs.map
|