@vielzeug/ore 1.0.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/dist/_dev.cjs +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js +1 -1
- package/dist/_dev.js.map +1 -1
- package/dist/aria.cjs +1 -1
- package/dist/aria.cjs.map +1 -1
- package/dist/aria.d.ts +16 -8
- package/dist/aria.d.ts.map +1 -1
- package/dist/aria.js +1 -1
- package/dist/aria.js.map +1 -1
- package/dist/base-element.cjs +1 -1
- package/dist/base-element.cjs.map +1 -1
- package/dist/base-element.d.ts +36 -1
- package/dist/base-element.d.ts.map +1 -1
- package/dist/base-element.js +1 -1
- package/dist/base-element.js.map +1 -1
- package/dist/component-types.d.ts +13 -76
- package/dist/component-types.d.ts.map +1 -1
- package/dist/context.cjs +1 -1
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.ts +11 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.ts +13 -12
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js.map +1 -1
- package/dist/directives/each.cjs +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.d.ts +6 -0
- package/dist/directives/each.d.ts.map +1 -1
- package/dist/directives/each.js +1 -1
- package/dist/directives/each.js.map +1 -1
- package/dist/directives/raw.cjs +1 -1
- package/dist/directives/raw.cjs.map +1 -1
- package/dist/directives/raw.d.ts.map +1 -1
- package/dist/directives/raw.js +1 -1
- package/dist/directives/raw.js.map +1 -1
- package/dist/directives/when.cjs +1 -1
- package/dist/directives/when.cjs.map +1 -1
- package/dist/directives/when.d.ts.map +1 -1
- package/dist/directives/when.js +1 -1
- package/dist/directives/when.js.map +1 -1
- package/dist/errors.cjs +2 -2
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +23 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/forms/context.cjs +2 -0
- package/dist/forms/context.cjs.map +1 -0
- package/dist/{form-context.d.ts → forms/context.d.ts} +5 -5
- package/dist/forms/context.d.ts.map +1 -0
- package/dist/forms/context.js +2 -0
- package/dist/forms/context.js.map +1 -0
- package/dist/forms/field.cjs +2 -0
- package/dist/forms/field.cjs.map +1 -0
- package/dist/{form-field.d.ts → forms/field.d.ts} +19 -2
- package/dist/forms/field.d.ts.map +1 -0
- package/dist/forms/field.js +2 -0
- package/dist/forms/field.js.map +1 -0
- package/dist/forms/index.d.ts +11 -0
- package/dist/forms/index.d.ts.map +1 -0
- package/dist/forms.cjs +1 -0
- package/dist/forms.js +1 -0
- package/dist/host-bind.cjs +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.d.ts +8 -2
- package/dist/host-bind.d.ts.map +1 -1
- package/dist/host-bind.js +1 -1
- package/dist/host-bind.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/ore.cjs +2 -2
- package/dist/ore.cjs.map +1 -1
- package/dist/ore.iife.js +2 -2
- package/dist/ore.iife.js.map +1 -1
- package/dist/ore.js +2 -2
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.d.ts.map +1 -1
- package/dist/props.js +1 -1
- package/dist/props.js.map +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.ts +32 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/slots.cjs +1 -1
- package/dist/slots.cjs.map +1 -1
- package/dist/slots.d.ts +12 -1
- package/dist/slots.d.ts.map +1 -1
- package/dist/slots.js +1 -1
- package/dist/slots.js.map +1 -1
- package/dist/template/bindings.cjs +1 -1
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.d.ts.map +1 -1
- package/dist/template/bindings.js +1 -1
- package/dist/template/bindings.js.map +1 -1
- package/dist/template/compiler.cjs +1 -1
- package/dist/template/compiler.cjs.map +1 -1
- package/dist/template/compiler.d.ts +11 -1
- package/dist/template/compiler.d.ts.map +1 -1
- package/dist/template/compiler.js +1 -1
- package/dist/template/compiler.js.map +1 -1
- package/dist/template/instantiator.cjs +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.d.ts +0 -5
- package/dist/template/instantiator.d.ts.map +1 -1
- package/dist/template/instantiator.js +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/testing/dom.cjs +2 -0
- package/dist/testing/dom.cjs.map +1 -0
- package/dist/testing/dom.d.ts +22 -0
- package/dist/testing/dom.d.ts.map +1 -0
- package/dist/testing/dom.js +2 -0
- package/dist/testing/dom.js.map +1 -0
- package/dist/testing/form-internals-polyfill.cjs +2 -0
- package/dist/testing/form-internals-polyfill.cjs.map +1 -0
- package/dist/testing/form-internals-polyfill.d.ts +17 -0
- package/dist/testing/form-internals-polyfill.d.ts.map +1 -0
- package/dist/testing/form-internals-polyfill.js +2 -0
- package/dist/testing/form-internals-polyfill.js.map +1 -0
- package/dist/testing/mount.cjs.map +1 -1
- package/dist/testing/mount.d.ts +4 -4
- package/dist/testing/mount.d.ts.map +1 -1
- package/dist/testing/mount.js.map +1 -1
- package/dist/testing/render-hook.cjs +1 -1
- package/dist/testing/render-hook.cjs.map +1 -1
- package/dist/testing/render-hook.d.ts +6 -6
- package/dist/testing/render-hook.d.ts.map +1 -1
- package/dist/testing/render-hook.js +1 -1
- package/dist/testing/render-hook.js.map +1 -1
- package/dist/testing/testing.cjs +1 -1
- package/dist/testing/testing.cjs.map +1 -1
- package/dist/testing/testing.d.ts +7 -1
- package/dist/testing/testing.d.ts.map +1 -1
- package/dist/testing/testing.js +1 -1
- package/dist/testing/testing.js.map +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.js +1 -1
- package/dist/utils/emit.cjs +1 -1
- package/dist/utils/emit.cjs.map +1 -1
- package/dist/utils/emit.d.ts +28 -5
- package/dist/utils/emit.d.ts.map +1 -1
- package/dist/utils/emit.js +1 -1
- package/dist/utils/emit.js.map +1 -1
- package/package.json +12 -5
- package/dist/context-bag.cjs +0 -2
- package/dist/context-bag.cjs.map +0 -1
- package/dist/context-bag.d.ts +0 -12
- package/dist/context-bag.d.ts.map +0 -1
- package/dist/context-bag.js +0 -2
- package/dist/context-bag.js.map +0 -1
- package/dist/form-context.cjs +0 -2
- package/dist/form-context.cjs.map +0 -1
- package/dist/form-context.d.ts.map +0 -1
- package/dist/form-context.js +0 -2
- package/dist/form-context.js.map +0 -1
- package/dist/form-field.cjs +0 -2
- package/dist/form-field.cjs.map +0 -1
- package/dist/form-field.d.ts.map +0 -1
- package/dist/form-field.js +0 -2
- package/dist/form-field.js.map +0 -1
package/dist/props.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./_dev.cjs"),t=require("./errors.cjs"),n=require("./utils/dom.cjs"),r=require("./runtime.cjs");let i=require("@vielzeug/ripple");function a(e){return{default:e,parse:()=>e,reflect:!1}}var o={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return a(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number(t){let n=t===void 0?void 0:t;return{default:n,parse:t=>{if(t==null)return n;let r=Number(t);return Number.isNaN(r)?(e.warn(`prop.number(): attribute value "${t}" is not a valid number, using default (${String(n)})`),n):r},reflect:!0}},oneOf(e,t){return{default:t,parse:n=>n!=null&&e.includes(n)?n:t,reflect:!0}},string(e){let t=e===void 0?void 0:e;return{default:t,parse:e=>e??t,reflect:!0}}},s=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function c(e,r){if(!s(e))throw new t.OreApiError(`Prop "${r}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof e}`);let i=e;if(!i.parse)throw new t.OreApiError(`Prop "${r}" must have a parse function. Use prop.* helpers.`);let a=i.reflect??!1;if(a&&n.isStructuredValue(i.default))throw new t.OreApiError(`Prop "${r}": ${t.ORE_ERRORS.propInvalidReflect}`);return{...i,reflect:a}}function l(e){let t=[];for(let[n,r]of Object.entries(e))try{c(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var u=new WeakMap,d=(e,t)=>u.get(e)?.get(t),f=(e,t,a,o)=>{u.has(e)||u.set(e,new Map);let{default:
|
|
1
|
+
const e=require("./_dev.cjs"),t=require("./errors.cjs"),n=require("./utils/dom.cjs"),r=require("./runtime.cjs");let i=require("@vielzeug/ripple");function a(e){return{default:e,parse:()=>e,reflect:!1}}var o={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return a(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number(t){let n=t===void 0?void 0:t;return{default:n,parse:t=>{if(t==null)return n;let r=Number(t);return Number.isNaN(r)?(e.warn(`prop.number(): attribute value "${t}" is not a valid number, using default (${String(n)})`),n):r},reflect:!0}},oneOf(e,t){return{default:t,parse:n=>n!=null&&e.includes(n)?n:t,reflect:!0}},string(e){let t=e===void 0?void 0:e;return{default:t,parse:e=>e??t,reflect:!0}}},s=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function c(e,r){if(!s(e))throw new t.OreApiError(`Prop "${r}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof e}`);let i=e;if(!i.parse)throw new t.OreApiError(`Prop "${r}" must have a parse function. Use prop.* helpers.`);let a=i.reflect??!1;if(a&&n.isStructuredValue(i.default))throw new t.OreApiError(`Prop "${r}": ${t.ORE_ERRORS.propInvalidReflect}`);return{...i,reflect:a}}function l(e){let t=[];for(let[n,r]of Object.entries(e))try{c(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var u=new WeakMap,d=(e,t)=>u.get(e)?.get(t),f=(e,t)=>typeof e==`string`?t(e):e,p=(e,a,o,s)=>{u.has(e)||u.set(e,new Map);let{default:c,parse:l,reflect:d=!1}=s,p=(0,i.signal)(c),m=Object.hasOwn(e,a),h=m?e[a]:void 0,g={parse:l,reflect:d,signal:p};m?(delete e[a],p.value=f(h,l)):e.hasAttribute(o)&&(p.value=l(e.getAttribute(o)));let _=u.get(e);return t.invariant(_,`propRegistry entry missing for <${e.localName}> — registerProp() must create it above`),_.set(o,g),Object.defineProperty(e,a,{configurable:!0,enumerable:!0,get:()=>p.value,set:e=>{p.value=f(e,l)}}),d&&r.watchEffect(()=>{let t=p.value;t==null?e.removeAttribute(o):typeof t==`boolean`?e.toggleAttribute(o,t):n.setAttr(e,o,t)}),p};function m(e,t){let r={};for(let[i,a]of Object.entries(t))r[i]=p(e,i,n.toKebab(i),a);return r}exports.createProps=m,exports.getPropMeta=d,exports.normalizePropDefinition=c,exports.prop=o,exports.validatePropDefs=l;
|
|
2
2
|
//# sourceMappingURL=props.cjs.map
|
package/dist/props.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.cjs","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { OreApiError, ORE_ERRORS } from './errors';\nimport { effect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\n/**\n * Define a custom prop with explicit parser and reflection behavior.\n * Use when prop.* helpers don't cover your type.\n *\n * @example\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\ntype PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n if (!propRegistry.has(el)) propRegistry.set(el, new Map());\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n // Frameworks (e.g. Vue, when hydrating or patching an already-upgraded custom\n // element) may assign the pre-upgrade value as a plain string rather than calling\n // setAttribute — the same way it would for a real HTML attribute. Route strings\n // through the same parser an attribute value would use so e.g. size=\"16\" doesn't\n // end up stored as the string \"16\" instead of the number 16. Already-typed values\n // (objects, functions, booleans, numbers set deliberately as JS properties) pass\n // through untouched.\n s.value = (typeof preUpgradeValue === 'string' ? parse(preUpgradeValue) : preUpgradeValue) as T;\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n propRegistry.get(el)!.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n // A framework can still reach this setter with a raw attribute-style string after\n // the element has already upgraded — e.g. Vue reconciling server-rendered markup\n // against a custom element that auto-upgraded (from its SSR attribute) before\n // hydration ran: it finds the property already defined and assigns the vnode's\n // plain string prop value directly instead of calling setAttribute. Route strings\n // through the same parser an attribute value would use so it isn't stored raw.\n s.value = (typeof value === 'string' ? parse(value) : value) as T;\n },\n });\n\n if (reflect) {\n effect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"props.cjs","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { invariant, OreApiError, ORE_ERRORS } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\n/**\n * Define a custom prop with explicit parser and reflection behavior.\n * Use when prop.* helpers don't cover your type.\n *\n * @example\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\ntype PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\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 if (!propRegistry.has(el)) propRegistry.set(el, new Map());\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n const registry = propRegistry.get(el);\n\n invariant(registry, `propRegistry entry missing for <${el.localName}> — registerProp() must create it above`);\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":"kJA4CA,SAAS,EAAe,EAAuD,CAC7E,MAAO,CAAE,QAAS,EAAc,UAAa,EAAc,QAAS,EAAM,CAC5E,CAEA,IAAa,EAAoB,CAM/B,KAAK,EAA0C,CAG7C,MAAO,CACL,QAHU,GAAgB,GAI1B,MAAQ,GAAU,IAAU,MAAQ,IAAU,QAC9C,QAAS,EACX,CACF,EACA,KAAQ,EAAuD,CAC7D,OAAO,EAAY,CAAY,CACjC,EAcA,KAAQ,EAA6B,CACnC,MAAO,CACL,QAAS,EACT,MAAQ,GAAU,CAChB,GAAI,GAAS,MAAQ,IAAU,GAAI,OAAO,EAE1C,GAAI,CACF,OAAO,KAAK,MAAM,CAAK,CACzB,MAAQ,CACN,OAAO,CACT,CACF,EACA,QAAS,EACX,CACF,EACA,OAAkC,EAA4D,CAC5F,IAAM,EAAM,IAAiB,IAAA,GAAkC,IAAA,GAArB,EAE1C,MAAO,CACL,QAAS,EACT,MAAQ,GAAU,CAChB,GAAI,GAAS,KAAM,OAAO,EAE1B,IAAM,EAAI,OAAO,CAAK,EAQtB,OANI,OAAO,MAAM,CAAC,GAChB,EAAA,KAAK,mCAAmC,EAAM,0CAA0C,OAAO,CAAG,EAAE,EAAE,EAE/F,GAGF,CACT,EACA,QAAS,EACX,CACF,EACA,MACE,EACA,EACY,CACZ,MAAO,CACL,QAAS,EACT,MAAQ,GAAW,GAAS,MAAQ,EAAQ,SAAS,CAAuB,EAAK,EAAc,EAC/F,QAAS,EACX,CACF,EACA,OAAkC,EAA4D,CAE5F,IAAM,EAAM,IAAiB,IAAA,GAAkC,IAAA,GAArB,EAE1C,MAAO,CACL,QAAS,EACT,MAAQ,GAA0B,GAAgB,EAClD,QAAS,EACX,CACF,CACF,EAgBM,EAAa,GACjB,OAAO,GAAU,YAAY,GAAkB,YAAa,GAAS,UAAW,EAMlF,SAAgB,EAA2B,EAAgB,EAA8B,CACvF,GAAI,CAAC,EAAU,CAAK,EAClB,MAAM,IAAI,EAAA,YACR,SAAS,EAAS,wEAAwE,OAAO,GACnG,EAGF,IAAM,EAAa,EAEnB,GAAI,CAAC,EAAW,MACd,MAAM,IAAI,EAAA,YAAY,SAAS,EAAS,kDAAkD,EAG5F,IAAM,EAAU,EAAW,SAAW,GAGtC,GAAI,GAAW,EAAA,kBAAkB,EAAW,OAAO,EACjD,MAAM,IAAI,EAAA,YAAY,SAAS,EAAS,KAAK,EAAA,WAAW,oBAAoB,EAG9E,MAAO,CACL,GAAG,EACH,SACF,CACF,CAMA,SAAgB,EAAiB,EAAyC,CACxE,IAAM,EAAmB,CAAC,EAE1B,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAI,EAC5C,GAAI,CACF,EAAwB,EAAO,CAAG,CACpC,OAAS,EAAO,CACd,EAAO,KAAK,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAAC,CACpE,CAGF,OAAO,CACT,CAQA,IAAM,EAAe,IAAI,QAMZ,GAAe,EAAiB,IAC3C,EAAa,IAAI,CAAE,CAAC,EAAE,IAAI,CAAQ,EAY9B,GAAqB,EAAgB,IACxC,OAAO,GAAU,SAAW,EAAM,CAAK,EAAI,EAGxC,GAAmB,EAAiB,EAAkB,EAAkB,IAAmC,CAC1G,EAAa,IAAI,CAAE,GAAG,EAAa,IAAI,EAAI,IAAI,GAAK,EAEzD,GAAM,CAAE,QAAS,EAAc,QAAO,UAAU,IAAU,EACpD,GAAA,EAAA,EAAA,OAAA,CAAc,CAAY,EAC1B,EAAwB,OAAO,OAAO,EAA0C,CAAQ,EACxF,EAAkB,EAAyB,EAA0C,GAAY,IAAA,GAEjG,EAA0B,CAC9B,QACA,UACA,OAAQ,CACV,EAEI,GACF,OAAQ,EAA0C,GAClD,EAAE,MAAQ,EAAe,EAAiB,CAAK,GACtC,EAAG,aAAa,CAAQ,IACjC,EAAE,MAAQ,EAAM,EAAG,aAAa,CAAQ,CAAC,GAG3C,IAAM,EAAW,EAAa,IAAI,CAAE,EA4BpC,OA1BA,EAAA,UAAU,EAAU,mCAAmC,EAAG,UAAU,wCAAwC,EAC5G,EAAS,IAAI,EAAU,CAAI,EAE3B,OAAO,eAAe,EAAI,EAAU,CAClC,aAAc,GACd,WAAY,GACZ,QAAW,EAAE,MACb,IAAM,GAAa,CACjB,EAAE,MAAQ,EAAe,EAAO,CAAK,CACvC,CACF,CAAC,EAEG,GACF,EAAA,gBAAkB,CAChB,IAAM,EAAI,EAAE,MAER,GAAK,KACP,EAAG,gBAAgB,CAAQ,EAClB,OAAO,GAAM,UACtB,EAAG,gBAAgB,EAAU,CAAC,EAE9B,EAAA,QAAQ,EAAI,EAAU,CAAC,CAE3B,CAAC,EAGI,CACT,EAmBA,SAAgB,EAAqC,EAAiB,EAAwB,CAC5F,IAAM,EAAQ,CAAC,EAEf,IAAK,GAAM,CAAC,EAAM,KAAQ,OAAO,QAAQ,CAAI,EAI3C,EAAM,GAAQ,EAAa,EAAI,EAFd,EAAA,QAAQ,CAEY,EAAU,CAAuB,EAGxE,OAAO,CACT"}
|
package/dist/props.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAU,MAAM,kBAAkB,CAAC;AAOtE,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;KAEvD,CAAC,IAAI,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AACjH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;AAE7D;;;GAGG;AACH,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC5D,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,SAAS,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,SAAS,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACtH,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC5D,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACrE,CAAC;AAOF,eAAO,MAAM,IAAI,EAAE,WAuFlB,CAAC;AAmBF;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAwBvF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAYxE;AAED,KAAK,QAAQ,CAAC,CAAC,GAAG,OAAO,IAAI;IAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,CAAC,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CACnB,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,WAAW,EAAE,UAAU,MAAM,KAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,SAC/C,CAAC;
|
|
1
|
+
{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"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"}
|
package/dist/props.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{warn as e}from"./_dev.js";import{ORE_ERRORS as t,OreApiError as n}from"./errors.js";import{isStructuredValue as
|
|
1
|
+
import{warn as e}from"./_dev.js";import{ORE_ERRORS as t,OreApiError as n,invariant as r}from"./errors.js";import{isStructuredValue as i,setAttr as a,toKebab as o}from"./utils/dom.js";import{watchEffect as s}from"./runtime.js";import{signal as c}from"@vielzeug/ripple";function l(e){return{default:e,parse:()=>e,reflect:!1}}var u={bool(e){return{default:e??!1,parse:e=>e!==null&&e!==`false`,reflect:!0}},data(e){return l(e)},json(e){return{default:e,parse:t=>{if(t==null||t===``)return e;try{return JSON.parse(t)}catch{return e}},reflect:!1}},number(t){let n=t===void 0?void 0:t;return{default:n,parse:t=>{if(t==null)return n;let r=Number(t);return Number.isNaN(r)?(e(`prop.number(): attribute value "${t}" is not a valid number, using default (${String(n)})`),n):r},reflect:!0}},oneOf(e,t){return{default:t,parse:n=>n!=null&&e.includes(n)?n:t,reflect:!0}},string(e){let t=e===void 0?void 0:e;return{default:t,parse:e=>e??t,reflect:!0}}},d=e=>typeof e==`object`&&!!e&&`default`in e&&`parse`in e;function f(e,r){if(!d(e))throw new n(`Prop "${r}" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof e}`);let a=e;if(!a.parse)throw new n(`Prop "${r}" must have a parse function. Use prop.* helpers.`);let o=a.reflect??!1;if(o&&i(a.default))throw new n(`Prop "${r}": ${t.propInvalidReflect}`);return{...a,reflect:o}}function p(e){let t=[];for(let[n,r]of Object.entries(e))try{f(r,n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return t}var m=new WeakMap,h=(e,t)=>m.get(e)?.get(t),g=(e,t)=>typeof e==`string`?t(e):e,_=(e,t,n,i)=>{m.has(e)||m.set(e,new Map);let{default:o,parse:l,reflect:u=!1}=i,d=c(o),f=Object.hasOwn(e,t),p=f?e[t]:void 0,h={parse:l,reflect:u,signal:d};f?(delete e[t],d.value=g(p,l)):e.hasAttribute(n)&&(d.value=l(e.getAttribute(n)));let _=m.get(e);return r(_,`propRegistry entry missing for <${e.localName}> — registerProp() must create it above`),_.set(n,h),Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:()=>d.value,set:e=>{d.value=g(e,l)}}),u&&s(()=>{let t=d.value;t==null?e.removeAttribute(n):typeof t==`boolean`?e.toggleAttribute(n,t):a(e,n,t)}),d};function v(e,t){let n={};for(let[r,i]of Object.entries(t))n[r]=_(e,r,o(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
|
package/dist/props.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.js","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { OreApiError, ORE_ERRORS } from './errors';\nimport { effect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\n/**\n * Define a custom prop with explicit parser and reflection behavior.\n * Use when prop.* helpers don't cover your type.\n *\n * @example\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\ntype PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n if (!propRegistry.has(el)) propRegistry.set(el, new Map());\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n // Frameworks (e.g. Vue, when hydrating or patching an already-upgraded custom\n // element) may assign the pre-upgrade value as a plain string rather than calling\n // setAttribute — the same way it would for a real HTML attribute. Route strings\n // through the same parser an attribute value would use so e.g. size=\"16\" doesn't\n // end up stored as the string \"16\" instead of the number 16. Already-typed values\n // (objects, functions, booleans, numbers set deliberately as JS properties) pass\n // through untouched.\n s.value = (typeof preUpgradeValue === 'string' ? parse(preUpgradeValue) : preUpgradeValue) as T;\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n propRegistry.get(el)!.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n // A framework can still reach this setter with a raw attribute-style string after\n // the element has already upgraded — e.g. Vue reconciling server-rendered markup\n // against a custom element that auto-upgraded (from its SSR attribute) before\n // hydration ran: it finds the property already defined and assigns the vnode's\n // plain string prop value directly instead of calling setAttribute. Route strings\n // through the same parser an attribute value would use so it isn't stored raw.\n s.value = (typeof value === 'string' ? parse(value) : value) as T;\n },\n });\n\n if (reflect) {\n effect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"props.js","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { invariant, OreApiError, ORE_ERRORS } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\n/**\n * Define a custom prop with explicit parser and reflection behavior.\n * Use when prop.* helpers don't cover your type.\n *\n * @example\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\ntype PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\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 if (!propRegistry.has(el)) propRegistry.set(el, new Map());\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n const meta: PropMeta<unknown> = {\n parse,\n reflect,\n signal: s,\n };\n\n if (hasPreUpgradeProperty) {\n delete (el as unknown as Record<string, unknown>)[propName];\n s.value = coerceIncoming(preUpgradeValue, parse);\n } else if (el.hasAttribute(attrName)) {\n s.value = parse(el.getAttribute(attrName)) as T;\n }\n\n const registry = propRegistry.get(el);\n\n invariant(registry, `propRegistry entry missing for <${el.localName}> — registerProp() must create it above`);\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/runtime.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./utils/dom.cjs");let n=require("@vielzeug/ripple");var r=null,i=(e,t)=>{let n=r;r=e;try{return t()}finally{r=n}},a=
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./utils/dom.cjs");let n=require("@vielzeug/ripple");var r=null,i=(e,t)=>{let n=r;r=e;try{return t()}finally{r=n}},a=t=>{if(r)return r;throw new e.OreApiError(`${t}: ${e.ORE_ERRORS.lifecycleOutsideSetup}`)},o=()=>a(`getHost`).element,s=e=>r?((0,n.onCleanup)(e),!0):!1,c=n.onCleanup,l=e=>{a(`onMounted`).mountCallbacks.push(e)},u=e=>{a(`onFormReset`).formResetCallbacks.push(e)},d=e=>{let t=(0,n.effect)(e),r=()=>t.dispose();return s(r),r};function f(e,n,r,i){if(a(`onEvent`),!e)return;let o=t.listen(e,n,r,i);s(o)||o()}var p=(e,t)=>d(()=>{let n=e.value;if(n)return t(n)});exports.getHost=o,exports.onCleanup=c,exports.onElement=p,exports.onEvent=f,exports.onFormReset=u,exports.onMounted=l,exports.requireSetupContext=a,exports.runWithContext=i,exports.tryRegisterCleanup=s,exports.watchEffect=d;
|
|
2
2
|
//# sourceMappingURL=runtime.cjs.map
|
package/dist/runtime.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"runtime.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Component runtime — implicit \"current component\" context plus every lifecycle\n * hook a `setup()` function can call.\n *\n * Design note: hooks are plain module-level functions (not a bag/context object\n * passed into `setup`). They resolve the active component through a single\n * module-level pointer (`currentContext`), set for the duration of `setup()` and\n * of each queued `onMounted` callback. This is the same mechanism React/Vue/Solid\n * use for their composable hooks — it lets any helper function (not just the\n * top-level `setup()` body) call `onMounted`/`onCleanup`/`bind`/... directly,\n * with no context object to thread through every layer of a composable.\n */\nimport {\n type CleanupFn,\n effect as _effect,\n type EffectCallback,\n onCleanup as _onCleanup,\n type Readable,\n} from '@vielzeug/ripple';\n\nimport { OreApiError, ORE_ERRORS } from './errors';\nimport { listen as listenInternal } from './utils/dom';\n\n// ─── Runtime context ──────────────────────────────────────────────────────────\n// A single context object carries both the host element and mount callbacks,\n// eliminating two parallel globals that were always set together.\n\nexport type OnMountedCallback = () => CleanupFn | void;\nexport type OnFormResetCallback = () => void;\n\nexport type RuntimeContext = {\n element: HTMLElement;\n formResetCallbacks: OnFormResetCallback[];\n mountCallbacks: OnMountedCallback[];\n};\n\nlet currentContext: RuntimeContext | null = null;\n\n/** @internal Execute fn with a given runtime context active. */\nexport const runWithContext = <T>(ctx: RuntimeContext, fn: () => T): T => {\n const prev = currentContext;\n\n currentContext = ctx;\n\n try {\n return fn();\n } finally {\n currentContext = prev;\n }\n};\n\n/**\n * Returns the current runtime context, throwing a consistently-worded error\n * (naming the calling API) if called outside `setup()`. Every lifecycle/context\n * hook below routes through this — it's the single place that decides both\n * \"are we inside setup?\" and what the resulting error looks like, so the error\n * message is never worse for one hook than another.\n * @internal\n */\nexport const requireSetupContext = (api: string): RuntimeContext => {\n if (currentContext) return currentContext;\n\n throw new OreApiError(`${api}: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Returns the current component's host element.\n * Only valid synchronously during component `setup()` (or inside a composable\n * called from it) — throws otherwise.\n */\nexport const getHost = (): HTMLElement => requireSetupContext('getHost').element;\n\nexport const tryRegisterCleanup = (fn: CleanupFn): boolean => {\n if (!currentContext) return false;\n\n _onCleanup(fn);\n\n return true;\n};\n\n/**\n * Register a cleanup function to run on component disconnect.\n * Must be called synchronously during component setup or inside scope.run().\n */\nexport const onCleanup = _onCleanup;\n\n/**\n * Register work to run after the component template mounts to the DOM.\n * Multiple callbacks run in registration order.\n */\nexport const onMounted = (fn: OnMountedCallback): void => {\n requireSetupContext('onMounted').mountCallbacks.push(fn);\n};\n\n/**\n * Register work to run when the ancestor `<form>` is reset (native `formResetCallback`,\n * only fires for `formAssociated: true` components). Multiple callbacks run in\n * registration order, every time the form resets — unlike `onMounted`, this isn't a\n * one-shot hook.\n */\nexport const onFormReset = (fn: OnFormResetCallback): void => {\n requireSetupContext('onFormReset').formResetCallbacks.push(fn);\n};\n\n/**\n * Create a reactive effect scoped to the component lifecycle.\n * Automatically cleaned up on component disconnect.\n * Returns a stop function that disposes the effect immediately.\n *\n * Named `watchEffect` (not `watch`) to avoid shadowing `@vielzeug/ripple`'s\n * `watch(source, callback)`, which has different semantics (explicit source,\n * old/new value pair) — the two are commonly imported in the same file.\n */\nexport const watchEffect = (fn: EffectCallback): (() => void) => {\n const sub = _effect(fn);\n const stop = (): void => sub.dispose();\n\n tryRegisterCleanup(stop);\n\n return stop;\n};\n\n/**\n * Attach a scoped event listener that is automatically removed on component disconnect.\n * Silently no-ops when `target` is `null` or `undefined` (safe for reactive targets).\n */\nexport function onEvent<K extends keyof HTMLElementEventMap>(\n target: EventTarget | null | undefined,\n event: K,\n listener: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n): void;\nexport function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void {\n requireSetupContext('onEvent');\n\n if (!target) return;\n\n const cleanup = listenInternal(target, event, listener, options);\n\n if (!tryRegisterCleanup(cleanup)) cleanup();\n}\n\n/**\n * Watch a ref signal and run a callback when it resolves to a non-null element.\n * The callback's return value is used as a cleanup function.\n */\nexport const onElement = <T extends HTMLElement>(\n ref: Readable<T | null>,\n callback: (el: T) => CleanupFn | undefined | void,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"+FAoCA,IAAI,EAAwC,KAG/B,GAAqB,EAAqB,IAAmB,CACxE,IAAM,EAAO,EAEb,EAAiB,EAEjB,GAAI,CACF,OAAO,EAAG,CACZ,QAAU,CACR,EAAiB,CACnB,CACF,EAUa,EAAuB,GAAgC,CAClE,GAAI,EAAgB,OAAO,EAE3B,MAAM,IAAI,EAAA,YAAY,GAAG,EAAI,IAAI,EAAA,WAAW,uBAAuB,CACrE,EAOa,MAA6B,EAAoB,SAAS,CAAC,CAAC,QAE5D,EAAsB,GAC5B,IAEL,EAAA,EAAA,UAAA,CAAW,CAAE,EAEN,IAJqB,GAWjB,EAAY,EAAA,UAMZ,EAAa,GAAgC,CACxD,EAAoB,WAAW,CAAC,CAAC,eAAe,KAAK,CAAE,CACzD,EAQa,EAAe,GAAkC,CAC5D,EAAoB,aAAa,CAAC,CAAC,mBAAmB,KAAK,CAAE,CAC/D,EAWa,EAAe,GAAqC,CAC/D,IAAM,GAAA,EAAA,EAAA,OAAA,CAAc,CAAE,EAChB,MAAmB,EAAI,QAAQ,EAIrC,OAFA,EAAmB,CAAI,EAEhB,CACT,EAYA,SAAgB,EACd,EACA,EACA,EACA,EACM,CAGN,GAFA,EAAoB,SAAS,EAEzB,CAAC,EAAQ,OAEb,IAAM,EAAU,EAAA,OAAe,EAAQ,EAAO,EAAU,CAAO,EAE1D,EAAmB,CAAO,GAAG,EAAQ,CAC5C,CAMA,IAAa,GACX,EACA,IAEO,MAAkB,CACvB,IAAM,EAAK,EAAI,MAEf,GAAI,EAAI,OAAO,EAAS,CAAE,CAC5B,CAAC"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component runtime — implicit "current component" context plus every lifecycle
|
|
3
|
+
* hook a `setup()` function can call.
|
|
4
|
+
*
|
|
5
|
+
* Design note: hooks are plain module-level functions (not a bag/context object
|
|
6
|
+
* passed into `setup`). They resolve the active component through a single
|
|
7
|
+
* module-level pointer (`currentContext`), set for the duration of `setup()` and
|
|
8
|
+
* of each queued `onMounted` callback. This is the same mechanism React/Vue/Solid
|
|
9
|
+
* use for their composable hooks — it lets any helper function (not just the
|
|
10
|
+
* top-level `setup()` body) call `onMounted`/`onCleanup`/`bind`/... directly,
|
|
11
|
+
* with no context object to thread through every layer of a composable.
|
|
12
|
+
*/
|
|
1
13
|
import { type CleanupFn, type EffectCallback, type Readable } from '@vielzeug/ripple';
|
|
2
14
|
export type OnMountedCallback = () => CleanupFn | void;
|
|
15
|
+
export type OnFormResetCallback = () => void;
|
|
3
16
|
export type RuntimeContext = {
|
|
4
17
|
element: HTMLElement;
|
|
18
|
+
formResetCallbacks: OnFormResetCallback[];
|
|
5
19
|
mountCallbacks: OnMountedCallback[];
|
|
6
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Returns the current component's host element.
|
|
23
|
+
* Only valid synchronously during component `setup()` (or inside a composable
|
|
24
|
+
* called from it) — throws otherwise.
|
|
25
|
+
*/
|
|
26
|
+
export declare const getHost: () => HTMLElement;
|
|
7
27
|
export declare const tryRegisterCleanup: (fn: CleanupFn) => boolean;
|
|
8
28
|
/**
|
|
9
29
|
* Register a cleanup function to run on component disconnect.
|
|
@@ -15,12 +35,23 @@ export declare const onCleanup: (fn: CleanupFn) => void;
|
|
|
15
35
|
* Multiple callbacks run in registration order.
|
|
16
36
|
*/
|
|
17
37
|
export declare const onMounted: (fn: OnMountedCallback) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Register work to run when the ancestor `<form>` is reset (native `formResetCallback`,
|
|
40
|
+
* only fires for `formAssociated: true` components). Multiple callbacks run in
|
|
41
|
+
* registration order, every time the form resets — unlike `onMounted`, this isn't a
|
|
42
|
+
* one-shot hook.
|
|
43
|
+
*/
|
|
44
|
+
export declare const onFormReset: (fn: OnFormResetCallback) => void;
|
|
18
45
|
/**
|
|
19
46
|
* Create a reactive effect scoped to the component lifecycle.
|
|
20
47
|
* Automatically cleaned up on component disconnect.
|
|
21
48
|
* Returns a stop function that disposes the effect immediately.
|
|
49
|
+
*
|
|
50
|
+
* Named `watchEffect` (not `watch`) to avoid shadowing `@vielzeug/ripple`'s
|
|
51
|
+
* `watch(source, callback)`, which has different semantics (explicit source,
|
|
52
|
+
* old/new value pair) — the two are commonly imported in the same file.
|
|
22
53
|
*/
|
|
23
|
-
export declare const
|
|
54
|
+
export declare const watchEffect: (fn: EffectCallback) => (() => void);
|
|
24
55
|
/**
|
|
25
56
|
* Attach a scoped event listener that is automatically removed on component disconnect.
|
|
26
57
|
* Silently no-ops when `target` is `null` or `undefined` (safe for reactive targets).
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,cAAc,EAEnB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAC;AAS1B,MAAM,MAAM,iBAAiB,GAAG,MAAM,SAAS,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,cAAc,EAEnB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAC;AAS1B,MAAM,MAAM,iBAAiB,GAAG,MAAM,SAAS,GAAG,IAAI,CAAC;AACvD,MAAM,MAAM,mBAAmB,GAAG,MAAM,IAAI,CAAC;AAE7C,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,WAAW,CAAC;IACrB,kBAAkB,EAAE,mBAAmB,EAAE,CAAC;IAC1C,cAAc,EAAE,iBAAiB,EAAE,CAAC;CACrC,CAAC;AA+BF;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAO,WAAqD,CAAC;AAEjF,eAAO,MAAM,kBAAkB,GAAI,IAAI,SAAS,KAAG,OAMlD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,yBAAa,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,IAAI,iBAAiB,KAAG,IAEjD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,mBAAmB,KAAG,IAErD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,cAAc,KAAG,CAAC,MAAM,IAAI,CAO3D,CAAC;AAEF;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,mBAAmB,EACzD,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,EACtC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7C,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAAC;AAgBR;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,WAAW,EAC7C,KAAK,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,EACvB,UAAU,CAAC,EAAE,EAAE,CAAC,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI,KAChD,CAAC,MAAM,IAAI,CAMb,CAAC"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{listen as n}from"./utils/dom.js";import{effect as r,onCleanup as i}from"@vielzeug/ripple";var a=null,o=(e,t)=>{let n=a;a=e;try{return t()}finally{a=n}},s=
|
|
1
|
+
import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{listen as n}from"./utils/dom.js";import{effect as r,onCleanup as i}from"@vielzeug/ripple";var a=null,o=(e,t)=>{let n=a;a=e;try{return t()}finally{a=n}},s=n=>{if(a)return a;throw new t(`${n}: ${e.lifecycleOutsideSetup}`)},c=()=>s(`getHost`).element,l=e=>a?(i(e),!0):!1,u=i,d=e=>{s(`onMounted`).mountCallbacks.push(e)},f=e=>{s(`onFormReset`).formResetCallbacks.push(e)},p=e=>{let t=r(e),n=()=>t.dispose();return l(n),n};function m(e,t,r,i){if(s(`onEvent`),!e)return;let a=n(e,t,r,i);l(a)||a()}var h=(e,t)=>p(()=>{let n=e.value;if(n)return t(n)});export{c as getHost,u as onCleanup,h as onElement,m as onEvent,f as onFormReset,d as onMounted,s as requireSetupContext,o as runWithContext,l as tryRegisterCleanup,p as watchEffect};
|
|
2
2
|
//# sourceMappingURL=runtime.js.map
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"runtime.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Component runtime — implicit \"current component\" context plus every lifecycle\n * hook a `setup()` function can call.\n *\n * Design note: hooks are plain module-level functions (not a bag/context object\n * passed into `setup`). They resolve the active component through a single\n * module-level pointer (`currentContext`), set for the duration of `setup()` and\n * of each queued `onMounted` callback. This is the same mechanism React/Vue/Solid\n * use for their composable hooks — it lets any helper function (not just the\n * top-level `setup()` body) call `onMounted`/`onCleanup`/`bind`/... directly,\n * with no context object to thread through every layer of a composable.\n */\nimport {\n type CleanupFn,\n effect as _effect,\n type EffectCallback,\n onCleanup as _onCleanup,\n type Readable,\n} from '@vielzeug/ripple';\n\nimport { OreApiError, ORE_ERRORS } from './errors';\nimport { listen as listenInternal } from './utils/dom';\n\n// ─── Runtime context ──────────────────────────────────────────────────────────\n// A single context object carries both the host element and mount callbacks,\n// eliminating two parallel globals that were always set together.\n\nexport type OnMountedCallback = () => CleanupFn | void;\nexport type OnFormResetCallback = () => void;\n\nexport type RuntimeContext = {\n element: HTMLElement;\n formResetCallbacks: OnFormResetCallback[];\n mountCallbacks: OnMountedCallback[];\n};\n\nlet currentContext: RuntimeContext | null = null;\n\n/** @internal Execute fn with a given runtime context active. */\nexport const runWithContext = <T>(ctx: RuntimeContext, fn: () => T): T => {\n const prev = currentContext;\n\n currentContext = ctx;\n\n try {\n return fn();\n } finally {\n currentContext = prev;\n }\n};\n\n/**\n * Returns the current runtime context, throwing a consistently-worded error\n * (naming the calling API) if called outside `setup()`. Every lifecycle/context\n * hook below routes through this — it's the single place that decides both\n * \"are we inside setup?\" and what the resulting error looks like, so the error\n * message is never worse for one hook than another.\n * @internal\n */\nexport const requireSetupContext = (api: string): RuntimeContext => {\n if (currentContext) return currentContext;\n\n throw new OreApiError(`${api}: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\n\n/**\n * Returns the current component's host element.\n * Only valid synchronously during component `setup()` (or inside a composable\n * called from it) — throws otherwise.\n */\nexport const getHost = (): HTMLElement => requireSetupContext('getHost').element;\n\nexport const tryRegisterCleanup = (fn: CleanupFn): boolean => {\n if (!currentContext) return false;\n\n _onCleanup(fn);\n\n return true;\n};\n\n/**\n * Register a cleanup function to run on component disconnect.\n * Must be called synchronously during component setup or inside scope.run().\n */\nexport const onCleanup = _onCleanup;\n\n/**\n * Register work to run after the component template mounts to the DOM.\n * Multiple callbacks run in registration order.\n */\nexport const onMounted = (fn: OnMountedCallback): void => {\n requireSetupContext('onMounted').mountCallbacks.push(fn);\n};\n\n/**\n * Register work to run when the ancestor `<form>` is reset (native `formResetCallback`,\n * only fires for `formAssociated: true` components). Multiple callbacks run in\n * registration order, every time the form resets — unlike `onMounted`, this isn't a\n * one-shot hook.\n */\nexport const onFormReset = (fn: OnFormResetCallback): void => {\n requireSetupContext('onFormReset').formResetCallbacks.push(fn);\n};\n\n/**\n * Create a reactive effect scoped to the component lifecycle.\n * Automatically cleaned up on component disconnect.\n * Returns a stop function that disposes the effect immediately.\n *\n * Named `watchEffect` (not `watch`) to avoid shadowing `@vielzeug/ripple`'s\n * `watch(source, callback)`, which has different semantics (explicit source,\n * old/new value pair) — the two are commonly imported in the same file.\n */\nexport const watchEffect = (fn: EffectCallback): (() => void) => {\n const sub = _effect(fn);\n const stop = (): void => sub.dispose();\n\n tryRegisterCleanup(stop);\n\n return stop;\n};\n\n/**\n * Attach a scoped event listener that is automatically removed on component disconnect.\n * Silently no-ops when `target` is `null` or `undefined` (safe for reactive targets).\n */\nexport function onEvent<K extends keyof HTMLElementEventMap>(\n target: EventTarget | null | undefined,\n event: K,\n listener: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n): void;\nexport function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void {\n requireSetupContext('onEvent');\n\n if (!target) return;\n\n const cleanup = listenInternal(target, event, listener, options);\n\n if (!tryRegisterCleanup(cleanup)) cleanup();\n}\n\n/**\n * Watch a ref signal and run a callback when it resolves to a non-null element.\n * The callback's return value is used as a cleanup function.\n */\nexport const onElement = <T extends HTMLElement>(\n ref: Readable<T | null>,\n callback: (el: T) => CleanupFn | undefined | void,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"2JAoCA,IAAI,EAAwC,KAG/B,GAAqB,EAAqB,IAAmB,CACxE,IAAM,EAAO,EAEb,EAAiB,EAEjB,GAAI,CACF,OAAO,EAAG,CACZ,QAAU,CACR,EAAiB,CACnB,CACF,EAUa,EAAuB,GAAgC,CAClE,GAAI,EAAgB,OAAO,EAE3B,MAAM,IAAI,EAAY,GAAG,EAAI,IAAI,EAAW,uBAAuB,CACrE,EAOa,MAA6B,EAAoB,SAAS,CAAC,CAAC,QAE5D,EAAsB,GAC5B,GAEL,EAAW,CAAE,EAEN,IAJqB,GAWjB,EAAY,EAMZ,EAAa,GAAgC,CACxD,EAAoB,WAAW,CAAC,CAAC,eAAe,KAAK,CAAE,CACzD,EAQa,EAAe,GAAkC,CAC5D,EAAoB,aAAa,CAAC,CAAC,mBAAmB,KAAK,CAAE,CAC/D,EAWa,EAAe,GAAqC,CAC/D,IAAM,EAAM,EAAQ,CAAE,EAChB,MAAmB,EAAI,QAAQ,EAIrC,OAFA,EAAmB,CAAI,EAEhB,CACT,EAYA,SAAgB,EACd,EACA,EACA,EACA,EACM,CAGN,GAFA,EAAoB,SAAS,EAEzB,CAAC,EAAQ,OAEb,IAAM,EAAU,EAAe,EAAQ,EAAO,EAAU,CAAO,EAE1D,EAAmB,CAAO,GAAG,EAAQ,CAC5C,CAMA,IAAa,GACX,EACA,IAEO,MAAkB,CACvB,IAAM,EAAK,EAAI,MAEf,GAAI,EAAI,OAAO,EAAS,CAAE,CAC5B,CAAC"}
|
package/dist/slots.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./runtime.cjs");let t=require("@vielzeug/ripple");var n=`default`,r=e=>e||n,i=n=>{let i=new Map,a=new Map,o=new Map,s=e=>{let n=i.get(e);return n||(n={elements:(0,t.signal)([]),presence:(0,t.signal)(!1)},i.set(e,n)),n},c=(e,t)=>{if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0},l=e=>{let t=r(e),n=a.get(t),i=[];if(n)for(let e of n)i.push(...e.assignedElements({flatten:!0}));let o=s(t);c(o.elements.value,i)||(o.elements.value=i);let l=i.length>0;o.presence.value!==l&&(o.presence.value=l)},u=e=>{if(o.has(e))return;let t=r(e.getAttribute(`name`)),n=a.get(t)??new Set;n.add(e),a.set(t,n);let i=()=>l(t);e.addEventListener(`slotchange`,i),o.set(e,()=>{e.removeEventListener(`slotchange`,i)}),l(t)},d=e=>{let t=o.get(e);if(!t)return;t(),o.delete(e);let n=r(e.getAttribute(`name`)),i=a.get(n);i&&(i.delete(e),i.size===0&&a.delete(n)),l(n)},f=()=>{n.shadowRoot?.querySelectorAll(`slot`).forEach(e=>u(e))},p=()=>{for(let e of a.keys())l(e)},m=null;return n.shadowRoot&&(m=new MutationObserver(e=>{for(let t of e)for(let e of t.removedNodes)e instanceof HTMLSlotElement&&d(e);f(),p()}),m.observe(n.shadowRoot,{childList:!0,subtree:!0})),f(),e.onMounted(()=>{f(),p()}),e.onCleanup(()=>{m?.disconnect(),m=null;for(let e of o.values())e();o.clear(),a.clear(),i.clear()}),{elements:e=>s(r(e)).elements,has:e=>s(r(e)).presence}};exports.
|
|
1
|
+
const e=require("./runtime.cjs");let t=require("@vielzeug/ripple");var n=`default`,r=e=>e||n,i=n=>{let i=new Map,a=new Map,o=new Map,s=e=>{let n=i.get(e);return n||(n={elements:(0,t.signal)([]),presence:(0,t.signal)(!1)},i.set(e,n)),n},c=(e,t)=>{if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0},l=e=>{let t=r(e),n=a.get(t),i=[];if(n)for(let e of n)i.push(...e.assignedElements({flatten:!0}));let o=s(t);c(o.elements.value,i)||(o.elements.value=i);let l=i.length>0;o.presence.value!==l&&(o.presence.value=l)},u=e=>{if(o.has(e))return;let t=r(e.getAttribute(`name`)),n=a.get(t)??new Set;n.add(e),a.set(t,n);let i=()=>l(t);e.addEventListener(`slotchange`,i),o.set(e,()=>{e.removeEventListener(`slotchange`,i)}),l(t)},d=e=>{let t=o.get(e);if(!t)return;t(),o.delete(e);let n=r(e.getAttribute(`name`)),i=a.get(n);i&&(i.delete(e),i.size===0&&a.delete(n)),l(n)},f=()=>{n.shadowRoot?.querySelectorAll(`slot`).forEach(e=>u(e))},p=()=>{for(let e of a.keys())l(e)},m=null;return n.shadowRoot&&(m=new MutationObserver(e=>{for(let t of e)for(let e of t.removedNodes)e instanceof HTMLSlotElement&&d(e);f(),p()}),m.observe(n.shadowRoot,{childList:!0,subtree:!0})),f(),e.onMounted(()=>{f(),p()}),e.onCleanup(()=>{m?.disconnect(),m=null;for(let e of o.values())e();o.clear(),a.clear(),i.clear()}),{elements:e=>s(r(e)).elements,has:e=>s(r(e)).presence}},a=new WeakMap,o=()=>{let t=e.requireSetupContext(`useSlots`),n=a.get(t);return n||(n=i(t.element),a.set(t,n)),n};exports.useSlots=o;
|
|
2
2
|
//# sourceMappingURL=slots.cjs.map
|
package/dist/slots.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slots.cjs","names":[],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean> — whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]> — assigned elements for a slot (flattened).\n */\n\nimport { type Readable, signal, type Signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n elements: (name?: SlotNames) => Readable<Element[]>;\n has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\
|
|
1
|
+
{"version":3,"file":"slots.cjs","names":[],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean> — whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]> — assigned elements for a slot (flattened).\n */\n\nimport { type Readable, signal, type Signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, type RuntimeContext, requireSetupContext } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n elements: (name?: SlotNames) => Readable<Element[]>;\n has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\nconst createSlots = (host: HTMLElement): ComponentSlots<string> => {\n type SlotEntry = {\n elements: Signal<Element[]>;\n presence: Signal<boolean>;\n };\n\n const slotSignals = new Map<string, SlotEntry>();\n const slotNodesByName = new Map<string, Set<HTMLSlotElement>>();\n const slotCleanupMap = new Map<HTMLSlotElement, () => void>();\n\n const ensureSlotEntry = (normalizedName: string): SlotEntry => {\n let entry = slotSignals.get(normalizedName);\n\n if (!entry) {\n entry = {\n elements: signal<Element[]>([]),\n presence: signal(false),\n };\n slotSignals.set(normalizedName, entry);\n }\n\n return entry;\n };\n\n const areElementsEqual = (prev: Element[], next: Element[]): boolean => {\n if (prev.length !== next.length) return false;\n\n for (let i = 0; i < prev.length; i++) {\n if (prev[i] !== next[i]) return false;\n }\n\n return true;\n };\n\n const recomputeSlot = (name: string): void => {\n const normalized = normalizeSlotName(name);\n const slotsForName = slotNodesByName.get(normalized);\n const assigned: Element[] = [];\n\n if (slotsForName) {\n for (const slotEl of slotsForName) {\n assigned.push(...slotEl.assignedElements({ flatten: true }));\n }\n }\n\n const entry = ensureSlotEntry(normalized);\n\n if (!areElementsEqual(entry.elements.value, assigned)) entry.elements.value = assigned;\n\n const hasElements = assigned.length > 0;\n\n if (entry.presence.value !== hasElements) entry.presence.value = hasElements;\n };\n\n const bindSlot = (slotEl: HTMLSlotElement): void => {\n if (slotCleanupMap.has(slotEl)) return;\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name) ?? new Set<HTMLSlotElement>();\n\n setForName.add(slotEl);\n slotNodesByName.set(name, setForName);\n\n const onChange = () => recomputeSlot(name);\n\n slotEl.addEventListener('slotchange', onChange);\n\n slotCleanupMap.set(slotEl, () => {\n slotEl.removeEventListener('slotchange', onChange);\n });\n\n recomputeSlot(name);\n };\n\n const unbindSlot = (slotEl: HTMLSlotElement): void => {\n const cleanup = slotCleanupMap.get(slotEl);\n\n if (!cleanup) return;\n\n cleanup();\n slotCleanupMap.delete(slotEl);\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name);\n\n if (setForName) {\n setForName.delete(slotEl);\n\n if (setForName.size === 0) slotNodesByName.delete(name);\n }\n\n recomputeSlot(name);\n };\n\n const bindAllSlots = (): void => {\n host.shadowRoot?.querySelectorAll('slot').forEach((slotEl) => bindSlot(slotEl));\n };\n\n const recomputeAllSlots = (): void => {\n for (const name of slotNodesByName.keys()) {\n recomputeSlot(name);\n }\n };\n\n // Watch for dynamically-inserted <slot> elements (e.g. inside when(), each()).\n let observer: MutationObserver | null = null;\n\n // Start MutationObserver immediately in setup so any slots inserted synchronously\n // before the first render are captured without a timing gap.\n if (host.shadowRoot) {\n observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.removedNodes) {\n if (node instanceof HTMLSlotElement) unbindSlot(node);\n }\n }\n\n bindAllSlots();\n recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n\n // Bind slots already present in the shadow root (pre-upgrade scenarios)\n // and schedule a post-render pass to pick up slots injected by the template.\n bindAllSlots();\n onMounted(() => {\n bindAllSlots();\n recomputeAllSlots();\n });\n\n onCleanup(() => {\n observer?.disconnect();\n observer = null;\n\n for (const cleanup of slotCleanupMap.values()) cleanup();\n\n slotCleanupMap.clear();\n slotNodesByName.clear();\n slotSignals.clear();\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n/** One slot registry per component instance — reused across repeated `useSlots()` calls. */\nconst slotsByContext = new WeakMap<RuntimeContext, ComponentSlots<string>>();\n\n/**\n * Returns reactive slot presence / element signals for the current component.\n * Safe to call multiple times during `setup()` — the underlying slot registry\n * (MutationObserver + `slotchange` listeners) is created once per instance.\n *\n * Pass a `SlotNames` type parameter for typed slot names:\n * ```ts\n * const slots = useSlots<'header' | 'footer'>();\n * slots.has('header'); // typed ✓\n * ```\n */\nexport const useSlots = <SlotNames extends string = string>(): ComponentSlots<SlotNames> => {\n const ctx = requireSetupContext('useSlots');\n let entry = slotsByContext.get(ctx);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByContext.set(ctx, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
|
package/dist/slots.d.ts
CHANGED
|
@@ -9,5 +9,16 @@ export type ComponentSlots<SlotNames extends string = string> = {
|
|
|
9
9
|
elements: (name?: SlotNames) => Readable<Element[]>;
|
|
10
10
|
has: (name?: SlotNames) => Readable<boolean>;
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Returns reactive slot presence / element signals for the current component.
|
|
14
|
+
* Safe to call multiple times during `setup()` — the underlying slot registry
|
|
15
|
+
* (MutationObserver + `slotchange` listeners) is created once per instance.
|
|
16
|
+
*
|
|
17
|
+
* Pass a `SlotNames` type parameter for typed slot names:
|
|
18
|
+
* ```ts
|
|
19
|
+
* const slots = useSlots<'header' | 'footer'>();
|
|
20
|
+
* slots.has('header'); // typed ✓
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare const useSlots: <SlotNames extends string = string>() => ComponentSlots<SlotNames>;
|
|
13
24
|
//# sourceMappingURL=slots.d.ts.map
|
package/dist/slots.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../src/slots.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,kBAAkB,CAAC;AAItE,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IAC9D,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpD,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,QAAQ,CAAC,OAAO,CAAC,CAAC;CAC9C,CAAC;
|
|
1
|
+
{"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../src/slots.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,kBAAkB,CAAC;AAItE,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IAC9D,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpD,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,QAAQ,CAAC,OAAO,CAAC,CAAC;CAC9C,CAAC;AA4JF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,GAAI,SAAS,SAAS,MAAM,GAAG,MAAM,OAAK,cAAc,CAAC,SAAS,CAUtF,CAAC"}
|
package/dist/slots.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{onCleanup as e,onMounted as t}from"./runtime.js";import{signal as
|
|
1
|
+
import{onCleanup as e,onMounted as t,requireSetupContext as n}from"./runtime.js";import{signal as r}from"@vielzeug/ripple";var i=`default`,a=e=>e||i,o=n=>{let i=new Map,o=new Map,s=new Map,c=e=>{let t=i.get(e);return t||(t={elements:r([]),presence:r(!1)},i.set(e,t)),t},l=(e,t)=>{if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0},u=e=>{let t=a(e),n=o.get(t),r=[];if(n)for(let e of n)r.push(...e.assignedElements({flatten:!0}));let i=c(t);l(i.elements.value,r)||(i.elements.value=r);let s=r.length>0;i.presence.value!==s&&(i.presence.value=s)},d=e=>{if(s.has(e))return;let t=a(e.getAttribute(`name`)),n=o.get(t)??new Set;n.add(e),o.set(t,n);let r=()=>u(t);e.addEventListener(`slotchange`,r),s.set(e,()=>{e.removeEventListener(`slotchange`,r)}),u(t)},f=e=>{let t=s.get(e);if(!t)return;t(),s.delete(e);let n=a(e.getAttribute(`name`)),r=o.get(n);r&&(r.delete(e),r.size===0&&o.delete(n)),u(n)},p=()=>{n.shadowRoot?.querySelectorAll(`slot`).forEach(e=>d(e))},m=()=>{for(let e of o.keys())u(e)},h=null;return n.shadowRoot&&(h=new MutationObserver(e=>{for(let t of e)for(let e of t.removedNodes)e instanceof HTMLSlotElement&&f(e);p(),m()}),h.observe(n.shadowRoot,{childList:!0,subtree:!0})),p(),t(()=>{p(),m()}),e(()=>{h?.disconnect(),h=null;for(let e of s.values())e();s.clear(),o.clear(),i.clear()}),{elements:e=>c(a(e)).elements,has:e=>c(a(e)).presence}},s=new WeakMap,c=()=>{let e=n(`useSlots`),t=s.get(e);return t||(t=o(e.element),s.set(e,t)),t};export{c as useSlots};
|
|
2
2
|
//# sourceMappingURL=slots.js.map
|
package/dist/slots.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slots.js","names":[],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean> — whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]> — assigned elements for a slot (flattened).\n */\n\nimport { type Readable, signal, type Signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n elements: (name?: SlotNames) => Readable<Element[]>;\n has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\
|
|
1
|
+
{"version":3,"file":"slots.js","names":[],"sources":["../src/slots.ts"],"sourcesContent":["/**\n * Slot observation and reactive slot signals.\n *\n * `slots.has(name?)`: Signal<boolean> — whether a named slot has assigned elements.\n * `slots.elements(name?)`: Signal<Element[]> — assigned elements for a slot (flattened).\n */\n\nimport { type Readable, signal, type Signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, type RuntimeContext, requireSetupContext } from './runtime';\n\nexport type ComponentSlots<SlotNames extends string = string> = {\n elements: (name?: SlotNames) => Readable<Element[]>;\n has: (name?: SlotNames) => Readable<boolean>;\n};\n\nconst SLOT_DEFAULT = 'default';\nconst normalizeSlotName = (slotName: string | null | undefined): string => slotName || SLOT_DEFAULT;\n\nconst createSlots = (host: HTMLElement): ComponentSlots<string> => {\n type SlotEntry = {\n elements: Signal<Element[]>;\n presence: Signal<boolean>;\n };\n\n const slotSignals = new Map<string, SlotEntry>();\n const slotNodesByName = new Map<string, Set<HTMLSlotElement>>();\n const slotCleanupMap = new Map<HTMLSlotElement, () => void>();\n\n const ensureSlotEntry = (normalizedName: string): SlotEntry => {\n let entry = slotSignals.get(normalizedName);\n\n if (!entry) {\n entry = {\n elements: signal<Element[]>([]),\n presence: signal(false),\n };\n slotSignals.set(normalizedName, entry);\n }\n\n return entry;\n };\n\n const areElementsEqual = (prev: Element[], next: Element[]): boolean => {\n if (prev.length !== next.length) return false;\n\n for (let i = 0; i < prev.length; i++) {\n if (prev[i] !== next[i]) return false;\n }\n\n return true;\n };\n\n const recomputeSlot = (name: string): void => {\n const normalized = normalizeSlotName(name);\n const slotsForName = slotNodesByName.get(normalized);\n const assigned: Element[] = [];\n\n if (slotsForName) {\n for (const slotEl of slotsForName) {\n assigned.push(...slotEl.assignedElements({ flatten: true }));\n }\n }\n\n const entry = ensureSlotEntry(normalized);\n\n if (!areElementsEqual(entry.elements.value, assigned)) entry.elements.value = assigned;\n\n const hasElements = assigned.length > 0;\n\n if (entry.presence.value !== hasElements) entry.presence.value = hasElements;\n };\n\n const bindSlot = (slotEl: HTMLSlotElement): void => {\n if (slotCleanupMap.has(slotEl)) return;\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name) ?? new Set<HTMLSlotElement>();\n\n setForName.add(slotEl);\n slotNodesByName.set(name, setForName);\n\n const onChange = () => recomputeSlot(name);\n\n slotEl.addEventListener('slotchange', onChange);\n\n slotCleanupMap.set(slotEl, () => {\n slotEl.removeEventListener('slotchange', onChange);\n });\n\n recomputeSlot(name);\n };\n\n const unbindSlot = (slotEl: HTMLSlotElement): void => {\n const cleanup = slotCleanupMap.get(slotEl);\n\n if (!cleanup) return;\n\n cleanup();\n slotCleanupMap.delete(slotEl);\n\n const name = normalizeSlotName(slotEl.getAttribute('name'));\n const setForName = slotNodesByName.get(name);\n\n if (setForName) {\n setForName.delete(slotEl);\n\n if (setForName.size === 0) slotNodesByName.delete(name);\n }\n\n recomputeSlot(name);\n };\n\n const bindAllSlots = (): void => {\n host.shadowRoot?.querySelectorAll('slot').forEach((slotEl) => bindSlot(slotEl));\n };\n\n const recomputeAllSlots = (): void => {\n for (const name of slotNodesByName.keys()) {\n recomputeSlot(name);\n }\n };\n\n // Watch for dynamically-inserted <slot> elements (e.g. inside when(), each()).\n let observer: MutationObserver | null = null;\n\n // Start MutationObserver immediately in setup so any slots inserted synchronously\n // before the first render are captured without a timing gap.\n if (host.shadowRoot) {\n observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.removedNodes) {\n if (node instanceof HTMLSlotElement) unbindSlot(node);\n }\n }\n\n bindAllSlots();\n recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n\n // Bind slots already present in the shadow root (pre-upgrade scenarios)\n // and schedule a post-render pass to pick up slots injected by the template.\n bindAllSlots();\n onMounted(() => {\n bindAllSlots();\n recomputeAllSlots();\n });\n\n onCleanup(() => {\n observer?.disconnect();\n observer = null;\n\n for (const cleanup of slotCleanupMap.values()) cleanup();\n\n slotCleanupMap.clear();\n slotNodesByName.clear();\n slotSignals.clear();\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n/** One slot registry per component instance — reused across repeated `useSlots()` calls. */\nconst slotsByContext = new WeakMap<RuntimeContext, ComponentSlots<string>>();\n\n/**\n * Returns reactive slot presence / element signals for the current component.\n * Safe to call multiple times during `setup()` — the underlying slot registry\n * (MutationObserver + `slotchange` listeners) is created once per instance.\n *\n * Pass a `SlotNames` type parameter for typed slot names:\n * ```ts\n * const slots = useSlots<'header' | 'footer'>();\n * slots.has('header'); // typed ✓\n * ```\n */\nexport const useSlots = <SlotNames extends string = string>(): ComponentSlots<SlotNames> => {\n const ctx = requireSetupContext('useSlots');\n let entry = slotsByContext.get(ctx);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByContext.set(ctx, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../types/bindings.cjs"),
|
|
1
|
+
const e=require("../errors.cjs"),t=require("../types/bindings.cjs"),n=require("../utils/dom.cjs"),r=require("../directives/live.cjs"),i=require("../props.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.value,c=(e,t)=>{o(e.signal,t=>{e.node.textContent=String(t??``)},t)},l=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,u=(e,t,n,r)=>{let i=t==null?``:String(t);n&&r.last!==void 0&&!Object.is(e.value,r.last)&&!Object.is(e.value,i)||(e.value=i,n&&(r.last=i))},d=(e,t,n,r)=>{let i=!!t;n&&r.last!==void 0&&e.checked!==!!r.last&&e.checked!==i||(e.checked=i,n&&(r.last=i))},f=(e,t,r,i)=>{let o=n.isStructuredValue(i)?i:t.parse(r.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(n.isStructuredValue(i))return;r.mode===`bool`?e.toggleAttribute(r.name,!!i):n.setAttr(e,r.name,i)}},p=(e,t)=>{let{el:r,mode:i,name:s,propMeta:c}=e,p={last:void 0},m=t=>{if(c){f(r,c,e,t);return}if(!(0,a.isReactive)(t)&&n.isStructuredValue(t)){s!==`__proto__`&&s!==`constructor`&&s!==`prototype`&&(r[s]=t);return}if(s===`value`&&l(r)){u(r,t,e.live,p);return}if(s===`checked`&&r instanceof HTMLInputElement){d(r,t,e.live,p);return}i===`bool`?r.toggleAttribute(s,!!t):n.setAttr(r,s,t)};`signal`in e?o(e.signal,m,t):m(e.value)},m=(e,t)=>{t(n.listen(e.el,e.name,e.handler,e.options))},h=(e,t)=>{let{el:n,ref:r}=e;if(typeof r==`function`){r(n),t(()=>r(null));return}r.value=n,t(()=>{r.value=null})},g=(n,r,i)=>{let a=[],o=r.parentNode;e.invariant(o,`html binding anchor has no parent node`);for(let e of n)if(t.isHtmlResult(e)){let t=Array.from(e.fragment.childNodes);o.insertBefore(e.fragment,r),e.apply(i),a.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));o.insertBefore(t,r),a.push(t)}return a},_=(e,t)=>{let{anchor:r,signal:i}=e,o=[],c=()=>{n.runAll(o),o=[]},l=[],u=(0,a.effect)(()=>{let e=s(i);c(),n.removeNodes(l),l=[],!(e==null||e.length===0)&&(0,a.untrack)(()=>{l=g(e,r,e=>o.push(e))})});t(()=>{u.dispose(),c(),n.removeNodes(l)})},v=(e,t)=>{e.directive.mount(e.anchor,t)},y=(e,t)=>{e.spread.apply(e.el,t)},b=(e,t)=>{switch(e.type){case`attr`:p(e,t);break;case`directive`:v(e,t);break;case`event`:m(e,t);break;case`html`:_(e,t);break;case`ref`:h(e,t);break;case`spread`:y(e,t);break;case`text`:c(e,t);break}},x=(e,t,n,o)=>{let s=i.getPropMeta(e,n);return r.isLiveSignal(o)?{el:e,live:!0,mode:t,name:n,propMeta:s,signal:o,type:`attr`}:typeof o==`function`?{el:e,mode:t,name:n,propMeta:s,signal:(0,a.computed)(o),type:`attr`}:(0,a.isReactive)(o)?{el:e,mode:t,name:n,propMeta:s,signal:o,type:`attr`}:{el:e,mode:t,name:n,propMeta:s,type:`attr`,value:o}},S=e=>e==null?``:String(e);exports.applyAttrBinding=p,exports.applyBinding=b,exports.applyHtmlBinding=_,exports.createAttrBindingFromValue=x,exports.resolveStaticText=S;
|
|
2
2
|
//# sourceMappingURL=bindings.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.cjs","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts — Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (text, attr, event, html, ref,\n * directive, spread).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n */\n\nimport { computed, effect as rawEffect, isReactive, type Readable, untrack } from '@vielzeug/ripple';\n\nimport { isLiveSignal } from '../directives/live';\nimport { getPropMeta } from '../props';\nimport {\n type AttrBinding,\n type AttrPropMeta,\n type Binding,\n type DirectiveBinding,\n type EventBinding,\n type HtmlBinding,\n type HtmlBindingValue,\n isHtmlResult,\n type RefBinding,\n type SpreadBinding,\n type TextBinding,\n} from '../types/bindings';\nimport { isStructuredValue, listen, removeNodes, runAll, setAttr } from '../utils/dom';\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(() => update(signal.value));\n\n registerCleanup(() => sub.dispose());\n};\n\n/**\n * Read a signal safely during scope teardown. Disposed signals and computeds\n * return their last known value (or undefined) without throwing — no try/catch needed.\n */\nconst readSignalSafe = <T>(sig: Readable<T>): T | undefined => sig.value;\n\n// ─── Text ─────────────────────────────────────────────────────────────────────\n\nconst applyTextBinding = (binding: TextBinding, registerCleanup: RegisterCleanup): void => {\n signalEffect(\n binding.signal,\n (v) => {\n binding.node.textContent = String(v ?? '');\n },\n registerCleanup,\n );\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\ntype LiveWriteState = { last: unknown };\n\nconst applyFormValue = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive: boolean | undefined,\n state: LiveWriteState,\n): void => {\n const next = value == null ? '' : String(value);\n\n if (isLive && state.last !== undefined && !Object.is(el.value, state.last) && !Object.is(el.value, next)) return;\n\n el.value = next;\n\n if (isLive) state.last = next;\n};\n\nconst applyCheckedValue = (\n el: HTMLInputElement,\n value: unknown,\n isLive: boolean | undefined,\n state: LiveWriteState,\n): void => {\n const next = Boolean(value);\n\n if (isLive && state.last !== undefined && el.checked !== Boolean(state.last) && el.checked !== next) return;\n\n el.checked = next;\n\n if (isLive) state.last = next;\n};\n\nconst syncRegisteredProp = (el: HTMLElement, meta: AttrPropMeta, 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 as never;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name, propMeta } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if (!isReactive(value) && isStructuredValue(value)) {\n if (name !== '__proto__' && name !== 'constructor' && name !== 'prototype') {\n (el as unknown as Record<string, unknown>)[name] = value;\n }\n\n return;\n }\n\n if (name === 'value' && isNativeFormInput(el)) {\n applyFormValue(el, value, binding.live, liveState);\n\n return;\n }\n\n if (name === 'checked' && el instanceof HTMLInputElement) {\n applyCheckedValue(el, value, binding.live, liveState);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n registerCleanup(listen(binding.el, binding.name, binding.handler, binding.options));\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 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 let currentCleanups: (() => void)[] = [];\n const clearCurrent = (): void => {\n runAll(currentCleanups);\n currentCleanups = [];\n };\n let currentNodes: Node[] = [];\n\n const stop = rawEffect(() => {\n const raw = readSignalSafe(signal);\n\n clearCurrent();\n removeNodes(currentNodes);\n currentNodes = [];\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n currentNodes = insertHtmlValues(raw, anchor, (fn) => currentCleanups.push(fn));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n clearCurrent();\n removeNodes(currentNodes);\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Spread ───────────────────────────────────────────────────────────────────\n\nconst applySpreadBinding = (binding: SpreadBinding, registerCleanup: RegisterCleanup): void => {\n binding.spread.apply(binding.el, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n case 'spread':\n applySpreadBinding(binding, registerCleanup);\n break;\n case 'text':\n applyTextBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n const propMeta = getPropMeta(el, name) as AttrPropMeta | undefined;\n\n if (isLiveSignal(value)) {\n return { el, live: true, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, propMeta, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, propMeta, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bindings.cjs","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts — Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (text, attr, event, html, ref,\n * directive, spread).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n */\n\nimport { computed, effect as rawEffect, isReactive, type Readable, untrack } from '@vielzeug/ripple';\n\nimport { isLiveSignal } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta } from '../props';\nimport {\n type AttrBinding,\n type AttrPropMeta,\n type Binding,\n type DirectiveBinding,\n type EventBinding,\n type HtmlBinding,\n type HtmlBindingValue,\n isHtmlResult,\n type RefBinding,\n type SpreadBinding,\n type TextBinding,\n} from '../types/bindings';\nimport { isStructuredValue, listen, removeNodes, runAll, setAttr } from '../utils/dom';\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(() => update(signal.value));\n\n registerCleanup(() => sub.dispose());\n};\n\n/**\n * Read a signal safely during scope teardown. Disposed signals and computeds\n * return their last known value (or undefined) without throwing — no try/catch needed.\n */\nconst readSignalSafe = <T>(sig: Readable<T>): T | undefined => sig.value;\n\n// ─── Text ─────────────────────────────────────────────────────────────────────\n\nconst applyTextBinding = (binding: TextBinding, registerCleanup: RegisterCleanup): void => {\n signalEffect(\n binding.signal,\n (v) => {\n binding.node.textContent = String(v ?? '');\n },\n registerCleanup,\n );\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\ntype LiveWriteState = { last: unknown };\n\nconst applyFormValue = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive: boolean | undefined,\n state: LiveWriteState,\n): void => {\n const next = value == null ? '' : String(value);\n\n if (isLive && state.last !== undefined && !Object.is(el.value, state.last) && !Object.is(el.value, next)) return;\n\n el.value = next;\n\n if (isLive) state.last = next;\n};\n\nconst applyCheckedValue = (\n el: HTMLInputElement,\n value: unknown,\n isLive: boolean | undefined,\n state: LiveWriteState,\n): void => {\n const next = Boolean(value);\n\n if (isLive && state.last !== undefined && el.checked !== Boolean(state.last) && el.checked !== next) return;\n\n el.checked = next;\n\n if (isLive) state.last = next;\n};\n\nconst syncRegisteredProp = (el: HTMLElement, meta: AttrPropMeta, 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 as never;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name, propMeta } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if (!isReactive(value) && isStructuredValue(value)) {\n if (name !== '__proto__' && name !== 'constructor' && name !== 'prototype') {\n (el as unknown as Record<string, unknown>)[name] = value;\n }\n\n return;\n }\n\n if (name === 'value' && isNativeFormInput(el)) {\n applyFormValue(el, value, binding.live, liveState);\n\n return;\n }\n\n if (name === 'checked' && el instanceof HTMLInputElement) {\n applyCheckedValue(el, value, binding.live, liveState);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n registerCleanup(listen(binding.el, binding.name, binding.handler, binding.options));\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 let currentCleanups: (() => void)[] = [];\n const clearCurrent = (): void => {\n runAll(currentCleanups);\n currentCleanups = [];\n };\n let currentNodes: Node[] = [];\n\n const stop = rawEffect(() => {\n const raw = readSignalSafe(signal);\n\n clearCurrent();\n removeNodes(currentNodes);\n currentNodes = [];\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n currentNodes = insertHtmlValues(raw, anchor, (fn) => currentCleanups.push(fn));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n clearCurrent();\n removeNodes(currentNodes);\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Spread ───────────────────────────────────────────────────────────────────\n\nconst applySpreadBinding = (binding: SpreadBinding, registerCleanup: RegisterCleanup): void => {\n binding.spread.apply(binding.el, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n case 'spread':\n applySpreadBinding(binding, registerCleanup);\n break;\n case 'text':\n applyTextBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n const propMeta = getPropMeta(el, name) as AttrPropMeta | undefined;\n\n if (isLiveSignal(value)) {\n return { el, live: true, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, propMeta, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, propMeta, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|