{"version":3,"file":"props.cjs","names":[],"sources":["../src/props.ts"],"sourcesContent":["import { type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { watchEffect } from './runtime';\nimport { isStructuredValue, setAttr, toKebab } from './utils/dom';\n\nexport type PropsDef<T extends Record<string, unknown>> = {\n  // All props must be explicitly defined via prop.* helpers or PropDef objects\n  [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\nexport type PropDef<T> = { readonly default: T; readonly parse: (value: string | null) => T; reflect?: boolean };\nexport type PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Prop definition factory — use these helpers for all prop definitions.\n * No implicit type inference; all parser behavior is explicit and intentional.\n */\ntype PropFactory = {\n  bool(defaultValue?: boolean): PropDef<boolean>;\n  /**\n   * JS-only property — never reads from or writes to an HTML attribute.\n   * Use for complex objects, arrays, callbacks, or any non-serialisable value.\n   *\n   * @example\n   * ```ts\n   * columns:   prop.data<DataGridColumn[]>([]),\n   * config:    prop.data<Options>(),\n   * getRowKey: prop.data<(row: T) => string>(),\n   * onSelect:  prop.data<(item: T) => void>(),\n   * ```\n   */\n  data<T>(): PropDef<T | undefined>;\n  data<T>(defaultValue: T): PropDef<T>;\n  json<T>(defaultValue: T): PropDef<T>;\n  number<T extends number = number>(): PropDef<T | undefined>;\n  number<T extends number = number>(defaultValue: number): PropDef<T>;\n  oneOf<T extends string | undefined, D extends T = T>(allowed: readonly NonNullable<T>[], defaultValue: D): PropDef<T>;\n  string<T extends string = string>(): PropDef<T | undefined>;\n  string<T extends string = string>(defaultValue: string): PropDef<T>;\n};\n\n/** @internal JS-only prop implementation — never reads/writes attributes. */\nfunction _jsOnlyProp<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n  return { default: defaultValue, parse: () => defaultValue, reflect: false } as PropDef<T | undefined>;\n}\n\nexport const prop: PropFactory = {\n  /**\n   * Boolean prop — reflects its value as a presence-only attribute (toggleAttribute).\n   * Reflection is always enabled. To suppress attribute reflection for a boolean,\n   * use `prop.data<boolean>(false)` instead.\n   */\n  bool(defaultValue?: boolean): PropDef<boolean> {\n    const def = defaultValue ?? false;\n\n    return {\n      default: def,\n      parse: (value) => value !== null && value !== 'false',\n      reflect: true,\n    };\n  },\n  data<T>(defaultValue?: T): PropDef<T | undefined> | PropDef<T> {\n    return _jsOnlyProp(defaultValue);\n  },\n  /**\n   * JSON-serialisable prop — the value is stored as a JS object and parsed\n   * from the attribute via `JSON.parse`. Reflection is always disabled\n   * because serialising complex objects back to attributes on every change\n   * would be expensive and produce unreadable HTML.\n   *\n   * @example\n   * ```ts\n   * columns: prop.json<Column[]>([]),\n   * // attribute: <my-grid columns='[{\"id\":\"name\"}]'>\n   * // change not reflected back → attribute stays static after upgrade\n   * ```\n   */\n  json<T>(defaultValue: T): PropDef<T> {\n    return {\n      default: defaultValue,\n      parse: (value) => {\n        if (value == null || value === '') return defaultValue;\n\n        try {\n          return JSON.parse(value) as T;\n        } catch {\n          return defaultValue;\n        }\n      },\n      reflect: false,\n    };\n  },\n  number<T extends number = number>(defaultValue?: number): PropDef<T> | PropDef<T | undefined> {\n    const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n    return {\n      default: def,\n      parse: (value) => {\n        if (value == null) return def;\n\n        const n = Number(value);\n\n        if (Number.isNaN(n)) {\n          warn(`prop.number(): attribute value \"${value}\" is not a valid number, using default (${String(def)})`);\n\n          return def;\n        }\n\n        return n as T;\n      },\n      reflect: true,\n    } as PropDef<T> | PropDef<T | undefined>;\n  },\n  oneOf<T extends string | undefined, D extends T = T>(\n    allowed: readonly NonNullable<T>[],\n    defaultValue: D,\n  ): PropDef<T> {\n    return {\n      default: defaultValue,\n      parse: (value) => (value != null && allowed.includes(value as NonNullable<T>) ? (value as T) : defaultValue),\n      reflect: true,\n    };\n  },\n  string<T extends string = string>(defaultValue?: string): PropDef<T> | PropDef<T | undefined> {\n    // When no default provided, undefined sentinel → attribute removed when value is absent\n    const def = defaultValue !== undefined ? (defaultValue as T) : undefined;\n\n    return {\n      default: def,\n      parse: (value: string | null) => (value == null ? def : (value as T)),\n      reflect: true,\n    } as PropDef<T> | PropDef<T | undefined>;\n  },\n};\n\nconst isPropDef = (value: unknown): value is PropDef<unknown> =>\n  typeof value === 'object' && value !== null && 'default' in value && 'parse' in value;\n\n/**\n * Validate and normalize a prop definition.\n * Throws if definition is invalid or incomplete.\n *\n * Accepts any object shaped like `PropDef` — not just `prop.*` helper output — so you can\n * define a custom prop with explicit parser and reflection behavior when the `prop.*` helpers\n * don't cover your type:\n *\n * ```ts\n * const customProp = (): PropDef<MyCustomType> => ({\n *   default: new MyCustomType(),\n *   parse: (value) => JSON.parse(value || '{}') as MyCustomType,\n *   reflect: false,\n * });\n * ```\n */\nexport function normalizePropDefinition<T>(value: unknown, propName: string): PropDef<T> {\n  if (!isPropDef(value)) {\n    throw new OreApiError(\n      `Prop \"${propName}\" must use a prop.* helper (string/number/bool/json/oneOf). Received: ${typeof value}`,\n    );\n  }\n\n  const descriptor = value as PropDef<T>;\n\n  if (!descriptor.parse) {\n    throw new OreApiError(`Prop \"${propName}\" must have a parse function. Use prop.* helpers.`);\n  }\n\n  const reflect = descriptor.reflect ?? false;\n\n  // Validate: structured defaults with reflect:true are not allowed\n  if (reflect && isStructuredValue(descriptor.default)) {\n    throw new OreApiError(`Prop \"${propName}\": ${ORE_ERRORS.propInvalidReflect}`);\n  }\n\n  return {\n    ...descriptor,\n    reflect,\n  };\n}\n\n/**\n * Validate all prop definitions at define-time.\n * Returns validation error messages or empty array if valid.\n */\nexport function validatePropDefs(defs: Record<string, unknown>): string[] {\n  const errors: string[] = [];\n\n  for (const [key, value] of Object.entries(defs)) {\n    try {\n      normalizePropDefinition(value, key);\n    } catch (error) {\n      errors.push(error instanceof Error ? error.message : String(error));\n    }\n  }\n\n  return errors;\n}\n\n/** @internal Per-attribute prop metadata. Consumed by the template binding layer (template/binding-types.ts). */\nexport type PropMeta<T = unknown> = {\n  parse: (value: string | null) => T;\n  reflect: boolean;\n  signal: Signal<T>;\n};\n\nconst propRegistry = new WeakMap<HTMLElement, Map<string, PropMeta<unknown>>>();\n\n/**\n * Look up the registered prop metadata for a given attribute name on an element.\n * Used by the template compiler to resolve prop bindings without exposing the registry.\n */\nexport const getPropMeta = (el: HTMLElement, attrName: string): PropMeta<unknown> | undefined =>\n  propRegistry.get(el)?.get(attrName);\n\n/**\n * A framework can hand a prop a raw attribute-style string well after the element\n * has upgraded — e.g. Vue reconciling server-rendered markup against a custom\n * element that auto-upgraded from its SSR attribute before hydration ran, or\n * assigning the pre-upgrade instance property it captured before `registerProp`\n * defined the real accessor. In both cases route the string through the same\n * parser an attribute value would use (so e.g. `size=\"16\"` doesn't end up stored\n * as the string `\"16\"` instead of the number `16`). Already-typed values (objects,\n * functions, booleans, numbers set deliberately as JS properties) pass through untouched.\n */\nconst coerceIncoming = <T>(value: unknown, parse: PropDef<T>['parse']): T =>\n  (typeof value === 'string' ? parse(value) : value) as T;\n\n/** @internal Runtime prop registration (called by createProps) */\nconst registerProp = <T>(el: HTMLElement, propName: string, attrName: string, propDef: PropDef<T>): Signal<T> => {\n  let registry = propRegistry.get(el);\n\n  if (!registry) {\n    registry = new Map();\n    propRegistry.set(el, registry);\n  }\n\n  const { default: defaultValue, parse, reflect = false } = propDef;\n  const s = signal<T>(defaultValue);\n\n  // On reconnection, the prop registry still holds the previous connection's\n  // PropMeta. Its signal carries the correct prop value (updated by\n  // attributeChangedCallback between connections). We must read from it rather\n  // than from `el[propName]`, because setup() may have overwritten the host\n  // property with a different getter/setter (e.g. `defineFieldValue()` in\n  // refine's form-field components redefines `el.value` to expose the internal\n  // field string). Reading that getter would yield the wrong value and lose\n  // the prop's actual state across reconnections.\n  const existingMeta = registry.get(attrName);\n  const hasPreUpgradeProperty = Object.hasOwn(el as unknown as Record<string, unknown>, propName);\n  const preUpgradeValue = hasPreUpgradeProperty ? (el as unknown as Record<string, unknown>)[propName] : undefined;\n\n  const meta: PropMeta<unknown> = {\n    parse,\n    reflect,\n    signal: s,\n  };\n\n  if (existingMeta) {\n    s.value = existingMeta.signal.peek() as T;\n  } else if (hasPreUpgradeProperty) {\n    delete (el as unknown as Record<string, unknown>)[propName];\n    s.value = coerceIncoming(preUpgradeValue, parse);\n  } else if (el.hasAttribute(attrName)) {\n    s.value = parse(el.getAttribute(attrName)) as T;\n  }\n\n  registry.set(attrName, meta);\n\n  Object.defineProperty(el, propName, {\n    configurable: true,\n    enumerable: true,\n    get: () => s.value,\n    set: (value: T) => {\n      s.value = coerceIncoming(value, parse);\n    },\n  });\n\n  if (reflect) {\n    watchEffect(() => {\n      const v = s.value;\n\n      if (v == null) {\n        el.removeAttribute(attrName);\n      } else if (typeof v === 'boolean') {\n        el.toggleAttribute(attrName, v);\n      } else {\n        setAttr(el, attrName, v);\n      }\n    });\n  }\n\n  return s;\n};\n\nexport type InferPropValue<T> = T extends PropDef<infer U> ? U : T;\n\n/**\n * Infer the reactive props object type from a `PropInputDefs` map.\n * Each entry becomes a `Reactive<T>` keyed by the prop name.\n *\n * @example\n * ```ts\n * const propDefs = { count: prop.number(0), label: prop.string('hi') };\n * type Props = InferProps<typeof propDefs>;\n * // => { readonly count: Readable<number>; readonly label: Readable<string> }\n * ```\n */\nexport type InferProps<D extends PropInputDefs> = {\n  readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\nexport function createProps<D extends PropInputDefs>(el: HTMLElement, defs: D): InferProps<D> {\n  const props = {} as Record<string, Signal<unknown>>;\n\n  for (const [name, def] of Object.entries(defs)) {\n    // defs passed here are already normalized by define(); skip re-normalization.\n    const attrName = toKebab(name);\n\n    props[name] = registerProp(el, name, attrName, def as PropDef<unknown>);\n  }\n\n  return props as unknown as InferProps<D>;\n}\n"],"mappings":"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"}