@vielzeug/ore 2.0.3 → 2.0.5

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.
Files changed (102) hide show
  1. package/dist/_component-class.cjs.map +1 -1
  2. package/dist/_component-class.js.map +1 -1
  3. package/dist/base-element.cjs +1 -1
  4. package/dist/base-element.cjs.map +1 -1
  5. package/dist/base-element.d.ts.map +1 -1
  6. package/dist/base-element.js +1 -1
  7. package/dist/base-element.js.map +1 -1
  8. package/dist/context.cjs.map +1 -1
  9. package/dist/context.js.map +1 -1
  10. package/dist/define.cjs.map +1 -1
  11. package/dist/define.d.ts +3 -3
  12. package/dist/define.d.ts.map +1 -1
  13. package/dist/define.js.map +1 -1
  14. package/dist/directives/each.cjs.map +1 -1
  15. package/dist/directives/each.d.ts.map +1 -1
  16. package/dist/directives/each.js.map +1 -1
  17. package/dist/directives/unsafe-html.cjs.map +1 -1
  18. package/dist/directives/unsafe-html.d.ts.map +1 -1
  19. package/dist/directives/unsafe-html.js.map +1 -1
  20. package/dist/directives/when.cjs.map +1 -1
  21. package/dist/directives/when.d.ts.map +1 -1
  22. package/dist/directives/when.js.map +1 -1
  23. package/dist/errors.cjs +1 -1
  24. package/dist/errors.cjs.map +1 -1
  25. package/dist/errors.d.ts +0 -2
  26. package/dist/errors.d.ts.map +1 -1
  27. package/dist/errors.js +1 -1
  28. package/dist/errors.js.map +1 -1
  29. package/dist/forms/field.cjs +1 -1
  30. package/dist/forms/field.cjs.map +1 -1
  31. package/dist/forms/field.d.ts +1 -1
  32. package/dist/forms/field.d.ts.map +1 -1
  33. package/dist/forms/field.js +1 -1
  34. package/dist/forms/field.js.map +1 -1
  35. package/dist/host-bind.cjs +1 -1
  36. package/dist/host-bind.cjs.map +1 -1
  37. package/dist/host-bind.js +1 -1
  38. package/dist/host-bind.js.map +1 -1
  39. package/dist/index.cjs +1 -1
  40. package/dist/index.d.ts +14 -14
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/ore.cjs +2 -2
  44. package/dist/ore.cjs.map +1 -1
  45. package/dist/ore.iife.js +2 -2
  46. package/dist/ore.iife.js.map +1 -1
  47. package/dist/ore.js +2 -2
  48. package/dist/ore.js.map +1 -1
  49. package/dist/props.cjs.map +1 -1
  50. package/dist/props.js.map +1 -1
  51. package/dist/runtime.cjs.map +1 -1
  52. package/dist/runtime.d.ts +3 -3
  53. package/dist/runtime.d.ts.map +1 -1
  54. package/dist/runtime.js.map +1 -1
  55. package/dist/slots.cjs.map +1 -1
  56. package/dist/slots.d.ts.map +1 -1
  57. package/dist/slots.js.map +1 -1
  58. package/dist/template/binding-types.d.ts +3 -3
  59. package/dist/template/binding-types.d.ts.map +1 -1
  60. package/dist/template/bindings.cjs +1 -1
  61. package/dist/template/bindings.cjs.map +1 -1
  62. package/dist/template/bindings.d.ts +1 -1
  63. package/dist/template/bindings.d.ts.map +1 -1
  64. package/dist/template/bindings.js +1 -1
  65. package/dist/template/bindings.js.map +1 -1
  66. package/dist/template/compiler.cjs +1 -1
  67. package/dist/template/compiler.cjs.map +1 -1
  68. package/dist/template/compiler.d.ts.map +1 -1
  69. package/dist/template/compiler.js +1 -1
  70. package/dist/template/compiler.js.map +1 -1
  71. package/dist/template/instantiator.cjs +1 -1
  72. package/dist/template/instantiator.cjs.map +1 -1
  73. package/dist/template/instantiator.js +1 -1
  74. package/dist/template/instantiator.js.map +1 -1
  75. package/dist/template/result.cjs.map +1 -1
  76. package/dist/template/result.d.ts.map +1 -1
  77. package/dist/template/result.js.map +1 -1
  78. package/dist/testing/index.d.ts +3 -3
  79. package/dist/testing/index.d.ts.map +1 -1
  80. package/dist/testing/mount.cjs +1 -1
  81. package/dist/testing/mount.cjs.map +1 -1
  82. package/dist/testing/mount.d.ts +11 -11
  83. package/dist/testing/mount.d.ts.map +1 -1
  84. package/dist/testing/mount.js +1 -1
  85. package/dist/testing/mount.js.map +1 -1
  86. package/dist/testing/render-hook.cjs.map +1 -1
  87. package/dist/testing/render-hook.d.ts +2 -2
  88. package/dist/testing/render-hook.d.ts.map +1 -1
  89. package/dist/testing/render-hook.js.map +1 -1
  90. package/dist/testing.cjs.map +1 -1
  91. package/dist/testing.js.map +1 -1
  92. package/dist/utils/dom.cjs +1 -1
  93. package/dist/utils/dom.cjs.map +1 -1
  94. package/dist/utils/dom.js +1 -1
  95. package/dist/utils/dom.js.map +1 -1
  96. package/dist/utils/emit.cjs.map +1 -1
  97. package/dist/utils/emit.d.ts +1 -1
  98. package/dist/utils/emit.d.ts.map +1 -1
  99. package/dist/utils/emit.js.map +1 -1
  100. package/package.json +28 -28
  101. package/dist/template/binding-types.cjs +0 -1
  102. package/dist/template/binding-types.js +0 -1
