react-f0rm 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +90 -1062
  2. package/devtools.d.ts +1 -0
  3. package/devtools.js +1 -0
  4. package/dist/array-3hAjl6Bz.d.ts +160 -0
  5. package/dist/devtools/index.cjs.js +1 -1
  6. package/dist/devtools/index.cjs.js.map +1 -1
  7. package/dist/devtools/index.d.cts +34 -0
  8. package/dist/devtools/index.d.mts +34 -0
  9. package/dist/devtools/index.d.ts +3 -2
  10. package/dist/devtools/index.mjs +1 -1
  11. package/dist/devtools/index.mjs.map +1 -1
  12. package/dist/errors-8oPux7GR.cjs.js +2 -0
  13. package/dist/errors-8oPux7GR.cjs.js.map +1 -0
  14. package/dist/errors-ClH3eV1R.mjs +2 -0
  15. package/dist/errors-ClH3eV1R.mjs.map +1 -0
  16. package/dist/index.cjs.js +1 -1
  17. package/dist/index.cjs.js.map +1 -1
  18. package/dist/index.d.cts +1833 -0
  19. package/dist/index.d.mts +1833 -0
  20. package/dist/index.d.ts +678 -128
  21. package/dist/index.mjs +1 -1
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/index.umd.js +1210 -480
  24. package/dist/index.umd.js.map +1 -1
  25. package/dist/index.umd.min.js +2 -2
  26. package/dist/index.umd.min.js.map +1 -1
  27. package/dist/persist.cjs.js +1 -1
  28. package/dist/persist.cjs.js.map +1 -1
  29. package/dist/persist.d.cts +49 -0
  30. package/dist/persist.d.mts +49 -0
  31. package/dist/persist.mjs +1 -1
  32. package/dist/persist.mjs.map +1 -1
  33. package/dist/resolvers/standard-schema.cjs.js +1 -1
  34. package/dist/resolvers/standard-schema.cjs.js.map +1 -1
  35. package/dist/resolvers/standard-schema.d.cts +2 -0
  36. package/dist/resolvers/standard-schema.d.mts +2 -0
  37. package/dist/resolvers/standard-schema.d.ts +1 -67
  38. package/dist/resolvers/standard-schema.mjs +1 -1
  39. package/dist/resolvers/standard-schema.mjs.map +1 -1
  40. package/dist/resolvers/yup.cjs.js +1 -1
  41. package/dist/resolvers/yup.cjs.js.map +1 -1
  42. package/dist/resolvers/yup.d.cts +6 -0
  43. package/dist/resolvers/yup.d.mts +6 -0
  44. package/dist/resolvers/yup.d.ts +0 -1
  45. package/dist/resolvers/yup.mjs +1 -1
  46. package/dist/resolvers/yup.mjs.map +1 -1
  47. package/dist/resolvers/zod.cjs.js +1 -1
  48. package/dist/resolvers/zod.cjs.js.map +1 -1
  49. package/dist/resolvers/zod.d.cts +6 -0
  50. package/dist/resolvers/zod.d.mts +6 -0
  51. package/dist/resolvers/zod.d.ts +0 -1
  52. package/dist/resolvers/zod.mjs +1 -1
  53. package/dist/resolvers/zod.mjs.map +1 -1
  54. package/dist/server/index.cjs.js +1 -1
  55. package/dist/server/index.cjs.js.map +1 -1
  56. package/dist/server/index.d.cts +106 -0
  57. package/dist/server/index.d.mts +106 -0
  58. package/dist/server/index.d.ts +31 -2
  59. package/dist/server/index.mjs +1 -1
  60. package/dist/server/index.mjs.map +1 -1
  61. package/dist/standard-schema-CYbnugL7.d.ts +1184 -0
  62. package/dist/standardSchema-BpZvg0tZ.mjs +2 -0
  63. package/dist/standardSchema-BpZvg0tZ.mjs.map +1 -0
  64. package/dist/standardSchema-CxV_DhBO.cjs.js +2 -0
  65. package/dist/standardSchema-CxV_DhBO.cjs.js.map +1 -0
  66. package/dist/validate-BEjNsNvd.mjs +2 -0
  67. package/dist/validate-BEjNsNvd.mjs.map +1 -0
  68. package/dist/validate-BvN6hiEG.cjs.js +2 -0
  69. package/dist/validate-BvN6hiEG.cjs.js.map +1 -0
  70. package/dist/values-Bod74Kzu.mjs +2 -0
  71. package/dist/values-Bod74Kzu.mjs.map +1 -0
  72. package/dist/values-DKpOCD5J.cjs.js +2 -0
  73. package/dist/values-DKpOCD5J.cjs.js.map +1 -0
  74. package/package.json +90 -34
  75. package/persist.d.ts +1 -0
  76. package/persist.js +1 -0
  77. package/resolvers/standard-schema.d.ts +1 -0
  78. package/resolvers/standard-schema.js +1 -0
  79. package/resolvers/yup.d.ts +1 -0
  80. package/resolvers/yup.js +1 -0
  81. package/resolvers/zod.d.ts +1 -0
  82. package/resolvers/zod.js +1 -0
  83. package/server.d.ts +1 -0
  84. package/server.js +1 -0
  85. package/dist/errors-CxSjrWJO.cjs.js +0 -2
  86. package/dist/errors-CxSjrWJO.cjs.js.map +0 -1
  87. package/dist/errors-CzWtwjO0.mjs +0 -2
  88. package/dist/errors-CzWtwjO0.mjs.map +0 -1
  89. package/dist/form-CvmWHUrd.d.ts +0 -423
  90. package/dist/validate-B1Gdjeaq.mjs +0 -2
  91. package/dist/validate-B1Gdjeaq.mjs.map +0 -1
  92. package/dist/validate-CUmNZqg6.d.ts +0 -238
  93. package/dist/validate-DAfz8Nbb.cjs.js +0 -2
  94. package/dist/validate-DAfz8Nbb.cjs.js.map +0 -1
  95. package/dist/values-B1IV-6V4.mjs +0 -2
  96. package/dist/values-B1IV-6V4.mjs.map +0 -1
  97. package/dist/values-CDNAYEOB.cjs.js +0 -2
  98. package/dist/values-CDNAYEOB.cjs.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/core/touched.ts","../../src/core/dirty.ts","../../node_modules/.pnpm/use-sync-external-store@1.6.0_react@19.2.8/node_modules/use-sync-external-store/shim/index.js","../../node_modules/.pnpm/use-sync-external-store@1.6.0_react@19.2.8/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js","../../node_modules/.pnpm/use-sync-external-store@1.6.0_react@19.2.8/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js","../../src/hooks/form.tsx","../../src/context.ts","../../src/devtools/JsonTree.tsx","../../src/devtools/styles.ts","../../src/devtools/Devtools.tsx"],"sourcesContent":["import {emit} from '@for-fun/event-emitter';\nimport createPath from '../path';\nimport type {Name, Path, PathSegments} from '../path';\nimport type {FieldPath} from '../types';\nimport type {Form} from '../form';\n\n/**\n * Set field touched state\n * @param form\n * @param name\n */\nexport function setTouched(form: Form, name: Name): void {\n setTouchedByPath(form, createPath(name));\n}\n\n/**\n * Set field touched state\n * @param form\n * @param path\n */\nexport function setTouchedByPath({emitter, touched}: Form, path: Path): void {\n if (touched.has(path.key)) return;\n touched.add(path.key);\n // Path payload lets key-scoped subscribers (onKeyEvent) skip unrelated\n // fields; payload-less listeners ignore it.\n emit(emitter, 'touched', path);\n}\n\n/**\n * Check if field has been touched\n * @param form\n * @param name\n */\nexport function hasTouched<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): boolean {\n return hasTouchedByPath(form, createPath(name));\n}\n\n/**\n * Check if field has been touched\n * @param form\n * @param path\n */\nexport function hasTouchedByPath({touched}: Form, path: Path): boolean {\n return touched.has(path.key);\n}\n\n/**\n * Is dirty -- any value differs from initialValues\n * @param form\n */\n/**\n * Get touched fields as user-facing dotted paths ('a.b', 'a.0.c'), unlike\n * the JSON array keys stored in the touched Set.\n * @param form\n * @return array of touched fields' dotted paths\n */\nexport function getTouchedFields({touched}: Form): string[] {\n return Array.from(touched, key =>\n (JSON.parse(key) as PathSegments).join('.')\n );\n}\n\n/**\n * Is touched -- any field has been touched\n * @param form\n */\nexport function isTouched({touched}: Form): boolean {\n return touched.size > 0;\n}\n\n/**\n * Remove a field: by default its live value drops out of reads and\n * `getValues()` (the path is tombstoned, so it never falls back to\n * initialValues), its dirty baseline, touched flag and errors are cleared.\n * The keep-flags preserve slices of that state instead.\n *\n * @param form\n * @param name\n */\n/**\n * Options accepted by {@link removeField}. All flags default to `false` —\n * the historical remove semantics (value dropped, path tombstoned, dirty\n * baseline/touched/errors cleared). Names mirror react-hook-form's\n * `unregister` options to ease migration; RHF's `shouldValidate` and\n * `keepDefaultValue` have no counterparts (removal never validates, and\n * the tombstone is exactly the \"do not revive from initialValues\" choice).\n */\n","import type {PathSegments} from '../path';\nimport type {Form} from '../form';\nimport {dirtyFieldsCaches, getDirtyBaseline} from './internals';\n\n/**\n * Is dirty -- any value differs from initialValues\n * @param form\n */\nexport function isDirty(form: Form): boolean {\n let dirty = false;\n forEachDirtyField(form, () => {\n dirty = true;\n });\n return dirty;\n}\n\nfunction forEachDirtyField(form: Form, fn: (dottedKey: string) => void): void {\n for (const [key, value] of form.values) {\n const path = JSON.parse(key) as PathSegments;\n if (getDirtyBaseline(form, key, path) !== value) fn(path.join('.'));\n }\n}\n\n/** Per-path dirty-comparison baselines installed by writes with\n * `shouldDirty: false`: the written value becomes that field's baseline —\n * the write reads as a commit, not an edit. Module-private (like\n * {@link dirtyFieldsCaches}) so the Form shape is untouched for forms that\n * never opt in. */\n\nfunction computeDirtyFields(form: Form): Record<string, boolean> {\n const dirtyFields: Record<string, boolean> = {};\n forEachDirtyField(form, key => {\n dirtyFields[key] = true;\n });\n return dirtyFields;\n}\n\n/** Dirty entries only ever map to `true`, so equal key sets mean shallow\n * equal results. */\nfunction sameDirtyKeys(\n a: Record<string, boolean>,\n b: Record<string, boolean>\n): boolean {\n const aKeys = Object.keys(a);\n if (aKeys.length !== Object.keys(b).length) return false;\n return aKeys.every(key => b[key] === true);\n}\n\n/**\n * Get dirty fields -- fields whose current value differs from initialValues.\n * Keys are user-facing dotted paths ('a.b', 'a.0.c'), unlike the JSON array\n * keys stored in the values Map.\n * @param form\n * @return object mapping each dirty field's dotted path to true; the same\n * reference is returned until the dirty set actually changes\n */\nexport function getDirtyFields(form: Form): Record<string, boolean> {\n let cache = dirtyFieldsCaches.get(form);\n if (!cache) {\n cache = {version: 0, result: computeDirtyFields(form)};\n dirtyFieldsCaches.set(form, cache);\n } else if (cache.version > 0) {\n const result = computeDirtyFields(form);\n // Keep the old reference when the dirty set is unchanged (values always\n // map to true) so subscribers see identity-stable snapshots.\n if (!sameDirtyKeys(cache.result, result)) cache.result = result;\n cache.version = 0;\n }\n return cache.result;\n}\n\n/**\n * Get touched fields as user-facing dotted paths ('a.b', 'a.0.c'), unlike\n * the JSON array keys stored in the touched Set.\n * @param form\n * @return array of touched fields' dotted paths\n */\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('../cjs/use-sync-external-store-shim.production.js');\n} else {\n module.exports = require('../cjs/use-sync-external-store-shim.development.js');\n}\n","/**\n * @license React\n * use-sync-external-store-shim.production.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\nvar React = require(\"react\");\nfunction is(x, y) {\n return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);\n}\nvar objectIs = \"function\" === typeof Object.is ? Object.is : is,\n useState = React.useState,\n useEffect = React.useEffect,\n useLayoutEffect = React.useLayoutEffect,\n useDebugValue = React.useDebugValue;\nfunction useSyncExternalStore$2(subscribe, getSnapshot) {\n var value = getSnapshot(),\n _useState = useState({ inst: { value: value, getSnapshot: getSnapshot } }),\n inst = _useState[0].inst,\n forceUpdate = _useState[1];\n useLayoutEffect(\n function () {\n inst.value = value;\n inst.getSnapshot = getSnapshot;\n checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });\n },\n [subscribe, value, getSnapshot]\n );\n useEffect(\n function () {\n checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });\n return subscribe(function () {\n checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });\n });\n },\n [subscribe]\n );\n useDebugValue(value);\n return value;\n}\nfunction checkIfSnapshotChanged(inst) {\n var latestGetSnapshot = inst.getSnapshot;\n inst = inst.value;\n try {\n var nextValue = latestGetSnapshot();\n return !objectIs(inst, nextValue);\n } catch (error) {\n return !0;\n }\n}\nfunction useSyncExternalStore$1(subscribe, getSnapshot) {\n return getSnapshot();\n}\nvar shim =\n \"undefined\" === typeof window ||\n \"undefined\" === typeof window.document ||\n \"undefined\" === typeof window.document.createElement\n ? useSyncExternalStore$1\n : useSyncExternalStore$2;\nexports.useSyncExternalStore =\n void 0 !== React.useSyncExternalStore ? React.useSyncExternalStore : shim;\n","/**\n * @license React\n * use-sync-external-store-shim.development.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\n\"production\" !== process.env.NODE_ENV &&\n (function () {\n function is(x, y) {\n return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);\n }\n function useSyncExternalStore$2(subscribe, getSnapshot) {\n didWarnOld18Alpha ||\n void 0 === React.startTransition ||\n ((didWarnOld18Alpha = !0),\n console.error(\n \"You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release.\"\n ));\n var value = getSnapshot();\n if (!didWarnUncachedGetSnapshot) {\n var cachedValue = getSnapshot();\n objectIs(value, cachedValue) ||\n (console.error(\n \"The result of getSnapshot should be cached to avoid an infinite loop\"\n ),\n (didWarnUncachedGetSnapshot = !0));\n }\n cachedValue = useState({\n inst: { value: value, getSnapshot: getSnapshot }\n });\n var inst = cachedValue[0].inst,\n forceUpdate = cachedValue[1];\n useLayoutEffect(\n function () {\n inst.value = value;\n inst.getSnapshot = getSnapshot;\n checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });\n },\n [subscribe, value, getSnapshot]\n );\n useEffect(\n function () {\n checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });\n return subscribe(function () {\n checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });\n });\n },\n [subscribe]\n );\n useDebugValue(value);\n return value;\n }\n function checkIfSnapshotChanged(inst) {\n var latestGetSnapshot = inst.getSnapshot;\n inst = inst.value;\n try {\n var nextValue = latestGetSnapshot();\n return !objectIs(inst, nextValue);\n } catch (error) {\n return !0;\n }\n }\n function useSyncExternalStore$1(subscribe, getSnapshot) {\n return getSnapshot();\n }\n \"undefined\" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&\n \"function\" ===\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart &&\n __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());\n var React = require(\"react\"),\n objectIs = \"function\" === typeof Object.is ? Object.is : is,\n useState = React.useState,\n useEffect = React.useEffect,\n useLayoutEffect = React.useLayoutEffect,\n useDebugValue = React.useDebugValue,\n didWarnOld18Alpha = !1,\n didWarnUncachedGetSnapshot = !1,\n shim =\n \"undefined\" === typeof window ||\n \"undefined\" === typeof window.document ||\n \"undefined\" === typeof window.document.createElement\n ? useSyncExternalStore$1\n : useSyncExternalStore$2;\n exports.useSyncExternalStore =\n void 0 !== React.useSyncExternalStore ? React.useSyncExternalStore : shim;\n \"undefined\" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&\n \"function\" ===\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&\n __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());\n })();\n","import {useState, useEffect, useCallback, useRef} from 'react';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {on} from '@for-fun/event-emitter';\nimport type {EventEmitter} from '@for-fun/event-emitter';\nimport {onKeyEvent, onPathEvent} from '../subscribe';\nimport type {SubscribeEvent} from '../subscribe';\nimport createForm, {\n FORM_ERROR,\n getErrorByPath,\n getFieldErrorsByPath,\n getValueByPath,\n hasTouchedByPath,\n hasErrors,\n isDirty,\n getDirtyFields,\n getTouchedFields,\n setInitialValues\n} from '../form';\nimport type {FieldError, Form, FormEvents, Options} from '../form';\nimport type {FieldPath, PathValueOf} from '../types';\nimport createPath from '../path';\nimport type {PathSegments, Path} from '../path';\nimport {isEqual, isPromise} from '../util';\n\n/**\n * Create a form instance bound to this component.\n *\n * Beyond {@link Options}, the optional `values` object enables controlled\n * usage: when it genuinely changes it is re-synced into the form with\n * setInitialValues semantics -- uncommitted user edits are discarded\n * (master-detail semantics: selecting another record replaces the draft),\n * while touched flags and errors survive. Change detection is\n * reference-first with a structural fallback, so re-renders that pass an\n * inline literal with equal content never re-sync -- the user's\n * in-progress typing is never clobbered.\n */\nexport default function useForm<T extends Record<string, any> = any>(\n options?: Options<T> & {values?: T}\n): Form<T> {\n // Lazy initialization: createForm runs once per mount and the returned\n // instance is stable across re-renders (and StrictMode double renders),\n // without writing to refs during render. A provided `values` object is\n // seeded synchronously here (createForm does the same for initialValues)\n // so the first paint and SSR already reflect the controlled values.\n const [form] = useState(() => {\n const created = createForm<T>(options);\n if (options && options.values !== undefined) {\n setInitialValues(created, options.values);\n }\n return created;\n });\n const initialValues = options && options.initialValues;\n const values = options && options.values;\n\n // Track which initialValues source object the form was last seeded from.\n // Inline options create a fresh object every render, and re-seeding\n // clears the values Map (setInitialValues semantics), which would revert\n // every committed edit right after each re-render -- on the client and\n // after hydration alike. Memoized callers are covered by the reference\n // check; inline literals by the structural one, so only genuinely new\n // content re-seeds.\n const seededRef = useRef<{done: boolean; source: any} | null>(null);\n if (seededRef.current === null)\n seededRef.current = {done: false, source: undefined};\n\n useEffect(() => {\n // undefined = no baseline requested (createForm already defaulted to\n // {}): installing it would clear the values Map on mount for no\n // semantic gain — wiping any render-time useField initialValue seeds.\n // Async sources (Promise or thunk) are excluded too: createForm owns\n // their one-shot resolution, and a thunk's identity changes every\n // render, so re-seeding here would clobber the resolution cycle with\n // the raw function itself.\n if (initialValues === undefined) return;\n if (typeof initialValues === 'function' || isPromise(initialValues)) {\n return;\n }\n const seeded = seededRef.current!;\n if (\n seeded.done &&\n (seeded.source === initialValues || isEqual(seeded.source, initialValues))\n ) {\n return;\n }\n seeded.done = true;\n seeded.source = initialValues;\n setInitialValues(form, initialValues);\n }, [form, initialValues]);\n\n // Controlled values: re-sync only when the incoming object genuinely\n // differs from what the form was last seeded from. The reference check\n // is the fast path (memoized callers); inline literals get a fresh\n // object identity every render, so without the structural comparison\n // each re-render would clear the values Map (setInitialValues\n // semantics) and revert the user's uncommitted edits -- same hazard the\n // initialValues seed guard above protects against. Master-detail\n // semantics still apply whenever the content actually changed.\n const controlledRef = useRef<{done: boolean; source: any} | null>(null);\n if (controlledRef.current === null) {\n controlledRef.current = {done: false, source: undefined};\n }\n\n useEffect(() => {\n if (values === undefined) return;\n const seeded = controlledRef.current!;\n if (\n seeded.done &&\n (seeded.source === values || isEqual(seeded.source, values))\n ) {\n return;\n }\n seeded.done = true;\n seeded.source = values;\n setInitialValues(form, values);\n }, [form, values]);\n\n return form;\n}\n\n/** Per-hook snapshot cache for {@link useWatch}. */\ntype WatchCache<T> = {hasValue: boolean; value?: T};\n\n/**\n * Shared core of {@link useWatch} and the path-scoped hooks: a\n * useSyncExternalStore binding over a custom event subscription.\n * `subscribeFactory` receives the invalidate callback (drop the snapshot\n * cache, then notify React) and returns its unsubscribe function, so the\n * core stays identical whether the subscription is global or scoped to\n * one path.\n *\n * The optional `isEqual` comparator redirects `invalidate`: instead of\n * dropping the cache and waking React unconditionally, an event first\n * recomputes the getter and asks `isEqual` whether anything observable\n * changed — an equal verdict keeps the cached snapshot and skips the\n * notify entirely (no render at all), an unequal one stores the fresh\n * snapshot and notifies. Omitted, the historical drop-and-notify pipeline\n * runs byte-for-byte unchanged.\n */\nexport function useWatchCore<T>(\n subscribeFactory: (invalidate: () => void) => () => void,\n getter: () => T,\n isEqual?: (prev: T, next: T) => boolean\n): T {\n // useSyncExternalStore requires getSnapshot to return the same reference\n // until the store actually changed, otherwise React warns and loops.\n // Cache the snapshot per hook instance and recompute it only on the first\n // read and after the watched event fired.\n const cacheRef = useRef<WatchCache<T> | null>(null);\n if (cacheRef.current === null) cacheRef.current = {hasValue: false};\n const cache = cacheRef.current;\n\n // Hold the latest getter in a ref so getSnapshot keeps a stable identity\n // (callers pass a freshly bound function on every render) while still\n // recomputing with the most recent getter when the cache is invalid.\n const getterRef = useRef(getter);\n getterRef.current = getter;\n // Same freshness treatment for the comparator: invalidate is created once\n // per subscription, so it must read the latest isEqual through a ref\n // rather than capturing whichever instance the first render passed.\n const isEqualRef = useRef(isEqual);\n isEqualRef.current = isEqual;\n\n const getSnapshot = useCallback(() => {\n if (!cache.hasValue) {\n cache.value = getterRef.current();\n cache.hasValue = true;\n }\n return cache.value as T;\n }, [cache]);\n\n const subscribe = useCallback(\n (notify: () => void) => {\n // The form may have changed between render and this subscription, and\n // those events were missed: drop the cache. React's consistency check\n // right after subscribing recomputes and re-renders only when the\n // fresh value differs from the committed snapshot.\n cache.hasValue = false;\n const invalidate = () => {\n const compare = isEqualRef.current;\n if (compare && cache.hasValue) {\n // Custom comparator: decide before waking React. Equal means the\n // fresh getter result is observably the same — keep the cached\n // reference and return without notifying, so React never even\n // schedules a render. Unequal stores the fresh snapshot up front,\n // so React's own post-notify Object.is check reads it without\n // recomputing the getter.\n const next = getterRef.current();\n if (compare(cache.value as T, next)) return;\n cache.value = next;\n notify();\n return;\n }\n cache.hasValue = false;\n notify();\n };\n return subscribeFactory(invalidate);\n },\n [subscribeFactory, cache]\n );\n\n // Form state lives entirely in synchronously readable Map/Set structures\n // seeded from initialValues/values during the lazy useState initializer,\n // so the server snapshot is computed exactly like the client's first\n // render -- pass getSnapshot itself as getServerSnapshot and hydration\n // matches.\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Subscribe to a form event and keep the component's snapshot of `getter()`\n * in sync with the form state.\n *\n * Built on useSyncExternalStore, so snapshots taken while React renders are\n * guaranteed consistent (no tearing under concurrent rendering) and changes\n * emitted before the subscription effect runs are still picked up.\n *\n * The first argument is the form for the unified `fn(form, ...)` context\n * shape every hook shares; the raw emitter form remains accepted for\n * back-compat and for subscription sources that are not a full form.\n *\n * By default the re-render surface is the event's own scope: every emit\n * the subscription hears drops the snapshot cache and wakes React, which\n * then bails out when the recomputed snapshot is reference-identical\n * (Object.is) — the path/leaf scoping every built-in reader relies on.\n * The optional `isEqual` comparator exists for wide-scope getters that\n * return a fresh reference per call (a whole-values selector, say): each\n * event recomputes the getter and asks `isEqual` whether the result is\n * observably the same, and an equal verdict skips notifying React\n * altogether — no render, not even a bailed-out one. An unequal verdict\n * stores the new snapshot and re-renders. Same contract as TanStack's\n * `useSelector` compare. Omitted, behavior is unchanged.\n */\nexport function useWatch<T>(\n formOrEmitter: Form | EventEmitter<FormEvents>,\n event: SubscribeEvent,\n getter: () => T,\n isEqual?: (prev: T, next: T) => boolean\n): T {\n // A form carries an `emitter` field the opaque emitter instance never\n // has, so the duck test cleanly discriminates the two accepted shapes.\n const emitter =\n 'emitter' in formOrEmitter ? formOrEmitter.emitter : formOrEmitter;\n const subscribeFactory = useCallback(\n (invalidate: () => void) => on(emitter, event, invalidate),\n [emitter, event]\n );\n return useWatchCore(subscribeFactory, getter, isEqual);\n}\n\n/**\n * Get field value state\n */\nexport function useValue<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): PathValueOf<T, P> {\n return useValueByPath(form, createPath(name));\n}\n\n/**\n * Get field value state by path\n */\nexport function useValueByPath(form: Form, path: Path): any {\n const {emitter} = form;\n const {key} = path;\n // 'leaf' scope: a leaf read depends only on its own key and its\n // ancestors' (getValueByPath fallback chain), so writes elsewhere --\n // siblings, descendants, string-prefix lookalikes ('[\"a\",\"bX\"]') -- never\n // invalidate the snapshot. Payload-less broadcasts (reset,\n // setInitialValues) still sync everything; removeField matches by path.\n const subscribeFactory = useCallback(\n (invalidate: () => void) =>\n onPathEvent(emitter, 'change', path, 'leaf', invalidate),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- deps are `key` on purpose: useValue creates a fresh Path per render, so the object must stay out of the deps while the key string pins the subscription\n [emitter, key]\n );\n return useWatchCore(subscribeFactory, getValueByPath.bind(null, form, path));\n}\n\n/**\n * Get field touched state\n */\nexport function useTouched<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): boolean {\n return useTouchedByPath(form, createPath(name));\n}\n\n/**\n * Get field touched state by path\n */\nexport function useTouchedByPath(form: Form, path: Path): boolean {\n const {emitter} = form;\n const {key} = path;\n // Touched is stored per exact key, so only this field's own setTouched\n // (now emitted with its path) matters; payload-less broadcasts (reset,\n // removeField) still sync everything.\n const subscribeFactory = useCallback(\n (invalidate: () => void) => onKeyEvent(emitter, 'touched', key, invalidate),\n [emitter, key]\n );\n return useWatchCore(\n subscribeFactory,\n hasTouchedByPath.bind(null, form, path)\n );\n}\n\n/**\n * Get field error message state\n * @return current error's message string (display text), or undefined\n */\nexport function useError<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): string | undefined {\n return useErrorByPath(form, createPath(name))?.message;\n}\n\n/**\n * Get field error state by path\n * @return current FieldError object ({type, message}), or undefined\n */\nexport function useErrorByPath(form: Form, path: Path): FieldError | undefined {\n const {emitter} = form;\n const {key} = path;\n // Errors are stored per exact key, so only writes to this field's error\n // (setErrorByPath now emits with its path) matter; payload-less\n // broadcasts (clearErrors, reset, removeField) still sync everything.\n const subscribeFactory = useCallback(\n (invalidate: () => void) => onKeyEvent(emitter, 'errors', key, invalidate),\n [emitter, key]\n );\n return useWatchCore(subscribeFactory, getErrorByPath.bind(null, form, path));\n}\n\n/**\n * Get all field errors\n * @return every error registered for the field (insertion order); an empty\n * array when the field has none\n */\nexport function useFieldErrors<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): FieldError[] {\n return useFieldErrorsByPath(form, createPath(name));\n}\n\n/**\n * Get all field errors by path\n * @return every error registered for the field (insertion order); an empty\n * array when the field has none\n */\nexport function useFieldErrorsByPath(form: Form, path: Path): FieldError[] {\n const {emitter} = form;\n const {key} = path;\n // Same exact-key subscription and snapshot rules as useErrorByPath: the\n // getter returns the shared empty constant when clean and the stored\n // array by reference otherwise, so the useSyncExternalStore snapshot is\n // reference-stable between unrelated events.\n const subscribeFactory = useCallback(\n (invalidate: () => void) => onKeyEvent(emitter, 'errors', key, invalidate),\n [emitter, key]\n );\n return useWatchCore(\n subscribeFactory,\n getFieldErrorsByPath.bind(null, form, path)\n );\n}\n\nexport function useIsDirty(form: Form): boolean {\n // Dirty state is driven by value changes, not touch state: subscribe to\n // 'change' so typing flips this immediately, even before a blur.\n return useWatch(form, 'change', isDirty.bind(null, form));\n}\n\n/**\n * Get dirty fields state -- object mapping each dirty field's user-facing\n * dotted path ('a.b', 'a.0.c') to true; recalculated after 'change' events\n */\nexport function useDirtyFields(form: Form): Record<string, boolean> {\n return useWatch(form, 'change', getDirtyFields.bind(null, form));\n}\n\n/**\n * Get touched fields state -- array of touched fields' user-facing dotted\n * paths ('a.b', 'a.0.c'); recalculated after 'touched' events\n */\nexport function useTouchedFields(form: Form): string[] {\n return useWatch(form, 'touched', getTouchedFields.bind(null, form));\n}\n\n/**\n * Aggregate snapshot of the whole form's state flags — the one-subscription\n * counterpart of react-hook-form's `formState` object (no errors object;\n * per-field error state stays with `useError`/`useFieldErrors`, and\n * `hasErrors`/`isValid` cover the whole-form questions).\n *\n * Recomputed on every state-bearing event; the field-wise comparator keeps\n * the returned reference stable while nothing observably changed, so\n * `useFormState(form).isDirty` re-renders no more often than the dedicated\n * {@link useIsDirty}. Cheaper than calling the granular hooks one by one\n * (one subscription and one snapshot instead of one per flag).\n */\nexport type FormState = {\n /** Any live value differs from its baseline (see {@link isDirty}). */\n isDirty: boolean;\n /** Dirty fields keyed by user-facing dotted path ('a.b', 'a.0.c'). */\n dirtyFields: Record<string, boolean>;\n /** At least one field is touched. */\n isTouched: boolean;\n /** Touched fields' user-facing dotted paths. */\n touchedFields: string[];\n /** Any error registered (field or form level). */\n hasErrors: boolean;\n /** No errors are registered — react-hook-form's `isValid` semantics.\n * In-flight validation is NOT factored in ({@link isValidating} is the\n * separate signal; async rounds temporarily pass this flag like RHF's). */\n isValid: boolean;\n isSubmitting: boolean;\n /** Any validation round is running (field, form-level, or a pending\n * debounce window). */\n isValidating: boolean;\n isSubmitSuccessful: boolean | undefined;\n submitCount: number;\n /** Async initialValues still pending ({@link Form.isLoading}). */\n isLoading: boolean;\n /** The form-level disabled flag (fields OR their own `disabled`). */\n disabled: boolean;\n};\n\n/** Events any FormState field can react to: each recomputes the whole\n * snapshot — the comparator, not per-flag subscriptions, keeps the\n * re-render surface minimal. */\nconst FORM_STATE_EVENTS: readonly SubscribeEvent[] = [\n 'change',\n 'errors',\n 'touched',\n 'validating',\n 'submitting',\n 'submitCount',\n 'submitSuccessful',\n 'disabled',\n 'loading'\n];\n\nfunction getFormState(form: Form): FormState {\n return {\n isDirty: isDirty(form),\n dirtyFields: getDirtyFields(form),\n isTouched: form.touched.size > 0,\n touchedFields: getTouchedFields(form),\n hasErrors: hasErrors(form),\n isValid: !hasErrors(form),\n isSubmitting: form.isSubmitting,\n isValidating: form.validating.size > 0,\n isSubmitSuccessful: form.isSubmitSuccessful,\n submitCount: form.submitCount,\n isLoading: form.isLoading,\n disabled: form.disabled\n };\n}\n\n/** Field-wise equality: reference checks where the getter already memoizes\n * (dirtyFields), element-wise for the fresh array getTouchedFields builds,\n * value checks for the flags. */\nfunction isSameFormState(a: FormState, b: FormState): boolean {\n const sameTouched =\n a.touchedFields.length === b.touchedFields.length &&\n a.touchedFields.every((path, i) => path === b.touchedFields[i]);\n return (\n a.isDirty === b.isDirty &&\n a.dirtyFields === b.dirtyFields &&\n a.isTouched === b.isTouched &&\n sameTouched &&\n a.hasErrors === b.hasErrors &&\n a.isValid === b.isValid &&\n a.isSubmitting === b.isSubmitting &&\n a.isValidating === b.isValidating &&\n a.isSubmitSuccessful === b.isSubmitSuccessful &&\n a.submitCount === b.submitCount &&\n a.isLoading === b.isLoading &&\n a.disabled === b.disabled\n );\n}\n\nexport function useFormState(form: Form): FormState {\n const getter = useCallback(() => getFormState(form), [form]);\n const subscribeFactory = useCallback(\n (invalidate: () => void) => {\n const offs = FORM_STATE_EVENTS.map(event =>\n on(form.emitter, event, invalidate)\n );\n return () => {\n for (const off of offs) off();\n };\n },\n [form.emitter]\n );\n return useWatchCore(subscribeFactory, getter, isSameFormState);\n}\n\nexport function useHasErrors(form: Form): boolean {\n return useWatch(form, 'errors', hasErrors.bind(null, form));\n}\n\n/**\n * Get whether the form currently has no errors — react-hook-form's\n * `formState.isValid` counterpart. Subscribes to the `'errors'` event only;\n * in-flight validation does not flip it (see {@link useIsValidating}).\n */\nexport function useIsValid(form: Form): boolean {\n return useWatch(form, 'errors', () => !hasErrors(form));\n}\n\nexport function useIsSubmitting(form: Form): boolean {\n return useWatch(form, 'submitting', () => form.isSubmitting);\n}\n\n/**\n * Get whether an async {@link Options.initialValues} source is still\n * pending — the flag a loading skeleton or a disabled submit button gates\n * on until the resolved baseline lands. Subscribes to the 'loading' event\n * the core emits around the resolution cycle.\n */\nexport function useIsLoading(form: Form): boolean {\n return useWatch(form, 'loading', () => form.isLoading);\n}\n\n/**\n * Get whether the form accepts a submit right now:\n * `!isSubmitting && !hasErrors`. This is the single flag a submit\n * button's `disabled` prop wants — it is `false` for the whole async\n * `onSubmit` span (not just the validation pass) and whenever any field\n * holds an error (client validation or server backfill), replacing the\n * hand-rolled `useHasErrors(form) || useIsSubmitting(form)` pair.\n * Deliberately no dirty or validating semantics: an untouched-but-clean\n * form can submit.\n */\nexport function useCanSubmit(form: Form): boolean {\n const {emitter} = form;\n // canSubmit folds two events into one boolean: error writes\n // ('errors') and submit-state flips ('submitting'). useWatch subscribes\n // to a single event, so subscribe to both through useWatchCore — the\n // snapshot recomputes on either wake and re-renders only when the\n // boolean itself flips, so unrelated single-field error churn costs no\n // extra render (the same granularity useHasErrors already has).\n const subscribeFactory = useCallback(\n (invalidate: () => void) => {\n const offErrors = on(emitter, 'errors', invalidate);\n const offSubmitting = on(emitter, 'submitting', invalidate);\n return () => {\n offErrors();\n offSubmitting();\n };\n },\n [emitter]\n );\n return useWatchCore(\n subscribeFactory,\n () => !form.isSubmitting && !hasErrors(form)\n );\n}\n\nexport function useSubmitCount(form: Form): number {\n return useWatch(form, 'submitCount', () => form.submitCount);\n}\n\n/**\n * Get whether any validation round is currently running: a field\n * validator's pending `validateDebounce` window, an async field validator\n * still in flight, or the form-level validate's debounce window / in-flight\n * round — every one of them holds a key in `form.validating`, and the\n * 'validating' events they emit (field rounds with a path payload, the\n * form-level round as a payload-less broadcast) are what this subscribes\n * to. The boolean snapshot is Object.is-stable, so churn among the marks\n * (a second field opening while the first settles) costs no render while\n * the flag holds. This is the flag a submit button disables itself on, or\n * spins a spinner with, through the pre-submit validation pass — it flips\n * true the moment the first round opens and back false when the last one\n * settles.\n */\nexport function useIsValidating(form: Form): boolean {\n return useWatch(form, 'validating', () => form.validating.size > 0);\n}\n\n/**\n * Get whether the last submit succeeded: `true` once a submit's validation\n * and `onSubmit` completed without throwing, `false` after a failed submit\n * (validation rejection or a thrown callback) and before any submit ran —\n * the falsy reading of the undefined initial/reset state. Subscribes to\n * the 'submitSuccessful' event the core's setSubmitSuccessful emits, so\n * the flag flips in the same tick the outcome lands: the usual consumers\n * are a success banner and a redirect-on-success effect.\n */\nexport function useIsSubmitSuccessful(form: Form): boolean {\n return useWatch(\n form.emitter,\n 'submitSuccessful',\n () => !!form.isSubmitSuccessful\n );\n}\n\n/**\n * Get the form-level error message: the first error stored under the\n * reserved {@link FORM_ERROR} key, as display text — or undefined while\n * the slot is clean. That key is where a form-level `validate` record's\n * `_form` entry lands and where the Standard Schema adapter drops\n * path-less issues, so errors that belong to no single field still have a\n * reader. The classic usage renders it once above the submit button —\n * `useFormError(form) || null` — and the imperative twin is\n * `getError(form, FORM_ERROR)`.\n */\nexport function useFormError(form: Form): string | undefined {\n return useErrorByPath(form, createPath(FORM_ERROR))?.message;\n}\n\n/**\n * Get every form-level error: all errors stored under the reserved\n * {@link FORM_ERROR} key (insertion order), an empty array when the slot\n * is clean. The plural twin of {@link useFormError} for forms that stack\n * several form-level issues — each path-less Standard Schema issue lands\n * in this slot. The array reference is stable between unrelated events\n * (the stored array or a shared empty constant), so consumers can memo on\n * it; the imperative counterpart is `getFieldErrors(form, FORM_ERROR)`.\n */\nexport function useFormErrors(form: Form): FieldError[] {\n return useFieldErrorsByPath(form, createPath(FORM_ERROR));\n}\n","import {createContext, createElement, useContext, type ReactNode} from 'react';\nimport {\n useFieldCore,\n type UseFieldOptions,\n type UseFieldResult\n} from './hooks/field';\nimport {\n useFieldArrayCore,\n useFieldArrayItemCore,\n type UseFieldArrayResult,\n type UseFieldArrayItemResult\n} from './hooks/fieldArray';\nimport type {Form} from './form';\nimport type {Name, PathSegments} from './path';\nimport type {FieldPath} from './types';\n\nexport const FormContext = createContext<Form<any> | null>(null);\n\nexport const FormProvider = FormContext.Provider;\n\n/**\n * Read the form from the module-level {@link FormContext}. Pass the values\n * shape — `useFormContext<Values>()` — to get a fully typed `Form<Values>`\n * headless API; the `any` default keeps untyped call sites compiling.\n *\n * For multiple forms in one subtree use {@link createFormContext} instead.\n *\n * @throws when no `<FormProvider>` is mounted above the call site.\n */\nexport function useFormContext<T extends Record<string, any> = any>(): Form<T> {\n const form = useContext(FormContext);\n if (!form) throw new Error('no form provided');\n return form;\n}\n\n/**\n * Create an isolated bundle of form-context bindings: its own React context\n * plus `useField` / `useFieldArray` / `useFieldArrayItem` /\n * `useFormContext` hooks that resolve their form from it.\n *\n * Why: the module-level {@link FormContext} works fine for a single form per\n * subtree, but nesting two forms (or reusing a component inside a different\n * form) makes them fight over one context. Calling this factory once per app\n * area — `const Ctx = createFormContext<Values>()` — fixes the value shape\n * (`Ctx.useField({name: 'user.name'})` gets its `name` constrained by\n * `FieldPath<Values>` and its `value` typed accordingly), so call sites stop\n * hand-writing generics, and each instance's Provider scopes a strictly\n * separate form. The bundle also carries its raw React context\n * (`Ctx.context`) so `<Form context={Ctx.context}>` can provide into it.\n */\nexport function createFormContext<TValues extends Record<string, any> = any>() {\n const Context = createContext<Form<TValues> | null>(null);\n\n // A `form`-prop wrapper instead of exposing Context.Provider directly:\n // callers shouldn't have to know about the raw `value` prop shape.\n function FormProvider({\n form,\n children\n }: {\n form: Form<TValues>;\n children: ReactNode;\n }): ReactNode {\n return createElement(Context.Provider, {value: form}, children);\n }\n\n function useFormContext(): Form<TValues> {\n const form = useContext(Context);\n if (!form) throw new Error('no form provided');\n return form;\n }\n\n function useField<\n TPath extends FieldPath<TValues> | PathSegments =\n FieldPath<TValues> | PathSegments\n >(\n // The bare `{name: TPath}` member keeps `name` a direct inference site\n // for TPath instead of routing it through the mapped Omit type.\n // `form` is omitted on purpose — the form always comes from this\n // factory's own Context.\n options: {name: TPath} & Omit<UseFieldOptions<TValues, TPath>, 'form'>\n ): UseFieldResult<TValues, TPath> {\n return useFieldCore(options as UseFieldOptions<TValues, TPath>, Context);\n }\n\n function useFieldArray(options: {\n name: FieldPath<TValues> | Name;\n }): UseFieldArrayResult {\n return useFieldArrayCore(options as {name: Name}, Context);\n }\n\n function useFieldArrayItem<TValue = any>(options: {\n name: FieldPath<TValues> | Name;\n id: string;\n }): UseFieldArrayItemResult<TValue> {\n return useFieldArrayItemCore(options as {name: Name; id: string}, Context);\n }\n\n // The raw React context, for `<Form context={...}>`: the component keeps\n // its submit machinery while providing into this instance's private\n // context, so the bound hooks above resolve the form it manages.\n return {\n context: Context,\n FormProvider,\n useFormContext,\n useField,\n useFieldArray,\n useFieldArrayItem\n };\n}\n\nexport const CheckboxGroupContext = createContext<any>(null);\n\nexport const CheckboxGroupProvider = CheckboxGroupContext.Provider;\n\nexport function useCheckboxGroupContext(): any {\n const group = useContext(CheckboxGroupContext);\n if (!group) throw new Error('no group provided');\n return group;\n}\n","import * as React from 'react';\nimport {useState} from 'react';\nimport type {ReactNode} from 'react';\n\n/** Nodes deeper than this start collapsed. */\nconst DEFAULT_OPEN_DEPTH = 1;\n\n/**\n * Read-only inspection: the tree never mutates form state, so structural\n * sharing of the inspected value is safe and re-renders stay cheap.\n */\ntype JsonNodeProps = {\n /** Property name (or array index) rendering before the value. */\n name?: string | number;\n /** Value to render. */\n value: unknown;\n /** Current nesting depth (root is 0). */\n depth?: number;\n};\n\n/**\n * One line of the tree: either a collapsible container row\n * (`▸ key: {`) or a leaf (`key: value`).\n */\nfunction JsonNode({name, value, depth = 0}: JsonNodeProps) {\n const [open, setOpen] = useState(depth <= DEFAULT_OPEN_DEPTH);\n\n const label =\n name === undefined ? null : (\n <>\n <span className=\"rf0-dt-key\">{String(name)}</span>\n <span className=\"rf0-dt-punct\">: </span>\n </>\n );\n\n if (value !== null && typeof value === 'object') {\n const isArray = Array.isArray(value);\n const entries: Array<[string | number, unknown]> = isArray\n ? (value as unknown[]).map((v, i) => [i, v])\n : Object.entries(value as Record<string, unknown>);\n const openBracket = isArray ? '[' : '{';\n const closeBracket = isArray ? ']' : '}';\n const summary = open\n ? ''\n : `${openBracket}…${closeBracket} ${entries.length}`;\n\n return (\n <div className=\"rf0-dt-row\" style={{paddingLeft: depth * 12}}>\n <button\n type=\"button\"\n className=\"rf0-dt-node-toggle\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n <span className=\"rf0-dt-caret\">{open ? '▾' : '▸'}</span>\n {label}\n <span className=\"rf0-dt-punct\">{open ? openBracket : summary}</span>\n </button>\n {open && (\n <>\n {entries.map(([k, v]) => (\n <JsonNode key={String(k)} name={k} value={v} depth={depth + 1} />\n ))}\n <span className=\"rf0-dt-punct\" style={{paddingLeft: depth * 12}}>\n {closeBracket}\n </span>\n </>\n )}\n </div>\n );\n }\n\n return (\n <span\n className=\"rf0-dt-row\"\n style={{paddingLeft: depth * 12, display: 'block'}}\n >\n {label}\n <Primitive value={value} />\n </span>\n );\n}\n\n/** Render a primitive leaf with terminal-style type coloring. */\nfunction Primitive({value}: {value: unknown}): ReactNode {\n if (value === undefined)\n return <span className=\"rf0-dt-null\">undefined</span>;\n if (value === null) return <span className=\"rf0-dt-null\">null</span>;\n if (typeof value === 'string')\n return <span className=\"rf0-dt-string\">&quot;{value}&quot;</span>;\n if (typeof value === 'boolean')\n return <span className=\"rf0-dt-boolean\">{String(value)}</span>;\n return <span className=\"rf0-dt-number\">{String(value)}</span>;\n}\n\nexport default JsonNode;\n","/**\n * Stylesheet for the Devtools panel.\n *\n * Zero runtime dependencies by design: a single CSS string injected once\n * into <head> (idempotent across module reloads and multiple bundles).\n *\n * Aesthetic: instrument panel / terminal — near-black layers, monospace\n * stack, dense rows, hairline borders. Semantic colors only: error red,\n * success green, neutral gray, with one dim amber accent for the active\n * tab indicator and the collapsed badge.\n */\n\nconst CSS = `\n.rf0-dt {\n position: fixed;\n z-index: 2147483000;\n box-sizing: border-box;\n width: 308px;\n max-width: calc(100vw - 16px);\n max-height: min(70vh, 560px);\n display: flex;\n flex-direction: column;\n font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',\n Menlo, Consolas, 'Liberation Mono', monospace;\n font-size: 11px;\n line-height: 1.45;\n color: #c7d0dc;\n background: #0c1017;\n border: 1px solid #1f2735;\n border-radius: 4px;\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.4);\n}\n.rf0-dt *,\n.rf0-dt-badge * {\n box-sizing: border-box;\n}\n.rf0-dt--top-right { top: 8px; right: 8px; }\n.rf0-dt--bottom-right { bottom: 8px; right: 8px; }\n.rf0-dt--top-left { top: 8px; left: 8px; }\n.rf0-dt--bottom-left { bottom: 8px; left: 8px; }\n\n/* ---- header -------------------------------------------------------- */\n.rf0-dt-header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 6px 5px 9px;\n border-bottom: 1px solid #1f2735;\n background: #10151e;\n}\n.rf0-dt-title {\n flex: 1;\n min-width: 0;\n color: #8b96a5;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.rf0-dt-title::before {\n content: '';\n display: inline-block;\n width: 6px;\n height: 6px;\n margin-right: 6px;\n border-radius: 50%;\n background: #e2b93b;\n vertical-align: 1px;\n}\n.rf0-dt-headerbtn {\n border: 1px solid transparent;\n border-radius: 3px;\n padding: 1px 5px;\n color: #8b96a5;\n background: transparent;\n font: inherit;\n font-size: 10px;\n cursor: pointer;\n}\n.rf0-dt-headerbtn:hover { color: #dce3ec; border-color: #2a3547; }\n.rf0-dt-headerbtn:focus-visible,\n.rf0-dt-tab:focus-visible,\n.rf0-dt-action:focus-visible,\n.rf0-dt-badge:focus-visible,\n.rf0-dt-node-toggle:focus-visible {\n outline: 1px solid #e2b93b;\n outline-offset: 1px;\n}\n\n/* ---- tabs ---------------------------------------------------------- */\n.rf0-dt-tablist {\n display: flex;\n border-bottom: 1px solid #1f2735;\n background: #0e131b;\n}\n.rf0-dt-tab {\n flex: 1;\n padding: 4px 2px 5px;\n border: 0;\n border-bottom: 2px solid transparent;\n background: transparent;\n color: #6b7686;\n font: inherit;\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n cursor: pointer;\n white-space: nowrap;\n}\n.rf0-dt-tab:hover { color: #aab5c4; }\n.rf0-dt-tab[aria-selected='true'] {\n color: #e7edf4;\n border-bottom-color: #e2b93b;\n}\n.rf0-dt-tab-count {\n margin-left: 3px;\n color: inherit;\n opacity: 0.75;\n}\n.rf0-dt-tab--danger[aria-selected='true'] {\n border-bottom-color: #f0647c;\n}\n\n/* ---- panels -------------------------------------------------------- */\n.rf0-dt-panel {\n flex: 1;\n min-height: 84px;\n overflow: auto;\n padding: 6px 8px;\n scrollbar-width: thin;\n scrollbar-color: #2a3547 transparent;\n}\n.rf0-dt-empty {\n padding: 10px 2px;\n color: #4d5766;\n font-style: italic;\n}\n\n/* json tree */\n.rf0-dt-row {\n display: block;\n white-space: pre;\n tab-size: 2;\n}\n.rf0-dt-node-toggle {\n border: 0;\n padding: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n white-space: pre;\n}\n.rf0-dt-node-toggle:hover .rf0-dt-key { color: #dce3ec; }\n.rf0-dt-caret {\n display: inline-block;\n width: 1.2em;\n color: #4d5766;\n}\n.rf0-dt-key { color: #8b96a5; }\n.rf0-dt-punct { color: #4d5766; }\n.rf0-dt-string { color: #8fd68a; }\n.rf0-dt-number { color: #e2b93b; }\n.rf0-dt-boolean { color: #6fb3d9; }\n.rf0-dt-null { color: #55607080; font-style: italic; }\n\n/* errors / touched / dirty lists */\n.rf0-dt-item {\n padding: 3px 2px;\n border-bottom: 1px dotted #1a2230;\n display: flex;\n gap: 8px;\n align-items: baseline;\n}\n.rf0-dt-item:last-child { border-bottom: 0; }\n.rf0-dt-item-path {\n color: #aab5c4;\n word-break: break-all;\n}\n.rf0-dt-item-msg {\n color: #f0647c;\n word-break: break-word;\n}\n.rf0-dt-item-msg--ok { color: #8fd68a; }\n.rf0-dt-item-tag {\n flex: none;\n color: #4d5766;\n font-size: 10px;\n}\n.rf0-dt-item--touched .rf0-dt-item-path { color: #6fb3d9; }\n.rf0-dt-item--dirty .rf0-dt-item-path { color: #e2b93b; }\n\n/* ---- submit status + actions --------------------------------------- */\n.rf0-dt-status {\n display: flex;\n gap: 10px;\n padding: 4px 9px;\n border-top: 1px solid #1f2735;\n background: #10151e;\n color: #6b7686;\n font-size: 10px;\n letter-spacing: 0.04em;\n white-space: nowrap;\n overflow: hidden;\n}\n.rf0-dt-status b { color: #aab5c4; font-weight: 400; }\n.rf0-dt-status .rf0-dt-on { color: #e2b93b; }\n.rf0-dt-status .rf0-dt-ok { color: #8fd68a; }\n.rf0-dt-status .rf0-dt-err { color: #f0647c; }\n.rf0-dt-actions {\n display: flex;\n gap: 6px;\n padding: 6px 8px;\n border-top: 1px solid #1f2735;\n background: #10151e;\n}\n.rf0-dt-action {\n flex: 1;\n padding: 3px 0;\n border: 1px solid #2a3547;\n border-radius: 3px;\n background: #151b26;\n color: #c7d0dc;\n font: inherit;\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n cursor: pointer;\n}\n.rf0-dt-action:hover { border-color: #3b4a61; background: #1a2230; color: #e7edf4; }\n.rf0-dt-action:active { transform: translateY(1px); }\n\n/* ---- collapsed badge ----------------------------------------------- */\n.rf0-dt-badge {\n position: fixed;\n z-index: 2147483000;\n width: 26px;\n height: 26px;\n border: 1px solid #2a3547;\n border-radius: 50%;\n background: #0c1017;\n color: #e2b93b;\n font: inherit;\n font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',\n Menlo, Consolas, 'Liberation Mono', monospace;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.02em;\n cursor: pointer;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);\n}\n.rf0-dt-badge:hover { border-color: #e2b93b; }\n.rf0-dt-badge--top-right { top: 10px; right: 10px; }\n.rf0-dt-badge--bottom-right { bottom: 10px; right: 10px; }\n.rf0-dt-badge--top-left { top: 10px; left: 10px; }\n.rf0-dt-badge--bottom-left { bottom: 10px; left: 10px; }\n.rf0-dt-badge .rf0-dt-dot {\n position: absolute;\n top: 3px;\n right: 3px;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: #f0647c;\n display: none;\n}\n.rf0-dt-badge--has-errors .rf0-dt-dot { display: block; }\n`;\n\nconst STYLE_ID = 'react-f0rm-devtools-style';\n\n/**\n * Inject the panel stylesheet into <head>. Idempotent: repeated calls\n * (module reloads, HMR, multiple Devtools mounts) never duplicate the\n * <style> element. No-ops outside a DOM environment (SSR).\n */\nexport function injectDevtoolsStyles(): void {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = CSS;\n document.head.appendChild(style);\n}\n","import * as React from 'react';\nimport {useContext, useId, useState} from 'react';\nimport type {KeyboardEvent} from 'react';\nimport {FormContext} from '../context';\nimport {getErrors, getValues, reset, trigger} from '../form';\nimport type {FieldErrorEntry, Form} from '../form';\nimport {\n useDirtyFields,\n useIsSubmitting,\n useSubmitCount,\n useTouchedFields,\n useWatch\n} from '../hooks/form';\nimport JsonTree from './JsonTree';\nimport {injectDevtoolsStyles} from './styles';\n\n/** Corner the panel docks to. */\nexport type DevtoolsPosition =\n 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left';\n\n/** Props for {@link Devtools}. */\nexport type DevtoolsProps<T extends Record<string, any> = any> = {\n /**\n * Form instance to inspect. When omitted, the panel reads the closest\n * `<Form>` / FormProvider ancestor and throws if there is none.\n */\n form?: Form<T>;\n /** Corner to dock the panel in. Defaults to `'top-right'`. */\n position?: DevtoolsPosition;\n};\n\ntype TabId = 'values' | 'errors' | 'touched' | 'dirty';\n\nconst TABS: TabId[] = ['values', 'errors', 'touched', 'dirty'];\n\n/** Status chip class for the submit-successful indicator. */\nfunction submitStatusClass(\n isSubmitSuccessful: boolean | undefined\n): string | undefined {\n if (isSubmitSuccessful === undefined) return undefined;\n return isSubmitSuccessful ? 'rf0-dt-ok' : 'rf0-dt-err';\n}\n\n/** Count primitive leaves of an inspected value tree. */\nfunction countLeaves(value: unknown): number {\n if (value === null || typeof value !== 'object') return 1;\n let count = 0;\n for (const v of Object.values(value as Record<string, unknown>)) {\n count += countLeaves(v);\n }\n return count;\n}\n\n/**\n * Live form inspector — a floating instrument panel for development.\n *\n * Renders four tabs (values / errors / touched / dirty), a submit status\n * strip (isSubmitting, submitCount, isSubmitSuccessful) and two actions:\n * Reset and Validate (full `trigger`). All state is read through the\n * library's own watch hooks, so the panel updates in real time without\n * participating in validation or submit flows. Docked at a corner,\n * collapsible to a small badge; fully keyboard operable.\n *\n * Ship it from the dedicated `react-f0rm/devtools` entry — it is never\n * re-exported by the main entry, so production bundles stay untouched.\n */\nexport default function Devtools<T extends Record<string, any> = any>({\n form,\n position = 'top-right'\n}: DevtoolsProps<T>) {\n // Idempotent + SSR-guarded; moving it off module scope keeps the\n // devtools entry free of import-time side effects (package.json\n // declares `sideEffects: false`, so bundlers may drop a bare\n // `import './styles'` in production builds).\n injectDevtoolsStyles();\n const contextForm = useContext(FormContext);\n const f: Form<any> | null = form ?? contextForm;\n if (!f) {\n throw new Error(\n '<Devtools> needs a form: pass the `form` prop or render it inside a <Form> / FormProvider.'\n );\n }\n\n const [open, setOpen] = useState(true);\n const [tab, setTab] = useState<TabId>('values');\n const idPrefix = useId().replace(/[^a-zA-Z0-9-]/g, '');\n\n // Live snapshots, straight through the public watch surface.\n const values = useWatch(f, 'change', getValues.bind(null, f));\n const errors = useWatch<FieldErrorEntry[]>(\n f,\n 'errors',\n getErrors.bind(null, f)\n );\n const touched = useTouchedFields(f);\n const dirty = useDirtyFields(f);\n const isSubmitting = useIsSubmitting(f);\n const submitCount = useSubmitCount(f);\n const isSubmitSuccessful = useWatch(\n f,\n 'submitSuccessful',\n () => f.isSubmitSuccessful\n );\n\n if (!open) {\n return (\n <button\n type=\"button\"\n className={`rf0-dt-badge rf0-dt-badge--${position}${\n errors.length > 0 ? ' rf0-dt-badge--has-errors' : ''\n }`}\n aria-expanded={false}\n aria-label={`Open react-f0rm devtools (${errors.length} errors)`}\n onClick={() => setOpen(true)}\n >\n f0\n <span className=\"rf0-dt-dot\" />\n </button>\n );\n }\n\n const counts: Record<TabId, number> = {\n values: countLeaves(values),\n errors: errors.length,\n touched: touched.length,\n dirty: Object.keys(dirty).length\n };\n\n /** Arrow-key tab navigation (buttons stay click/Enter/Space operable). */\n const onTabKeyDown = (e: KeyboardEvent) => {\n const deltas: Record<string, number> = {\n ArrowRight: 1,\n ArrowLeft: -1\n };\n const delta = deltas[e.key];\n if (!delta) return;\n e.preventDefault();\n const next = TABS[(TABS.indexOf(tab) + delta + TABS.length) % TABS.length];\n setTab(next);\n document.getElementById(`${idPrefix}-tab-${next}`)?.focus();\n };\n\n return (\n <section\n className={`rf0-dt rf0-dt--${position}`}\n aria-label=\"react-f0rm devtools\"\n >\n <header className=\"rf0-dt-header\">\n <span className=\"rf0-dt-title\">react-f0rm</span>\n <button\n type=\"button\"\n className=\"rf0-dt-headerbtn\"\n aria-label=\"Collapse devtools\"\n onClick={() => setOpen(false)}\n >\n –\n </button>\n </header>\n\n <div\n className=\"rf0-dt-tablist\"\n role=\"tablist\"\n aria-label=\"Form state\"\n tabIndex={-1}\n onKeyDown={onTabKeyDown}\n >\n {TABS.map(id => (\n <button\n key={id}\n id={`${idPrefix}-tab-${id}`}\n type=\"button\"\n role=\"tab\"\n className={`rf0-dt-tab${id === 'errors' ? ' rf0-dt-tab--danger' : ''}`}\n aria-selected={tab === id}\n aria-controls={`${idPrefix}-panel-${id}`}\n tabIndex={tab === id ? 0 : -1}\n onClick={() => setTab(id)}\n >\n {id}\n <span className=\"rf0-dt-tab-count\">{counts[id]}</span>\n </button>\n ))}\n </div>\n\n <div\n id={`${idPrefix}-panel-${tab}`}\n role=\"tabpanel\"\n aria-labelledby={`${idPrefix}-tab-${tab}`}\n className=\"rf0-dt-panel\"\n >\n {tab === 'values' && <JsonTree value={values} />}\n {tab === 'errors' &&\n (errors.length === 0 ? (\n <p className=\"rf0-dt-empty\">no errors</p>\n ) : (\n errors.map(({path, type, message}, index) => (\n // Same path can hold several errors now; index keeps keys\n // unique without changing what is rendered (messages may\n // legitimately repeat for one path).\n\n <div key={`${path}:${index}`} className=\"rf0-dt-item\">\n <span className=\"rf0-dt-item-path\">{path}</span>\n <span className=\"rf0-dt-item-msg\">{message}</span>\n <span className=\"rf0-dt-item-tag\">{type}</span>\n </div>\n ))\n ))}\n {tab === 'touched' &&\n (touched.length === 0 ? (\n <p className=\"rf0-dt-empty\">no touched fields</p>\n ) : (\n touched.map(path => (\n <div key={path} className=\"rf0-dt-item rf0-dt-item--touched\">\n <span className=\"rf0-dt-item-path\">{path}</span>\n </div>\n ))\n ))}\n {tab === 'dirty' &&\n (Object.keys(dirty).length === 0 ? (\n <p className=\"rf0-dt-empty\">no dirty fields</p>\n ) : (\n Object.keys(dirty).map(path => (\n <div key={path} className=\"rf0-dt-item rf0-dt-item--dirty\">\n <span className=\"rf0-dt-item-path\">{path}</span>\n <span className=\"rf0-dt-item-msg rf0-dt-item-msg--ok\">\n changed\n </span>\n </div>\n ))\n ))}\n </div>\n\n <p className=\"rf0-dt-status\" aria-live=\"polite\">\n <span className={isSubmitting ? 'rf0-dt-on' : undefined}>\n submitting <b>{String(isSubmitting)}</b>\n </span>\n <span>\n submits <b>{submitCount}</b>\n </span>\n <span className={submitStatusClass(isSubmitSuccessful)}>\n ok{' '}\n <b>\n {isSubmitSuccessful === undefined\n ? '–'\n : String(isSubmitSuccessful)}\n </b>\n </span>\n </p>\n\n <div className=\"rf0-dt-actions\">\n <button\n type=\"button\"\n className=\"rf0-dt-action\"\n onClick={() => reset(f, f.initialValues)}\n >\n Reset\n </button>\n <button\n type=\"button\"\n className=\"rf0-dt-action\"\n onClick={() => trigger(f)}\n >\n Validate\n </button>\n </div>\n </section>\n );\n}\n"],"names":["getTouchedFields","touched","Array","from","JSON","parse","key","join","computeDirtyFields","form","dirtyFields","fn","value","values","path","getDirtyBaseline","forEachDirtyField","getDirtyFields","cache","dirtyFieldsCaches","get","version","result","a","b","aKeys","Object","keys","length","every","sameDirtyKeys","set","process","env","NODE_ENV","shimModule","exports","React","require$$0","objectIs","is","x","y","useState","useEffect","useLayoutEffect","useDebugValue","checkIfSnapshotChanged","inst","latestGetSnapshot","getSnapshot","nextValue","error","shim","window","document","createElement","subscribe","_useState","forceUpdate","useSyncExternalStoreShim_production","useSyncExternalStore","__REACT_DEVTOOLS_GLOBAL_HOOK__","registerInternalModuleStart","Error","didWarnOld18Alpha","didWarnUncachedGetSnapshot","startTransition","console","cachedValue","useSyncExternalStoreShim_development","registerInternalModuleStop","useWatch","formOrEmitter","event","getter","isEqual","emitter","subscribeFactory","cacheRef","useRef","current","hasValue","getterRef","isEqualRef","useCallback","notify","compare","next","useWatchCore","invalidate","on","useTouchedFields","bind","FormContext","createContext","Provider","JsonNode","name","depth","open","setOpen","label","Fragment","className","String","isArray","entries","map","v","i","openBracket","closeBracket","summary","style","paddingLeft","type","onClick","k","display","Primitive","STYLE_ID","TABS","submitStatusClass","isSubmitSuccessful","countLeaves","count","Devtools","position","getElementById","id","textContent","head","appendChild","injectDevtoolsStyles","contextForm","useContext","f","tab","setTab","idPrefix","useId","replace","getValues","errors","getErrors","dirty","useDirtyFields","isSubmitting","useIsSubmitting","submitCount","useSubmitCount","counts","role","tabIndex","onKeyDown","e","delta","ArrowRight","ArrowLeft","preventDefault","indexOf","focus","JsonTree","message","index","reset","initialValues","trigger"],"mappings":"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","x_google_ignoreList":[2,3,4]}
1
+ {"version":3,"file":"index.mjs","sources":["../../src/core/dirty.ts","../../src/hooks/form.tsx","../../src/context.ts","../../src/devtools/JsonTree.tsx","../../src/devtools/styles.ts","../../src/devtools/Devtools.tsx"],"sourcesContent":["import type {Path, PathSegments} from '../path';\nimport type {Form} from '../form';\nimport {dirtyFieldsCaches, getDirtyBaseline} from './internals';\n\n/**\n * Is one field dirty — the per-field rule behind `getFieldState`'s\n * `isDirty` and {@link useIsFieldDirty}: a live value exists at the path\n * and differs from the field's effective baseline (committed\n * `shouldDirty: false` baselines included). A leaf under a wholesale\n * ancestor write reports clean — dirtiness belongs to the branch that\n * actually diverged, the same attribution {@link getDirtyFields} applies.\n */\nexport function isFieldDirtyByPath(form: Form, path: Path): boolean {\n const live = form.values.get(path.key);\n return (\n form.values.has(path.key) &&\n getDirtyBaseline(form, path.key, path.value) !== live\n );\n}\n\n/**\n * Is dirty -- any value differs from initialValues\n * @param form\n */\nexport function isDirty(form: Form): boolean {\n let dirty = false;\n forEachDirtyField(form, () => {\n dirty = true;\n });\n return dirty;\n}\n\nfunction forEachDirtyField(form: Form, fn: (dottedKey: string) => void): void {\n for (const [key, value] of form.values) {\n const path = JSON.parse(key) as PathSegments;\n if (getDirtyBaseline(form, key, path) !== value) fn(path.join('.'));\n }\n}\n\n/** Per-path dirty-comparison baselines installed by writes with\n * `shouldDirty: false`: the written value becomes that field's baseline —\n * the write reads as a commit, not an edit. Module-private (like\n * {@link dirtyFieldsCaches}) so the Form shape is untouched for forms that\n * never opt in. */\n\nfunction computeDirtyFields(form: Form): Record<string, boolean> {\n const dirtyFields: Record<string, boolean> = {};\n forEachDirtyField(form, key => {\n dirtyFields[key] = true;\n });\n return dirtyFields;\n}\n\n/** Dirty entries only ever map to `true`, so equal key sets mean shallow\n * equal results. */\nfunction sameDirtyKeys(\n a: Record<string, boolean>,\n b: Record<string, boolean>\n): boolean {\n const aKeys = Object.keys(a);\n if (aKeys.length !== Object.keys(b).length) return false;\n return aKeys.every(key => b[key] === true);\n}\n\n/**\n * Get dirty fields -- fields whose current value differs from initialValues.\n * Keys are user-facing dotted paths ('a.b', 'a.0.c'), unlike the JSON array\n * keys stored in the values Map.\n * @param form\n * @return object mapping each dirty field's dotted path to true; the same\n * reference is returned until the dirty set actually changes\n */\nexport function getDirtyFields(form: Form): Record<string, boolean> {\n let cache = dirtyFieldsCaches.get(form);\n if (!cache) {\n cache = {version: 0, result: computeDirtyFields(form)};\n dirtyFieldsCaches.set(form, cache);\n } else if (cache.version > 0) {\n const result = computeDirtyFields(form);\n // Keep the old reference when the dirty set is unchanged (values always\n // map to true) so subscribers see identity-stable snapshots.\n if (!sameDirtyKeys(cache.result, result)) cache.result = result;\n cache.version = 0;\n }\n return cache.result;\n}\n\n/**\n * Get touched fields as user-facing dotted paths ('a.b', 'a.0.c'), unlike\n * the JSON array keys stored in the touched Set.\n * @param form\n * @return array of touched fields' dotted paths\n */\n","import {\n useState,\n useEffect,\n useCallback,\n useRef,\n useSyncExternalStore\n} from 'react';\nimport {on} from '../emitter';\nimport type {EventEmitter} from '../emitter';\nimport {onKeyEvent, onPathEvent} from '../subscribe';\nimport type {SubscribeEvent, WatchScope} from '../subscribe';\nimport createForm, {\n FORM_ERROR,\n getErrorByPath,\n getErrorsRecord,\n getErrorsTree,\n getFieldErrorsByPath,\n getValueByPath,\n getValues,\n hasTouchedByPath,\n hasErrors,\n isDirty,\n isFieldDirtyByPath,\n getDirtyFields,\n getTouchedFields,\n setInitialValues,\n runFormValidate\n} from '../form';\nimport type {\n FieldError,\n FieldErrors,\n FieldErrorsTree,\n Form,\n FormEvents,\n Options\n} from '../form';\nimport type {FieldPath, PathValueOf} from '../types';\nimport createPath from '../path';\nimport type {PathSegments, Path} from '../path';\nimport {get, isEqual, isPromise} from '../util';\n\n/**\n * Create a form instance bound to this component.\n *\n * Beyond {@link Options}, the optional `values` object enables controlled\n * usage: when it genuinely changes it is re-synced into the form with\n * setInitialValues semantics -- uncommitted user edits are discarded\n * (master-detail semantics: selecting another record replaces the draft),\n * while touched flags and errors survive. Change detection is\n * reference-first with a structural fallback, so re-renders that pass an\n * inline literal with equal content never re-sync -- the user's\n * in-progress typing is never clobbered.\n */\nexport default function useForm<T extends Record<string, any> = any>(\n options?: Options<T> & {values?: T}\n): Form<T> {\n // Lazy initialization: createForm runs once per mount and the returned\n // instance is stable across re-renders (and StrictMode double renders),\n // without writing to refs during render. A provided `values` object is\n // seeded synchronously here (createForm does the same for initialValues)\n // so the first paint and SSR already reflect the controlled values.\n const [form] = useState(() => {\n const created = createForm<T>(options);\n if (options && options.values !== undefined) {\n setInitialValues(created, options.values);\n }\n return created;\n });\n const initialValues = options && options.initialValues;\n const values = options && options.values;\n\n // Track which initialValues source object the form was last seeded from.\n // Inline options create a fresh object every render, and re-seeding\n // clears the values Map (setInitialValues semantics), which would revert\n // every committed edit right after each re-render -- on the client and\n // after hydration alike. Memoized callers are covered by the reference\n // check; inline literals by the structural one, so only genuinely new\n // content re-seeds.\n const seededRef = useRef<{done: boolean; source: any} | null>(null);\n if (seededRef.current === null)\n seededRef.current = {done: false, source: undefined};\n\n useEffect(() => {\n // undefined = no baseline requested (createForm already defaulted to\n // {}): installing it would clear the values Map on mount for no\n // semantic gain — wiping any render-time useField initialValue seeds.\n // Async sources (Promise or thunk) are excluded too: createForm owns\n // their one-shot resolution, and a thunk's identity changes every\n // render, so re-seeding here would clobber the resolution cycle with\n // the raw function itself.\n if (initialValues === undefined) return;\n if (typeof initialValues === 'function' || isPromise(initialValues)) {\n return;\n }\n const seeded = seededRef.current!;\n if (\n seeded.done &&\n (seeded.source === initialValues || isEqual(seeded.source, initialValues))\n ) {\n return;\n }\n seeded.done = true;\n seeded.source = initialValues;\n setInitialValues(form, initialValues);\n }, [form, initialValues]);\n\n // Controlled values: re-sync only when the incoming object genuinely\n // differs from what the form was last seeded from. The reference check\n // is the fast path (memoized callers); inline literals get a fresh\n // object identity every render, so without the structural comparison\n // each re-render would clear the values Map (setInitialValues\n // semantics) and revert the user's uncommitted edits -- same hazard the\n // initialValues seed guard above protects against. Master-detail\n // semantics still apply whenever the content actually changed.\n const controlledRef = useRef<{done: boolean; source: any} | null>(null);\n if (controlledRef.current === null) {\n controlledRef.current = {done: false, source: undefined};\n }\n\n useEffect(() => {\n if (values === undefined) return;\n const seeded = controlledRef.current!;\n if (\n seeded.done &&\n (seeded.source === values || isEqual(seeded.source, values))\n ) {\n return;\n }\n seeded.done = true;\n seeded.source = values;\n setInitialValues(form, values);\n }, [form, values]);\n\n // Mount validation: the form-level `validate` runs once after mount when\n // the form opted into `validateOnMount` (field kicks are the fields' own\n // — useValidate schedules them per registration). Children's effects run\n // before the parent's, so field registrations are in place by the time\n // this fires. While an async initialValues source is pending, defer to\n // the 'loading' event — it fires after the resolved baseline has landed.\n // The run is fire-and-forget: a rejected validator is the submit path's\n // business, and a sync throw propagates exactly like trigger's.\n useEffect(() => {\n if (!form.validateOnMount || !form.validate) return;\n const run = () => {\n void runFormValidate(form).catch(() => undefined);\n };\n if (!form.isLoading) {\n run();\n return;\n }\n return on(form.emitter, 'loading', () => {\n run();\n });\n }, [form]);\n\n return form;\n}\n\n/** Per-hook snapshot cache for {@link useWatch}. */\ntype WatchCache<T> = {hasValue: boolean; value?: T};\n\n/**\n * Shared core of {@link useWatch} and the path-scoped hooks: a\n * useSyncExternalStore binding over a custom event subscription.\n * `subscribeFactory` receives the invalidate callback (drop the snapshot\n * cache, then notify React) and returns its unsubscribe function, so the\n * core stays identical whether the subscription is global or scoped to\n * one path.\n *\n * The optional `isEqual` comparator redirects `invalidate`: instead of\n * dropping the cache and waking React unconditionally, an event first\n * recomputes the getter and asks `isEqual` whether anything observable\n * changed — an equal verdict keeps the cached snapshot and skips the\n * notify entirely (no render at all), an unequal one stores the fresh\n * snapshot and notifies. Omitted, the historical drop-and-notify pipeline\n * runs byte-for-byte unchanged.\n */\nexport function useWatchCore<T>(\n subscribeFactory: (invalidate: () => void) => () => void,\n getter: () => T,\n isEqual?: (prev: T, next: T) => boolean\n): T {\n // useSyncExternalStore requires getSnapshot to return the same reference\n // until the store actually changed, otherwise React warns and loops.\n // Cache the snapshot per hook instance and recompute it only on the first\n // read and after the watched event fired.\n const cacheRef = useRef<WatchCache<T> | null>(null);\n if (cacheRef.current === null) cacheRef.current = {hasValue: false};\n const cache = cacheRef.current;\n\n // Hold the latest getter in a ref so getSnapshot keeps a stable identity\n // (callers pass a freshly bound function on every render) while still\n // recomputing with the most recent getter when the cache is invalid.\n const getterRef = useRef(getter);\n getterRef.current = getter;\n // Same freshness treatment for the comparator: invalidate is created once\n // per subscription, so it must read the latest isEqual through a ref\n // rather than capturing whichever instance the first render passed.\n const isEqualRef = useRef(isEqual);\n isEqualRef.current = isEqual;\n\n const getSnapshot = useCallback(() => {\n if (!cache.hasValue) {\n cache.value = getterRef.current();\n cache.hasValue = true;\n }\n return cache.value as T;\n }, [cache]);\n\n const subscribe = useCallback(\n (notify: () => void) => {\n // The form may have changed between render and this subscription, and\n // those events were missed: drop the cache. React's consistency check\n // right after subscribing recomputes and re-renders only when the\n // fresh value differs from the committed snapshot.\n cache.hasValue = false;\n const invalidate = () => {\n const compare = isEqualRef.current;\n if (compare && cache.hasValue) {\n // Custom comparator: decide before waking React. Equal means the\n // fresh getter result is observably the same — keep the cached\n // reference and return without notifying, so React never even\n // schedules a render. Unequal stores the fresh snapshot up front,\n // so React's own post-notify Object.is check reads it without\n // recomputing the getter.\n const next = getterRef.current();\n if (compare(cache.value as T, next)) return;\n cache.value = next;\n notify();\n return;\n }\n cache.hasValue = false;\n notify();\n };\n return subscribeFactory(invalidate);\n },\n [subscribeFactory, cache]\n );\n\n // Form state lives entirely in synchronously readable Map/Set structures\n // seeded from initialValues/values during the lazy useState initializer,\n // so the server snapshot is computed exactly like the client's first\n // render -- pass getSnapshot itself as getServerSnapshot and hydration\n // matches.\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Subscribe to a form event and keep the component's snapshot of `getter()`\n * in sync with the form state.\n *\n * Built on useSyncExternalStore, so snapshots taken while React renders are\n * guaranteed consistent (no tearing under concurrent rendering) and changes\n * emitted before the subscription effect runs are still picked up.\n *\n * The first argument is the form for the unified `fn(form, ...)` context\n * shape every hook shares; the raw emitter form remains accepted for\n * back-compat and for subscription sources that are not a full form.\n *\n * Framework-free counterpart: {@link watch} — the same event/getter/\n * `isEqual` contract exposed as a named export returning a\n * subscribe/getSnapshot handle any reactive runtime can bind to\n * (useWatch is its React binding; the ref-based cache here keeps inline\n * getters from re-subscribing per render).\n *\n * By default the re-render surface is the event's own scope: every emit\n * the subscription hears drops the snapshot cache and wakes React, which\n * then bails out when the recomputed snapshot is reference-identical\n * (Object.is) — the path/leaf scoping every built-in reader relies on.\n * The optional `isEqual` comparator exists for wide-scope getters that\n * return a fresh reference per call (a whole-values selector, say): each\n * event recomputes the getter and asks `isEqual` whether the result is\n * observably the same, and an equal verdict skips notifying React\n * altogether — no render, not even a bailed-out one. An unequal verdict\n * stores the new snapshot and re-renders. Same contract as TanStack's\n * `useSelector` compare. Omitted, behavior is unchanged.\n */\nexport function useWatch<T>(\n formOrEmitter: Form | EventEmitter<FormEvents>,\n event: SubscribeEvent,\n getter: () => T,\n isEqual?: (prev: T, next: T) => boolean\n): T {\n // A form carries an `emitter` field the opaque emitter instance never\n // has, so the duck test cleanly discriminates the two accepted shapes.\n const emitter =\n 'emitter' in formOrEmitter ? formOrEmitter.emitter : formOrEmitter;\n const subscribeFactory = useCallback(\n (invalidate: () => void) => on(emitter, event, invalidate),\n [emitter, event]\n );\n return useWatchCore(subscribeFactory, getter, isEqual);\n}\n\n/** Options for {@link useValue} and {@link useValueByPath}. */\nexport type UseValueOptions<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n> = {\n /** Value to return while the field reads undefined — react-hook-form's\n * `useWatch` `defaultValue`: an untouched, never-seeded field reads\n * this instead of `undefined`. */\n defaultValue?: PathValueOf<T, P>;\n /** Watch descendants too (react-hook-form's `exact: false`): a write\n * to `a.b` invalidates a `useValue(form, 'a')` read, and the read\n * resolves the merged subtree (the copy-on-write `getValues` tree) so\n * descendant edits show up in the result. Defaults to true — the leaf\n * scope, where only the exact key and its ancestors invalidate (the\n * long-standing behavior). */\n exact?: boolean;\n};\n\n/**\n * Get field value state\n */\nexport function useValue<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P, options?: UseValueOptions<T, P>): PathValueOf<T, P> {\n return useValueByPath(form, createPath(name), options);\n}\n\n/**\n * Get field value state by path\n */\nexport function useValueByPath(\n form: Form,\n path: Path,\n options?: {defaultValue?: any; exact?: boolean}\n): any {\n const {emitter} = form;\n const {key} = path;\n const scope: WatchScope = options?.exact === false ? 'branch' : 'leaf';\n // 'leaf' scope: a leaf read depends only on its own key and its\n // ancestors' (getValueByPath fallback chain), so writes elsewhere --\n // siblings, descendants, string-prefix lookalikes ('[\"a\",\"bX\"]') -- never\n // invalidate the snapshot. 'branch' (exact: false) additionally wakes on\n // descendant writes and resolves the merged subtree through `getValues`\n // (getValueByPath only walks live ancestors, so a descendant edit would\n // otherwise read back a stale reference). Payload-less broadcasts\n // (reset, setInitialValues) still sync everything; removeField matches\n // by path.\n const subscribeFactory = useCallback(\n (invalidate: () => void) =>\n onPathEvent(\n emitter,\n 'change',\n createPath(JSON.parse(key) as PathSegments),\n scope,\n invalidate\n ),\n [emitter, key, scope]\n );\n return useWatchCore(subscribeFactory, () => {\n if (options?.exact === false) {\n return get(getValues(form), path.value);\n }\n const value = getValueByPath(form, path);\n return value === undefined ? options?.defaultValue : value;\n });\n}\n\n/**\n * Subscribe to the whole values tree: re-renders the calling component\n * whenever any 'change' event lands — react-hook-form's `watch()` with no\n * arguments. The snapshot is the memoized `getValues(form)` result, so\n * repeated reads during one render share one reference and, under\n * `__DEV__`, one frozen copy.\n *\n * Broad scope by design: read it in components that must stay cheap and\n * need the full tree; per-field readers should reach for {@link useValue}\n * instead so a keystroke re-renders exactly the affected field.\n */\nexport function useValues<T extends Record<string, any> = any>(\n form: Form<T>\n): T {\n // Arrow wrapper, not getValues.bind: bind collapses the generic, so the\n // bound getter would type as () => Record<string, any> and lose the T.\n return useWatch<T>(form, 'change', () => getValues(form));\n}\n\n/**\n * Get field touched state\n */\nexport function useTouched<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): boolean {\n return useTouchedByPath(form, createPath(name));\n}\n\n/**\n * Get field touched state by path\n */\nexport function useTouchedByPath(form: Form, path: Path): boolean {\n const {emitter} = form;\n const {key} = path;\n // Touched is stored per exact key, so only this field's own setTouched\n // (now emitted with its path) matters; payload-less broadcasts (reset,\n // removeField) still sync everything.\n const subscribeFactory = useCallback(\n (invalidate: () => void) => onKeyEvent(emitter, 'touched', key, invalidate),\n [emitter, key]\n );\n return useWatchCore(\n subscribeFactory,\n hasTouchedByPath.bind(null, form, path)\n );\n}\n\n/**\n * Get field error message state\n * @return current error's message string (display text), or undefined\n */\nexport function useError<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): string | undefined {\n return useErrorByPath(form, createPath(name))?.message;\n}\n\n/**\n * Get field error state by path\n * @return current FieldError object ({type, message}), or undefined\n */\nexport function useErrorByPath(form: Form, path: Path): FieldError | undefined {\n const {emitter} = form;\n const {key} = path;\n // Errors are stored per exact key, so only writes to this field's error\n // (setErrorByPath now emits with its path) matter; payload-less\n // broadcasts (clearErrors, reset, removeField) still sync everything.\n const subscribeFactory = useCallback(\n (invalidate: () => void) => onKeyEvent(emitter, 'errors', key, invalidate),\n [emitter, key]\n );\n return useWatchCore(subscribeFactory, getErrorByPath.bind(null, form, path));\n}\n\n/**\n * Get all field errors\n * @return every error registered for the field (insertion order); an empty\n * array when the field has none\n */\nexport function useFieldErrors<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): FieldError[] {\n return useFieldErrorsByPath(form, createPath(name));\n}\n\n/**\n * Get all field errors by path\n * @return every error registered for the field (insertion order); an empty\n * array when the field has none\n */\nexport function useFieldErrorsByPath(form: Form, path: Path): FieldError[] {\n const {emitter} = form;\n const {key} = path;\n // Same exact-key subscription and snapshot rules as useErrorByPath: the\n // getter returns the shared empty constant when clean and the stored\n // array by reference otherwise, so the useSyncExternalStore snapshot is\n // reference-stable between unrelated events.\n const subscribeFactory = useCallback(\n (invalidate: () => void) => onKeyEvent(emitter, 'errors', key, invalidate),\n [emitter, key]\n );\n return useWatchCore(\n subscribeFactory,\n getFieldErrorsByPath.bind(null, form, path)\n );\n}\n\nexport function useIsDirty(form: Form): boolean {\n // Dirty state is driven by value changes, not touch state: subscribe to\n // 'change' so typing flips this immediately, even before a blur.\n return useWatch(form, 'change', isDirty.bind(null, form));\n}\n\n/**\n * Get whether one field is dirty: its live value exists and differs from\n * the field's effective baseline — the same per-field rule\n * `getFieldState(form, name).isDirty` applies (committed\n * `shouldDirty: false` baselines included). Subscribes to 'change' at\n * leaf scope like {@link useValue}: own-key and ancestor writes re-check\n * the flag, payload-less broadcasts (reset, setInitialValues) always\n * sync, and writes elsewhere never re-render it.\n */\nexport function useIsFieldDirty<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): boolean {\n return useIsFieldDirtyByPath(form, createPath(name));\n}\n\n/**\n * Get whether one field is dirty, by parsed path. See {@link\n * useIsFieldDirty}.\n */\nexport function useIsFieldDirtyByPath(form: Form, path: Path): boolean {\n const {emitter} = form;\n const {key} = path;\n const subscribeFactory = useCallback(\n (invalidate: () => void) =>\n onPathEvent(\n emitter,\n 'change',\n createPath(JSON.parse(key) as PathSegments),\n 'leaf',\n invalidate\n ),\n [emitter, key]\n );\n return useWatchCore(subscribeFactory, () => isFieldDirtyByPath(form, path));\n}\n\n/**\n * Get dirty fields state -- object mapping each dirty field's user-facing\n * dotted path ('a.b', 'a.0.c') to true; recalculated after 'change' events\n */\nexport function useDirtyFields(form: Form): Record<string, boolean> {\n return useWatch(form, 'change', getDirtyFields.bind(null, form));\n}\n\n/**\n * Get touched fields state -- array of touched fields' user-facing dotted\n * paths ('a.b', 'a.0.c'); recalculated after 'touched' events\n */\nexport function useTouchedFields(form: Form): string[] {\n return useWatch(form, 'touched', getTouchedFields.bind(null, form));\n}\n\n/**\n * Aggregate snapshot of the whole form's state flags — the one-subscription\n * counterpart of react-hook-form's `formState` object (no errors object;\n * per-field error state stays with `useError`/`useFieldErrors`, and\n * `hasErrors`/`isValid` cover the whole-form questions).\n *\n * Recomputed on every state-bearing event; the field-wise comparator keeps\n * the returned reference stable while nothing observably changed, so\n * `useFormState(form).isDirty` re-renders no more often than the dedicated\n * {@link useIsDirty}. Cheaper than calling the granular hooks one by one\n * (one subscription and one snapshot instead of one per flag).\n */\nexport type FormState<T extends Record<string, any> = any> = {\n /** Any live value differs from its baseline (see {@link isDirty}). */\n isDirty: boolean;\n /** Dirty fields keyed by user-facing dotted path ('a.b', 'a.0.c'). */\n dirtyFields: Record<string, boolean>;\n /** At least one field is touched. */\n isTouched: boolean;\n /** Touched fields' user-facing dotted paths. */\n touchedFields: string[];\n /** Any error registered (field or form level). */\n hasErrors: boolean;\n /** No errors are registered — react-hook-form's `isValid` semantics.\n * In-flight validation is NOT factored in ({@link isValidating} is the\n * separate signal; async rounds temporarily pass this flag like RHF's). */\n isValid: boolean;\n /** Every error as one record keyed by user-facing dotted path\n * ('a.b', 'list.0') — react-hook-form's `formState.errors` shape,\n * typed as {@link FieldErrors} (per-key values optional). Memoized\n * (see {@link getErrorsRecord}): the reference is stable between error\n * writes, so the snapshot comparator can bail on it. */\n errors: FieldErrors<T>;\n isSubmitting: boolean;\n /**\n * Whether a submit has been attempted on this form — set on the first\n * `handleSubmit` call (validation outcome aside) and cleared by\n * `reset`, react-hook-form's `formState.isSubmitted` semantics. Read\n * after a failed submit to render a \"fix the errors below\" panel.\n */\n isSubmitted: boolean;\n /** Any validation round is running (field, form-level, or a pending\n * debounce window). */\n isValidating: boolean;\n isSubmitSuccessful: boolean | undefined;\n submitCount: number;\n /** Async initialValues still pending ({@link Form.isLoading}). */\n isLoading: boolean;\n /** The form-level disabled flag (fields OR their own `disabled`). */\n disabled: boolean;\n};\n\n/** Events any FormState field can react to: each recomputes the whole\n * snapshot — the comparator, not per-flag subscriptions, keeps the\n * re-render surface minimal. */\nconst FORM_STATE_EVENTS: readonly SubscribeEvent[] = [\n 'change',\n 'errors',\n 'touched',\n 'validating',\n 'submitting',\n 'submitCount',\n 'submitSuccessful',\n 'disabled',\n 'loading'\n];\n\nfunction getFormState<T extends Record<string, any>>(\n form: Form<T>\n): FormState<T> {\n return {\n isDirty: isDirty(form),\n dirtyFields: getDirtyFields(form),\n isTouched: form.touched.size > 0,\n touchedFields: getTouchedFields(form),\n hasErrors: hasErrors(form),\n isValid: !hasErrors(form),\n errors: getErrorsRecord(form),\n isSubmitting: form.isSubmitting,\n isSubmitted: form.isSubmitted,\n isValidating: form.validating.size > 0,\n isSubmitSuccessful: form.isSubmitSuccessful,\n submitCount: form.submitCount,\n isLoading: form.isLoading,\n disabled: form.disabled\n };\n}\n\n/** Field-wise equality: reference checks where the getter already memoizes\n * (dirtyFields), element-wise for the fresh array getTouchedFields builds,\n * value checks for the flags. */\nfunction isSameFormState<T extends Record<string, any>>(\n a: FormState<T>,\n b: FormState<T>\n): boolean {\n const sameTouched =\n a.touchedFields.length === b.touchedFields.length &&\n a.touchedFields.every((path, i) => path === b.touchedFields[i]);\n return (\n a.isDirty === b.isDirty &&\n a.dirtyFields === b.dirtyFields &&\n a.isTouched === b.isTouched &&\n sameTouched &&\n a.hasErrors === b.hasErrors &&\n a.isValid === b.isValid &&\n a.errors === b.errors &&\n a.isSubmitting === b.isSubmitting &&\n a.isSubmitted === b.isSubmitted &&\n a.isValidating === b.isValidating &&\n a.isSubmitSuccessful === b.isSubmitSuccessful &&\n a.submitCount === b.submitCount &&\n a.isLoading === b.isLoading &&\n a.disabled === b.disabled\n );\n}\n\nexport function useFormState<T extends Record<string, any> = any>(\n form: Form<T>\n): FormState<T> {\n const getter = useCallback(() => getFormState(form), [form]);\n const subscribeFactory = useCallback(\n (invalidate: () => void) => {\n const offs = FORM_STATE_EVENTS.map(event =>\n on(form.emitter, event, invalidate)\n );\n return () => {\n for (const off of offs) off();\n };\n },\n [form.emitter]\n );\n return useWatchCore(subscribeFactory, getter, isSameFormState);\n}\n\n/**\n * The official selector primitive — TanStack Form's\n * `useStore(store, selector)` counterpart. Subscribes to every\n * state-bearing form event and keeps `selector()`'s result as the\n * snapshot; the selector itself is a plain closure (read the form through\n * any getter — `getValue`, `getValues`, `form.errors.size`, …).\n *\n * Without `isEqual` the subscription is object-identity based: each event\n * recomputes the selector and React bails out when the result is\n * reference-identical (the contract every `useWatch` reader relies on).\n * The optional `isEqual(prev, next)` comparator is for wide-scope\n * selectors that return a fresh reference per call (a whole-values\n * projection, say): an equal verdict skips notifying React altogether —\n * no render, not even a bailed-out one (TanStack `useSelector` compare\n * contract).\n *\n * `useFormState` is the built-in aggregate selector; {@link useWatch} is\n * the single-event version. Reach for `useStore` when the projection is\n * yours and spans state events — e.g. `form.validating.size > 0 &&\n * form.isSubmitting` as one flag.\n */\nexport function useStore<T>(\n form: Form,\n selector: () => T,\n isEqual?: (prev: T, next: T) => boolean\n): T {\n const subscribeFactory = useCallback(\n (invalidate: () => void) => {\n const offs = FORM_STATE_EVENTS.map(event =>\n on(form.emitter, event, invalidate)\n );\n return () => {\n for (const off of offs) off();\n };\n },\n [form.emitter]\n );\n return useWatchCore(subscribeFactory, selector, isEqual);\n}\n\nexport function useHasErrors(form: Form): boolean {\n return useWatch(form, 'errors', hasErrors.bind(null, form));\n}\n\n/**\n * Get every error as one record keyed by user-facing dotted path\n * ('a.b', 'list.0') — react-hook-form's `formState.errors` shape, for\n * error-summary panels and a11y announcements. Values are the stored\n * FieldError[] arrays shared with the form (treat as read-only). The\n * record is memoized per form (see {@link getErrorsRecord}): the hook\n * re-renders only when an error write actually changed the record's\n * content.\n */\nexport function useErrors<T extends Record<string, any> = any>(\n form: Form<T>\n): FieldErrors<T> {\n return useWatch(form, 'errors', () => getErrorsRecord(form));\n}\n\n/**\n * Get every error as one nested object following the values tree\n * (`errors.items?.[0]?.name` reads) — the typed optional-chaining\n * counterpart of {@link useErrors}' flat dotted record. Leaves hold the\n * stored FieldError[] arrays shared with the form (treat as read-only).\n * Memoized alongside the record (see {@link getErrorsTree}): the hook\n * re-renders only when an error write actually changed the tree's\n * content.\n */\nexport function useErrorsTree<T extends Record<string, any> = any>(\n form: Form<T>\n): FieldErrorsTree<T> {\n return useWatch(form, 'errors', () => getErrorsTree(form));\n}\n\n/**\n * Get whether the form currently has no errors — react-hook-form's\n * `formState.isValid` counterpart. Subscribes to the `'errors'` event only;\n * in-flight validation does not flip it (see {@link useIsValidating}).\n */\nexport function useIsValid(form: Form): boolean {\n return useWatch(form, 'errors', () => !hasErrors(form));\n}\n\nexport function useIsSubmitting(form: Form): boolean {\n return useWatch(form, 'submitting', () => form.isSubmitting);\n}\n\n/**\n * Get whether an async {@link Options.initialValues} source is still\n * pending — the flag a loading skeleton or a disabled submit button gates\n * on until the resolved baseline lands. Subscribes to the 'loading' event\n * the core emits around the resolution cycle.\n */\nexport function useIsLoading(form: Form): boolean {\n return useWatch(form, 'loading', () => form.isLoading);\n}\n\n/**\n * Get the form's user-owned metadata slot reactively (Formik's `status`\n * counterpart): any value the app stores through {@link setStatus} —\n * server session flags, wizard step state, non-field errors. Subscribes\n * to the payload-less 'status' event, so unrelated events never re-render\n * the caller, and the returned reference is stable between writes that\n * store an equal value (useSyncExternalStore's Object.is bailout).\n */\nexport function useStatus<T = any>(form: Form): T {\n return useWatch(form, 'status', () => form.status);\n}\n\n/**\n * Get whether the form accepts a submit right now:\n * `!isSubmitting && !hasErrors`. This is the single flag a submit\n * button's `disabled` prop wants — it is `false` for the whole async\n * `onSubmit` span (not just the validation pass) and whenever any field\n * holds an error (client validation or server backfill), replacing the\n * hand-rolled `useHasErrors(form) || useIsSubmitting(form)` pair.\n * Deliberately no dirty or validating semantics: an untouched-but-clean\n * form can submit.\n */\nexport function useCanSubmit(form: Form): boolean {\n const {emitter} = form;\n // canSubmit folds two events into one boolean: error writes\n // ('errors') and submit-state flips ('submitting'). useWatch subscribes\n // to a single event, so subscribe to both through useWatchCore — the\n // snapshot recomputes on either wake and re-renders only when the\n // boolean itself flips, so unrelated single-field error churn costs no\n // extra render (the same granularity useHasErrors already has).\n const subscribeFactory = useCallback(\n (invalidate: () => void) => {\n const offErrors = on(emitter, 'errors', invalidate);\n const offSubmitting = on(emitter, 'submitting', invalidate);\n return () => {\n offErrors();\n offSubmitting();\n };\n },\n [emitter]\n );\n return useWatchCore(\n subscribeFactory,\n () => !form.isSubmitting && !hasErrors(form)\n );\n}\n\nexport function useSubmitCount(form: Form): number {\n return useWatch(form, 'submitCount', () => form.submitCount);\n}\n\n/**\n * Get whether any validation round is currently running: a field\n * validator's pending `validateDebounce` window, an async field validator\n * still in flight, or the form-level validate's debounce window / in-flight\n * round — every one of them holds a key in `form.validating`, and the\n * 'validating' events they emit (field rounds with a path payload, the\n * form-level round as a payload-less broadcast) are what this subscribes\n * to. The boolean snapshot is Object.is-stable, so churn among the marks\n * (a second field opening while the first settles) costs no render while\n * the flag holds. This is the flag a submit button disables itself on, or\n * spins a spinner with, through the pre-submit validation pass — it flips\n * true the moment the first round opens and back false when the last one\n * settles.\n */\nexport function useIsValidating(form: Form): boolean {\n return useWatch(form, 'validating', () => form.validating.size > 0);\n}\n\n/**\n * Get whether the last submit succeeded: `true` once a submit's validation\n * and `onSubmit` completed without throwing, `false` after a failed submit\n * (validation rejection or a thrown callback) and before any submit ran —\n * the falsy reading of the undefined initial/reset state. Subscribes to\n * the 'submitSuccessful' event the core's setSubmitSuccessful emits, so\n * the flag flips in the same tick the outcome lands: the usual consumers\n * are a success banner and a redirect-on-success effect.\n */\nexport function useIsSubmitSuccessful(form: Form): boolean {\n return useWatch(\n form.emitter,\n 'submitSuccessful',\n () => !!form.isSubmitSuccessful\n );\n}\n\n/**\n * Get the form-level error message: the first error stored under the\n * reserved {@link FORM_ERROR} key, as display text — or undefined while\n * the slot is clean. That key is where a form-level `validate` record's\n * `_form` entry lands and where the Standard Schema adapter drops\n * path-less issues, so errors that belong to no single field still have a\n * reader. The classic usage renders it once above the submit button —\n * `useFormError(form) || null` — and the imperative twin is\n * `getError(form, FORM_ERROR)`.\n */\nexport function useFormError(form: Form): string | undefined {\n return useErrorByPath(form, createPath(FORM_ERROR))?.message;\n}\n\n/**\n * Get every form-level error: all errors stored under the reserved\n * {@link FORM_ERROR} key (insertion order), an empty array when the slot\n * is clean. The plural twin of {@link useFormError} for forms that stack\n * several form-level issues — each path-less Standard Schema issue lands\n * in this slot. The array reference is stable between unrelated events\n * (the stored array or a shared empty constant), so consumers can memo on\n * it; the imperative counterpart is `getFieldErrors(form, FORM_ERROR)`.\n */\nexport function useFormErrors(form: Form): FieldError[] {\n return useFieldErrorsByPath(form, createPath(FORM_ERROR));\n}\n","import {\n createContext,\n createElement,\n useContext,\n type Context,\n type Provider,\n type ReactNode\n} from 'react';\nimport {\n useFieldCore,\n type UseFieldOptions,\n type UseFieldResult\n} from './hooks/field';\nimport {\n useFieldArrayCore,\n useFieldArrayItemCore,\n type UseFieldArrayOptions,\n type UseFieldArrayResult,\n type UseFieldArrayItemResult\n} from './hooks/fieldArray';\nimport type {FieldRules} from './rules';\nimport type {Form} from './form';\nimport type {Name, PathSegments} from './path';\nimport type {FieldPath} from './types';\n\nexport const FormContext: Context<Form<any> | null> =\n createContext<Form<any> | null>(null);\n\nexport const FormProvider: Provider<Form<any> | null> = FormContext.Provider;\n\n/**\n * Read the form from the module-level {@link FormContext}. Pass the values\n * shape — `useFormContext<Values>()` — to get a fully typed `Form<Values>`\n * headless API; the `any` default keeps untyped call sites compiling.\n *\n * For multiple forms in one subtree use {@link createFormContext} instead.\n *\n * @throws when no `<FormProvider>` is mounted above the call site.\n */\nexport function useFormContext<T extends Record<string, any> = any>(): Form<T> {\n const form = useContext(FormContext);\n if (!form) throw new Error('no form provided');\n return form;\n}\n\n/** The bundle {@link createFormContext} returns: a private React context\n * plus the hooks pre-bound to it, all typed against `TValues`. */\nexport type FormContextBundle<TValues extends Record<string, any> = any> = {\n /** The raw React context, for `<Form context={...}>`: the component\n * keeps its submit machinery while providing into this instance's\n * private context. */\n context: Context<Form<TValues> | null>;\n FormProvider: (props: {\n form: Form<TValues>;\n children: ReactNode;\n }) => ReactNode;\n useFormContext: () => Form<TValues>;\n useField: <\n TPath extends FieldPath<TValues> | PathSegments =\n FieldPath<TValues> | PathSegments\n >(\n options: {name: TPath} & Omit<UseFieldOptions<TValues, TPath>, 'form'>\n ) => UseFieldResult<TValues, TPath>;\n useFieldArray: <TItem = any, K extends string = 'id'>(options: {\n name: FieldPath<TValues> | Name;\n keyName?: K;\n rules?: FieldRules;\n shouldUnregister?: boolean;\n }) => UseFieldArrayResult<TItem, K>;\n useFieldArrayItem: <TValue = any>(options: {\n name: FieldPath<TValues> | Name;\n id: string;\n }) => UseFieldArrayItemResult<TValue>;\n};\n\n/**\n * Create an isolated bundle of form-context bindings: its own React context\n * plus `useField` / `useFieldArray` / `useFieldArrayItem` /\n * `useFormContext` hooks that resolve their form from it.\n *\n * Why: the module-level {@link FormContext} works fine for a single form per\n * subtree, but nesting two forms (or reusing a component inside a different\n * form) makes them fight over one context. Calling this factory once per app\n * area — `const Ctx = createFormContext<Values>()` — fixes the value shape\n * (`Ctx.useField({name: 'user.name'})` gets its `name` constrained by\n * `FieldPath<Values>` and its `value` typed accordingly), so call sites stop\n * hand-writing generics, and each instance's Provider scopes a strictly\n * separate form. The bundle also carries its raw React context\n * (`Ctx.context`) so `<Form context={Ctx.context}>` can provide into it.\n */\nexport function createFormContext<\n TValues extends Record<string, any> = any\n>(): FormContextBundle<TValues> {\n const Context = createContext<Form<TValues> | null>(null);\n\n // A `form`-prop wrapper instead of exposing Context.Provider directly:\n // callers shouldn't have to know about the raw `value` prop shape.\n function FormProvider({\n form,\n children\n }: {\n form: Form<TValues>;\n children: ReactNode;\n }): ReactNode {\n return createElement(Context.Provider, {value: form}, children);\n }\n\n function useFormContext(): Form<TValues> {\n const form = useContext(Context);\n if (!form) throw new Error('no form provided');\n return form;\n }\n\n function useField<\n TPath extends FieldPath<TValues> | PathSegments =\n FieldPath<TValues> | PathSegments\n >(\n // The bare `{name: TPath}` member keeps `name` a direct inference site\n // for TPath instead of routing it through the mapped Omit type.\n // `form` is omitted on purpose — the form always comes from this\n // factory's own Context.\n options: {name: TPath} & Omit<UseFieldOptions<TValues, TPath>, 'form'>\n ): UseFieldResult<TValues, TPath> {\n return useFieldCore(options as UseFieldOptions<TValues, TPath>, Context);\n }\n\n function useFieldArray<TItem = any, K extends string = 'id'>(options: {\n name: FieldPath<TValues> | Name;\n keyName?: K;\n rules?: FieldRules;\n shouldUnregister?: boolean;\n }): UseFieldArrayResult<TItem, K> {\n return useFieldArrayCore<TItem, K>(\n options as UseFieldArrayOptions<K>,\n Context\n );\n }\n\n function useFieldArrayItem<TValue = any>(options: {\n name: FieldPath<TValues> | Name;\n id: string;\n }): UseFieldArrayItemResult<TValue> {\n return useFieldArrayItemCore(options as {name: Name; id: string}, Context);\n }\n\n // The raw React context, for `<Form context={...}>`: the component keeps\n // its submit machinery while providing into this instance's private\n // context, so the bound hooks above resolve the form it manages.\n return {\n context: Context,\n FormProvider,\n useFormContext,\n useField,\n useFieldArray,\n useFieldArrayItem\n };\n}\n\nexport const CheckboxGroupContext: Context<any> = createContext<any>(null);\n\nexport const CheckboxGroupProvider: Provider<any> =\n CheckboxGroupContext.Provider;\n\nexport function useCheckboxGroupContext(): any {\n const group = useContext(CheckboxGroupContext);\n if (!group) throw new Error('no group provided');\n return group;\n}\n","import * as React from 'react';\nimport {useState} from 'react';\nimport type {ReactNode} from 'react';\n\n/** Nodes deeper than this start collapsed. */\nconst DEFAULT_OPEN_DEPTH = 1;\n\n/**\n * Read-only inspection: the tree never mutates form state, so structural\n * sharing of the inspected value is safe and re-renders stay cheap.\n */\ntype JsonNodeProps = {\n /** Property name (or array index) rendering before the value. */\n name?: string | number;\n /** Value to render. */\n value: unknown;\n /** Current nesting depth (root is 0). */\n depth?: number;\n};\n\n/**\n * One line of the tree: either a collapsible container row\n * (`▸ key: {`) or a leaf (`key: value`).\n */\nfunction JsonNode({name, value, depth = 0}: JsonNodeProps): React.JSX.Element {\n const [open, setOpen] = useState(depth <= DEFAULT_OPEN_DEPTH);\n\n const label =\n name === undefined ? null : (\n <>\n <span className=\"rf0-dt-key\">{String(name)}</span>\n <span className=\"rf0-dt-punct\">: </span>\n </>\n );\n\n if (value !== null && typeof value === 'object') {\n const isArray = Array.isArray(value);\n const entries: Array<[string | number, unknown]> = isArray\n ? (value as unknown[]).map((v, i) => [i, v])\n : Object.entries(value as Record<string, unknown>);\n const openBracket = isArray ? '[' : '{';\n const closeBracket = isArray ? ']' : '}';\n const summary = open\n ? ''\n : `${openBracket}…${closeBracket} ${entries.length}`;\n\n return (\n <div className=\"rf0-dt-row\" style={{paddingLeft: depth * 12}}>\n <button\n type=\"button\"\n className=\"rf0-dt-node-toggle\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n <span className=\"rf0-dt-caret\">{open ? '▾' : '▸'}</span>\n {label}\n <span className=\"rf0-dt-punct\">{open ? openBracket : summary}</span>\n </button>\n {open && (\n <>\n {entries.map(([k, v]) => (\n <JsonNode key={String(k)} name={k} value={v} depth={depth + 1} />\n ))}\n <span className=\"rf0-dt-punct\" style={{paddingLeft: depth * 12}}>\n {closeBracket}\n </span>\n </>\n )}\n </div>\n );\n }\n\n return (\n <span\n className=\"rf0-dt-row\"\n style={{paddingLeft: depth * 12, display: 'block'}}\n >\n {label}\n <Primitive value={value} />\n </span>\n );\n}\n\n/** Render a primitive leaf with terminal-style type coloring. */\nfunction Primitive({value}: {value: unknown}): ReactNode {\n if (value === undefined)\n return <span className=\"rf0-dt-null\">undefined</span>;\n if (value === null) return <span className=\"rf0-dt-null\">null</span>;\n if (typeof value === 'string')\n return <span className=\"rf0-dt-string\">&quot;{value}&quot;</span>;\n if (typeof value === 'boolean')\n return <span className=\"rf0-dt-boolean\">{String(value)}</span>;\n return <span className=\"rf0-dt-number\">{String(value)}</span>;\n}\n\nexport default JsonNode;\n","/**\n * Stylesheet for the Devtools panel.\n *\n * Zero runtime dependencies by design: a single CSS string injected once\n * into <head> (idempotent across module reloads and multiple bundles).\n *\n * Aesthetic: instrument panel / terminal — near-black layers, monospace\n * stack, dense rows, hairline borders. Semantic colors only: error red,\n * success green, neutral gray, with one dim amber accent for the active\n * tab indicator and the collapsed badge.\n */\n\nconst CSS = `\n.rf0-dt {\n position: fixed;\n z-index: 2147483000;\n box-sizing: border-box;\n width: 308px;\n max-width: calc(100vw - 16px);\n max-height: min(70vh, 560px);\n display: flex;\n flex-direction: column;\n font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',\n Menlo, Consolas, 'Liberation Mono', monospace;\n font-size: 11px;\n line-height: 1.45;\n color: #c7d0dc;\n background: #0c1017;\n border: 1px solid #1f2735;\n border-radius: 4px;\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.4);\n}\n.rf0-dt *,\n.rf0-dt-badge * {\n box-sizing: border-box;\n}\n.rf0-dt--top-right { top: 8px; right: 8px; }\n.rf0-dt--bottom-right { bottom: 8px; right: 8px; }\n.rf0-dt--top-left { top: 8px; left: 8px; }\n.rf0-dt--bottom-left { bottom: 8px; left: 8px; }\n\n/* ---- header -------------------------------------------------------- */\n.rf0-dt-header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 6px 5px 9px;\n border-bottom: 1px solid #1f2735;\n background: #10151e;\n}\n.rf0-dt-title {\n flex: 1;\n min-width: 0;\n color: #8b96a5;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.rf0-dt-title::before {\n content: '';\n display: inline-block;\n width: 6px;\n height: 6px;\n margin-right: 6px;\n border-radius: 50%;\n background: #e2b93b;\n vertical-align: 1px;\n}\n.rf0-dt-headerbtn {\n border: 1px solid transparent;\n border-radius: 3px;\n padding: 1px 5px;\n color: #8b96a5;\n background: transparent;\n font: inherit;\n font-size: 10px;\n cursor: pointer;\n}\n.rf0-dt-headerbtn:hover { color: #dce3ec; border-color: #2a3547; }\n.rf0-dt-headerbtn:focus-visible,\n.rf0-dt-tab:focus-visible,\n.rf0-dt-action:focus-visible,\n.rf0-dt-badge:focus-visible,\n.rf0-dt-node-toggle:focus-visible {\n outline: 1px solid #e2b93b;\n outline-offset: 1px;\n}\n\n/* ---- tabs ---------------------------------------------------------- */\n.rf0-dt-tablist {\n display: flex;\n border-bottom: 1px solid #1f2735;\n background: #0e131b;\n}\n.rf0-dt-tab {\n flex: 1;\n padding: 4px 2px 5px;\n border: 0;\n border-bottom: 2px solid transparent;\n background: transparent;\n color: #6b7686;\n font: inherit;\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n cursor: pointer;\n white-space: nowrap;\n}\n.rf0-dt-tab:hover { color: #aab5c4; }\n.rf0-dt-tab[aria-selected='true'] {\n color: #e7edf4;\n border-bottom-color: #e2b93b;\n}\n.rf0-dt-tab-count {\n margin-left: 3px;\n color: inherit;\n opacity: 0.75;\n}\n.rf0-dt-tab--danger[aria-selected='true'] {\n border-bottom-color: #f0647c;\n}\n\n/* ---- panels -------------------------------------------------------- */\n.rf0-dt-panel {\n flex: 1;\n min-height: 84px;\n overflow: auto;\n padding: 6px 8px;\n scrollbar-width: thin;\n scrollbar-color: #2a3547 transparent;\n}\n.rf0-dt-empty {\n padding: 10px 2px;\n color: #4d5766;\n font-style: italic;\n}\n\n/* json tree */\n.rf0-dt-row {\n display: block;\n white-space: pre;\n tab-size: 2;\n}\n.rf0-dt-node-toggle {\n border: 0;\n padding: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n white-space: pre;\n}\n.rf0-dt-node-toggle:hover .rf0-dt-key { color: #dce3ec; }\n.rf0-dt-caret {\n display: inline-block;\n width: 1.2em;\n color: #4d5766;\n}\n.rf0-dt-key { color: #8b96a5; }\n.rf0-dt-punct { color: #4d5766; }\n.rf0-dt-string { color: #8fd68a; }\n.rf0-dt-number { color: #e2b93b; }\n.rf0-dt-boolean { color: #6fb3d9; }\n.rf0-dt-null { color: #55607080; font-style: italic; }\n\n/* errors / touched / dirty lists */\n.rf0-dt-item {\n padding: 3px 2px;\n border-bottom: 1px dotted #1a2230;\n display: flex;\n gap: 8px;\n align-items: baseline;\n}\n.rf0-dt-item:last-child { border-bottom: 0; }\n.rf0-dt-item-path {\n color: #aab5c4;\n word-break: break-all;\n}\n.rf0-dt-item-msg {\n color: #f0647c;\n word-break: break-word;\n}\n.rf0-dt-item-msg--ok { color: #8fd68a; }\n.rf0-dt-item-tag {\n flex: none;\n color: #4d5766;\n font-size: 10px;\n}\n.rf0-dt-item--touched .rf0-dt-item-path { color: #6fb3d9; }\n.rf0-dt-item--dirty .rf0-dt-item-path { color: #e2b93b; }\n\n/* ---- submit status + actions --------------------------------------- */\n.rf0-dt-status {\n display: flex;\n gap: 10px;\n padding: 4px 9px;\n border-top: 1px solid #1f2735;\n background: #10151e;\n color: #6b7686;\n font-size: 10px;\n letter-spacing: 0.04em;\n white-space: nowrap;\n overflow: hidden;\n}\n.rf0-dt-status b { color: #aab5c4; font-weight: 400; }\n.rf0-dt-status .rf0-dt-on { color: #e2b93b; }\n.rf0-dt-status .rf0-dt-ok { color: #8fd68a; }\n.rf0-dt-status .rf0-dt-err { color: #f0647c; }\n.rf0-dt-actions {\n display: flex;\n gap: 6px;\n padding: 6px 8px;\n border-top: 1px solid #1f2735;\n background: #10151e;\n}\n.rf0-dt-action {\n flex: 1;\n padding: 3px 0;\n border: 1px solid #2a3547;\n border-radius: 3px;\n background: #151b26;\n color: #c7d0dc;\n font: inherit;\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n cursor: pointer;\n}\n.rf0-dt-action:hover { border-color: #3b4a61; background: #1a2230; color: #e7edf4; }\n.rf0-dt-action:active { transform: translateY(1px); }\n\n/* ---- collapsed badge ----------------------------------------------- */\n.rf0-dt-badge {\n position: fixed;\n z-index: 2147483000;\n width: 26px;\n height: 26px;\n border: 1px solid #2a3547;\n border-radius: 50%;\n background: #0c1017;\n color: #e2b93b;\n font: inherit;\n font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',\n Menlo, Consolas, 'Liberation Mono', monospace;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.02em;\n cursor: pointer;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);\n}\n.rf0-dt-badge:hover { border-color: #e2b93b; }\n.rf0-dt-badge--top-right { top: 10px; right: 10px; }\n.rf0-dt-badge--bottom-right { bottom: 10px; right: 10px; }\n.rf0-dt-badge--top-left { top: 10px; left: 10px; }\n.rf0-dt-badge--bottom-left { bottom: 10px; left: 10px; }\n.rf0-dt-badge .rf0-dt-dot {\n position: absolute;\n top: 3px;\n right: 3px;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: #f0647c;\n display: none;\n}\n.rf0-dt-badge--has-errors .rf0-dt-dot { display: block; }\n\n/* ---- submit traces -------------------------------------------------- */\n.rf0-dt-submit { border-bottom: 1px solid #1f2735; }\n.rf0-dt-submit:last-child { border-bottom: none; }\n.rf0-dt-submit-summary {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n cursor: pointer;\n list-style: none;\n color: #8b96a5;\n}\n.rf0-dt-submit-summary::-webkit-details-marker { display: none; }\n.rf0-dt-submit-summary:hover { color: #dce3ec; }\n.rf0-dt-submit-n { color: #dce3ec; font-weight: 700; }\n.rf0-dt-submit-time { margin-left: auto; color: #56616f; }\n.rf0-dt-submit[open] .rf0-dt-submit-summary { border-bottom: 1px dashed #1f2735; }\n.rf0-dt-submit-errors { padding: 2px 6px; }\n`;\n\nconst STYLE_ID = 'react-f0rm-devtools-style';\n\n/**\n * Inject the panel stylesheet into <head>. Idempotent: repeated calls\n * (module reloads, HMR, multiple Devtools mounts) never duplicate the\n * <style> element. No-ops outside a DOM environment (SSR).\n */\nexport function injectDevtoolsStyles(): void {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = CSS;\n document.head.appendChild(style);\n}\n","import * as React from 'react';\nimport {useContext, useEffect, useId, useState} from 'react';\nimport type {KeyboardEvent} from 'react';\nimport {on} from '../emitter';\nimport {FormContext} from '../context';\nimport {getErrors, getValues, reset, trigger} from '../form';\nimport type {FieldErrorEntry, Form} from '../form';\nimport type {Path} from '../path';\nimport {\n useDirtyFields,\n useIsSubmitting,\n useSubmitCount,\n useTouchedFields,\n useWatch\n} from '../hooks/form';\nimport JsonTree from './JsonTree';\nimport {injectDevtoolsStyles} from './styles';\n\n/** Corner the panel docks to. */\nexport type DevtoolsPosition =\n 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left';\n\n/** Props for {@link Devtools}. */\nexport type DevtoolsProps<T extends Record<string, any> = any> = {\n /**\n * Form instance to inspect. When omitted, the panel reads the closest\n * `<Form>` / FormProvider ancestor and throws if there is none.\n */\n form?: Form<T>;\n /** Corner to dock the panel in. Defaults to `'top-right'`. */\n position?: DevtoolsPosition;\n};\n\ntype TabId = 'values' | 'errors' | 'touched' | 'dirty' | 'submits' | 'events';\n\nconst TABS: TabId[] = [\n 'values',\n 'errors',\n 'touched',\n 'dirty',\n 'submits',\n 'events'\n];\n\n/** One emitted form event, newest first in the event timeline tab. */\ntype EventTrace = {\n n: number;\n event: string;\n at: number;\n path?: string;\n};\n\n/** One completed submit attempt, snapshotted when `isSubmitting` flips\n * back to false — the outcome, final (schema-coerced) values and errors\n * are all settled by then (submit.ts lands them before the flip). */\ntype SubmitTrace = {\n n: number;\n ok: boolean | undefined;\n at: number;\n values: unknown;\n errors: FieldErrorEntry[];\n};\n\n/** Status chip class for the submit-successful indicator. */\nfunction submitStatusClass(\n isSubmitSuccessful: boolean | undefined\n): string | undefined {\n if (isSubmitSuccessful === undefined) return undefined;\n return isSubmitSuccessful ? 'rf0-dt-ok' : 'rf0-dt-err';\n}\n\n/** Count primitive leaves of an inspected value tree. */\nfunction countLeaves(value: unknown): number {\n if (value === null || typeof value !== 'object') return 1;\n let count = 0;\n for (const v of Object.values(value as Record<string, unknown>)) {\n count += countLeaves(v);\n }\n return count;\n}\n\n/**\n * Live form inspector — a floating instrument panel for development.\n *\n * Renders five tabs (values / errors / touched / dirty / submits — the\n * last a per-attempt trace of outcome, final values and errors), a submit\n * status strip (isSubmitting, submitCount, isSubmitSuccessful) and two actions:\n * Reset and Validate (full `trigger`). All state is read through the\n * library's own watch hooks, so the panel updates in real time without\n * participating in validation or submit flows. Docked at a corner,\n * collapsible to a small badge; fully keyboard operable.\n *\n * Ship it from the dedicated `react-f0rm/devtools` entry — it is never\n * re-exported by the main entry, so production bundles stay untouched.\n */\nexport default function Devtools<T extends Record<string, any> = any>({\n form,\n position = 'top-right'\n}: DevtoolsProps<T>): React.JSX.Element {\n // Idempotent + SSR-guarded; moving it off module scope keeps the\n // devtools entry free of import-time side effects (package.json\n // declares `sideEffects: false`, so bundlers may drop a bare\n // `import './styles'` in production builds).\n injectDevtoolsStyles();\n const contextForm = useContext(FormContext);\n const f: Form<any> | null = form ?? contextForm;\n if (!f) {\n throw new Error(\n '<Devtools> needs a form: pass the `form` prop or render it inside a <Form> / FormProvider.'\n );\n }\n\n const [open, setOpen] = useState(true);\n const [tab, setTab] = useState<TabId>('values');\n const idPrefix = useId().replace(/[^a-zA-Z0-9-]/g, '');\n\n // Live snapshots, straight through the public watch surface.\n const values = useWatch(f, 'change', getValues.bind(null, f));\n const errors = useWatch<FieldErrorEntry[]>(\n f,\n 'errors',\n getErrors.bind(null, f)\n );\n const touched = useTouchedFields(f);\n const dirty = useDirtyFields(f);\n const isSubmitting = useIsSubmitting(f);\n const submitCount = useSubmitCount(f);\n const isSubmitSuccessful = useWatch(\n f,\n 'submitSuccessful',\n () => f.isSubmitSuccessful\n );\n\n // Submit traces: each completed attempt (the 'submitting' emit carrying\n // isSubmitting=false) is snapshotted once — outcome, values and errors,\n // kept capped at the 10 most recent attempts, newest first. The push is\n // deferred one microtask because the failed path flips isSubmitting\n // before isSubmitSuccessful; by microtask time the attempt's state is\n // final. setState from an event-driven callback, never from the effect\n // body or during render.\n const [submits, setSubmits] = useState<SubmitTrace[]>([]);\n useEffect(() => {\n return on(f.emitter, 'submitting', () => {\n if (f.isSubmitting) return;\n queueMicrotask(() => {\n if (f.isSubmitting || f.submitCount === 0) return;\n setSubmits(prev =>\n [\n {\n n: f.submitCount,\n ok: f.isSubmitSuccessful,\n at: Date.now(),\n values: getValues(f),\n errors: getErrors(f)\n },\n ...prev\n ].slice(0, 10)\n );\n });\n });\n }, [f]);\n\n // Event timeline: every form event lands here, newest first, capped at\n // the 50 most recent. State writes ride the event callback (set-state\n // from an event-driven subscription, never from the effect body or\n // render). Payloads are summarized: path events carry their segments,\n // focusError its key (pretty-printed), payload-less events nothing.\n const [events, setEvents] = useState<EventTrace[]>([]);\n useEffect(() => {\n let seq = 0;\n const push = (event: string, path?: string) => {\n seq += 1;\n const trace: EventTrace = {n: seq, event, at: Date.now()};\n if (path !== undefined) trace.path = path;\n setEvents(prev => [trace, ...prev].slice(0, 50));\n };\n const disposers: Array<() => void> = [];\n for (const event of [\n 'change',\n 'errors',\n 'touched',\n 'validating'\n ] as const) {\n disposers.push(\n on(f.emitter, event, (path?: Path) =>\n push(event, path ? path.value.join('.') : undefined)\n )\n );\n }\n disposers.push(\n on(f.emitter, 'focusError', (key: string) => {\n // The key is JSON-serialized segments — pretty-print it like the\n // other path events; malformed input falls back to the raw key.\n try {\n push('focusError', (JSON.parse(key) as string[]).join('.'));\n } catch {\n push('focusError', key);\n }\n })\n );\n for (const event of [\n 'submitting',\n 'submitCount',\n 'submitSuccessful',\n 'reset',\n 'disabled',\n 'status',\n 'loading'\n ] as const) {\n disposers.push(on(f.emitter, event, () => push(event)));\n }\n return () => disposers.forEach(dispose => dispose());\n }, [f]);\n\n if (!open) {\n return (\n <button\n type=\"button\"\n className={`rf0-dt-badge rf0-dt-badge--${position}${\n errors.length > 0 ? ' rf0-dt-badge--has-errors' : ''\n }`}\n aria-expanded={false}\n aria-label={`Open react-f0rm devtools (${errors.length} errors)`}\n onClick={() => setOpen(true)}\n >\n f0\n <span className=\"rf0-dt-dot\" />\n </button>\n );\n }\n\n const counts: Record<TabId, number> = {\n values: countLeaves(values),\n errors: errors.length,\n touched: touched.length,\n dirty: Object.keys(dirty).length,\n submits: submits.length,\n events: events.length\n };\n\n /** Arrow-key tab navigation (buttons stay click/Enter/Space operable). */\n const onTabKeyDown = (e: KeyboardEvent) => {\n const deltas: Record<string, number> = {\n ArrowRight: 1,\n ArrowLeft: -1\n };\n const delta = deltas[e.key];\n if (!delta) return;\n e.preventDefault();\n const next = TABS[(TABS.indexOf(tab) + delta + TABS.length) % TABS.length];\n setTab(next);\n document.getElementById(`${idPrefix}-tab-${next}`)?.focus();\n };\n\n return (\n <section\n className={`rf0-dt rf0-dt--${position}`}\n aria-label=\"react-f0rm devtools\"\n >\n <header className=\"rf0-dt-header\">\n <span className=\"rf0-dt-title\">react-f0rm</span>\n <button\n type=\"button\"\n className=\"rf0-dt-headerbtn\"\n aria-label=\"Collapse devtools\"\n onClick={() => setOpen(false)}\n >\n –\n </button>\n </header>\n\n <div\n className=\"rf0-dt-tablist\"\n role=\"tablist\"\n aria-label=\"Form state\"\n tabIndex={-1}\n onKeyDown={onTabKeyDown}\n >\n {TABS.map(id => (\n <button\n key={id}\n id={`${idPrefix}-tab-${id}`}\n type=\"button\"\n role=\"tab\"\n className={`rf0-dt-tab${id === 'errors' ? ' rf0-dt-tab--danger' : ''}`}\n aria-selected={tab === id}\n aria-controls={`${idPrefix}-panel-${id}`}\n tabIndex={tab === id ? 0 : -1}\n onClick={() => setTab(id)}\n >\n {id}\n <span className=\"rf0-dt-tab-count\">{counts[id]}</span>\n </button>\n ))}\n </div>\n\n <div\n id={`${idPrefix}-panel-${tab}`}\n role=\"tabpanel\"\n aria-labelledby={`${idPrefix}-tab-${tab}`}\n className=\"rf0-dt-panel\"\n >\n {tab === 'values' && <JsonTree value={values} />}\n {tab === 'errors' &&\n (errors.length === 0 ? (\n <p className=\"rf0-dt-empty\">no errors</p>\n ) : (\n errors.map(({path, type, message}, index) => (\n // Same path can hold several errors now; index keeps keys\n // unique without changing what is rendered (messages may\n // legitimately repeat for one path).\n\n <div key={`${path}:${index}`} className=\"rf0-dt-item\">\n <span className=\"rf0-dt-item-path\">{path}</span>\n <span className=\"rf0-dt-item-msg\">{message}</span>\n <span className=\"rf0-dt-item-tag\">{type}</span>\n </div>\n ))\n ))}\n {tab === 'touched' &&\n (touched.length === 0 ? (\n <p className=\"rf0-dt-empty\">no touched fields</p>\n ) : (\n touched.map(path => (\n <div key={path} className=\"rf0-dt-item rf0-dt-item--touched\">\n <span className=\"rf0-dt-item-path\">{path}</span>\n </div>\n ))\n ))}\n {tab === 'dirty' &&\n (Object.keys(dirty).length === 0 ? (\n <p className=\"rf0-dt-empty\">no dirty fields</p>\n ) : (\n Object.keys(dirty).map(path => (\n <div key={path} className=\"rf0-dt-item rf0-dt-item--dirty\">\n <span className=\"rf0-dt-item-path\">{path}</span>\n <span className=\"rf0-dt-item-msg rf0-dt-item-msg--ok\">\n changed\n </span>\n </div>\n ))\n ))}\n {tab === 'submits' &&\n (submits.length === 0 ? (\n <p className=\"rf0-dt-empty\">no submits yet</p>\n ) : (\n submits.map(trace => (\n <details key={`${trace.n}-${trace.at}`} className=\"rf0-dt-submit\">\n <summary className=\"rf0-dt-submit-summary\">\n <span className=\"rf0-dt-submit-n\">#{trace.n}</span>\n <span className={submitStatusClass(trace.ok)}>\n {trace.ok === true\n ? 'ok'\n : trace.ok === false\n ? 'failed'\n : '–'}\n </span>\n <span className=\"rf0-dt-submit-time\">\n {new Date(trace.at).toLocaleTimeString()}\n </span>\n </summary>\n {trace.errors.length > 0 && (\n <div className=\"rf0-dt-submit-errors\">\n {trace.errors.map(({path, type, message}, index) => (\n <div key={`${path}:${index}`} className=\"rf0-dt-item\">\n <span className=\"rf0-dt-item-path\">{path}</span>\n <span className=\"rf0-dt-item-msg\">{message}</span>\n <span className=\"rf0-dt-item-tag\">{type}</span>\n </div>\n ))}\n </div>\n )}\n <JsonTree value={trace.values} />\n </details>\n ))\n ))}\n {tab === 'events' &&\n (events.length === 0 ? (\n <p className=\"rf0-dt-empty\">no events yet</p>\n ) : (\n <>\n <div>\n <button\n type=\"button\"\n className=\"rf0-dt-action\"\n onClick={() => setEvents([])}\n >\n Clear\n </button>\n </div>\n {events.map(trace => (\n <div key={`${trace.n}:${trace.at}`} className=\"rf0-dt-item\">\n <span className=\"rf0-dt-item-tag\">{trace.event}</span>\n <span className=\"rf0-dt-item-path\">{trace.path ?? '—'}</span>\n <span className=\"rf0-dt-item-msg rf0-dt-item-msg--ok\">\n {new Date(trace.at).toLocaleTimeString()}\n </span>\n </div>\n ))}\n </>\n ))}\n </div>\n\n <p className=\"rf0-dt-status\" aria-live=\"polite\">\n <span className={isSubmitting ? 'rf0-dt-on' : undefined}>\n submitting <b>{String(isSubmitting)}</b>\n </span>\n <span>\n submits <b>{submitCount}</b>\n </span>\n <span className={submitStatusClass(isSubmitSuccessful)}>\n ok{' '}\n <b>\n {isSubmitSuccessful === undefined\n ? '–'\n : String(isSubmitSuccessful)}\n </b>\n </span>\n </p>\n\n <div className=\"rf0-dt-actions\">\n <button\n type=\"button\"\n className=\"rf0-dt-action\"\n onClick={() => reset(f, f.initialValues)}\n >\n Reset\n </button>\n <button\n type=\"button\"\n className=\"rf0-dt-action\"\n onClick={() => trigger(f)}\n >\n Validate\n </button>\n </div>\n </section>\n );\n}\n"],"names":["computeDirtyFields","form","dirtyFields","fn","key","value","values","path","JSON","parse","getDirtyBaseline","join","forEachDirtyField","getDirtyFields","cache","dirtyFieldsCaches","get","version","result","a","b","aKeys","Object","keys","length","every","sameDirtyKeys","set","useWatch","formOrEmitter","event","getter","isEqual","emitter","subscribeFactory","cacheRef","useRef","current","hasValue","getterRef","isEqualRef","getSnapshot","useCallback","subscribe","notify","compare","next","useSyncExternalStore","useWatchCore","invalidate","on","FormContext","createContext","Provider","JsonNode","name","depth","open","setOpen","useState","label","React","createElement","Fragment","className","String","isArray","Array","entries","map","v","i","openBracket","closeBracket","summary","style","paddingLeft","type","onClick","k","display","Primitive","STYLE_ID","TABS","submitStatusClass","isSubmitSuccessful","countLeaves","count","Devtools","position","document","getElementById","id","textContent","head","appendChild","injectDevtoolsStyles","contextForm","useContext","f","Error","tab","setTab","idPrefix","useId","replace","getValues","bind","errors","getErrors","touched","getTouchedFields","useTouchedFields","dirty","useDirtyFields","isSubmitting","useIsSubmitting","submitCount","useSubmitCount","submits","setSubmits","useEffect","queueMicrotask","prev","n","ok","at","Date","now","slice","events","setEvents","seq","push","trace","disposers","forEach","dispose","counts","role","tabIndex","onKeyDown","e","delta","ArrowRight","ArrowLeft","preventDefault","indexOf","focus","JsonTree","message","index","toLocaleTimeString","reset","initialValues","trigger"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("@for-fun/event-emitter");const t=new Map;function r(e){if(Array.isArray(e))return e;const r=t.get(e);if(r)return r;const o=function(e){const t=[];let r="";const o=()=>{if(n(r)){const n=[...t.map(String),r].join("."),o=t.reduce((e,t)=>e+("number"==typeof t?`[${t}]`:`${e?".":""}${t}`),"");throw new TypeError(`Numeric path segment must use bracket notation: "${n}" → "${o}[${r}]" (path: ${e})`)}t.push(r),r=""};for(let s=0;s<e.length;s++){const i=e[s];if("."===i)""!==r&&o();else if("["===i){""!==r&&o();const i=e[s+1];if('"'===i||"'"===i){const r=e.indexOf(i,s+2);if(-1===r)throw new TypeError(`Unterminated quote in path: ${e}`);if("]"!==e[r+1])throw new TypeError(`Expected "]" after quoted segment in path: ${e}`);t.push(e.slice(s+2,r)),s=r+1}else{const r=e.indexOf("]",s+1);if(-1===r)throw new TypeError(`Unterminated bracket in path: ${e}`);const o=e.slice(s+1,r);t.push(n(o)?Number(o):o),s=r}}else r+=i}""===r&&0!==t.length||o();return t}(e);if(t.size>=1e4){const e=t.keys().next();e.done||t.delete(e.value)}return t.set(e,o),o}const n=e=>/^-?\d+$/.test(e);function o(e,t){return t.reduce((e,t)=>{if(null!=e)return e[t]},e)}function s(e,t,r){if(!t.length)return r;const[o,...i]=t,u="number"==typeof o?o:Array.isArray(e)&&"string"==typeof o&&n(o)?Number(o):void 0;if(void 0!==u){const t=Array.isArray(e)?e.slice():[];return t[u]=s(t[u],i,r),t}return{...e,[o]:s(e&&e[o],i,r)}}function i(e){const t=r(e);return{value:t,key:JSON.stringify(t)}}const u=new WeakMap;function c(e){const t=u.get(e);t&&t.version++}const f=new WeakMap;function a(e){const t=f.get(e);t&&t.version++}const p=new WeakMap;const l=new WeakMap;function y(e){return!!e&&"object"==typeof e&&"string"==typeof e.type&&"string"==typeof e.message}const g=Symbol("validation-outcome"),h=[];function d(t,r,n,o){const{emitter:s,errors:i}=t,u=function(e){if("string"==typeof e)return e?[{type:"custom",message:e}]:void 0;if(y(e))return[e];if(!e)return;const t=[];return e.forEach(e=>{"string"==typeof e&&e?t.push({type:"custom",message:e}):y(e)&&t.push(e)}),t.length?t:void 0}(n);u?i.set(r.key,u):i.delete(r.key),a(t),e.emit(s,"errors",r)}exports.FORM_ERROR="_form",exports.VALIDATION_OUTCOME=g,exports.bumpDirtyVersion=function(e){const t=l.get(e);t&&t.version++},exports.bumpErrorsVersion=a,exports.bumpValuesVersion=c,exports.clearDirtyBaselines=function(e,t){const r=p.get(e);r&&(void 0===t?r.clear():r.delete(t))},exports.clearErrors=function(t,r){const{emitter:n,errors:o}=t;return o.clear(),a(t),void e.emit(n,"errors")},exports.create=i,exports.dirtyFieldsCaches=l,exports.get=o,exports.getDirtyBaseline=function(e,t,r){const n=p.get(e);return n?.has(t)?n.get(t):o(e.initialValues,r)},exports.getErrors=function({errors:e}){const t=[];for(const[r,n]of e){const e=JSON.parse(r).join(".");for(const{type:r,message:o}of n)t.push({path:e,type:r,message:o})}return t},exports.getFieldErrorsByPath=function({errors:e},t){return e.get(t.key)??h},exports.hasErrors=function({errors:e}){return e.size>0},exports.isEqual=function e(t,r){if(Object.is(t,r))return!0;if(t instanceof Date&&r instanceof Date)return t.getTime()===r.getTime();if(!t||!r||"object"!=typeof t||"object"!=typeof r)return!1;const n=Array.isArray(t);if(n!==Array.isArray(r))return!1;if(n){if(t.length!==r.length)return!1;for(let n=0;n<t.length;n++)if(!e(t[n],r[n]))return!1;return!0}const o=Object.getPrototypeOf(t);if(o!==Object.prototype&&null!==o)return!1;if(Object.getPrototypeOf(r)!==o)return!1;const s=Object.keys(t),i=Object.keys(r);if(s.length!==i.length)return!1;for(const n of s)if(!e(t[n],r[n]))return!1;return!0},exports.isFieldError=y,exports.isIndex=n,exports.isPromise=function(e){return e&&"function"==typeof e.then},exports.normalizePath=r,exports.pruneDirtyBaselines=function(e,{key:t}){const r=p.get(e);if(!r?.size)return;const n=`${t.slice(0,-1)},`;for(const e of r.keys())e.startsWith(n)&&r.delete(e)},exports.setDirtyBaseline=function(e,{key:t},r){let n=p.get(e);n||(n=new Map,p.set(e,n)),n.set(t,r)},exports.setError=function(e,t,r,n){d(e,i(t),r)},exports.setErrorByPath=d,exports.setOwned=function(e,t,r,o){if(!t.length)return r;let s=e,i=null,u="";for(let c=0;c<t.length;c++){const f=t[c];if(!o.has(s)){let t;t="number"==typeof f||Array.isArray(s)&&"string"==typeof f&&n(f)?Array.isArray(s)?s.slice():[]:{...s},o.add(t),0===c?e=t:i[u]=t,s=t}c===t.length-1?s[f]=r:(i=s,u=f,s=s[f])}return e},exports.setParsedValues=function(t,r){void 0!==r&&r!==t.parsedValues&&(t.parsedValues=r,c(t),e.emit(t.emitter,"change"))},exports.unset=function e(t,r){if(!r.length||null==t)return t;const[n,...o]=r;if(o.length){const r=e(t[n],o);return r===t[n]?t:s(t,[n],r)}if(Array.isArray(t)){if(!(n in t))return t;const e=t.slice();return delete e[n],e}if("object"!=typeof t||!(n in t))return t;const i={...t};return delete i[n],i},exports.valuesCaches=u,exports.waitUntil=function(t,r,n,o){return new Promise((s,i)=>{if(o())return void i();if(n())return void s();const u=e.on(t,r,()=>{if(o())return u(),void i();n()&&(u(),s())})})};
2
+ //# sourceMappingURL=errors-8oPux7GR.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-8oPux7GR.cjs.js","sources":["../src/util.ts","../src/path.ts","../src/core/internals.ts","../src/core/errors.ts"],"sourcesContent":["import {on} from './emitter';\nimport type {EventEmitter} from './emitter';\n\n/** FIFO bound for {@link pathCache}. Static field names ('user.email')\n * number in the dozens per app, so the cache barely grows in practice;\n * the cap is only a backstop for dynamic keys ('items[' + id + ']'\n * style — array inputs skip the cache entirely), where long-lived forms\n * would otherwise accumulate entries forever. Map preserves insertion\n * order, so eviction drops the oldest entry; a dropped string only\n * costs a re-parse the next time normalizePath sees it — results are\n * deterministic, so eviction affects performance, never behavior. */\nconst PATH_CACHE_LIMIT = 1e4;\n\nconst pathCache = new Map<string, (string | number)[]>();\n\n/** Test-only view of the path cache size. Not re-exported from the\n * package entry — tests import this module directly to assert the\n * FIFO bound above. */\nexport const pathCacheSize: () => number = () => pathCache.size;\n\nexport function normalizePath(\n path: string | (string | number)[]\n): (string | number)[] {\n if (Array.isArray(path)) return path;\n const cached = pathCache.get(path);\n if (cached) return cached;\n const value = parsePath(path);\n if (pathCache.size >= PATH_CACHE_LIMIT) {\n // Insertion order is FIFO order; the cache is non-empty here\n // because size >= 1e4.\n const oldest = pathCache.keys().next();\n if (!oldest.done) pathCache.delete(oldest.value);\n }\n pathCache.set(path, value);\n return value;\n}\n\n/** Does a segment string denote an integer array index (optionally\n * negative)? The parser feeds it bracket contents ('items[0]' → number)\n * and rejects dotted numerics outright ('items.0' throws), so parsed\n * paths never carry index-looking strings. Its remaining duty is\n * defending programmatic segments: normalizePath also accepts raw\n * (string | number)[] arrays, and index-shaped strings can still arrive\n * from internal callers (error-tree keys) or array containers met\n * during set/setOwned walks. */\nexport const isIndex: (segment: string) => boolean = segment =>\n /^-?\\d+$/.test(segment);\n\nfunction parsePath(path: string): (string | number)[] {\n const result: (string | number)[] = [];\n let identifier = '';\n const flushIdentifier = () => {\n if (isIndex(identifier)) {\n // Rebuild both spellings for an actionable message: the dotted\n // form the caller wrote vs. the bracket form the parser accepts.\n const dotted = [...result.map(String), identifier].join('.');\n const bracket = result.reduce(\n (acc: string, seg: string | number) =>\n acc +\n (typeof seg === 'number' ? `[${seg}]` : `${acc ? '.' : ''}${seg}`),\n ''\n );\n throw new TypeError(\n `Numeric path segment must use bracket notation: \"${dotted}\" → \"${bracket}[${identifier}]\" (path: ${path})`\n );\n }\n result.push(identifier);\n identifier = '';\n };\n\n for (let i = 0; i < path.length; i++) {\n const char = path[i];\n if (char === '.') {\n if (identifier !== '') flushIdentifier();\n } else if (char === '[') {\n if (identifier !== '') flushIdentifier();\n const quote = path[i + 1];\n if (quote === '\"' || quote === \"'\") {\n const close = path.indexOf(quote, i + 2);\n if (close === -1) {\n throw new TypeError(`Unterminated quote in path: ${path}`);\n }\n if (path[close + 1] !== ']') {\n throw new TypeError(\n `Expected \"]\" after quoted segment in path: ${path}`\n );\n }\n result.push(path.slice(i + 2, close));\n i = close + 1;\n } else {\n const close = path.indexOf(']', i + 1);\n if (close === -1) {\n throw new TypeError(`Unterminated bracket in path: ${path}`);\n }\n const content = path.slice(i + 1, close);\n result.push(isIndex(content) ? Number(content) : content);\n i = close;\n }\n } else {\n identifier += char;\n }\n }\n if (identifier !== '' || result.length === 0) flushIdentifier();\n return result;\n}\n\nexport function get(values: any, path: (string | number)[]): any {\n return path.reduce((current: any, p: string | number) => {\n if (current == null) return undefined;\n return current[p];\n }, values);\n}\n\n/**\n * Immutable counterpart of {@link set}: removes the path from the value\n * tree, copying only along the touched branch (untouched branches stay\n * shared with the source, like set). Deletes the key entirely rather than\n * writing undefined, so the result carries no `a: undefined` entries.\n */\nexport function unset(values: any, path: (string | number)[]): any {\n if (!path.length || values == null) return values;\n const [prop, ...props] = path;\n if (props.length) {\n const next = unset(values[prop], props);\n // Reattach the pruned child at its parent key — NOT at the full path,\n // which would write the pruned subtree back under the removed key.\n return next === values[prop] ? values : set(values, [prop], next);\n }\n if (Array.isArray(values)) {\n if (!(prop in values)) return values;\n const arr = values.slice();\n delete arr[prop as number];\n return arr;\n }\n if (typeof values !== 'object' || !(prop in values)) return values;\n const copy = {...values};\n delete copy[prop as string];\n return copy;\n}\n\nexport function set(values: any, path: (string | number)[], value: any): any {\n if (!path.length) return value;\n\n const [prop, ...props] = path;\n // A numeric segment lands with the array copy rule — numbers, and the\n // string form dotted paths parse to ('a.0'), on an array container: an\n // object spread there would corrupt the array into {'0': ...}.\n const index =\n typeof prop === 'number'\n ? prop\n : Array.isArray(values) && typeof prop === 'string' && isIndex(prop)\n ? Number(prop)\n : undefined;\n if (index !== undefined) {\n const arr = Array.isArray(values) ? values.slice() : [];\n arr[index] = set(arr[index], props, value);\n return arr;\n }\n return {...values, [prop]: set(values && values[prop], props, value)};\n}\n\n/**\n * Ownership-tracked {@link set}: merge many paths into one tree without\n * re-copying containers the merge itself already created.\n *\n * Containers present in `owned` (freshly created by an earlier call of the\n * same merge) are mutated in place; every other container -- nodes borrowed\n * from the seed tree and user leaf values -- is copied first with the exact\n * copy rules `set` applies (numeric prop: array slice, or a fresh array\n * when the node is not one; string prop: object spread). Chaining\n * `setOwned` over a list of paths therefore produces the tree chaining\n * `set` would, in the same insertion order, but allocates each distinct\n * container once (O(distinct path prefixes)) instead of re-copying the\n * whole branch for every path (O(paths x depth)).\n *\n * Use a fresh `owned` set per merge and thread the returned root (a copied\n * replacement when the seed root itself had to be copied) into the next\n * call. Borrowed containers are never mutated.\n */\nexport function setOwned(\n root: any,\n path: (string | number)[],\n value: any,\n owned: Set<object>\n): any {\n if (!path.length) return value;\n let container = root;\n let parent: any = null;\n let parentProp: string | number = '';\n for (let i = 0; i < path.length; i++) {\n const prop = path[i];\n if (!owned.has(container)) {\n let copy: any;\n if (\n typeof prop === 'number' ||\n (Array.isArray(container) && typeof prop === 'string' && isIndex(prop))\n ) {\n copy = Array.isArray(container) ? container.slice() : [];\n } else {\n copy = {...container};\n }\n owned.add(copy);\n if (i === 0) root = copy;\n else parent[parentProp] = copy;\n container = copy;\n }\n if (i === path.length - 1) {\n container[prop] = value;\n } else {\n parent = container;\n parentProp = prop;\n container = container[prop];\n }\n }\n return root;\n}\n\nexport function isEmpty(value: any): boolean {\n if (value == null) return true;\n if (typeof value !== 'object') return false;\n\n const values = Object.values(value);\n return values.length === 0 || values.every(isEmpty);\n}\n\nexport function isPromise(value: any): value is Promise<any> {\n return value && typeof value.then === 'function';\n}\n\n/**\n * The shared DOM event → value protocol every binding uses (Field's\n * `eventToValue` default, `useField`'s `inputProps`, `form.register`):\n * - file inputs store their `FileList` (a value prop can never control\n * them, and validation/submit want the files);\n * - checkbox inputs store `checked` (the value attribute is not the\n * control's state);\n * - `valueAsNumber`/`valueAsDate` store the typed DOM accessors\n * (number/date inputs, RHF's `register` options; number wins when both\n * are set);\n * - everything else stores the string `value`;\n * - a non-DOM first argument (no `target`) passes through unchanged, so\n * custom controls can hand raw values.\n */\nexport function extractEventValue(\n e: any,\n options?: {\n valueAsNumber?: boolean;\n valueAsDate?: boolean;\n }\n): any {\n const target = e?.target;\n if (!target) return e;\n if (target.type === 'file') return target.files;\n if (target.type === 'checkbox') return target.checked;\n if (options?.valueAsNumber) return target.valueAsNumber;\n if (options?.valueAsDate) return target.valueAsDate;\n return target.value;\n}\n\n/** Structural equality for form default data (primitives, arrays, plain\n * objects, Dates). Class instances and other exotic objects compare as\n * unequal, which errs on the side of re-seeding when {@link\n * setInitialValues} uses it to tell a re-rendered inline literal from\n * genuinely changed content. */\nexport function isEqual(a: any, b: any): boolean {\n if (Object.is(a, b)) return true;\n if (a instanceof Date && b instanceof Date)\n return a.getTime() === b.getTime();\n if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false;\n const isArray = Array.isArray(a);\n if (isArray !== Array.isArray(b)) return false;\n if (isArray) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!isEqual(a[i], b[i])) return false;\n }\n return true;\n }\n const proto = Object.getPrototypeOf(a);\n if (proto !== Object.prototype && proto !== null) return false;\n if (Object.getPrototypeOf(b) !== proto) return false;\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n for (const key of keysA) {\n if (!isEqual(a[key], b[key])) return false;\n }\n return true;\n}\n\nexport function waitUntil(\n emitter: EventEmitter<any>,\n event: string,\n isResolve: () => boolean,\n isReject: () => boolean\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (isReject()) return void reject();\n if (isResolve()) return void resolve();\n\n const off = on(emitter, event as any, () => {\n if (isReject()) {\n off();\n reject();\n return;\n }\n // Condition not reached yet — stay subscribed and keep waiting. (The\n // previous `if (isResolve()) return` was inverted: it hung forever\n // once the condition held and resolved prematurely while it didn't.)\n if (!isResolve()) return;\n off();\n resolve();\n });\n });\n}\n\n/**\n * DEV-only snapshot guard for {@link getValues}: deep-clones and freezes\n * plain objects and arrays so a consumer mutating the returned tree throws\n * immediately instead of silently corrupting the shared memoized result\n * (getValues hands the same cached reference to every reader between\n * writes). Non-plain values (Date, File, Blob, class instances, Map/Set)\n * pass through by reference, unfrozen — cloning would strip their\n * prototypes and freezing would break legitimate methods like\n * Date#setHours. Callers gate the call behind `__DEV__` so production\n * builds pay nothing.\n */\nexport function freezeValues(value: any): any {\n if (value === null || typeof value !== 'object') return value;\n if (Array.isArray(value)) {\n return Object.freeze(value.map(freezeValues));\n }\n if (Object.getPrototypeOf(value) !== Object.prototype) return value;\n const clone: Record<string, any> = {};\n for (const key of Object.keys(value)) clone[key] = freezeValues(value[key]);\n return Object.freeze(clone);\n}\n","import {normalizePath} from './util';\n\nexport type PathSegments = (string | number)[];\nexport type Name = string | PathSegments;\nexport type Path = {value: PathSegments; key: string};\n\nexport default function create(name: Name): Path {\n const value = normalizePath(name);\n return {value, key: JSON.stringify(value)};\n}\n","import {emit} from '../emitter';\nimport {get} from '../util';\nimport type {Name, Path, PathSegments} from '../path';\nimport type {FieldError, Form} from '../form';\n\n/** Per-form memoization of {@link getValues}, the same version-bump/read\n * pattern {@link dirtyFieldsCaches} gives {@link getDirtyFields}. */\ntype ValuesCache = {version: number; result: any};\n\nexport const valuesCaches: WeakMap<Form, ValuesCache> = new WeakMap();\n\n/**\n * Invalidate `form`'s cached {@link getValues} result. Called at every\n * point that can change values, parsedValues or initialValues\n * (setValueByPath, removeFieldByPath, setInitialValues, reset, resetField,\n * setParsedValues) so repeated reads hand back a stable reference until\n * the next write.\n */\nexport function bumpValuesVersion(form: Form): void {\n const cache = valuesCaches.get(form);\n if (cache) cache.version++;\n}\n\n/** Per-form memoization of {@link getErrorsRecord} /\n * {@link getErrorsTree} (src/core/errors.ts), the same version-bump/read\n * pattern {@link valuesCaches} gives {@link getValues}: every errors-Map\n * mutation bumps the counter, reads reset it, so consecutive reads hand\n * back one stable record/tree reference until the next error write. */\ntype ErrorsCache = {\n version: number;\n result: Record<string, FieldError[]>;\n tree: any;\n};\n\nexport const errorsCaches: WeakMap<Form, ErrorsCache> = new WeakMap();\n\n/**\n * Invalidate `form`'s cached {@link getErrorsRecord} result. Called at\n * every point that mutates the errors Map — setErrorByPath, clearErrors,\n * setServerErrors/clearServerErrors, clearFormValidateErrors (the\n * form-level round's pre-land cleanup), removeFieldByPath and resetField's\n * error drops. A no-op until the first read installed a cache entry.\n */\nexport function bumpErrorsVersion(form: Form): void {\n const cache = errorsCaches.get(form);\n if (cache) cache.version++;\n}\n\n/**\n * Get form values: the values Map layered over parsedValues (when a schema\n * validation produced them) layered over initialValues.\n *\n * Merged with copy-on-write ownership tracking ({@link setOwned}): every\n * distinct container on a written path is allocated once and shared by all\n * paths through it, instead of re-copying the whole branch for every key.\n * One owned set spans the whole merge, so containers borrowed from the\n * parsedValues tree are copied before mutation exactly like initialValues\n * ones. The result is a freshly merged tree per mutation, with untouched\n * branches sharing references with the baseline exactly like chained\n * `set` did.\n *\n * Memoized per form like {@link getDirtyFields}: every value write bumps a\n * `version` counter ({@link bumpValuesVersion}) while reads reset it, so\n * consecutive reads hand back the same reference (submit, changeValue and\n * form-level validate all read the whole tree, often several times per\n * interaction). Treat the result as read-only — the next read after a\n * write returns a fresh tree, but between writes the cached one is shared\n * with every other reader.\n *\n * parsedValues is the schema's complete output tree: once validation\n * succeeds it replaces the initialValues baseline (fields the schema\n * dropped disappear), while live edits in the values Map still win over\n * both. It never affects dirty state — {@link isDirty} and\n * {@link getDirtyFields} compare live edits against initialValues only,\n * because parsing is not a user edit.\n *\n * @param form\n */\n/** Per-path dirty-comparison baselines installed by writes with\n * `shouldDirty: false`: the written value becomes that field's baseline —\n * the write reads as a commit, not an edit. Module-private (like\n * {@link dirtyFieldsCaches}) so the Form shape is untouched for forms that\n * never opt in. */\nconst dirtyBaselines = new WeakMap<Form, Map<string, any>>();\n\n/** The value a field's dirtiness is measured against: its committed\n * baseline when one exists, initialValues at the path otherwise. */\nexport function getDirtyBaseline(\n form: Form,\n key: string,\n segments: PathSegments\n): any {\n const baselines = dirtyBaselines.get(form);\n if (baselines?.has(key)) return baselines.get(key);\n return get(form.initialValues, segments);\n}\n\n/** Install a committed baseline for a `shouldDirty: false` write: the\n * field reads clean until a later write diverges from the new baseline. */\nexport function setDirtyBaseline(form: Form, {key}: Path, value: any): void {\n let baselines = dirtyBaselines.get(form);\n if (!baselines) {\n baselines = new Map();\n dirtyBaselines.set(form, baselines);\n }\n baselines.set(key, value);\n}\n\n/** A wholesale write at a path replaces the subtree below it, so baselines\n * committed under the branch die with the data they were committed against\n * (array movers rewrite the parent path, re-aligning row indices). Called\n * from every write — a no-op unless the form ever opted in. */\nexport function pruneDirtyBaselines(form: Form, {key}: Path): void {\n const baselines = dirtyBaselines.get(form);\n if (!baselines?.size) return;\n const stem = `${key.slice(0, -1)},`;\n for (const baselineKey of baselines.keys()) {\n if (baselineKey.startsWith(stem)) baselines.delete(baselineKey);\n }\n}\n\n/** Drop committed baselines at one path ({@link removeFieldByPath} /\n * {@link resetField}) or all of them ({@link setInitialValues} /\n * {@link reset}) — the state they were measured against is gone. */\nexport function clearDirtyBaselines(form: Form, key?: string): void {\n const baselines = dirtyBaselines.get(form);\n if (!baselines) return;\n if (key === undefined) baselines.clear();\n else baselines.delete(key);\n}\n\n/** Per-form memoization of {@link getDirtyFields}. `version` counts value\n * mutations since the cached `result` was computed: bump points increment\n * it, reads reset it, so a non-zero version means the cache is stale. */\ntype DirtyFieldsCache = {version: number; result: Record<string, boolean>};\n\nexport const dirtyFieldsCaches: WeakMap<Form, DirtyFieldsCache> = new WeakMap();\n\n/**\n * Invalidate `form`'s cached {@link getDirtyFields} result. Called at every\n * point that can change values or initialValues (setValueByPath,\n * removeFieldByPath, setInitialValues, reset) so repeated reads hand out a\n * stable reference and useWatch's Object.is snapshot check can skip\n * re-renders.\n */\nexport function bumpDirtyVersion(form: Form): void {\n const cache = dirtyFieldsCaches.get(form);\n if (cache) cache.version++;\n}\n/** Numbers only occur inside a segments path (`['a', 0]`), never as\n * standalone names, so a top-level number marks `name` as one single path\n * rather than a list of names. */\nexport function isSegmentsPath(\n name: PathSegments | Name[]\n): name is PathSegments {\n return (name as (number | unknown)[]).some(part => typeof part === 'number');\n}\n\nexport function isFieldError(value: any): value is FieldError {\n return (\n !!value &&\n typeof value === 'object' &&\n typeof value.type === 'string' &&\n typeof value.message === 'string'\n );\n}\n\n/**\n * Flatten a form-level validate result and write each leaf error through\n * setError. Nested objects descend into deeper paths ({a: {b: 'msg'}} sets\n * the 'a.b' error), array values contribute every non-empty string they\n * hold as separate errors (zod flatten() formErrors style), and\n * FieldError-shaped objects are stored as-is. Falsy values are skipped.\n *\n * When `footprint` is passed (validateDeps forms only), every leaf this\n * round actually stored is recorded into it — the exact stored array —\n * so the next round can drop exactly what this one wrote.\n/** Store a schema validator's parsed output as the getValues baseline\n * layer above initialValues. Payload-less 'change' notifies value\n * watchers (useValue, useDirtyFields, ...); dirty state is untouched —\n * it only compares live edits against initialValues, and parsing is not\n * an edit. */\nexport function setParsedValues(form: Form, values: any): void {\n if (values === undefined || values === form.parsedValues) return;\n form.parsedValues = values;\n // parsedValues is a getValues layer above initialValues, so a new parse\n // invalidates the cached merge (dirty state is untouched — parsing is\n // not an edit, hence no bumpDirtyVersion here).\n bumpValuesVersion(form);\n emit(form.emitter, 'change');\n}\n\n/**\n * Land a form-level validate result. A plain record keeps the\n * long-standing behavior — flattened into field errors by\n * {@link setFormErrors}. A branded {@link ValidationOutcome} splits\n * instead: `errors` flattens exactly like a plain record, and `values`\n * (the schema's parsed output — coerced/transformed values included)\n * becomes the form's parsedValues baseline. Falsy results are skipped,\n * branded or not.\n *\n * Forms that opted into `validateDeps` additionally get round-scoped\n * error ownership: before the new result lands, the errors the previous\n * round wrote are dropped ({@link clearFormValidateErrors}), so a re-run\n * that passes makes the cross-field error disappear — and the new\n * round's own writes become the tracked footprint. Forms without the\n * option keep the historical write-only behavior untouched.\n */\n","import {emit} from '../emitter';\nimport createPath from '../path';\nimport type {Name, Path, PathSegments} from '../path';\nimport type {FieldPath, OpaqueTypes} from '../types';\nimport type {FieldError, FieldErrorEntry, Form} from '../form';\nimport {\n bumpErrorsVersion,\n errorsCaches,\n isFieldError,\n isSegmentsPath\n} from './internals';\n\n/** Reserved top-level path segment for form-level errors. The Standard\n * Schema form-level adapter lands path-less issues under this key; the\n * exported constant replaces the magic string, and readers consume it via\n * getError(form, FORM_ERROR) / getFieldErrors(form, FORM_ERROR). */\nexport const FORM_ERROR = '_form';\n\n/** When a field is validated:\n * - `'onSubmit'` (default): only on submit\n * - `'onBlur'`: when the field loses focus\n * - `'onChange'`: on every change\n * - `'onTouched'`: on first blur, then on every change\n * - `'all'`: on both change and blur\n */\n/** Brand marking a form-level validate result as a structured\n * {@link ValidationOutcome} (parsed values and/or errors) rather than a\n * plain nested error record. Symbols cannot collide with user error\n * records, so detection is an exact `VALIDATION_OUTCOME in result`. */\nexport const VALIDATION_OUTCOME: unique symbol = Symbol('validation-outcome');\n\n/** Structured form-level validate result: `errors` uses the same nested\n * shape a plain error record uses, `values` is the schema's parsed output\n * (coerce/transform results included). Either side may be omitted. */\n/**\n * Get field error\n * @param form\n * @param name\n * @return FieldError object or undefined\n */\nexport function getError<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): FieldError | undefined {\n return getErrorByPath(form, createPath(name));\n}\n\n/**\n * Get field error by path\n * @param form\n * @param path\n * @return first FieldError of the field, or undefined\n */\nexport function getErrorByPath(\n {errors}: Form,\n path: Path\n): FieldError | undefined {\n return errors.get(path.key)?.[0];\n}\n\n/** Shared empty result for {@link getFieldErrorsByPath}: a fresh `[]` per\n * call would allocate on the hot no-error path, and the stored arrays are\n * handed out by reference too, so callers must treat results as read-only. */\nconst NO_ERRORS: FieldError[] = [];\n\n/**\n * Get all errors of a field\n * @param form\n * @param name\n * @return every error registered for the field (insertion order); an empty\n * array when the field has none\n */\nexport function getFieldErrors<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): FieldError[] {\n return getFieldErrorsByPath(form, createPath(name));\n}\n\n/**\n * Get all errors of a field by path\n * @param form\n * @param path\n * @return every error registered for the field (insertion order); an empty\n * array when the field has none\n */\nexport function getFieldErrorsByPath({errors}: Form, path: Path): FieldError[] {\n return errors.get(path.key) ?? NO_ERRORS;\n}\n\n/**\n * Get all errors\n * @param form\n * @return array of {path, type, message} entries, in insertion order; path\n * is the user-facing dotted field path ('a.b', 'list.0'), and a\n * field holding several errors contributes one entry per error\n */\nexport function getErrors({errors}: Form): FieldErrorEntry[] {\n const entries: FieldErrorEntry[] = [];\n for (const [key, list] of errors) {\n const path = (JSON.parse(key) as PathSegments).join('.');\n for (const {type, message} of list) entries.push({path, type, message});\n }\n return entries;\n}\n\n/**\n * Convert a {@link FieldPath} string to the key form error records use at\n * runtime: paths spell array access with brackets ('items[0].name') while\n * {@link getErrorsRecord} keys are dot-joined segments ('items.0.name').\n * The declared keys follow the runtime, so typed reads\n * (`errors['items.0.name']`) match what the record actually holds. Quoted\n * segments ('items[\"0\"]') drop their quotes like the parser does.\n *\n * Public so consumers can name the key style in their own types —\n * `const key: DottedPath<'items[0].name'> = 'items.0.name'`.\n */\nexport type DottedPath<P extends string> =\n P extends `${infer H}[${infer N}]${infer R}`\n ? `${H extends '' ? '' : `${H}.`}${N extends `\"${infer K}\"` | `'${infer K}'` ? K : N}${DottedPath<R>}`\n : P;\n\n/**\n * Every error as one record keyed by user-facing dotted path, typed\n * against the values shape — react-hook-form's `FieldErrors<T>` shape\n * (per-key values are optional there too). Keys follow the runtime form:\n * dotted paths ('a.b', 'list.0'), plus the {@link FORM_ERROR} slot for\n * form-level errors. Values are the stored FieldError[] arrays shared\n * with the form, so treat the whole result as read-only.\n */\nexport type FieldErrors<T extends Record<string, any> = any> = Partial<\n Record<DottedPath<Extract<FieldPath<T>, string>>, FieldError[]>\n> & {\n [FORM_ERROR]?: FieldError[];\n};\n\n/** `true` only for the `any` type (the same probe {@link FieldPath}\n * uses), so the tree of an untyped form degrades to `any` instead of an\n * infinite mapped-type expansion. */\ntype IsAnyTree<T> = 0 extends 1 & T ? true : false;\n\ntype TreePrimitive =\n null | undefined | string | number | boolean | symbol | bigint;\n\n/** One level of the nested error tree: arrays become arrays of the item's\n * tree, objects recurse per key, everything else — primitives, functions,\n * registered {@link OpaqueTypes} leaves, and the common opaque DOM/date\n * containers (Date, File, FileList, Map, Set) — is a leaf holding the\n * stored FieldError[] (shared with the form — treat as read-only). */\ntype FieldErrorsTreeNode<T> =\n IsAnyTree<T> extends true\n ? any\n : T extends OpaqueTypes[keyof OpaqueTypes]\n ? FieldError[]\n : T extends\n | TreePrimitive\n | Function\n | Date\n | File\n | FileList\n | Map<any, any>\n | Set<any>\n ? FieldError[]\n : T extends ReadonlyArray<infer E>\n ? FieldErrorsTreeNode<E>[]\n : {[K in keyof T]?: FieldErrorsTreeNode<T[K]>};\n\n/**\n * Every error as one nested object following the values tree —\n * react-hook-form's `formState.errors` shape with typed optional chains\n * (`errors.items?.[0]?.name`), the readable counterpart of\n * {@link FieldErrors}' flat dotted keys. Array positions become array\n * indices (holes stay absent), object fields become optional keys,\n * leaves hold the stored FieldError[] arrays shared with the form — treat\n * the whole result as read-only. The {@link FORM_ERROR} slot holds\n * form-level errors at the top level.\n *\n * One conflict is resolved by insertion order: a row-level error at\n * `items[0]` and a field error at `items[0].name` cannot both occupy the\n * `items[0]` slot, so whichever landed later owns it (react-hook-form's\n * nested `setError` clobbers the same way). The flat record never\n * conflicts — read it when both coexist.\n */\nexport type FieldErrorsTree<T = any> = FieldErrorsTreeNode<T> & {\n [FORM_ERROR]?: FieldError[];\n};\n\n/** Shared rebuild for {@link getErrorsRecord} / {@link getErrorsTree}:\n * one pass over the errors Map computes both views (the dotted record\n * and the nested tree), each sharing the stored FieldError[] references.\n * The version-bump/read pattern (see {@link errorsCaches}) guarantees\n * consecutive reads hand back stable references until the next error\n * write. */\nfunction computeErrorsViews(form: Form): void {\n const cached = errorsCaches.get(form);\n if (cached && cached.version === 0) return;\n const result: Record<string, FieldError[]> = {};\n const tree: any = {};\n for (const [key, list] of form.errors) {\n // The raw key preserves the parser's number-vs-string segment\n // distinction: 'items[0]' segments carry the number 0 (array index in\n // the tree) while 'items[\"0\"]' carries the string '0' (object key).\n const segments = JSON.parse(key) as PathSegments;\n result[segments.join('.')] = list;\n let node = tree;\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i];\n const slot = node[segment];\n // Descend: a leaf error list already sitting here (a shallower\n // error inserted earlier) yields to the deeper path — insertion\n // order owns the conflict, mirroring RHF's nested set.\n const container =\n slot &&\n !(Array.isArray(slot) && slot.length > 0 && isFieldError(slot[0]))\n ? slot\n : typeof segments[i + 1] === 'number'\n ? []\n : {};\n node[segment] = container;\n node = container;\n }\n node[segments[segments.length - 1]] = list;\n }\n if (cached) {\n cached.result = result;\n cached.tree = tree;\n cached.version = 0;\n } else {\n errorsCaches.set(form, {version: 0, result, tree});\n }\n}\n\n/**\n * Get every error as one record keyed by user-facing dotted path\n * ('a.b', 'list.0') — react-hook-form's `formState.errors` shape. Values\n * are the stored FieldError[] arrays shared with the form, so treat the\n * whole result as read-only. Memoized per form with the same\n * version-bump/read pattern {@link getValues} uses: every error write\n * bumps {@link bumpErrorsVersion}, consecutive reads hand back one stable\n * reference, so {@link useErrors} / `useFormState().errors` only re-render\n * when an error actually changed.\n *\n * @param form\n */\nexport function getErrorsRecord<T extends Record<string, any> = any>(\n form: Form<T>\n): FieldErrors<T> {\n computeErrorsViews(form);\n return errorsCaches.get(form)!.result;\n}\n\n/**\n * Get every error as one nested object following the values tree\n * (`errors.items[0].name` reads — the shape react-hook-form's\n * `formState.errors` uses), the optional-chaining counterpart of\n * {@link getErrorsRecord}'s flat dotted keys. Array positions become\n * array indices, leaves hold the stored FieldError[] arrays shared with\n * the form — treat the whole result as read-only. Memoized alongside the\n * record through the same version-bump/read pattern, so {@link\n * useErrorsTree} only re-renders when an error actually changed.\n *\n * @param form\n */\nexport function getErrorsTree<T extends Record<string, any> = any>(\n form: Form<T>\n): FieldErrorsTree<T> {\n computeErrorsViews(form);\n return errorsCaches.get(form)!.tree;\n}\n\n/**\n * Convert a field path (bracket spelling, the `name` every API takes) into\n * the dotted key {@link getErrorsRecord} / {@link getErrors} use:\n * `'items[0].name'` → `'items.0.name'`. Useful for error-record reads —\n * `errors[fieldPathToDottedKey(name)]` — and for centralizing the\n * translation when the two spellings meet in one component.\n */\nexport function fieldPathToDottedKey(name: Name): string {\n return createPath(name).value.join('.');\n}\n\n/**\n * Convert a dotted errors-record key back into bracket path spelling:\n * `'items.0.name'` → `'items[0].name'` — the form every API takes, so the\n * result feeds straight into `getError(form, …)` / `<Field name=…>`.\n *\n * Numeric-shaped segments become bracket segments; everything else stays\n * dot-joined. Inherently lossy by the record's own convention: the dotted\n * key cannot distinguish a segment containing a literal dot (a quoted path\n * like `a[\"b.c\"]` joins to the same `'a.b.c'` as `a.b.c`) — for such paths\n * keep the bracket spelling and use {@link fieldPathToDottedKey} for the\n * record read instead.\n */\nexport function dottedKeyToFieldPath(key: string): string {\n const segments = key.split('.');\n const numeric = (segment: string) => /^(0|[1-9]\\d*)$/.test(segment);\n return segments\n .map((segment, i) =>\n i > 0 && numeric(segment)\n ? `[${segment}]`\n : `${i > 0 ? '.' : ''}${segment}`\n )\n .join('');\n}\n\n/**\n * Get first error message\n * @param form\n * @return first error's message string, or undefined when there are no errors\n */\nexport function getFirstError({errors}: Form): string | undefined {\n return errors.values().next().value?.[0]?.message;\n}\n\n/** Snapshot of one field's aggregated state, as {@link getFieldState}\n * returns it. `errors` is the stored array shared with the form — treat it\n * as read-only, like every {@link getFieldErrors} result. */\n/** Options accepted by {@link setError}. */\nexport type SetErrorOptions = {\n /**\n * Focus the named field's element after the error lands (react-hook-form's\n * `setError` `shouldFocus`). Rides the same 'focusError' channel\n * `setFocus` and a failed submit's auto-focus use: only mounted bound\n * fields react, unmounted ones are silent no-ops.\n */\n shouldFocus?: boolean;\n};\n\n/**\n * Set field error\n * @param form\n * @param name\n * @param error string is normalized to {type: 'custom', message}; a\n * FieldError object is stored as-is; an array holds several errors\n * (falsy items dropped, strings normalized); undefined clears\n * @param options {@link SetErrorOptions} — `shouldFocus` focuses the field\n * after the error lands\n */\nexport function setError<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(\n form: Form<T>,\n name: P,\n error: string | FieldError | (string | FieldError)[] | undefined,\n options?: SetErrorOptions\n): void {\n setErrorByPath(form, createPath(name), error, options);\n}\n\n/**\n * Set field error\n * @param form\n * @param path\n * @param error string is normalized to {type: 'custom', message}; a\n * FieldError object is stored as-is; an array holds several errors\n * (falsy items dropped, strings normalized); undefined clears\n * @param options {@link SetErrorOptions} — `shouldFocus` focuses the field\n * after the error lands\n */\nexport function setErrorByPath(\n form: Form,\n path: Path,\n error: string | FieldError | (string | FieldError)[] | undefined,\n options?: SetErrorOptions\n): void {\n const {emitter, errors} = form;\n const list = normalizeErrors(error);\n // An empty result (undefined, '', or an array of only falsy items) clears\n // the key: the errors Map never stores an empty list, so hasErrors stays\n // a plain size check and readers can index [0] unguarded.\n if (list) errors.set(path.key, list);\n else errors.delete(path.key);\n bumpErrorsVersion(form);\n // Path payload lets key-scoped subscribers (onKeyEvent) skip unrelated\n // fields; payload-less listeners ignore it.\n emit(emitter, 'errors', path);\n if (options?.shouldFocus) emit(emitter, 'focusError', path.key);\n}\n\n/** Normalize any {@link setErrorByPath} input into the stored non-empty\n * FieldError[] shape, or undefined when there is nothing to store. */\nfunction normalizeErrors(\n error: string | FieldError | (string | FieldError)[] | undefined\n): FieldError[] | undefined {\n if (typeof error === 'string') {\n return error ? [{type: 'custom', message: error}] : undefined;\n }\n if (isFieldError(error)) return [error];\n if (!error) return undefined;\n // Falsy items drop out before normalization, so '' never becomes a\n // stored {type: 'custom', message: ''} placeholder.\n const list: FieldError[] = [];\n error.forEach(item => {\n if (typeof item === 'string' && item) {\n list.push({type: 'custom', message: item});\n } else if (isFieldError(item)) {\n list.push(item);\n }\n });\n return list.length ? list : undefined;\n}\n\n/**\n * Clear errors\n * @param form\n * @param name a single path or a list of paths; omit to clear every error\n */\nexport function clearErrors(form: Form, name?: Name | Name[]): void {\n const {emitter, errors} = form;\n if (name === undefined) {\n errors.clear();\n bumpErrorsVersion(form);\n // Payload-less broadcast: every error subscriber re-syncs.\n emit(emitter, 'errors');\n return;\n }\n // Same single-path vs list discrimination as trigger: a segment array\n // holding a number is one path ('a.0' shape), not a list of names.\n const paths =\n typeof name === 'string' || isSegmentsPath(name)\n ? [createPath(name)]\n : name.map(one => createPath(one));\n for (const {key} of paths) errors.delete(key);\n bumpErrorsVersion(form);\n // Path-payload emits — the setErrorByPath scoping — wake exactly the\n // affected fields' subscribers.\n for (const path of paths) emit(emitter, 'errors', path);\n}\n\n/** Options accepted by {@link setServerErrors}. */\nexport type SetServerErrorsOptions = {\n /** Keep existing field errors instead of clearing them first. Defaults\n * to `false`: a fresh server response replaces the prior error state. */\n keepExisting?: boolean;\n};\n\n/**\n * Land a server-side error response on the form: each entry becomes the\n * named field's error(s) with `type: 'server'`, ready for the same\n * renderError/`useError` channel client-side validation uses. Takes the\n * flat `Record<string, string | string[]>` shape REST APIs commonly\n * return (RealWorld: `422 {errors: {email: ['has already been taken']}}`)\n * without a hand-rolled `Object.entries` + `setError` loop.\n *\n * A string value lands as one error, a string array as several (first one\n * is what `getError`/`error` expose); an empty array clears that field's\n * errors. By default every existing error is cleared first — a fresh\n * response describes the current state, not a patch onto stale client\n * errors; pass `keepExisting: true` to layer instead.\n * @param form\n * @param errors field errors keyed by name\n * @param options\n */\nexport function setServerErrors(\n form: Form,\n errors: Record<string, string | string[]>,\n options?: SetServerErrorsOptions\n): void {\n if (!options?.keepExisting) clearErrors(form);\n for (const [name, error] of Object.entries(errors)) {\n setError(\n form,\n name,\n (Array.isArray(error) ? error : [error]).map(message => ({\n type: 'server',\n message\n }))\n );\n }\n}\n\n/**\n * Drop every `type: 'server'` error from the form — the round-trip state\n * a previous submit landed. {@link handleSubmit} runs this before its\n * validation round so a retry is judged on the fresh attempt, not on the\n * server's verdict for the last payload (client errors are untouched:\n * they describe the current form state). Emits payload-less 'errors' when\n * anything changed.\n */\nexport function clearServerErrors(form: Form): void {\n let changed = false;\n for (const [key, errors] of form.errors) {\n const kept = errors.filter(error => error.type !== 'server');\n if (kept.length === 0) {\n form.errors.delete(key);\n changed = true;\n } else if (kept.length !== errors.length) {\n form.errors.set(key, kept);\n changed = true;\n }\n }\n if (changed) {\n bumpErrorsVersion(form);\n emit(form.emitter, 'errors');\n }\n}\n\n/**\n * Set field touched state\n * @param form\n * @param name\n */\n/**\n * @param form\n */\nexport function hasErrors({errors}: Form): boolean {\n return errors.size > 0;\n}\n\n/** Options accepted by {@link trigger}. `shouldTouch` defaults to `false`;\n * omitting the options object entirely keeps the plain validate-only\n * behavior, so the historical two-argument calls are untouched. */\n"],"names":["pathCache","Map","normalizePath","path","Array","isArray","cached","get","value","result","identifier","flushIdentifier","isIndex","dotted","map","String","join","bracket","reduce","acc","seg","TypeError","push","i","length","char","quote","close","indexOf","slice","content","Number","parsePath","size","oldest","keys","next","done","delete","set","segment","test","values","current","p","prop","props","index","arr","create","name","key","JSON","stringify","valuesCaches","WeakMap","bumpValuesVersion","form","cache","version","errorsCaches","bumpErrorsVersion","dirtyBaselines","dirtyFieldsCaches","isFieldError","type","message","VALIDATION_OUTCOME","NO_ERRORS","setErrorByPath","error","options","emitter","errors","list","forEach","item","normalizeErrors","emit","baselines","clear","segments","has","initialValues","entries","parse","isEqual","a","b","Object","is","Date","getTime","proto","getPrototypeOf","prototype","keysA","keysB","then","stem","baselineKey","startsWith","createPath","root","owned","container","parent","parentProp","copy","add","parsedValues","unset","event","isResolve","isReject","Promise","resolve","reject","off","on"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{on as e,emit as t}from"@for-fun/event-emitter";const n=new Map;function r(e){if(Array.isArray(e))return e;const t=n.get(e);if(t)return t;const r=function(e){const t=[];let n="";const r=()=>{if(o(n)){const r=[...t.map(String),n].join("."),o=t.reduce((e,t)=>e+("number"==typeof t?`[${t}]`:`${e?".":""}${t}`),"");throw new TypeError(`Numeric path segment must use bracket notation: "${r}" → "${o}[${n}]" (path: ${e})`)}t.push(n),n=""};for(let s=0;s<e.length;s++){const i=e[s];if("."===i)""!==n&&r();else if("["===i){""!==n&&r();const i=e[s+1];if('"'===i||"'"===i){const n=e.indexOf(i,s+2);if(-1===n)throw new TypeError(`Unterminated quote in path: ${e}`);if("]"!==e[n+1])throw new TypeError(`Expected "]" after quoted segment in path: ${e}`);t.push(e.slice(s+2,n)),s=n+1}else{const n=e.indexOf("]",s+1);if(-1===n)throw new TypeError(`Unterminated bracket in path: ${e}`);const r=e.slice(s+1,n);t.push(o(r)?Number(r):r),s=n}}else n+=i}""===n&&0!==t.length||r();return t}(e);if(n.size>=1e4){const e=n.keys().next();e.done||n.delete(e.value)}return n.set(e,r),r}const o=e=>/^-?\d+$/.test(e);function s(e,t){return t.reduce((e,t)=>{if(null!=e)return e[t]},e)}function i(e,t){if(!t.length||null==e)return e;const[n,...r]=t;if(r.length){const t=i(e[n],r);return t===e[n]?e:c(e,[n],t)}if(Array.isArray(e)){if(!(n in e))return e;const t=e.slice();return delete t[n],t}if("object"!=typeof e||!(n in e))return e;const o={...e};return delete o[n],o}function c(e,t,n){if(!t.length)return n;const[r,...s]=t,i="number"==typeof r?r:Array.isArray(e)&&"string"==typeof r&&o(r)?Number(r):void 0;if(void 0!==i){const t=Array.isArray(e)?e.slice():[];return t[i]=c(t[i],s,n),t}return{...e,[r]:c(e&&e[r],s,n)}}function u(e,t,n,r){if(!t.length)return n;let s=e,i=null,c="";for(let u=0;u<t.length;u++){const f=t[u];if(!r.has(s)){let t;t="number"==typeof f||Array.isArray(s)&&"string"==typeof f&&o(f)?Array.isArray(s)?s.slice():[]:{...s},r.add(t),0===u?e=t:i[c]=t,s=t}u===t.length-1?s[f]=n:(i=s,c=f,s=s[f])}return e}function f(e){return e&&"function"==typeof e.then}function a(e,t){if(Object.is(e,t))return!0;if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();if(!e||!t||"object"!=typeof e||"object"!=typeof t)return!1;const n=Array.isArray(e);if(n!==Array.isArray(t))return!1;if(n){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(!a(e[n],t[n]))return!1;return!0}const r=Object.getPrototypeOf(e);if(r!==Object.prototype&&null!==r)return!1;if(Object.getPrototypeOf(t)!==r)return!1;const o=Object.keys(e),s=Object.keys(t);if(o.length!==s.length)return!1;for(const n of o)if(!a(e[n],t[n]))return!1;return!0}function l(t,n,r,o){return new Promise((s,i)=>{if(o())return void i();if(r())return void s();const c=e(t,n,()=>{if(o())return c(),void i();r()&&(c(),s())})})}function y(e){const t=r(e);return{value:t,key:JSON.stringify(t)}}const p=new WeakMap;function g(e){const t=p.get(e);t&&t.version++}const h=new WeakMap;function d(e){const t=h.get(e);t&&t.version++}const m=new WeakMap;function b(e,t,n){const r=m.get(e);return r?.has(t)?r.get(t):s(e.initialValues,n)}function v(e,{key:t},n){let r=m.get(e);r||(r=new Map,m.set(e,r)),r.set(t,n)}function k(e,{key:t}){const n=m.get(e);if(!n?.size)return;const r=`${t.slice(0,-1)},`;for(const e of n.keys())e.startsWith(r)&&n.delete(e)}function w(e,t){const n=m.get(e);n&&(void 0===t?n.clear():n.delete(t))}const A=new WeakMap;function j(e){const t=A.get(e);t&&t.version++}function O(e){return!!e&&"object"==typeof e&&"string"==typeof e.type&&"string"==typeof e.message}function $(e,n){void 0!==n&&n!==e.parsedValues&&(e.parsedValues=n,g(e),t(e.emitter,"change"))}const x="_form",E=Symbol("validation-outcome"),M=[];function T({errors:e},t){return e.get(t.key)??M}function N({errors:e}){const t=[];for(const[n,r]of e){const e=JSON.parse(n).join(".");for(const{type:n,message:o}of r)t.push({path:e,type:n,message:o})}return t}function W(e,t,n,r){z(e,y(t),n)}function z(e,n,r,o){const{emitter:s,errors:i}=e,c=function(e){if("string"==typeof e)return e?[{type:"custom",message:e}]:void 0;if(O(e))return[e];if(!e)return;const t=[];return e.forEach(e=>{"string"==typeof e&&e?t.push({type:"custom",message:e}):O(e)&&t.push(e)}),t.length?t:void 0}(r);c?i.set(n.key,c):i.delete(n.key),d(e),t(s,"errors",n)}function S(e,n){const{emitter:r,errors:o}=e;return o.clear(),d(e),void t(r,"errors")}function V({errors:e}){return e.size>0}export{x as F,E as V,W as a,d as b,y as c,O as d,z as e,f,S as g,V as h,o as i,w as j,j as k,g as l,u as m,r as n,a as o,k as p,s as q,v as r,$ as s,A as t,i as u,p as v,l as w,b as x,N as y,T as z};
2
+ //# sourceMappingURL=errors-ClH3eV1R.mjs.map