{"version":3,"file":"field.cjs","names":[],"sources":["../../src/forms/field.ts"],"sourcesContent":["import type { Readable, Signal } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { ORE_ERRORS, OreApiError } from '../errors';\nimport { getHost, onCleanup, onFormReset, watchEffect } from '../runtime';\n\n/** @internal */\nconst internalsRegistry = new WeakMap<HTMLElement, ElementInternals>();\n/** @internal */\nconst activeFieldRegistry = new WeakSet<HTMLElement>();\n\nexport type FormFieldOptions<T = unknown> = {\n  disabled?: Readable<boolean>;\n  /**\n   * The host element to attach `ElementInternals` to.\n   * Defaults to the element currently being set up via `getHost()`.\n   * Pass this explicitly when calling `useField` from a composable that is\n   * not called directly during `setup()` (e.g. a helper factory function).\n   */\n  el?: HTMLElement;\n  /**\n   * When `true`, a `null` or `undefined` value is submitted as an empty string\n   * (`''`) instead of `null`. This keeps the field's key present in `FormData`\n   * even when the value is absent — useful when the server expects the field to\n   * always be included.\n   *\n   * @default false\n   */\n  emptyStringForNull?: boolean;\n  /**\n   * Called when the ancestor `<form>` is reset (see `onFormReset`). Use to restore\n   * whatever local state backs `value` — `useField` itself owns no field state to reset.\n   */\n  onReset?: () => void;\n  toFormValue?: (value: T) => File | FormData | string | null;\n  /**\n   * Recomputed reactively and passed straight to `internals.setValidity()` — `null`\n   * (or omitting `validity` entirely) means always valid. Pair with `validationMessage`.\n   *\n   * @example\n   * ```ts\n   * validity: computed(() => (required.value && isBlank(value.value)) ? { valueMissing: true } : null),\n   * validationMessage: computed(() => (required.value && isBlank(value.value)) ? 'Required.' : ''),\n   * ```\n   */\n  validationMessage?: Readable<string>;\n  validity?: Readable<ValidityStateFlags | null>;\n  value: Signal<T> | Readable<T>;\n};\n\nexport type FormFieldHandle = {\n  checkValidity: () => boolean;\n  readonly internals: ElementInternals;\n  reportValidity: () => boolean;\n  /**\n   * Set (non-empty message) or clear (empty string) a custom validity error.\n   * Guards the platform's throw-on-empty-message contract — prefer this over\n   * calling `internals.setValidity` directly.\n   */\n  setCustomValidity: (message: string) => void;\n};\n\nexport const useField = <T = unknown>(options: FormFieldOptions<T>): FormFieldHandle => {\n  const host = options.el ?? getHost();\n  const ctor = host.constructor as typeof HTMLElement & { formAssociated?: boolean };\n\n  if (!ctor.formAssociated) {\n    throw new OreApiError(ORE_ERRORS.defineFieldRequiresFormAssociated(host.localName));\n  }\n\n  if (activeFieldRegistry.has(host)) {\n    throw new OreApiError(ORE_ERRORS.useFieldAlreadyCalled(host.localName));\n  }\n\n  // ElementInternals may only be attached once for an element's lifetime, while setup\n  // legitimately runs again when a custom element is reconnected.\n  const internals = internalsRegistry.get(host) ?? host.attachInternals();\n\n  internalsRegistry.set(host, internals);\n  activeFieldRegistry.add(host);\n  onCleanup(() => activeFieldRegistry.delete(host));\n\n  const toFormValue =\n    options.toFormValue ??\n    ((v: T): File | FormData | string | null => {\n      if (v == null) return options.emptyStringForNull ? '' : null;\n\n      if (v instanceof File || v instanceof FormData) return v;\n\n      return String(v);\n    });\n\n  watchEffect(() => {\n    internals.setFormValue(toFormValue(options.value.value));\n  });\n\n  const disabled = options.disabled;\n\n  if (disabled) {\n    if (!('states' in internals)) {\n      warn(\n        'useField(): ElementInternals.states (CustomStateSet) is not available in this environment — disabled state tracking skipped.',\n      );\n    } else {\n      const states = internals.states as CustomStateSet;\n\n      watchEffect(() => {\n        if (disabled.value) states.add('disabled');\n        else states.delete('disabled');\n      });\n    }\n  }\n\n  if (options.validity) {\n    watchEffect(() => {\n      const flags = options.validity?.value ?? {};\n      // Per spec, ElementInternals.setValidity() throws if any flag is true and message is\n      // empty — a caller-supplied `validity` with no matching `validationMessage` would crash\n      // this effect in a real browser. Fail safe with a generic message instead of propagating\n      // that crash, and say so loudly in dev so the real fix (pass validationMessage) gets made.\n      const hasFlag = Object.values(flags).some(Boolean);\n      const message = options.validationMessage?.value ?? '';\n\n      if (hasFlag && !message) {\n        warn(\n          'useField(): `validity` has a truthy flag but `validationMessage` is empty — internals.setValidity() ' +\n            'requires a non-empty message whenever any flag is true. Falling back to a generic message; pass ' +\n            '`validationMessage` to customize it.',\n        );\n        internals.setValidity(flags, 'Invalid value.');\n\n        return;\n      }\n\n      internals.setValidity(flags, message);\n    });\n  }\n\n  if (options.onReset) onFormReset(options.onReset);\n\n  const checkValidity = () => internals.checkValidity();\n  const reportValidity = () => internals.reportValidity();\n  const setCustomValidity = (message: string) =>\n    message ? internals.setValidity({ customError: true }, message) : internals.setValidity({});\n\n  return {\n    checkValidity,\n    internals,\n    reportValidity,\n    setCustomValidity,\n  };\n};\n"],"mappings":"oFAOA,IAAM,EAAoB,IAAI,QAExB,EAAsB,IAAI,QAqDnB,EAAyB,GAAkD,CACtF,IAAM,EAAO,EAAQ,IAAM,EAAA,QAAQ,EAGnC,GAAI,CAFS,EAAK,YAER,eACR,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,kCAAkC,EAAK,SAAS,CAAC,EAGpF,GAAI,EAAoB,IAAI,CAAI,EAC9B,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,sBAAsB,EAAK,SAAS,CAAC,EAKxE,IAAM,EAAY,EAAkB,IAAI,CAAI,GAAK,EAAK,gBAAgB,EAEtE,EAAkB,IAAI,EAAM,CAAS,EACrC,EAAoB,IAAI,CAAI,EAC5B,EAAA,cAAgB,EAAoB,OAAO,CAAI,CAAC,EAEhD,IAAM,EACJ,EAAQ,cACN,GACI,GAAK,KAAa,EAAQ,mBAAqB,GAAK,KAEpD,aAAa,MAAQ,aAAa,SAAiB,EAEhD,OAAO,CAAC,GAGnB,EAAA,gBAAkB,CAChB,EAAU,aAAa,EAAY,EAAQ,MAAM,KAAK,CAAC,CACzD,CAAC,EAED,IAAM,EAAW,EAAQ,SAEzB,GAAI,GACI,WAAY,EAIX,CACL,IAAM,EAAS,EAAU,OAEzB,EAAA,gBAAkB,CACZ,EAAS,MAAO,EAAO,IAAI,UAAU,EACpC,EAAO,OAAO,UAAU,CAC/B,CAAC,CACH,CAmCF,OAhCI,EAAQ,UACV,EAAA,gBAAkB,CAChB,IAAM,EAAQ,EAAQ,UAAU,OAAS,CAAC,EAKpC,EAAU,OAAO,OAAO,CAAK,CAAC,CAAC,KAAK,OAAO,EAC3C,EAAU,EAAQ,mBAAmB,OAAS,GAEpD,GAAI,GAAW,CAAC,EAAS,CAMvB,EAAU,YAAY,EAAO,gBAAgB,EAE7C,MACF,CAEA,EAAU,YAAY,EAAO,CAAO,CACtC,CAAC,EAGC,EAAQ,SAAS,EAAA,YAAY,EAAQ,OAAO,EAOzC,CACL,kBAN0B,EAAU,cAAc,EAOlD,YACA,mBAP2B,EAAU,eAAe,EAQpD,kBAPyB,GACzB,EAAU,EAAU,YAAY,CAAE,YAAa,EAAK,EAAG,CAAO,EAAI,EAAU,YAAY,CAAC,CAAC,CAO5F,CACF"}