@@ -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 { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef` — not just `prop.*` helper output — so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded — e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n 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 registry.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"props.cjs","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef` — not just `prop.*` helper output — so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded — e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n 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 registry.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
package/dist/props.js.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 { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef` — not just `prop.*` helper output — so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded — e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n 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 registry.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"props.js","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n // All props must be explicitly defined via prop.* helpers or PropDef objects\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n bool(defaultValue?: boolean): PropDef<boolean>;\n /**\n * JS-only property — never reads from or writes to an HTML attribute.\n * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n *\n * @example\n * ```ts\n * columns: prop.data<DataGridColumn[]>([]),\n * config: prop.data<Options>(),\n * getRowKey: prop.data<(row: T) => string>(),\n * onSelect: prop.data<(item: T) => void>(),\n * ```\n */\n data<T>(): PropDef<T | undefined>;\n data<T>(defaultValue: T): PropDef<T>;\n json<T>(defaultValue: T): PropDef<T>;\n number<T extends number = number>(): PropDef<T | undefined>;\n number<T extends number = number>(defaultValue: number): PropDef<T>;\n oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n string<T extends string = string>(): PropDef<T | undefined>;\n string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n /**\n * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n * Reflection is always enabled. To suppress attribute reflection for a boolean,\n * use `prop.data<boolean>(false)` instead.\n */\n bool(defaultValue?: boolean): PropDef<boolean> {\n const def = defaultValue ?? false;\n\n return {\n default: def,\n parse: (value) => value !== null && value !== 'false',\n reflect: true,\n };\n },\n data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n return _jsOnlyProp(defaultValue);\n },\n /**\n * JSON-serialisable prop — the value is stored as a JS object and parsed\n * from the attribute via `JSON.parse`. Reflection is always disabled\n * because serialising complex objects back to attributes on every change\n * would be expensive and produce unreadable HTML.\n *\n * @example\n * ```ts\n * columns: prop.json<Column[]>([]),\n * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n * // change not reflected back → attribute stays static after upgrade\n * ```\n */\n json<T>(defaultValue: T): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => {\n if (value == null || value === '') return defaultValue;\n\n try {\n return JSON.parse(value) as T;\n } catch {\n return defaultValue;\n }\n },\n reflect: false,\n };\n },\n number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value) => {\n if (value == null) return def;\n\n const n = Number(value);\n\n if (Number.isNaN(n)) {\n warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n return def;\n }\n\n return n as T;\n },\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n oneOf<T extends string | undefined, D extends T = T>(\n allowed: readonly NonNullable<T>[],\n defaultValue: D,\n ): PropDef<T> {\n return {\n default: defaultValue,\n parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n reflect: true,\n };\n },\n string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n // When no default provided, undefined sentinel → attribute removed when value is absent\n const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n return {\n default: def,\n parse: (value: string | null) => (value == null ? def : (value as T)),\n reflect: true,\n } as PropDef<T> | PropDef<T | undefined>;\n },\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef` — not just `prop.*` helper output — so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n * default: new MyCustomType(),\n * parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n * reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n if (!isPropDef(value)) {\n throw new OreApiError(\n `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n );\n }\n\n const descriptor = value as PropDef<T>;\n\n if (!descriptor.parse) {\n throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n }\n\n const reflect = descriptor.reflect ?? false;\n\n // Validate: structured defaults with reflect:true are not allowed\n if (reflect && isStructuredValue(descriptor.default)) {\n throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n }\n\n return {\n ...descriptor,\n reflect,\n };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(defs)) {\n try {\n normalizePropDefinition(value, key);\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n }\n }\n\n return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n parse: (value: string | null) => T;\n reflect: boolean;\n signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded — e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n let registry = propRegistry.get(el);\n\n if (!registry) {\n registry = new Map();\n propRegistry.set(el, registry);\n }\n\n const { default: defaultValue, parse, reflect = false } = propDef;\n const s = signal<T>(defaultValue);\n 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 registry.set(attrName, meta);\n\n Object.defineProperty(el, propName, {\n configurable: true,\n enumerable: true,\n get: () => s.value,\n set: (value: T) => {\n s.value = coerceIncoming(value, parse);\n },\n });\n\n if (reflect) {\n watchEffect(() => {\n const v = s.value;\n\n if (v == null) {\n el.removeAttribute(attrName);\n } else if (typeof v === 'boolean') {\n el.toggleAttribute(attrName, v);\n } else {\n setAttr(el, attrName, v);\n }\n });\n }\n\n return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n const props = {} as Record<string, Signal<unknown>>;\n\n for (const [name, def] of Object.entries(defs)) {\n // defs passed here are already normalized by define(); skip re-normalization.\n const attrName = toKebab(name);\n\n props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n }\n\n return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
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 { type Cleanup, effect as _effect, type Readable } 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 = () => Cleanup | 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/**\n * @internal Create a fresh runtime context for a component element. The single\n * construction site for `RuntimeContext` — used by `BaseElement` (setup and\n * per-callback mount contexts) and by `testing/render-hook.ts`, so the test\n * harness can never silently desync from a new required field.\n */\nexport const createRuntimeContext = (element: HTMLElement): RuntimeContext => ({\n element,\n formResetCallbacks: [],\n mountCallbacks: [],\n});\n\n// ─── Pending work tracking ──────────────────────────────────────────────────\n// A single counter of \"in-flight scheduled work\" across every live component\n// instance on the page — incremented when a mount-callback microtask is scheduled\n// (base-element.ts's _scheduleMountCallbacks()), decremented when it completes.\n//\n// Why this exists: `@vielzeug/ripple`'s reactive graph settles fully synchronously on\n// every signal write (see ripple's scheduling.ts) — there is no async flush queue to wait\n// for there. The only genuinely async work `testing/flush()` needs to wait for is ore's own\n// bounded, internal scheduling: `queueMicrotask`-scheduled onMounted callbacks.\n// `testing/flush()` polls `hasPendingWork()` to know precisely when that work has\n// settled, instead of draining a fixed, guessed number of microtask turns.\nlet pendingWork = 0;\n\n/**\n * @internal Mark one scheduled mount-callback microtask as started. Call the\n * returned function exactly once when it completes.\n */\nexport const beginPendingWork = (): (() => void) => {\n pendingWork++;\n\n let ended = false;\n\n return () => {\n if (ended) return;\n\n ended = true;\n pendingWork--;\n };\n};\n\n/** @internal True while any tracked component work is in flight. Polled by `testing/flush()`. */\nexport const hasPendingWork = (): boolean => pendingWork > 0;\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: Cleanup): boolean => {\n if (!currentContext) return false;\n\n _effect(() => fn);\n\n return true;\n};\n\n/** Registers cleanup work for component disconnect. */\nexport const onCleanup = (fn: Cleanup): void => {\n if (!tryRegisterCleanup(fn)) throw new OreApiError(`onCleanup: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\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: () => Cleanup | void): (() => 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) => Cleanup | undefined | void,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"+FA8BA,IAAI,EAAwC,KAQ/B,EAAwB,IAA0C,CAC7E,UACA,mBAAoB,CAAC,EACrB,eAAgB,CAAC,CACnB,GAaI,EAAc,EAML,MAAuC,CAClD,IAEA,IAAI,EAAQ,GAEZ,UAAa,CACP,IAEJ,EAAQ,GACR,IACF,CACF,EAGa,MAAgC,EAAc,EAG9C,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,OAAA,KAAc,CAAE,EAET,IAJqB,GAQjB,EAAa,GAAsB,CAC9C,GAAI,CAAC,EAAmB,CAAE,EAAG,MAAM,IAAI,EAAA,YAAY,cAAc,EAAA,WAAW,uBAAuB,CACrG,EAMa,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,GAA2C,CACrE,IAAM,GAAA,EAAM,EAAA,OAAA,CAAQ,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"}
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 { effect as _effect, type Cleanup, type Readable } from '@vielzeug/ripple';\n\nimport { ORE_ERRORS, OreApiError } 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 = () => Cleanup | undefined;\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/**\n * @internal Create a fresh runtime context for a component element. The single\n * construction site for `RuntimeContext` — used by `BaseElement` (setup and\n * per-callback mount contexts) and by `testing/render-hook.ts`, so the test\n * harness can never silently desync from a new required field.\n */\nexport const createRuntimeContext = (element: HTMLElement): RuntimeContext => ({\n element,\n formResetCallbacks: [],\n mountCallbacks: [],\n});\n\n// ─── Pending work tracking ──────────────────────────────────────────────────\n// A single counter of \"in-flight scheduled work\" across every live component\n// instance on the page — incremented when a mount-callback microtask is scheduled\n// (base-element.ts's _scheduleMountCallbacks()), decremented when it completes.\n//\n// Why this exists: `@vielzeug/ripple`'s reactive graph settles fully synchronously on\n// every signal write (see ripple's scheduling.ts) — there is no async flush queue to wait\n// for there. The only genuinely async work `testing/flush()` needs to wait for is ore's own\n// bounded, internal scheduling: `queueMicrotask`-scheduled onMounted callbacks.\n// `testing/flush()` polls `hasPendingWork()` to know precisely when that work has\n// settled, instead of draining a fixed, guessed number of microtask turns.\nlet pendingWork = 0;\n\n/**\n * @internal Mark one scheduled mount-callback microtask as started. Call the\n * returned function exactly once when it completes.\n */\nexport const beginPendingWork = (): (() => void) => {\n pendingWork++;\n\n let ended = false;\n\n return () => {\n if (ended) return;\n\n ended = true;\n pendingWork--;\n };\n};\n\n/** @internal True while any tracked component work is in flight. Polled by `testing/flush()`. */\nexport const hasPendingWork = (): boolean => pendingWork > 0;\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: Cleanup): boolean => {\n if (!currentContext) return false;\n\n _effect(() => fn);\n\n return true;\n};\n\n/** Registers cleanup work for component disconnect. */\nexport const onCleanup = (fn: Cleanup): void => {\n if (!tryRegisterCleanup(fn)) throw new OreApiError(`onCleanup: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\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: () => Cleanup | undefined): (() => 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) => Cleanup | undefined | undefined,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"+FA8BA,IAAI,EAAwC,KAQ/B,EAAwB,IAA0C,CAC7E,UACA,mBAAoB,CAAC,EACrB,eAAgB,CAAC,CACnB,GAaI,EAAc,EAML,MAAuC,CAClD,IAEA,IAAI,EAAQ,GAEZ,UAAa,CACP,IAEJ,EAAQ,GACR,IACF,CACF,EAGa,MAAgC,EAAc,EAG9C,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,OAAA,KAAc,CAAE,EAET,IAJqB,GAQjB,EAAa,GAAsB,CAC9C,GAAI,CAAC,EAAmB,CAAE,EAAG,MAAM,IAAI,EAAA,YAAY,cAAc,EAAA,WAAW,uBAAuB,CACrG,EAMa,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,GAAgD,CAC1E,IAAM,GAAA,EAAM,EAAA,OAAA,CAAQ,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
@@ -11,7 +11,7 @@
11
11
  * with no context object to thread through every layer of a composable.
12
12
  */
13
13
  import { type Cleanup, type Readable } from '@vielzeug/ripple';
14
- export type OnMountedCallback = () => Cleanup | void;
14
+ export type OnMountedCallback = () => Cleanup | undefined;
15
15
  export type OnFormResetCallback = () => void;
16
16
  export type RuntimeContext = {
17
17
  element: HTMLElement;
@@ -48,7 +48,7 @@ export declare const onFormReset: (fn: OnFormResetCallback) => void;
48
48
  * `watch(source, callback)`, which has different semantics (explicit source,
49
49
  * old/new value pair) — the two are commonly imported in the same file.
50
50
  */
51
- export declare const watchEffect: (fn: () => Cleanup | void) => (() => void);
51
+ export declare const watchEffect: (fn: () => Cleanup | undefined) => (() => void);
52
52
  /**
53
53
  * Attach a scoped event listener that is automatically removed on component disconnect.
54
54
  * Silently no-ops when `target` is `null` or `undefined` (safe for reactive targets).
@@ -58,5 +58,5 @@ export declare function onEvent<K extends keyof HTMLElementEventMap>(target: Eve
58
58
  * Watch a ref signal and run a callback when it resolves to a non-null element.
59
59
  * The callback's return value is used as a cleanup function.
60
60
  */
61
- export declare const onElement: <T extends HTMLElement>(ref: Readable<T | null>, callback: (el: T) => Cleanup | undefined | void) => (() => void);
61
+ export declare const onElement: <T extends HTMLElement>(ref: Readable<T | null>, callback: (el: T) => Cleanup | undefined | undefined) => (() => void);
62
62
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,KAAK,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AASlF,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,GAAG,IAAI,CAAC;AACrD,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;AA4EF;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAO,WAAqD,CAAC;AAEjF,eAAO,MAAM,kBAAkB,GAAI,IAAI,OAAO,KAAG,OAMhD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,SAAS,GAAI,IAAI,OAAO,KAAG,IAEvC,CAAC;AAEF;;;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,MAAM,OAAO,GAAG,IAAI,KAAG,CAAC,MAAM,IAAI,CAOjE,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,OAAO,GAAG,SAAS,GAAG,IAAI,KAC9C,CAAC,MAAM,IAAI,CAMb,CAAC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAqB,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AASlF,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,GAAG,SAAS,CAAC;AAC1D,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;AA4EF;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAO,WAAqD,CAAC;AAEjF,eAAO,MAAM,kBAAkB,GAAI,IAAI,OAAO,KAAG,OAMhD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,SAAS,GAAI,IAAI,OAAO,KAAG,IAEvC,CAAC;AAEF;;;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,MAAM,OAAO,GAAG,SAAS,KAAG,CAAC,MAAM,IAAI,CAOtE,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,OAAO,GAAG,SAAS,GAAG,SAAS,KACnD,CAAC,MAAM,IAAI,CAMb,CAAC"}
@@ -1 +1 @@
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 { type Cleanup, effect as _effect, type Readable } 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 = () => Cleanup | 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/**\n * @internal Create a fresh runtime context for a component element. The single\n * construction site for `RuntimeContext` — used by `BaseElement` (setup and\n * per-callback mount contexts) and by `testing/render-hook.ts`, so the test\n * harness can never silently desync from a new required field.\n */\nexport const createRuntimeContext = (element: HTMLElement): RuntimeContext => ({\n element,\n formResetCallbacks: [],\n mountCallbacks: [],\n});\n\n// ─── Pending work tracking ──────────────────────────────────────────────────\n// A single counter of \"in-flight scheduled work\" across every live component\n// instance on the page — incremented when a mount-callback microtask is scheduled\n// (base-element.ts's _scheduleMountCallbacks()), decremented when it completes.\n//\n// Why this exists: `@vielzeug/ripple`'s reactive graph settles fully synchronously on\n// every signal write (see ripple's scheduling.ts) — there is no async flush queue to wait\n// for there. The only genuinely async work `testing/flush()` needs to wait for is ore's own\n// bounded, internal scheduling: `queueMicrotask`-scheduled onMounted callbacks.\n// `testing/flush()` polls `hasPendingWork()` to know precisely when that work has\n// settled, instead of draining a fixed, guessed number of microtask turns.\nlet pendingWork = 0;\n\n/**\n * @internal Mark one scheduled mount-callback microtask as started. Call the\n * returned function exactly once when it completes.\n */\nexport const beginPendingWork = (): (() => void) => {\n pendingWork++;\n\n let ended = false;\n\n return () => {\n if (ended) return;\n\n ended = true;\n pendingWork--;\n };\n};\n\n/** @internal True while any tracked component work is in flight. Polled by `testing/flush()`. */\nexport const hasPendingWork = (): boolean => pendingWork > 0;\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: Cleanup): boolean => {\n if (!currentContext) return false;\n\n _effect(() => fn);\n\n return true;\n};\n\n/** Registers cleanup work for component disconnect. */\nexport const onCleanup = (fn: Cleanup): void => {\n if (!tryRegisterCleanup(fn)) throw new OreApiError(`onCleanup: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\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: () => Cleanup | void): (() => 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) => Cleanup | undefined | void,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"4IA8BA,IAAI,EAAwC,KAQ/B,EAAwB,IAA0C,CAC7E,UACA,mBAAoB,CAAC,EACrB,eAAgB,CAAC,CACnB,GAaI,EAAc,EAML,MAAuC,CAClD,IAEA,IAAI,EAAQ,GAEZ,UAAa,CACP,IAEJ,EAAQ,GACR,IACF,CACF,EAGa,MAAgC,EAAc,EAG9C,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,MAAc,CAAE,EAET,IAJqB,GAQjB,EAAa,GAAsB,CAC9C,GAAI,CAAC,EAAmB,CAAE,EAAG,MAAM,IAAI,EAAY,cAAc,EAAW,uBAAuB,CACrG,EAMa,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,GAA2C,CACrE,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"}
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 { effect as _effect, type Cleanup, type Readable } from '@vielzeug/ripple';\n\nimport { ORE_ERRORS, OreApiError } 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 = () => Cleanup | undefined;\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/**\n * @internal Create a fresh runtime context for a component element. The single\n * construction site for `RuntimeContext` — used by `BaseElement` (setup and\n * per-callback mount contexts) and by `testing/render-hook.ts`, so the test\n * harness can never silently desync from a new required field.\n */\nexport const createRuntimeContext = (element: HTMLElement): RuntimeContext => ({\n element,\n formResetCallbacks: [],\n mountCallbacks: [],\n});\n\n// ─── Pending work tracking ──────────────────────────────────────────────────\n// A single counter of \"in-flight scheduled work\" across every live component\n// instance on the page — incremented when a mount-callback microtask is scheduled\n// (base-element.ts's _scheduleMountCallbacks()), decremented when it completes.\n//\n// Why this exists: `@vielzeug/ripple`'s reactive graph settles fully synchronously on\n// every signal write (see ripple's scheduling.ts) — there is no async flush queue to wait\n// for there. The only genuinely async work `testing/flush()` needs to wait for is ore's own\n// bounded, internal scheduling: `queueMicrotask`-scheduled onMounted callbacks.\n// `testing/flush()` polls `hasPendingWork()` to know precisely when that work has\n// settled, instead of draining a fixed, guessed number of microtask turns.\nlet pendingWork = 0;\n\n/**\n * @internal Mark one scheduled mount-callback microtask as started. Call the\n * returned function exactly once when it completes.\n */\nexport const beginPendingWork = (): (() => void) => {\n pendingWork++;\n\n let ended = false;\n\n return () => {\n if (ended) return;\n\n ended = true;\n pendingWork--;\n };\n};\n\n/** @internal True while any tracked component work is in flight. Polled by `testing/flush()`. */\nexport const hasPendingWork = (): boolean => pendingWork > 0;\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: Cleanup): boolean => {\n if (!currentContext) return false;\n\n _effect(() => fn);\n\n return true;\n};\n\n/** Registers cleanup work for component disconnect. */\nexport const onCleanup = (fn: Cleanup): void => {\n if (!tryRegisterCleanup(fn)) throw new OreApiError(`onCleanup: ${ORE_ERRORS.lifecycleOutsideSetup}`);\n};\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: () => Cleanup | undefined): (() => 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) => Cleanup | undefined | undefined,\n): (() => void) => {\n return watchEffect(() => {\n const el = ref.value;\n\n if (el) return callback(el);\n });\n};\n"],"mappings":"4IA8BA,IAAI,EAAwC,KAQ/B,EAAwB,IAA0C,CAC7E,UACA,mBAAoB,CAAC,EACrB,eAAgB,CAAC,CACnB,GAaI,EAAc,EAML,MAAuC,CAClD,IAEA,IAAI,EAAQ,GAEZ,UAAa,CACP,IAEJ,EAAQ,GACR,IACF,CACF,EAGa,MAAgC,EAAc,EAG9C,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,MAAc,CAAE,EAET,IAJqB,GAQjB,EAAa,GAAsB,CAC9C,GAAI,CAAC,EAAmB,CAAE,EAAG,MAAM,IAAI,EAAY,cAAc,EAAW,uBAAuB,CACrG,EAMa,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,GAAgD,CAC1E,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"}
@@ -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, 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 // Single init pass, run after the first render: binds slots already present\n // (pre-upgrade markup and template-rendered ones alike) and starts observation\n // for slots inserted later. The observer must stay connected for the component's\n // lifetime — it is the only way to detect a *first* <slot> appearing dynamically\n // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n // the bound-slot count drops to zero.\n const initSlots = (): void => {\n bindAllSlots();\n recomputeAllSlots();\n\n if (!observer && 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\n if (slotCleanupMap.size > 0) recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n };\n\n onMounted(initSlots);\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 // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n // but this registry's observer/listeners are torn down above — drop the cache entry so a\n // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n slotsByElement.delete(host);\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext` — `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()` — a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance — reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, 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 = slotsByElement.get(ctx.element);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByElement.set(ctx.element, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
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, type Signal, signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, 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 // Single init pass, run after the first render: binds slots already present\n // (pre-upgrade markup and template-rendered ones alike) and starts observation\n // for slots inserted later. The observer must stay connected for the component's\n // lifetime — it is the only way to detect a *first* <slot> appearing dynamically\n // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n // the bound-slot count drops to zero.\n const initSlots = (): undefined => {\n bindAllSlots();\n recomputeAllSlots();\n\n if (!observer && 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\n if (slotCleanupMap.size > 0) recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n\n return undefined;\n };\n\n onMounted(initSlots);\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 // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n // but this registry's observer/listeners are torn down above — drop the cache entry so a\n // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n slotsByElement.delete(host);\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext` — `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()` — a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance — reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, 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 = slotsByElement.get(ctx.element);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByElement.set(ctx.element, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
@@ -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;AA2KF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,GAAI,SAAS,SAAS,MAAM,GAAG,MAAM,OAAK,cAAc,CAAC,SAAS,CAUtF,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;AA6KF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,GAAI,SAAS,SAAS,MAAM,GAAG,MAAM,OAAK,cAAc,CAAC,SAAS,CAUtF,CAAC"}
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, 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 // Single init pass, run after the first render: binds slots already present\n // (pre-upgrade markup and template-rendered ones alike) and starts observation\n // for slots inserted later. The observer must stay connected for the component's\n // lifetime — it is the only way to detect a *first* <slot> appearing dynamically\n // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n // the bound-slot count drops to zero.\n const initSlots = (): void => {\n bindAllSlots();\n recomputeAllSlots();\n\n if (!observer && 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\n if (slotCleanupMap.size > 0) recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n };\n\n onMounted(initSlots);\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 // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n // but this registry's observer/listeners are torn down above — drop the cache entry so a\n // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n slotsByElement.delete(host);\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext` — `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()` — a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance — reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, 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 = slotsByElement.get(ctx.element);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByElement.set(ctx.element, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
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, type Signal, signal } from '@vielzeug/ripple';\n\nimport { onCleanup, onMounted, 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 // Single init pass, run after the first render: binds slots already present\n // (pre-upgrade markup and template-rendered ones alike) and starts observation\n // for slots inserted later. The observer must stay connected for the component's\n // lifetime — it is the only way to detect a *first* <slot> appearing dynamically\n // (e.g. a when() branch that renders a slot), so it cannot be disconnected when\n // the bound-slot count drops to zero.\n const initSlots = (): undefined => {\n bindAllSlots();\n recomputeAllSlots();\n\n if (!observer && 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\n if (slotCleanupMap.size > 0) recomputeAllSlots();\n });\n observer.observe(host.shadowRoot, { childList: true, subtree: true });\n }\n\n return undefined;\n };\n\n onMounted(initSlots);\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 // The element instance survives disconnect/reconnect (custom elements aren't recreated),\n // but this registry's observer/listeners are torn down above — drop the cache entry so a\n // subsequent reconnect's setup() rebuilds a live registry instead of reusing a dead one.\n slotsByElement.delete(host);\n });\n\n return {\n elements: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).elements,\n has: (name?: string) => ensureSlotEntry(normalizeSlotName(name)).presence,\n };\n};\n\n// Keyed by the host element, not the ephemeral `RuntimeContext` — `onMounted()` callbacks each\n// run with their own freshly-created context object (see base-element.ts's\n// `_scheduleMountCallbacks`), so keying this on `RuntimeContext` would silently create a second,\n// independent registry (a second `MutationObserver`, a second signal set) every time `useSlots()`\n// was called from inside `onMounted()` rather than directly in `setup()` — a real bug the\n// \"one registry per instance\" doc comment below never actually held for that (common) case.\n/** One slot registry per component instance — reused across repeated `useSlots()` calls. */\nconst slotsByElement = new WeakMap<HTMLElement, 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 = slotsByElement.get(ctx.element);\n\n if (!entry) {\n entry = createSlots(ctx.element);\n slotsByElement.set(ctx.element, entry);\n }\n\n return entry as ComponentSlots<SlotNames>;\n};\n"],"mappings":"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"}
@@ -5,9 +5,9 @@
5
5
  * by path navigation on the cloned template (see compiler.ts / instantiator.ts).
6
6
  * Runtime factories and brand guards live in `result.ts` — this module is types only.
7
7
  */
8
- import { type Readable } from '@vielzeug/ripple';
9
- import { type PropMeta } from '../props';
10
- import { type DirectiveResult, type HTMLResult, type Ref, type RefCallback } from './result';
8
+ import type { Readable } from '@vielzeug/ripple';
9
+ import type { PropMeta } from '../props';
10
+ import type { DirectiveResult, HTMLResult, Ref, RefCallback } from './result';
11
11
  type AttrBaseFields = {
12
12
  el: HTMLElement;
13
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"binding-types.d.ts","sourceRoot":"","sources":["../../src/template/binding-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,GAAG,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAI7F,KAAK,cAAc,GAAG;IACpB,EAAE,EAAE,WAAW,CAAC;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpE,qDAAqD;AACrD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC;AAEjF,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,WAAW,CAAC;IAChB,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,WAAW,CAAC;IAChB,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,CAAC"}
1
+ {"version":3,"file":"binding-types.d.ts","sourceRoot":"","sources":["../../src/template/binding-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI9E,KAAK,cAAc,GAAG;IACpB,EAAE,EAAE,WAAW,CAAC;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpE,qDAAqD;AACrD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC;AAEjF,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,WAAW,CAAC;IAChB,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,WAAW,CAAC;IAChB,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,CAAC"}
@@ -1,2 +1,2 @@
1
- const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../props.cjs"),r=require("./result.cjs");require("./binding-types.cjs");const i=require("../directives/live.cjs");let a=require("@vielzeug/ripple");var o=(e,t,n)=>{let r=(0,a.effect)(()=>t(e.value));n(()=>r.dispose())},s=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,c=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),l=(e,t,n,r={last:void 0})=>{let i=c(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;n&&r.last!==void 0&&!Object.is(o,r.last)&&!Object.is(o,a)||(i?e.checked=a:e.value=a,n&&(r.last=a))},u=(e,n,r,i)=>{let o=t.isStructuredValue(i)?i:n.parse(r.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>n.signal.value),o)||(n.signal.value=o),!n.reflect){if(t.isStructuredValue(i))return;r.mode===`bool`?e.toggleAttribute(r.name,!!i):t.setAttr(e,r.name,i)}},d=(e,n)=>{let{el:r,mode:i,name:c,propMeta:d}=e,f={last:void 0},p=n=>{if(d){u(r,d,e,n);return}if(!(0,a.isReactive)(n)&&t.isStructuredValue(n)){c!==`__proto__`&&c!==`constructor`&&c!==`prototype`&&(r[c]=n);return}if(c===`value`&&s(r)||c===`checked`&&r instanceof HTMLInputElement){l(r,n,e.live,f);return}i===`bool`?r.toggleAttribute(c,!!n):t.setAttr(r,c,n)};`signal`in e?o(e.signal,p,n):p(e.value)},f=(e,n)=>{n(t.listen(e.el,e.name,e.handler))},p=(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})},m=(t,n,i)=>{let a=[],o=n.parentNode;e.invariant(o,`html binding anchor has no parent node`);for(let e of t)if(r.isHtmlResult(e)){let t=Array.from(e.fragment.childNodes);o.insertBefore(e.fragment,n),e.apply(i),a.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));o.insertBefore(t,n),a.push(t)}return a},h=(e,n)=>{let{anchor:r,signal:i}=e,o=t.createReplaceableSlot(),s=(0,a.effect)(()=>{let e=i.value;o.clear(),e!=null&&e.length!==0&&(0,a.untrack)(()=>{o.setNodes(m(e,r,o.registerCleanup))})});n(()=>{s.dispose(),o.clear()})},g=(e,t)=>{e.directive.mount(e.anchor,t)},_=(e,t)=>{switch(e.type){case`attr`:d(e,t);break;case`directive`:g(e,t);break;case`event`:f(e,t);break;case`html`:h(e,t);break;case`ref`:p(e,t)}},v=(e,t,r,o)=>{let s=n.getPropMeta(e,r);return i.isLiveBinding(o)?{el:e,live:!0,mode:t,name:r,propMeta:s,signal:o.source,type:`attr`}:typeof o==`function`?{el:e,mode:t,name:r,propMeta:s,signal:(0,a.computed)(o),type:`attr`}:(0,a.isReactive)(o)?{el:e,mode:t,name:r,propMeta:s,signal:o,type:`attr`}:{el:e,mode:t,name:r,propMeta:s,type:`attr`,value:o}},y=e=>e==null?``:String(e);exports.applyAttrBinding=d,exports.applyBinding=_,exports.applyHtmlBinding=h,exports.createAttrBindingFromValue=v,exports.resolveStaticText=y,exports.syncFormControl=l;
1
+ const e=require("../errors.cjs"),t=require("../utils/dom.cjs"),n=require("../props.cjs"),r=require("./result.cjs"),i=require("../directives/live.cjs");let a=require("@vielzeug/ripple");var o=(e,t,n)=>{let r=(0,a.effect)(()=>{t(e.value)});n(()=>r.dispose())},s=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,c=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),l=(e,t,n,r={last:void 0})=>{let i=c(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;n&&r.last!==void 0&&!Object.is(o,r.last)&&!Object.is(o,a)||(i?e.checked=a:e.value=a,n&&(r.last=a))},u=(e,n,r,i)=>{let o=t.isStructuredValue(i)?i:n.parse(r.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>n.signal.value),o)||(n.signal.value=o),!n.reflect){if(t.isStructuredValue(i))return;r.mode===`bool`?e.toggleAttribute(r.name,!!i):t.setAttr(e,r.name,i)}},d=(e,n)=>{let{el:r,mode:i,name:c,propMeta:d}=e,f={last:void 0},p=n=>{if(d){u(r,d,e,n);return}if(!(0,a.isReactive)(n)&&t.isStructuredValue(n)){c!==`__proto__`&&c!==`constructor`&&c!==`prototype`&&(r[c]=n);return}if(c===`value`&&s(r)||c===`checked`&&r instanceof HTMLInputElement){l(r,n,e.live,f);return}i===`bool`?r.toggleAttribute(c,!!n):t.setAttr(r,c,n)};`signal`in e?o(e.signal,p,n):p(e.value)},f=(e,n)=>{n(t.listen(e.el,e.name,e.handler))},p=(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})},m=(t,n,i)=>{let a=[],o=n.parentNode;e.invariant(o,`html binding anchor has no parent node`);for(let e of t)if(r.isHtmlResult(e)){let t=Array.from(e.fragment.childNodes);o.insertBefore(e.fragment,n),e.apply(i),a.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));o.insertBefore(t,n),a.push(t)}return a},h=(e,n)=>{let{anchor:r,signal:i}=e,o=t.createReplaceableSlot(),s=(0,a.effect)(()=>{let e=i.value;o.clear(),e!=null&&e.length!==0&&(0,a.untrack)(()=>{o.setNodes(m(e,r,o.registerCleanup))})});n(()=>{s.dispose(),o.clear()})},g=(e,t)=>{e.directive.mount(e.anchor,t)},_=(e,t)=>{switch(e.type){case`attr`:d(e,t);break;case`directive`:g(e,t);break;case`event`:f(e,t);break;case`html`:h(e,t);break;case`ref`:p(e,t)}},v=(e,t,r,o)=>{let s=n.getPropMeta(e,r);return i.isLiveBinding(o)?{el:e,live:!0,mode:t,name:r,propMeta:s,signal:o.source,type:`attr`}:typeof o==`function`?{el:e,mode:t,name:r,propMeta:s,signal:(0,a.computed)(o),type:`attr`}:(0,a.isReactive)(o)?{el:e,mode:t,name:r,propMeta:s,signal:o,type:`attr`}:{el:e,mode:t,name:r,propMeta:s,type:`attr`,value:o}},y=e=>e==null?``:String(e);exports.applyAttrBinding=d,exports.applyBinding=_,exports.applyHtmlBinding=h,exports.createAttrBindingFromValue=v,exports.resolveStaticText=y,exports.syncFormControl=l;
2
2
  //# sourceMappingURL=bindings.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.cjs","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts — Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, ref,\n * directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings.\n */\n\nimport { computed, effect as rawEffect, isReactive, type Readable, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport {\n type AttrBinding,\n type Binding,\n type DirectiveBinding,\n type EventBinding,\n type HtmlBinding,\n type HtmlBindingValue,\n type RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => update(signal.value));\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input) — unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name, propMeta } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if (!isReactive(value) && isStructuredValue(value)) {\n if (name !== '__proto__' && name !== 'constructor' && name !== 'prototype') {\n (el as unknown as Record<string, unknown>)[name] = value;\n }\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n registerCleanup(listen(binding.el, binding.name, binding.handler));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n const propMeta = getPropMeta(el, name);\n\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, propMeta, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, propMeta, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, propMeta, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"bindings.cjs","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts — Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, ref,\n * directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input) — unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name, propMeta } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if (!isReactive(value) && isStructuredValue(value)) {\n if (name !== '__proto__' && name !== 'constructor' && name !== 'prototype') {\n (el as unknown as Record<string, unknown>)[name] = value;\n }\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n registerCleanup(listen(binding.el, binding.name, binding.handler));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n const propMeta = getPropMeta(el, name);\n\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, propMeta, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, propMeta, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, propMeta, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, propMeta, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
