@vielzeug/ore 26.11.4 → 26.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-element.cjs +1 -1
- package/dist/base-element.cjs.map +1 -1
- package/dist/base-element.js +1 -1
- package/dist/define.cjs +1 -1
- package/dist/define.cjs.map +1 -1
- package/dist/define.js +1 -1
- package/dist/directives/each.cjs +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.js +1 -1
- package/dist/directives/unsafe-html.cjs +1 -1
- package/dist/directives/unsafe-html.cjs.map +1 -1
- package/dist/directives/unsafe-html.js +1 -1
- package/dist/directives/when.cjs +1 -1
- package/dist/directives/when.cjs.map +1 -1
- package/dist/directives/when.js +1 -1
- package/dist/host-bind.cjs +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ore.js +12 -25
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.js +1 -1
- package/dist/template/bindings.cjs +1 -1
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.js +1 -1
- package/dist/template/instantiator.cjs +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.js +1 -1
- package/dist/testing/mount.cjs +1 -1
- package/dist/testing/mount.cjs.map +1 -1
- package/dist/testing/mount.js +1 -1
- package/dist/testing/render-hook.cjs +1 -1
- package/dist/testing/render-hook.cjs.map +1 -1
- package/dist/testing/render-hook.js +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +1 -1
- package/package.json +11 -11
package/dist/props.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("./_dev.cjs");const e=require("./errors.cjs"),t=require("./
|
|
1
|
+
require("./_dev.cjs");const e=require("./errors.cjs"),t=require("./runtime.cjs"),n=require("./utils/dom.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(t,r){if(!c(t))throw new e.OreApiError(`Prop "${r}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof t}`);let i=t;if(!i.parse)throw new e.OreApiError(`Prop "${r}" must have a parse function. Use prop.* helpers.`);let a=i.reflect??!1;if(a&&n.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&&t.watchEffect(()=>{let t=f.value;t==null?e.removeAttribute(a):typeof t==`boolean`?e.toggleAttribute(a,t):n.setAttr(e,a,t)}),f};function h(e,t){let r={};for(let[i,a]of Object.entries(t))r[i]=m(e,i,n.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":["OreApiError","isStructuredValue","ORE_ERRORS","signal","toKebab","require_errors","require_dom","require_runtime","_vielzeug_ripple","_jsOnlyProp","defaultValue","_numberProp","def","value","_stringProp","prop","members","allowed","direct","asNumber","byNumber","isPropDef","normalizePropDefinition","propName","descriptor","reflect","validatePropDefs","errors","key","defs","error","propRegistry","getPropMeta","el","attrName","coerceIncoming","parse","registerProp","registry","propDef","s","existingMeta","hasPreUpgradeProperty","preUpgradeValue","meta","v","createProps","props","name"],"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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"}
|
|
1
|
+
{"version":3,"file":"props.cjs","names":["OreApiError","isStructuredValue","ORE_ERRORS","signal","toKebab","require_errors","require_runtime","require_dom","_vielzeug_ripple","_jsOnlyProp","defaultValue","_numberProp","def","value","_stringProp","prop","members","allowed","direct","asNumber","byNumber","isPropDef","normalizePropDefinition","propName","descriptor","reflect","validatePropDefs","errors","key","defs","error","propRegistry","getPropMeta","el","attrName","coerceIncoming","parse","registerProp","registry","propDef","s","existingMeta","hasPreUpgradeProperty","preUpgradeValue","meta","v","createProps","props","name"],"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.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{
|
|
1
|
+
import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{watchEffect as n}from"./runtime.js";import{isStructuredValue as r,setAttr as i,toKebab as a}from"./utils/dom.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(n,i){if(!d(n))throw new t(`Prop "${i}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof n}`);let a=n;if(!a.parse)throw new t(`Prop "${i}" must have a parse function. Use prop.* helpers.`);let o=a.reflect??!1;if(o&&r(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=/* @__PURE__ */ new WeakMap,h=(e,t)=>m.get(e)?.get(t),g=(e,t)=>typeof e==`string`?t(e):e,_=(e,t,r,a)=>{let s=m.get(e);s||(s=/* @__PURE__ */ new Map,m.set(e,s));let{default:c,parse:l,reflect:u=!1}=a,d=o(c),f=s.get(r),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(r)&&(d.value=l(e.getAttribute(r))),s.set(r,_),Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:()=>d.value,set:e=>{d.value=g(e,l)}}),u&&n(()=>{let t=d.value;t==null?e.removeAttribute(r):typeof t==`boolean`?e.toggleAttribute(r,t):i(e,r,t)}),d};function v(e,t){let n={};for(let[r,i]of Object.entries(t))n[r]=_(e,r,a(r),i);return n}export{v as createProps,h as getPropMeta,f as normalizePropDefinition,u as prop,p as validatePropDefs};
|
|
2
2
|
//# sourceMappingURL=props.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../
|
|
1
|
+
const e=require("../directives/live.cjs"),t=require("../errors.cjs"),n=require("../props.cjs"),r=require("../utils/dom.cjs"),i=require("./result.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,t,n,i)=>{let o=r.isStructuredValue(i)?i:t.parse(n.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(r.isStructuredValue(i))return;n.mode===`bool`?e.toggleAttribute(n.name,!!i):r.setAttr(e,n.name,i)}},d=(e,t)=>{let{el:i,mode:a,name:c}=e,d={last:void 0},f=t=>{let o=n.getPropMeta(i,c);o?u(i,o,e,t):c===`value`&&s(i)||c===`checked`&&i instanceof HTMLInputElement?l(i,t,e.live,d):c===`indeterminate`&&i instanceof HTMLInputElement?i.indeterminate=!!t:a===`bool`?i.toggleAttribute(c,!!t):r.setAttr(i,c,t)};`signal`in e?o(e.signal,f,t):f(e.value)},f=(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.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,t)=>{let{el:n,get:i,signal:o}=e,c={last:void 0},u=e=>{s(n)?l(n,e,!0,c):n.value=e},d=(0,a.effect)(()=>{u(o.value)});t(()=>d.dispose()),t(r.listen(n,e.event,()=>{let e=i(n);Object.is((0,a.untrack)(()=>o.value),e)||(o.value=e)}))},h=(e,t)=>{let{el:n,ref:r}=e;typeof r==`function`?(r(n),t(()=>r(null))):(r.value=n,t(()=>{r.value=null}))},g=(e,n,r)=>{let a=[],o=n.parentNode;t.invariant(o,`html binding anchor has no parent node`);for(let t of e)if(i.isHtmlResult(t)){let e=Array.from(t.fragment.childNodes);o.insertBefore(t.fragment,n),t.apply(r),a.push(...e)}else if(t!=null&&t!==!1){let e=document.createTextNode(String(t));o.insertBefore(e,n),a.push(e)}return a},_=(e,t)=>{let{anchor:n,signal:i}=e,o=r.createReplaceableSlot(),s=(0,a.effect)(()=>{let e=i.value;o.clear(),e!=null&&e.length!==0&&(0,a.untrack)(()=>{o.setNodes(g(e,n,o.registerCleanup))})});t(()=>{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=(t,n,r,i)=>e.isLiveBinding(i)?{el:t,live:!0,mode:n,name:r,signal:i.source,type:`attr`}:typeof i==`function`?{el:t,mode:n,name:r,signal:(0,a.computed)(i),type:`attr`}:(0,a.isReactive)(i)?{el:t,mode:n,name:r,signal:i,type:`attr`}:{el:t,mode:n,name:r,type:`attr`,value:i},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":["rawEffect","isStructuredValue","untrack","getPropMeta","listen","isHtmlResult","createReplaceableSlot","isLiveBinding","computed","isReactive","require_errors","require_dom","require_props","require_result","require_live","_vielzeug_ripple","signalEffect","sub","update","signal","registerCleanup","isNativeFormInput","el","isCheckableInput","syncFormControl","checkable","next","value","current","state","isLive","syncRegisteredProp","parsed","meta","binding","applyAttrBinding","liveState","propMeta","name","mode","applyEventBinding","handler","modifiers","e","options","applyPropBinding","applyModelBinding","write","get","applyRefBinding","ref","insertHtmlValues","nodes","parent","insertBefore","v","values","captured","text","applyHtmlBinding","slot","stop","raw","anchor","applyDirectiveBinding","applyBinding","createAttrBindingFromValue","resolveStaticText"],"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
|
+
{"version":3,"file":"bindings.cjs","names":["rawEffect","isStructuredValue","untrack","getPropMeta","listen","isHtmlResult","createReplaceableSlot","isLiveBinding","computed","isReactive","require_live","require_errors","require_props","require_dom","require_result","_vielzeug_ripple","signalEffect","sub","update","signal","registerCleanup","isNativeFormInput","el","isCheckableInput","syncFormControl","checkable","next","value","current","state","isLive","syncRegisteredProp","parsed","meta","binding","applyAttrBinding","liveState","propMeta","name","mode","applyEventBinding","handler","modifiers","e","options","applyPropBinding","applyModelBinding","write","get","applyRefBinding","ref","insertHtmlValues","nodes","parent","insertBefore","v","values","captured","text","applyHtmlBinding","slot","stop","raw","anchor","applyDirectiveBinding","applyBinding","createAttrBindingFromValue","resolveStaticText"],"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
|
-
import{
|
|
1
|
+
import{isLiveBinding as e}from"../directives/live.js";import{invariant as t}from"../errors.js";import{getPropMeta as n}from"../props.js";import{createReplaceableSlot as r,isStructuredValue as i,listen as a,setAttr as o}from"../utils/dom.js";import{isHtmlResult as s}from"./result.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,n,r)=>{let a=i(r)?r:t.parse(n.mode===`bool`?r?``:null:r==null||r===!1?null:String(r));if(Object.is(d(()=>t.signal.value),a)||(t.signal.value=a),!t.reflect){if(i(r))return;n.mode===`bool`?e.toggleAttribute(n.name,!!r):o(e,n.name,r)}},_=(e,t)=>{let{el:r,mode:i,name:a}=e,s={last:void 0},c=t=>{let c=n(r,a);c?g(r,c,e,t):a===`value`&&p(r)||a===`checked`&&r instanceof HTMLInputElement?h(r,t,e.live,s):a===`indeterminate`&&r instanceof HTMLInputElement?r.indeterminate=!!t:i===`bool`?r.toggleAttribute(a,!!t):o(r,a,t)};`signal`in e?f(e.signal,c,t):c(e.value)},v=(e,t)=>{let{modifiers:n}=e,r=n?.stop||n?.prevent?t=>{n.stop&&t.stopPropagation(),n.prevent&&t.preventDefault(),e.handler(t)}:e.handler,i=n?.once||n?.capture?{capture:n.capture,once:n.once}:void 0;t(a(e.el,e.name,r,i))},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:r,signal:i}=e,o={last:void 0},s=e=>{p(n)?h(n,e,!0,o):n.value=e},c=l(()=>{s(i.value)});t(()=>c.dispose()),t(a(n,e.event,()=>{let e=r(n);Object.is(d(()=>i.value),e)||(i.value=e)}))},x=(e,t)=>{let{el:n,ref:r}=e;typeof r==`function`?(r(n),t(()=>r(null))):(r.value=n,t(()=>{r.value=null}))},S=(e,n,r)=>{let i=[],a=n.parentNode;t(a,`html binding anchor has no parent node`);for(let t of e)if(s(t)){let e=Array.from(t.fragment.childNodes);a.insertBefore(t.fragment,n),t.apply(r),i.push(...e)}else if(t!=null&&t!==!1){let e=document.createTextNode(String(t));a.insertBefore(e,n),i.push(e)}return i},C=(e,t)=>{let{anchor:n,signal:i}=e,a=r(),o=l(()=>{let e=i.value;a.clear(),e!=null&&e.length!==0&&d(()=>{a.setNodes(S(e,n,a.registerCleanup))})});t(()=>{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=(t,n,r,i)=>e(i)?{el:t,live:!0,mode:n,name:r,signal:i.source,type:`attr`}:typeof i==`function`?{el:t,mode:n,name:r,signal:c(i),type:`attr`}:u(i)?{el:t,mode:n,name:r,signal:i,type:`attr`}:{el:t,mode:n,name:r,type:`attr`,value:i},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,2 +1,2 @@
|
|
|
1
|
-
const e=require("../
|
|
1
|
+
const e=require("../directives/model.cjs"),t=require("../errors.cjs"),n=require("./bindings.cjs"),r=require("./compiler.cjs"),i=require("./result.cjs");let a=require("@vielzeug/ripple");var o="html`...`: node-slot comment anchor has no parent node",s=e=>Array.isArray(e)?e:[e],c=(e,n,r)=>{let i=n.parentNode;for(t.invariant(i,o);e.fragment.firstChild;)i.insertBefore(e.fragment.firstChild,n);r.push(e.apply.bind(e))},l=(l,u)=>{let d=r.getStaticTemplate(l),f=d.element.content.cloneNode(!0),p=[],m=[],h=d.slots.map((e,n)=>{let i=u[n];if(e.kind===r.SlotKind.NODE){let n=e.commentId===void 0?void 0:d.commentPaths.get(e.commentId);return t.invariant(n,`compiled template is missing a comment path for node slot ${e.commentId}`),{comment:r.followPath(f,n),slot:e,value:i}}let a=e.elementId===void 0?void 0:d.elementPaths.get(e.elementId);return t.invariant(a,`compiled template is missing an element path for slot ${e.elementId}`),{el:r.followPath(f,a),slot:e,value:i}});for(let{comment:l,el:u,slot:d,value:f}of h)if(d.kind===r.SlotKind.NODE){let e=l;if(t.invariant(e,`compiled template produced a node slot without a comment anchor`),i.isDirectiveResult(f)){p.push({anchor:e,directive:f,type:`directive`});continue}if(i.isHtmlResult(f)){c(f,e,m),e.remove();continue}if(typeof f==`function`||(0,a.isReactive)(f)){let t=typeof f==`function`?(0,a.computed)(()=>s(f())):(0,a.computed)(()=>s(f.value));p.push({anchor:e,signal:t,type:`html`});continue}if(Array.isArray(f)){for(let r of f)if(i.isHtmlResult(r))c(r,e,m);else{let i=e.parentNode;t.invariant(i,o),i.insertBefore(document.createTextNode(n.resolveStaticText(r)),e)}e.remove();continue}e.replaceWith(document.createTextNode(n.resolveStaticText(f)))}else if(t.invariant(u,`compiled template produced an element slot without an element`),d.kind===r.SlotKind.EVENT){let e=d.name;if(t.invariant(e,`compiled template produced an event slot without an event name`),typeof f==`function`)p.push({el:u,handler:f,modifiers:d.eventModifiers,name:e,type:`event`});else if((0,a.isReactive)(f)){let t=f;p.push({el:u,handler:e=>{let n=t.value;typeof n==`function`&&n(e)},modifiers:d.eventModifiers,name:e,type:`event`})}}else if(d.kind===r.SlotKind.PROP){let e=d.name;t.invariant(e,`compiled template produced a prop slot without a property name`),typeof f==`function`?p.push({el:u,name:e,signal:(0,a.computed)(f),type:`prop`}):(0,a.isReactive)(f)?p.push({el:u,name:e,signal:f,type:`prop`}):p.push({el:u,name:e,type:`prop`,value:f})}else if(d.kind===r.SlotKind.REF)f&&p.push({el:u,ref:f,type:`ref`});else if(t.invariant(d.name,`compiled template produced an attr slot without an attribute name`),e.isModelSource(f)){if(d.mode!==`attr`)throw new t.OreApiError(t.ORE_ERRORS.modelRequiresValueBinding(d.name));p.push({el:u,event:f.event??e.modelDefaultEvent(u),get:f.get??e.modelDefaultGet,signal:f.source,type:`model`})}else p.push(n.createAttrBindingFromValue(u,d.mode??`attr`,d.name,f));return i.createHtmlResult(f,e=>{for(let t of p)n.applyBinding(t,e);for(let t of m)t(e)})},u=(e,...t)=>l(e,t);exports.compileTemplate=l,exports.html=u;
|
|
2
2
|
//# sourceMappingURL=instantiator.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.cjs","names":["getStaticTemplate","SlotKind","followPath","isDirectiveResult","isHtmlResult","isReactive","computed","resolveStaticText","isModelSource","OreApiError","ORE_ERRORS","modelDefaultEvent","modelDefaultGet","createAttrBindingFromValue","createHtmlResult","require_errors","require_result","require_model","require_bindings","require_compiler","_vielzeug_ripple","NODE_SLOT_NO_PARENT_MSG","toHtmlValues","raw","embedStaticResult","parent","anchor","result","chainedApplies","compileTemplate","compiled","strings","fragment","bindings","boundSlots","value","values","i","slot","commentPath","elementPath","comment","sig","item","el","name","signalValue","h","e","binding","registerCleanup","chainedApply","html"],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts: Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport type { Binding, HtmlBindingValue } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/** Normalize a reactive node-slot value to the HtmlBinding signal's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values: it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n const sig =\n typeof value === 'function'\n ? computed(() => toHtmlValues((value as () => unknown)()))\n : computed(() => toHtmlValues((value as Readable<unknown>).value));\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instantiator.cjs","names":["getStaticTemplate","SlotKind","followPath","isDirectiveResult","isHtmlResult","isReactive","computed","resolveStaticText","isModelSource","OreApiError","ORE_ERRORS","modelDefaultEvent","modelDefaultGet","createAttrBindingFromValue","createHtmlResult","require_model","require_errors","require_bindings","require_compiler","require_result","_vielzeug_ripple","NODE_SLOT_NO_PARENT_MSG","toHtmlValues","raw","embedStaticResult","parent","anchor","result","chainedApplies","compileTemplate","compiled","strings","fragment","bindings","boundSlots","value","values","i","slot","commentPath","elementPath","comment","sig","item","el","name","signalValue","h","binding","registerCleanup","chainedApply","html"],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts: Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport type { Binding, HtmlBindingValue } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/** Normalize a reactive node-slot value to the HtmlBinding signal's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values: it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n const sig =\n typeof value === 'function'\n ? computed(() => toHtmlValues((value as () => unknown)()))\n : computed(() => toHtmlValues((value as Readable<unknown>).value));\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{isModelSource as e,modelDefaultEvent as t,modelDefaultGet as n}from"../directives/model.js";import{ORE_ERRORS as r,OreApiError as i,invariant as a}from"../errors.js";import{applyBinding as o,createAttrBindingFromValue as s,resolveStaticText as c}from"./bindings.js";import{SlotKind as l,followPath as u,getStaticTemplate as d}from"./compiler.js";import{createHtmlResult as f,isDirectiveResult as p,isHtmlResult as m}from"./result.js";import{computed as h,isReactive as g}from"@vielzeug/ripple";var _="html`...`: node-slot comment anchor has no parent node",v=e=>Array.isArray(e)?e:[e],y=(e,t,n)=>{let r=t.parentNode;for(a(r,_);e.fragment.firstChild;)r.insertBefore(e.fragment.firstChild,t);n.push(e.apply.bind(e))},b=(b,x)=>{let S=d(b),C=S.element.content.cloneNode(!0),w=[],T=[],E=S.slots.map((e,t)=>{let n=x[t];if(e.kind===l.NODE){let t=e.commentId===void 0?void 0:S.commentPaths.get(e.commentId);return a(t,`compiled template is missing a comment path for node slot ${e.commentId}`),{comment:u(C,t),slot:e,value:n}}let r=e.elementId===void 0?void 0:S.elementPaths.get(e.elementId);return a(r,`compiled template is missing an element path for slot ${e.elementId}`),{el:u(C,r),slot:e,value:n}});for(let{comment:o,el:u,slot:d,value:f}of E)if(d.kind===l.NODE){let e=o;if(a(e,`compiled template produced a node slot without a comment anchor`),p(f)){w.push({anchor:e,directive:f,type:`directive`});continue}if(m(f)){y(f,e,T),e.remove();continue}if(typeof f==`function`||g(f)){let t=h(typeof f==`function`?()=>v(f()):()=>v(f.value));w.push({anchor:e,signal:t,type:`html`});continue}if(Array.isArray(f)){for(let t of f)if(m(t))y(t,e,T);else{let n=e.parentNode;a(n,_),n.insertBefore(document.createTextNode(c(t)),e)}e.remove();continue}e.replaceWith(document.createTextNode(c(f)))}else if(a(u,`compiled template produced an element slot without an element`),d.kind===l.EVENT){let e=d.name;if(a(e,`compiled template produced an event slot without an event name`),typeof f==`function`)w.push({el:u,handler:f,modifiers:d.eventModifiers,name:e,type:`event`});else if(g(f)){let t=f;w.push({el:u,handler:e=>{let n=t.value;typeof n==`function`&&n(e)},modifiers:d.eventModifiers,name:e,type:`event`})}}else if(d.kind===l.PROP){let e=d.name;a(e,`compiled template produced a prop slot without a property name`),typeof f==`function`?w.push({el:u,name:e,signal:h(f),type:`prop`}):g(f)?w.push({el:u,name:e,signal:f,type:`prop`}):w.push({el:u,name:e,type:`prop`,value:f})}else if(d.kind===l.REF)f&&w.push({el:u,ref:f,type:`ref`});else if(a(d.name,`compiled template produced an attr slot without an attribute name`),e(f)){if(d.mode!==`attr`)throw new i(r.modelRequiresValueBinding(d.name));w.push({el:u,event:f.event??t(u),get:f.get??n,signal:f.source,type:`model`})}else w.push(s(u,d.mode??`attr`,d.name,f));return f(C,e=>{for(let t of w)o(t,e);for(let t of T)t(e)})},x=(e,...t)=>b(e,t);export{b as compileTemplate,x as html};
|
|
2
2
|
//# sourceMappingURL=instantiator.js.map
|
package/dist/testing/mount.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../
|
|
1
|
+
const e=require("../define.cjs"),t=require("../utils/dom.cjs"),n=require("./flush.cjs"),r=require("./render-hook.cjs"),i=require("./reset.cjs");let a=require("@vielzeug/assay");var o=[],s=0;function c(e,n,r){t.setAttr(e,n,r)}var l=e=>e instanceof Error?e:Error(String(e)),u=async e=>{if(typeof window>`u`)return e();let t=null,n=e=>{t=l(e.error??e.message),e.preventDefault()},r=e=>{t=l(e.reason),e.preventDefault()};window.addEventListener(`error`,n),window.addEventListener(`unhandledrejection`,r);try{let n=await e();if(t)throw t;return n}finally{window.removeEventListener(`error`,n),window.removeEventListener(`unhandledrejection`,r)}};async function d(t,r={}){let{attrs:i={},componentOptions:l,container:d=document.body,html:f,props:p={}}=r,m,h;typeof t==`string`?m=t:(m=`trial-${++s}`,h={...l??{},setup:t}),h&&e.define(m,h);let g=document.createElement(m);f&&(g.innerHTML=f),Object.keys(p).length&&Object.assign(g,p);for(let[e,t]of Object.entries(i))c(g,e,t);try{await u(async()=>{d.appendChild(g),o.push(g),await n.flush()})}catch(e){g.remove();let t=o.indexOf(g);throw t!==-1&&o.splice(t,1),e}let _=!1;function v(){if(_)return;_=!0,g.remove();let e=o.indexOf(g);e!==-1&&o.splice(e,1)}return{...(0,a.within)(g.shadowRoot??g),async act(e){await e(),await n.flush()},async attr(e,t){c(g,e,t),await n.flush()},async attrs(e){for(let[t,n]of Object.entries(e))c(g,t,n);await n.flush()},dispose:v,get disposed(){return _},element:g,flush:n.flush,get shadow(){return g.shadowRoot},[Symbol.dispose](){v()}}}function f(){for(let e of o)e.remove();o.length=0,r._disposeRenderHooks(),i.resetOreForTests()}exports._mountedElements=o,exports.cleanup=f,exports.mount=d;
|
|
2
2
|
//# sourceMappingURL=mount.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.cjs","names":["flush","within","require_dom","require_define","require_flush","require_render_hook","require_reset","_vielzeug_assay","_mountedElements","_componentTagCounter","applyAttr","element","name","value","toError","withWindowErrorCapture","action","captured","onError","event","onUnhandledRejection","result","mount","options","tagName","inlineDefinition","tagOrSetup","componentOptions","html","props","attrs","container","err","i","isDisposed","dispose","fn","record","cleanup","el"],"sources":["../../src/testing/mount.ts"],"sourcesContent":["/**\n * Component mounting utilities for test environments.\n */\n\nimport { type QueryScope, within } from '@vielzeug/assay';\nimport type { Readable } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from '../component-types';\nimport { define } from '../define';\nimport type { HTMLResult } from '../template/result';\nimport { setAttr } from '../utils/dom';\nimport { type FlushOptions, flush } from './flush';\nimport { _disposeRenderHooks } from './render-hook';\nimport { resetOreForTests } from './reset';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n// Fixture inherits Assay's complete QueryScope. Keeping that relationship structural means new\n// scoped query capabilities are immediately available to fixtures without a duplicate contract.\n/**\n * A mounted component ready for assertions. All inherited `QueryScope` methods are scoped to\n * `element.shadowRoot`, falling back to `element` for light-DOM components (`shadow: false`).\n */\nexport interface Fixture<T extends HTMLElement = HTMLElement> extends QueryScope {\n /** Run a callback then flush: the standard way to trigger and assert a reactive update */\n act(fn: () => unknown): Promise<void>;\n /** Set an attribute (boolean `false` removes it) then flush */\n attr(name: string, value: string | number | boolean): Promise<void>;\n /** Set multiple attributes then flush */\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n /** Remove the component from the DOM. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** The component element */\n element: T;\n /** Wait for all reactive updates and animation frames */\n flush(options?: FlushOptions): Promise<void>;\n /** The component's shadow root (null for light-DOM components) */\n readonly shadow: ShadowRoot | null;\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n}\n\nexport interface MountOptions {\n /** HTML attributes to set on the element */\n attrs?: Record<string, string | number | boolean>;\n /** Extra component options when passing an inline setup function */\n componentOptions?: Omit<ComponentDefinition<Record<string, unknown>>, 'setup'>;\n /** Parent container (default: document.body) */\n container?: HTMLElement | ShadowRoot;\n /** Inner HTML for slot content */\n html?: string;\n /** Properties assigned directly onto the element */\n props?: Record<string, unknown>;\n}\n\ntype MountProps = { readonly [x: string]: Readable<unknown> };\n\n// Bivariant callback type keeps inline test callbacks ergonomic across varying prop specializations.\nexport type MountSetup = {\n bivarianceHack: (props: MountProps) => HTMLResult | null;\n}['bivarianceHack'];\n\n// ─── Test environment state ───────────────────────────────────────────────────\n\nexport const _mountedElements: HTMLElement[] = [];\n\n// Monotonic across the whole test run: never reset: custom element registrations\n// are permanent, so a re-used tag name would throw on re-define. Deterministic\n// within a run (trial-1, trial-2, ...) without a random suffix.\nlet _componentTagCounter = 0;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction applyAttr(element: Element, name: string, value: string | number | boolean): void {\n // Same write path as the runtime's attribute bindings, so tests never set up\n // states the runtime itself can't produce (e.g. boolean true → \"true\").\n setAttr(element, name, value);\n}\n\nconst toError = (value: unknown): Error => {\n return value instanceof Error ? value : new Error(String(value));\n};\n\nconst withWindowErrorCapture = async <T>(action: () => Promise<T>): Promise<T> => {\n if (typeof window === 'undefined') return action();\n\n let captured: Error | null = null;\n const onError = (event: ErrorEvent) => {\n captured = toError(event.error ?? event.message);\n event.preventDefault();\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n captured = toError(event.reason);\n event.preventDefault();\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n\n try {\n const result = await action();\n\n if (captured) throw captured;\n\n return result;\n } finally {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n }\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mount a component into the DOM and return a test fixture.\n *\n * Accepts a registered tag name, an inline setup function, or a component\n * options object. Setup functions are auto-registered with generated tag names.\n *\n * @example: inline setup function\n * const { query } = await mount(() => {\n * const count = signal(0);\n * return html`<button @click=${() => count.value++}>${count}</button>`;\n * });\n *\n * @example: registered tag name\n * const { query } = await mount('my-counter');\n */\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: MountSetup,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string | MountSetup,\n options: MountOptions = {},\n): Promise<Fixture<T>> {\n const { attrs = {}, componentOptions, container = document.body, html, props = {} } = options;\n\n let tagName: string;\n let inlineDefinition: ComponentDefinition<Record<string, unknown>> | undefined;\n\n if (typeof tagOrSetup === 'string') {\n tagName = tagOrSetup;\n } else {\n tagName = `trial-${++_componentTagCounter}`;\n inlineDefinition = {\n ...(componentOptions ?? {}),\n setup: tagOrSetup,\n };\n }\n\n if (inlineDefinition) {\n define(tagName, inlineDefinition);\n }\n\n const element = document.createElement(tagName) as T;\n\n if (html) element.innerHTML = html;\n\n if (Object.keys(props).length) Object.assign(element, props);\n\n for (const [name, value] of Object.entries(attrs)) applyAttr(element, name, value);\n\n try {\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n } catch (err) {\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n\n throw err;\n }\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n }\n\n const scope = within((element.shadowRoot ?? element) as Element);\n\n return {\n ...scope,\n\n async act(fn) {\n await fn();\n await flush();\n },\n\n async attr(name, value) {\n applyAttr(element, name, value);\n await flush();\n },\n\n async attrs(record) {\n for (const [name, value] of Object.entries(record)) applyAttr(element, name, value);\n await flush();\n },\n\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n element,\n\n flush,\n\n get shadow(): ShadowRoot | null {\n return element.shadowRoot;\n },\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n\n/**\n * Remove all elements mounted via `mount()`.\n * Call in `afterEach` to keep tests isolated.\n *\n * @example\n * afterEach(() => cleanup());\n */\nexport function cleanup(): void {\n for (const el of _mountedElements) el.remove();\n _mountedElements.length = 0;\n _disposeRenderHooks();\n resetOreForTests();\n}\n\n/** @internal re-export for within() */\nexport type { QueryScope };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mount.cjs","names":["flush","within","require_define","require_dom","require_flush","require_render_hook","require_reset","_vielzeug_assay","_mountedElements","_componentTagCounter","applyAttr","element","name","value","toError","withWindowErrorCapture","action","captured","onError","event","onUnhandledRejection","result","mount","options","tagName","inlineDefinition","tagOrSetup","componentOptions","html","props","attrs","container","err","i","isDisposed","dispose","fn","record","cleanup","el"],"sources":["../../src/testing/mount.ts"],"sourcesContent":["/**\n * Component mounting utilities for test environments.\n */\n\nimport { type QueryScope, within } from '@vielzeug/assay';\nimport type { Readable } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from '../component-types';\nimport { define } from '../define';\nimport type { HTMLResult } from '../template/result';\nimport { setAttr } from '../utils/dom';\nimport { type FlushOptions, flush } from './flush';\nimport { _disposeRenderHooks } from './render-hook';\nimport { resetOreForTests } from './reset';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n// Fixture inherits Assay's complete QueryScope. Keeping that relationship structural means new\n// scoped query capabilities are immediately available to fixtures without a duplicate contract.\n/**\n * A mounted component ready for assertions. All inherited `QueryScope` methods are scoped to\n * `element.shadowRoot`, falling back to `element` for light-DOM components (`shadow: false`).\n */\nexport interface Fixture<T extends HTMLElement = HTMLElement> extends QueryScope {\n /** Run a callback then flush: the standard way to trigger and assert a reactive update */\n act(fn: () => unknown): Promise<void>;\n /** Set an attribute (boolean `false` removes it) then flush */\n attr(name: string, value: string | number | boolean): Promise<void>;\n /** Set multiple attributes then flush */\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n /** Remove the component from the DOM. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** The component element */\n element: T;\n /** Wait for all reactive updates and animation frames */\n flush(options?: FlushOptions): Promise<void>;\n /** The component's shadow root (null for light-DOM components) */\n readonly shadow: ShadowRoot | null;\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n}\n\nexport interface MountOptions {\n /** HTML attributes to set on the element */\n attrs?: Record<string, string | number | boolean>;\n /** Extra component options when passing an inline setup function */\n componentOptions?: Omit<ComponentDefinition<Record<string, unknown>>, 'setup'>;\n /** Parent container (default: document.body) */\n container?: HTMLElement | ShadowRoot;\n /** Inner HTML for slot content */\n html?: string;\n /** Properties assigned directly onto the element */\n props?: Record<string, unknown>;\n}\n\ntype MountProps = { readonly [x: string]: Readable<unknown> };\n\n// Bivariant callback type keeps inline test callbacks ergonomic across varying prop specializations.\nexport type MountSetup = {\n bivarianceHack: (props: MountProps) => HTMLResult | null;\n}['bivarianceHack'];\n\n// ─── Test environment state ───────────────────────────────────────────────────\n\nexport const _mountedElements: HTMLElement[] = [];\n\n// Monotonic across the whole test run: never reset: custom element registrations\n// are permanent, so a re-used tag name would throw on re-define. Deterministic\n// within a run (trial-1, trial-2, ...) without a random suffix.\nlet _componentTagCounter = 0;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction applyAttr(element: Element, name: string, value: string | number | boolean): void {\n // Same write path as the runtime's attribute bindings, so tests never set up\n // states the runtime itself can't produce (e.g. boolean true → \"true\").\n setAttr(element, name, value);\n}\n\nconst toError = (value: unknown): Error => {\n return value instanceof Error ? value : new Error(String(value));\n};\n\nconst withWindowErrorCapture = async <T>(action: () => Promise<T>): Promise<T> => {\n if (typeof window === 'undefined') return action();\n\n let captured: Error | null = null;\n const onError = (event: ErrorEvent) => {\n captured = toError(event.error ?? event.message);\n event.preventDefault();\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n captured = toError(event.reason);\n event.preventDefault();\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n\n try {\n const result = await action();\n\n if (captured) throw captured;\n\n return result;\n } finally {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n }\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mount a component into the DOM and return a test fixture.\n *\n * Accepts a registered tag name, an inline setup function, or a component\n * options object. Setup functions are auto-registered with generated tag names.\n *\n * @example: inline setup function\n * const { query } = await mount(() => {\n * const count = signal(0);\n * return html`<button @click=${() => count.value++}>${count}</button>`;\n * });\n *\n * @example: registered tag name\n * const { query } = await mount('my-counter');\n */\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: MountSetup,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string | MountSetup,\n options: MountOptions = {},\n): Promise<Fixture<T>> {\n const { attrs = {}, componentOptions, container = document.body, html, props = {} } = options;\n\n let tagName: string;\n let inlineDefinition: ComponentDefinition<Record<string, unknown>> | undefined;\n\n if (typeof tagOrSetup === 'string') {\n tagName = tagOrSetup;\n } else {\n tagName = `trial-${++_componentTagCounter}`;\n inlineDefinition = {\n ...(componentOptions ?? {}),\n setup: tagOrSetup,\n };\n }\n\n if (inlineDefinition) {\n define(tagName, inlineDefinition);\n }\n\n const element = document.createElement(tagName) as T;\n\n if (html) element.innerHTML = html;\n\n if (Object.keys(props).length) Object.assign(element, props);\n\n for (const [name, value] of Object.entries(attrs)) applyAttr(element, name, value);\n\n try {\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n } catch (err) {\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n\n throw err;\n }\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n }\n\n const scope = within((element.shadowRoot ?? element) as Element);\n\n return {\n ...scope,\n\n async act(fn) {\n await fn();\n await flush();\n },\n\n async attr(name, value) {\n applyAttr(element, name, value);\n await flush();\n },\n\n async attrs(record) {\n for (const [name, value] of Object.entries(record)) applyAttr(element, name, value);\n await flush();\n },\n\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n element,\n\n flush,\n\n get shadow(): ShadowRoot | null {\n return element.shadowRoot;\n },\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n\n/**\n * Remove all elements mounted via `mount()`.\n * Call in `afterEach` to keep tests isolated.\n *\n * @example\n * afterEach(() => cleanup());\n */\nexport function cleanup(): void {\n for (const el of _mountedElements) el.remove();\n _mountedElements.length = 0;\n _disposeRenderHooks();\n resetOreForTests();\n}\n\n/** @internal re-export for within() */\nexport type { QueryScope };\n"],"mappings":"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"}
|
package/dist/testing/mount.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{define as e}from"../define.js";import{setAttr as t}from"../utils/dom.js";import{flush as n}from"./flush.js";import{_disposeRenderHooks as r}from"./render-hook.js";import{resetOreForTests as i}from"./reset.js";import{within as a}from"@vielzeug/assay";var o=[],s=0;function c(e,n,r){t(e,n,r)}var l=e=>e instanceof Error?e:Error(String(e)),u=async e=>{if(typeof window>`u`)return e();let t=null,n=e=>{t=l(e.error??e.message),e.preventDefault()},r=e=>{t=l(e.reason),e.preventDefault()};window.addEventListener(`error`,n),window.addEventListener(`unhandledrejection`,r);try{let n=await e();if(t)throw t;return n}finally{window.removeEventListener(`error`,n),window.removeEventListener(`unhandledrejection`,r)}};async function d(t,r={}){let{attrs:i={},componentOptions:l,container:d=document.body,html:f,props:p={}}=r,m,h;typeof t==`string`?m=t:(m=`trial-${++s}`,h={...l??{},setup:t}),h&&e(m,h);let g=document.createElement(m);f&&(g.innerHTML=f),Object.keys(p).length&&Object.assign(g,p);for(let[e,t]of Object.entries(i))c(g,e,t);try{await u(async()=>{d.appendChild(g),o.push(g),await n()})}catch(e){g.remove();let t=o.indexOf(g);throw t!==-1&&o.splice(t,1),e}let _=!1;function v(){if(_)return;_=!0,g.remove();let e=o.indexOf(g);e!==-1&&o.splice(e,1)}return{...a(g.shadowRoot??g),async act(e){await e(),await n()},async attr(e,t){c(g,e,t),await n()},async attrs(e){for(let[t,n]of Object.entries(e))c(g,t,n);await n()},dispose:v,get disposed(){return _},element:g,flush:n,get shadow(){return g.shadowRoot},[Symbol.dispose](){v()}}}function f(){for(let e of o)e.remove();o.length=0,r(),i()}export{o as _mountedElements,f as cleanup,d as mount};
|
|
2
2
|
//# sourceMappingURL=mount.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../errors.cjs"),t=require("../
|
|
1
|
+
const e=require("../errors.cjs"),t=require("../props.cjs"),n=require("../runtime.cjs"),r=require("./flush.cjs");let i=require("@vielzeug/ripple");var a=new Set,o=()=>{for(let e of[...a])e()};async function s(o,s){let c=typeof o!=`function`,l=c?o:void 0,u=c?s:o,d=(0,i.createScope)(),f=document.createElement(`div`);document.body.appendChild(f);let p=n.createRuntimeContext(f),{mountCallbacks:m}=p,h;try{d.run(()=>{n.runWithContext(p,()=>{let e=l?t.createProps(f,l):{};h=u(e)})});for(let t of m)try{d.run(()=>{n.runWithContext(p,()=>{let e=t();typeof e==`function`&&n.onCleanup(e)})})}catch(t){let n=t instanceof Error?t:Error(String(t));e.reportRuntimeError(new e.OreLifecycleError(`renderHook() mount callback failed`,{cause:n,component:`renderHook`,phase:`mounted`}),f)}}catch(e){throw d.dispose(),f.remove(),e}await r.flush();let g=!1;function _(){g||(g=!0,d.dispose(),f.remove(),a.delete(_))}return a.add(_),{dispose:_,get disposed(){return g},async flush(){await r.flush()},result:h,[Symbol.dispose](){_()}}}exports._disposeRenderHooks=o,exports.renderHook=s;
|
|
2
2
|
//# sourceMappingURL=render-hook.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-hook.cjs","names":["createScope","createRuntimeContext","createProps","OreLifecycleError","flush","require_errors","
|
|
1
|
+
{"version":3,"file":"render-hook.cjs","names":["createScope","createRuntimeContext","createProps","OreLifecycleError","flush","require_errors","require_props","require_runtime","require_flush","_vielzeug_ripple","hookDisposers","_disposeRenderHooks","dispose","renderHook","hasPropDefs","setupOrDefs","propDefs","setup","maybeSetup","hostScope","hostEl","ctx","result","props","cb","mountCallbacks","cleanup","error","err","isDisposed"],"sources":["../../src/testing/render-hook.ts"],"sourcesContent":["/**\n * renderHook: test composable hooks (lifecycle, reactive state, etc.) in isolation\n * without rendering a full component template.\n */\n\nimport { createScope } from '@vielzeug/ripple';\n\nimport { OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createProps, type InferProps, type PropInputDefs } from '../props';\nimport { createRuntimeContext, onCleanup, runWithContext } from '../runtime';\nimport { flush } from './flush';\n\nexport interface HookFixture<T> {\n /** Teardown the hook (runs cleanups). Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Flush pending reactive updates */\n flush(): Promise<void>;\n /** The value returned by the setup function */\n result: T;\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n}\n\nconst hookDisposers = new Set<() => void>();\n\n/** @internal Dispose every active hook fixture for test isolation. */\nexport const _disposeRenderHooks = (): void => {\n for (const dispose of [...hookDisposers]) dispose();\n};\n\n/**\n * Run a setup function in a component-like context and return the result.\n * `onMounted`/`onCleanup`/`bind`/... work exactly as they would inside a real\n * `setup()`, since they resolve the same implicit current-component context.\n *\n * @example No props\n * ```ts\n * const { result, flush } = await renderHook(() => {\n * const count = signal(0);\n * onMounted(() => { count.value = 1; });\n * return count;\n * });\n * expect(result.value).toBe(1);\n * ```\n *\n * @example With props\n * ```ts\n * const { result } = await renderHook(\n * { label: prop.string('hello') },\n * (props) => props.label,\n * );\n * expect(result.value).toBe('hello');\n * ```\n */\nexport async function renderHook<T>(setup: (_props: Record<never, never>) => T): Promise<HookFixture<T>>;\nexport async function renderHook<D extends PropInputDefs, T>(\n propDefs: D,\n setup: (props: InferProps<D>) => T,\n): Promise<HookFixture<T>>;\nexport async function renderHook<D extends PropInputDefs, T>(\n setupOrDefs: ((_props: Record<never, never>) => T) | D,\n maybeSetup?: (props: InferProps<D>) => T,\n): Promise<HookFixture<T>> {\n const hasPropDefs = typeof setupOrDefs !== 'function';\n const propDefs = hasPropDefs ? (setupOrDefs as D) : undefined;\n const setup = hasPropDefs ? maybeSetup! : (setupOrDefs as (_props: Record<never, never>) => T);\n\n const hostScope = createScope();\n // Minimal host element so getHost() works inside setup\n const hostEl = document.createElement('div');\n\n document.body.appendChild(hostEl);\n\n // Built via the runtime's own factory, so the harness can never desync from a\n // new required RuntimeContext field. Note: `onFormReset()` registrations land\n // in this context's formResetCallbacks but are never fired: renderHook has no\n // ancestor <form>; test form-reset behaviour through mount() instead.\n const ctx = createRuntimeContext(hostEl);\n const { mountCallbacks } = ctx;\n\n let result!: T;\n\n try {\n hostScope.run(() => {\n runWithContext(ctx, () => {\n const props = propDefs ? createProps(hostEl, propDefs as PropInputDefs) : ({} as Record<never, never>);\n\n result = setup(props as InferProps<D>);\n });\n });\n\n // Run mount callbacks (simulates connectedCallback → queueMicrotask)\n for (const cb of mountCallbacks) {\n try {\n hostScope.run(() => {\n runWithContext(ctx, () => {\n const cleanup = cb();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n } catch (error) {\n // Mirror BaseElement's real onMounted error handling (see\n // base-element.ts's `_scheduleMountCallbacks`): report the error\n // (console + `ore:error` event on the host) and keep running the\n // remaining callbacks, instead of hiding the failure entirely.\n const err = error instanceof Error ? error : new Error(String(error));\n\n reportRuntimeError(\n new OreLifecycleError(`renderHook() mount callback failed`, {\n cause: err,\n component: 'renderHook',\n phase: 'mounted',\n }),\n hostEl,\n );\n }\n }\n } catch (err) {\n hostScope.dispose();\n hostEl.remove();\n throw err;\n }\n\n await flush();\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n hostScope.dispose();\n hostEl.remove();\n hookDisposers.delete(dispose);\n }\n\n hookDisposers.add(dispose);\n\n return {\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n async flush() {\n await flush();\n },\n\n result,\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n"],"mappings":"A,kJAyBA,IAAMU,EAAgB,IAAI,IAGbC,MAAkC,CAC7C,IAAK,IAAMC,IAAW,CAAC,GAAGF,CAAa,EAAGE,EAAQ,CACpD,EA+BA,eAAsBC,EACpB,EACA,EACyB,CACzB,IAAMC,EAAc,OAAOC,GAAgB,WACrCC,EAAWF,EAAeC,EAAoB,IAAA,GAC9CE,EAAQH,EAAcI,EAAeH,EAErCI,GAAA,EAAYnB,EAAAA,YAAAA,CAAY,EAExBoB,EAAS,SAAS,cAAc,KAAK,EAE3C,SAAS,KAAK,YAAYA,CAAM,EAMhC,IAAMC,EAAMpB,EAAAA,qBAAqBmB,CAAM,EACjC,CAAE,kBAAmBC,EAEvBC,EAEJ,GAAI,CACFH,EAAU,QAAU,CAClBZ,EAAA,eAAec,MAAW,CACxB,IAAME,EAAQP,EAAWd,EAAAA,YAAYkB,EAAQJ,CAAyB,EAAK,CAAC,EAE5E,EAASC,EAAMM,CAAsB,CACvC,CAAC,CACH,CAAC,EAGD,IAAK,IAAMC,KAAMC,EACf,GAAI,CACFN,EAAU,QAAU,CAClBZ,EAAA,eAAec,MAAW,CACxB,IAAMK,EAAUF,EAAG,EAEf,OAAOE,GAAY,YAAYnB,EAAA,UAAUmB,CAAO,CACtD,CAAC,CACH,CAAC,CACH,OAASC,EAAO,CAKd,IAAMC,EAAMD,aAAiB,MAAQA,EAAY,MAAM,OAAOA,CAAK,CAAC,EAEpEtB,EAAA,mBACE,IAAIF,EAAAA,kBAAkB,qCAAsC,CAC1D,MAAOyB,EACP,UAAW,aACX,MAAO,SACT,CAAC,EACDR,CACF,CACF,CAEJ,OAASQ,EAAK,CAGZ,MAFAT,EAAU,QAAQ,EAClBC,EAAO,OAAO,EACRQ,CACR,CAEA,MAAMxB,EAAAA,MAAM,EAEZ,IAAIyB,EAAa,GAEjB,SAASjB,GAAU,CACbiB,IAEJ,EAAa,GACbV,EAAU,QAAQ,EAClBC,EAAO,OAAO,EACdV,EAAc,OAAOE,CAAO,EAC9B,CAIA,OAFAF,EAAc,IAAIE,CAAO,EAElB,CACL,UAEA,IAAI,UAAoB,CACtB,OAAOiB,CACT,EAEA,MAAM,OAAQ,CACZ,MAAMzB,EAAAA,MAAM,CACd,EAEA,SAEA,CAAC,OAAO,UAAW,CACjBQ,EAAQ,CACV,CACF,CACF,C"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{OreLifecycleError as e,reportRuntimeError as t}from"../errors.js";import{
|
|
1
|
+
import{OreLifecycleError as e,reportRuntimeError as t}from"../errors.js";import{createProps as n}from"../props.js";import{createRuntimeContext as r,onCleanup as i,runWithContext as a}from"../runtime.js";import{flush as o}from"./flush.js";import{createScope as s}from"@vielzeug/ripple";var c=/* @__PURE__ */ new Set,l=()=>{for(let e of[...c])e()};async function u(l,u){let d=typeof l!=`function`,f=d?l:void 0,p=d?u:l,m=s(),h=document.createElement(`div`);document.body.appendChild(h);let g=r(h),{mountCallbacks:_}=g,v;try{m.run(()=>{a(g,()=>{let e=f?n(h,f):{};v=p(e)})});for(let n of _)try{m.run(()=>{a(g,()=>{let e=n();typeof e==`function`&&i(e)})})}catch(n){let r=n instanceof Error?n:Error(String(n));t(new e(`renderHook() mount callback failed`,{cause:r,component:`renderHook`,phase:`mounted`}),h)}}catch(e){throw m.dispose(),h.remove(),e}await o();let y=!1;function b(){y||(y=!0,m.dispose(),h.remove(),c.delete(b))}return c.add(b),{dispose:b,get disposed(){return y},async flush(){await o()},result:v,[Symbol.dispose](){b()}}}export{l as _disposeRenderHooks,u as renderHook};
|
|
2
2
|
//# sourceMappingURL=render-hook.js.map
|
package/dist/testing.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./testing/
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./testing/mount.cjs"),t=require("./testing/flush.cjs"),n=require("./testing/render-hook.cjs");function r(t){t(e.cleanup)}exports.OreTimeoutError=t.OreTimeoutError,exports.cleanup=e.cleanup,exports.flush=t.flush,exports.install=r,exports.mount=e.mount,exports.renderHook=n.renderHook;
|
|
2
2
|
//# sourceMappingURL=testing.cjs.map
|
package/dist/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.cjs","names":["cleanup","
|
|
1
|
+
{"version":3,"file":"testing.cjs","names":["cleanup","require_mount","require_flush","require_render_hook","install","afterEachHook"],"sources":["../src/testing/index.ts"],"sourcesContent":["/**\n * Testing utilities for Ore components\n *\n * ⚠️ Requires DOM environment (browser / jsdom / happy-dom)\n *\n * This is the package's only public testing entry point.\n */\n\nimport { cleanup } from './mount';\n\n// Testing-only error: flush() is its only thrower in this package (a pending-work timeout :\n// see flush.ts). Exported here (not from the main `.` entry) so components with no interest\n// in the testing sub-path don't carry a testing-only error class in their public type surface.\n// Generic DOM interaction helpers belong to @vielzeug/assay, not this Ore-specific test API:\n// including the jsdom ElementInternals/FormData polyfill, which form-associated-component\n// suites install directly from assay (`installFormInternalsPolyfill`).\nexport { type FlushOptions, flush, OreTimeoutError } from './flush';\nexport { cleanup, type Fixture, type MountOptions, type MountSetup, mount } from './mount';\nexport { type HookFixture, renderHook } from './render-hook';\n\n/**\n * Register auto-cleanup after each test. Call once in your test setup file.\n *\n * @example\n * // vitest.setup.ts\n * import { afterEach } from 'vitest';\n * import { install } from '@vielzeug/ore/testing';\n * install(afterEach);\n */\nexport function install(afterEachHook: (fn: () => void) => void): void {\n afterEachHook(cleanup);\n}\n"],"mappings":"A,kLA6BA,SAAgBI,EAAQ,EAA+C,CACrEC,EAAcL,EAAAA,OAAO,CACvB,C"}
|
package/dist/testing.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{cleanup as e,mount as t}from"./testing/mount.js";import{OreTimeoutError as n,flush as r}from"./testing/flush.js";import{renderHook as i}from"./testing/render-hook.js";function a(t){t(e)}export{n as OreTimeoutError,e as cleanup,r as flush,a as install,t as mount,i as renderHook};
|
|
2
2
|
//# sourceMappingURL=testing.js.map
|