@@ -9,7 +9,7 @@
9
9
  * - Own the signal-to-form-control write path (`syncFormControl`) for regular
10
10
  * attribute bindings.
11
11
  */
12
- import { type AttrBinding, type Binding, type HtmlBinding } from './binding-types';
12
+ import type { AttrBinding, Binding, HtmlBinding } from './binding-types';
13
13
  export type RegisterCleanup = (fn: () => void) => void;
14
14
  type LiveWriteState = {
15
15
  last: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/template/bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,OAAO,EAGZ,KAAK,WAAW,EAGjB,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAsBvD,KAAK,cAAc,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAC1B,IAAI,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,EAC9D,OAAO,OAAO,EACd,SAAS,OAAO,EAChB,QAAO,cAAoC,KAC1C,IAWF,CAAC;AA4BF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IAkCzF,CAAC;AAwDF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IAqBzF,CAAC;AAUF,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,EAAE,iBAAiB,eAAe,KAAG,IAkBjF,CAAC;AAIF,eAAO,MAAM,0BAA0B,GACrC,IAAI,WAAW,EACf,MAAM,MAAM,GAAG,MAAM,EACrB,MAAM,MAAM,EACZ,OAAO,OAAO,KACb,WAgBF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,MAIlD,CAAC"}
1
+ {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/template/bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EACV,WAAW,EACX,OAAO,EAGP,WAAW,EAGZ,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAyBvD,KAAK,cAAc,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAC1B,IAAI,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,EAC9D,OAAO,OAAO,EACd,SAAS,OAAO,EAChB,QAAO,cAAoC,KAC1C,IAWF,CAAC;AA4BF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IAkCzF,CAAC;AAwDF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IAqBzF,CAAC;AAUF,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,EAAE,iBAAiB,eAAe,KAAG,IAkBjF,CAAC;AAIF,eAAO,MAAM,0BAA0B,GACrC,IAAI,WAAW,EACf,MAAM,MAAM,GAAG,MAAM,EACrB,MAAM,MAAM,EACZ,OAAO,OAAO,KACb,WAgBF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,MAIlD,CAAC"}
@@ -1,2 +1,2 @@
1
- import{invariant as e}from"../errors.js";import{createReplaceableSlot as t,isStructuredValue as n,listen as r,setAttr as i}from"../utils/dom.js";import{getPropMeta as a}from"../props.js";import{isHtmlResult as o}from"./result.js";import"./binding-types.js";import{isLiveBinding as s}from"../directives/live.js";import{computed as c,effect as l,isReactive as u,untrack as d}from"@vielzeug/ripple";var f=(e,t,n)=>{let r=l(()=>t(e.value));n(()=>r.dispose())},p=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,m=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),h=(e,t,n,r={last:void 0})=>{let i=m(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;n&&r.last!==void 0&&!Object.is(o,r.last)&&!Object.is(o,a)||(i?e.checked=a:e.value=a,n&&(r.last=a))},g=(e,t,r,a)=>{let o=n(a)?a:t.parse(r.mode===`bool`?a?``:null:a==null||a===!1?null:String(a));if(Object.is(d(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(n(a))return;r.mode===`bool`?e.toggleAttribute(r.name,!!a):i(e,r.name,a)}},_=(e,t)=>{let{el:r,mode:a,name:o,propMeta:s}=e,c={last:void 0},l=t=>{if(s){g(r,s,e,t);return}if(!u(t)&&n(t)){o!==`__proto__`&&o!==`constructor`&&o!==`prototype`&&(r[o]=t);return}if(o===`value`&&p(r)||o===`checked`&&r instanceof HTMLInputElement){h(r,t,e.live,c);return}a===`bool`?r.toggleAttribute(o,!!t):i(r,o,t)};`signal`in e?f(e.signal,l,t):l(e.value)},v=(e,t)=>{t(r(e.el,e.name,e.handler))},y=(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})},b=(t,n,r)=>{let i=[],a=n.parentNode;e(a,`html binding anchor has no parent node`);for(let e of t)if(o(e)){let t=Array.from(e.fragment.childNodes);a.insertBefore(e.fragment,n),e.apply(r),i.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));a.insertBefore(t,n),i.push(t)}return i},x=(e,n)=>{let{anchor:r,signal:i}=e,a=t(),o=l(()=>{let e=i.value;a.clear(),e!=null&&e.length!==0&&d(()=>{a.setNodes(b(e,r,a.registerCleanup))})});n(()=>{o.dispose(),a.clear()})},S=(e,t)=>{e.directive.mount(e.anchor,t)},C=(e,t)=>{switch(e.type){case`attr`:_(e,t);break;case`directive`:S(e,t);break;case`event`:v(e,t);break;case`html`:x(e,t);break;case`ref`:y(e,t)}},w=(e,t,n,r)=>{let i=a(e,n);return s(r)?{el:e,live:!0,mode:t,name:n,propMeta:i,signal:r.source,type:`attr`}:typeof r==`function`?{el:e,mode:t,name:n,propMeta:i,signal:c(r),type:`attr`}:u(r)?{el:e,mode:t,name:n,propMeta:i,signal:r,type:`attr`}:{el:e,mode:t,name:n,propMeta:i,type:`attr`,value:r}},T=e=>e==null?``:String(e);export{_ as applyAttrBinding,C as applyBinding,x as applyHtmlBinding,w as createAttrBindingFromValue,T as resolveStaticText,h as syncFormControl};
1
+ import{invariant as e}from"../errors.js";import{createReplaceableSlot as t,isStructuredValue as n,listen as r,setAttr as i}from"../utils/dom.js";import{getPropMeta as a}from"../props.js";import{isHtmlResult as o}from"./result.js";import{isLiveBinding as s}from"../directives/live.js";import{computed as c,effect as l,isReactive as u,untrack as d}from"@vielzeug/ripple";var f=(e,t,n)=>{let r=l(()=>{t(e.value)});n(()=>r.dispose())},p=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,m=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),h=(e,t,n,r={last:void 0})=>{let i=m(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;n&&r.last!==void 0&&!Object.is(o,r.last)&&!Object.is(o,a)||(i?e.checked=a:e.value=a,n&&(r.last=a))},g=(e,t,r,a)=>{let o=n(a)?a:t.parse(r.mode===`bool`?a?``:null:a==null||a===!1?null:String(a));if(Object.is(d(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(n(a))return;r.mode===`bool`?e.toggleAttribute(r.name,!!a):i(e,r.name,a)}},_=(e,t)=>{let{el:r,mode:a,name:o,propMeta:s}=e,c={last:void 0},l=t=>{if(s){g(r,s,e,t);return}if(!u(t)&&n(t)){o!==`__proto__`&&o!==`constructor`&&o!==`prototype`&&(r[o]=t);return}if(o===`value`&&p(r)||o===`checked`&&r instanceof HTMLInputElement){h(r,t,e.live,c);return}a===`bool`?r.toggleAttribute(o,!!t):i(r,o,t)};`signal`in e?f(e.signal,l,t):l(e.value)},v=(e,t)=>{t(r(e.el,e.name,e.handler))},y=(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})},b=(t,n,r)=>{let i=[],a=n.parentNode;e(a,`html binding anchor has no parent node`);for(let e of t)if(o(e)){let t=Array.from(e.fragment.childNodes);a.insertBefore(e.fragment,n),e.apply(r),i.push(...t)}else if(e!=null&&e!==!1){let t=document.createTextNode(String(e));a.insertBefore(t,n),i.push(t)}return i},x=(e,n)=>{let{anchor:r,signal:i}=e,a=t(),o=l(()=>{let e=i.value;a.clear(),e!=null&&e.length!==0&&d(()=>{a.setNodes(b(e,r,a.registerCleanup))})});n(()=>{o.dispose(),a.clear()})},S=(e,t)=>{e.directive.mount(e.anchor,t)},C=(e,t)=>{switch(e.type){case`attr`:_(e,t);break;case`directive`:S(e,t);break;case`event`:v(e,t);break;case`html`:x(e,t);break;case`ref`:y(e,t)}},w=(e,t,n,r)=>{let i=a(e,n);return s(r)?{el:e,live:!0,mode:t,name:n,propMeta:i,signal:r.source,type:`attr`}:typeof r==`function`?{el:e,mode:t,name:n,propMeta:i,signal:c(r),type:`attr`}:u(r)?{el:e,mode:t,name:n,propMeta:i,signal:r,type:`attr`}:{el:e,mode:t,name:n,propMeta:i,type:`attr`,value:r}},T=e=>e==null?``:String(e);export{_ as applyAttrBinding,C as applyBinding,x as applyHtmlBinding,w as createAttrBindingFromValue,T as resolveStaticText,h as syncFormControl};
2
2
  //# sourceMappingURL=bindings.js.map