react-f0rm 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -24
- package/dist/devtools/index.cjs.js +1 -1
- package/dist/devtools/index.cjs.js.map +1 -1
- package/dist/devtools/index.mjs +1 -1
- package/dist/devtools/index.mjs.map +1 -1
- package/dist/errors-BKrUdpfI.cjs.js +2 -0
- package/dist/errors-BKrUdpfI.cjs.js.map +1 -0
- package/dist/errors-CrQBddrJ.mjs +2 -0
- package/dist/errors-CrQBddrJ.mjs.map +1 -0
- package/dist/{form-CvmWHUrd.d.ts → form-CeKSBs31.d.ts} +68 -5
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +344 -49
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +379 -283
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +2 -2
- package/dist/index.umd.min.js.map +1 -1
- package/dist/persist.cjs.js +1 -1
- package/dist/persist.cjs.js.map +1 -1
- package/dist/persist.mjs +1 -1
- package/dist/persist.mjs.map +1 -1
- package/dist/resolvers/standard-schema.cjs.js +1 -1
- package/dist/resolvers/standard-schema.mjs +1 -1
- package/dist/resolvers/yup.cjs.js +1 -1
- package/dist/resolvers/yup.d.ts +1 -1
- package/dist/resolvers/yup.mjs +1 -1
- package/dist/resolvers/zod.cjs.js +1 -1
- package/dist/resolvers/zod.d.ts +1 -1
- package/dist/resolvers/zod.mjs +1 -1
- package/dist/server/index.cjs.js +1 -1
- package/dist/server/index.cjs.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.mjs +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/{validate-B1Gdjeaq.mjs → validate-CNtuUhmk.mjs} +2 -2
- package/dist/validate-CNtuUhmk.mjs.map +1 -0
- package/dist/{validate-DAfz8Nbb.cjs.js → validate-Cl4ksNFu.cjs.js} +2 -2
- package/dist/validate-Cl4ksNFu.cjs.js.map +1 -0
- package/dist/{validate-CUmNZqg6.d.ts → validate-nksgv1pR.d.ts} +37 -3
- package/dist/values-Cu6awQOJ.cjs.js +2 -0
- package/dist/values-Cu6awQOJ.cjs.js.map +1 -0
- package/dist/values-DRY-a32G.mjs +2 -0
- package/dist/values-DRY-a32G.mjs.map +1 -0
- package/package.json +20 -9
- package/dist/errors-CxSjrWJO.cjs.js +0 -2
- package/dist/errors-CxSjrWJO.cjs.js.map +0 -1
- package/dist/errors-CzWtwjO0.mjs +0 -2
- package/dist/errors-CzWtwjO0.mjs.map +0 -1
- package/dist/validate-B1Gdjeaq.mjs.map +0 -1
- package/dist/validate-DAfz8Nbb.cjs.js.map +0 -1
- package/dist/values-B1IV-6V4.mjs +0 -2
- package/dist/values-B1IV-6V4.mjs.map +0 -1
- package/dist/values-CDNAYEOB.cjs.js +0 -2
- package/dist/values-CDNAYEOB.cjs.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","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\">"{value}"</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","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.cjs.js","sources":["../../src/core/dirty.ts","../../src/core/touched.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 {emit} from '../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 {\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 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 {FieldError, Form, FormEvents, Options} 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 * 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(emitter, 'change', path, scope, 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, 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 * 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(emitter, 'change', path, 'leaf', invalidate),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- same key-pinning convention as useValueByPath\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 = {\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 /**\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(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 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(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.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(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 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 {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 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 = 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<K extends string = 'id'>(options: {\n name: FieldPath<TValues> | Name;\n keyName?: K;\n rules?: FieldRules;\n shouldUnregister?: boolean;\n }): UseFieldArrayResult<K> {\n return useFieldArrayCore(options as UseFieldArrayOptions<K>, 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\">"{value}"</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":["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","getTouchedFields","touched","Array","from","useWatch","formOrEmitter","event","getter","isEqual","emitter","subscribeFactory","cacheRef","useRef","current","hasValue","getterRef","isEqualRef","getSnapshot","useCallback","subscribe","notify","compare","next","useSyncExternalStore","useWatchCore","invalidate","on","useTouchedFields","bind","FormContext","createContext","Provider","JsonNode","name","depth","open","setOpen","useState","label","React","createElement","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","position","document","getElementById","id","textContent","head","appendChild","injectDevtoolsStyles","contextForm","useContext","f","Error","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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"}
|
package/dist/devtools/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import*as e from"react";import t,{useCallback as n,useRef as r,createContext as o,useState as a,useContext as s,useId as l}from"react";import{on as i}from"@for-fun/event-emitter";import{g as d,r as c}from"../values-B1IV-6V4.mjs";import{d as f,g as p,a as u}from"../errors-CzWtwjO0.mjs";import{t as m}from"../validate-B1Gdjeaq.mjs";function b({touched:e}){return Array.from(e,e=>JSON.parse(e).join("."))}function g(e){const t={};return function(e,t){for(const[n,r]of e.values){const o=JSON.parse(n);p(e,n,o)!==r&&t(o.join("."))}}(e,e=>{t[e]=!0}),t}function x(e){let t=f.get(e);if(t){if(t.version>0){const n=g(e);(function(e,t){const n=Object.keys(e);return n.length===Object.keys(t).length&&n.every(e=>!0===t[e])})(t.result,n)||(t.result=n),t.version=0}}else t={version:0,result:g(e)},f.set(e,t);return t.result}var h,v={exports:{}},y={};var E,w,S={};var k=(w||(w=1,"production"===process.env.NODE_ENV?v.exports=function(){if(h)return y;h=1;var e=t,n="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},r=e.useState,o=e.useEffect,a=e.useLayoutEffect,s=e.useDebugValue;function l(e){var t=e.getSnapshot;e=e.value;try{var r=t();return!n(e,r)}catch(e){return!0}}var i="undefined"==typeof window||void 0===window.document||void 0===window.document.createElement?function(e,t){return t()}:function(e,t){var n=t(),i=r({inst:{value:n,getSnapshot:t}}),d=i[0].inst,c=i[1];return a(function(){d.value=n,d.getSnapshot=t,l(d)&&c({inst:d})},[e,n,t]),o(function(){return l(d)&&c({inst:d}),e(function(){l(d)&&c({inst:d})})},[e]),s(n),n};return y.useSyncExternalStore=void 0!==e.useSyncExternalStore?e.useSyncExternalStore:i,y}():v.exports=(E||(E=1,"production"!==process.env.NODE_ENV&&function(){function e(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!r(e,n)}catch(e){return!0}}"undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());var n=t,r="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},o=n.useState,a=n.useEffect,s=n.useLayoutEffect,l=n.useDebugValue,i=!1,d=!1,c="undefined"==typeof window||void 0===window.document||void 0===window.document.createElement?function(e,t){return t()}:function(t,c){i||void 0===n.startTransition||(i=!0,console.error("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."));var f=c();if(!d){var p=c();r(f,p)||(console.error("The result of getSnapshot should be cached to avoid an infinite loop"),d=!0)}var u=(p=o({inst:{value:f,getSnapshot:c}}))[0].inst,m=p[1];return s(function(){u.value=f,u.getSnapshot=c,e(u)&&m({inst:u})},[t,f,c]),a(function(){return e(u)&&m({inst:u}),t(function(){e(u)&&m({inst:u})})},[t]),l(f),f};S.useSyncExternalStore=void 0!==n.useSyncExternalStore?n.useSyncExternalStore:c,"undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error())}()),S)),v.exports);function N(e,t,o,a){const s="emitter"in e?e.emitter:e;return function(e,t,o){const a=r(null);null===a.current&&(a.current={hasValue:!1});const s=a.current,l=r(t);l.current=t;const i=r(o);i.current=o;const d=n(()=>(s.hasValue||(s.value=l.current(),s.hasValue=!0),s.value),[s]),c=n(t=>(s.hasValue=!1,e(()=>{const e=i.current;if(e&&s.hasValue){const n=l.current();if(e(s.value,n))return;return s.value=n,void t()}s.hasValue=!1,t()})),[e,s]);return k.useSyncExternalStore(c,d,d)}(n(e=>i(s,t,e),[s,t]),o,a)}function O(e){return N(e,"touched",b.bind(null,e))}const _=o(null);_.Provider;o(null).Provider;function L({name:t,value:n,depth:r=0}){const[o,s]=a(r<=1),l=void 0===t?null:e.createElement(e.Fragment,null,e.createElement("span",{className:"rf0-dt-key"},String(t)),e.createElement("span",{className:"rf0-dt-punct"},": "));if(null!==n&&"object"==typeof n){const t=Array.isArray(n),a=t?n.map((e,t)=>[t,e]):Object.entries(n),i=t?"[":"{",d=t?"]":"}",c=o?"":`${i}…${d} ${a.length}`;return e.createElement("div",{className:"rf0-dt-row",style:{paddingLeft:12*r}},e.createElement("button",{type:"button",className:"rf0-dt-node-toggle","aria-expanded":o,onClick:()=>s(!o)},e.createElement("span",{className:"rf0-dt-caret"},o?"▾":"▸"),l,e.createElement("span",{className:"rf0-dt-punct"},o?i:c)),o&&e.createElement(e.Fragment,null,a.map(([t,n])=>e.createElement(L,{key:String(t),name:t,value:n,depth:r+1})),e.createElement("span",{className:"rf0-dt-punct",style:{paddingLeft:12*r}},d)))}return e.createElement("span",{className:"rf0-dt-row",style:{paddingLeft:12*r,display:"block"}},l,e.createElement(C,{value:n}))}function C({value:t}){return void 0===t?e.createElement("span",{className:"rf0-dt-null"},"undefined"):null===t?e.createElement("span",{className:"rf0-dt-null"},"null"):"string"==typeof t?e.createElement("span",{className:"rf0-dt-string"},'"',t,'"'):"boolean"==typeof t?e.createElement("span",{className:"rf0-dt-boolean"},String(t)):e.createElement("span",{className:"rf0-dt-number"},String(t))}const j="react-f0rm-devtools-style";const $=["values","errors","touched","dirty"];function A(e){if(void 0!==e)return e?"rf0-dt-ok":"rf0-dt-err"}function V(e){if(null===e||"object"!=typeof e)return 1;let t=0;for(const n of Object.values(e))t+=V(n);return t}function T({form:t,position:n="top-right"}){!function(){if("undefined"==typeof document)return;if(document.getElementById(j))return;const e=document.createElement("style");e.id=j,e.textContent="\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",document.head.appendChild(e)}();const r=s(_),o=t??r;if(!o)throw new Error("<Devtools> needs a form: pass the `form` prop or render it inside a <Form> / FormProvider.");const[i,f]=a(!0),[p,b]=a("values"),g=l().replace(/[^a-zA-Z0-9-]/g,""),h=N(o,"change",d.bind(null,o)),v=N(o,"errors",u.bind(null,o)),y=O(o),E=function(e){return N(e,"change",x.bind(null,e))}(o),w=function(e){return N(e,"submitting",()=>e.isSubmitting)}(o),S=function(e){return N(e,"submitCount",()=>e.submitCount)}(o),k=N(o,"submitSuccessful",()=>o.isSubmitSuccessful);if(!i)return e.createElement("button",{type:"button",className:`rf0-dt-badge rf0-dt-badge--${n}${v.length>0?" rf0-dt-badge--has-errors":""}`,"aria-expanded":!1,"aria-label":`Open react-f0rm devtools (${v.length} errors)`,onClick:()=>f(!0)},"f0",e.createElement("span",{className:"rf0-dt-dot"}));const C={values:V(h),errors:v.length,touched:y.length,dirty:Object.keys(E).length};return e.createElement("section",{className:`rf0-dt rf0-dt--${n}`,"aria-label":"react-f0rm devtools"},e.createElement("header",{className:"rf0-dt-header"},e.createElement("span",{className:"rf0-dt-title"},"react-f0rm"),e.createElement("button",{type:"button",className:"rf0-dt-headerbtn","aria-label":"Collapse devtools",onClick:()=>f(!1)},"–")),e.createElement("div",{className:"rf0-dt-tablist",role:"tablist","aria-label":"Form state",tabIndex:-1,onKeyDown:e=>{const t={ArrowRight:1,ArrowLeft:-1}[e.key];if(!t)return;e.preventDefault();const n=$[($.indexOf(p)+t+$.length)%$.length];b(n),document.getElementById(`${g}-tab-${n}`)?.focus()}},$.map(t=>e.createElement("button",{key:t,id:`${g}-tab-${t}`,type:"button",role:"tab",className:"rf0-dt-tab"+("errors"===t?" rf0-dt-tab--danger":""),"aria-selected":p===t,"aria-controls":`${g}-panel-${t}`,tabIndex:p===t?0:-1,onClick:()=>b(t)},t,e.createElement("span",{className:"rf0-dt-tab-count"},C[t])))),e.createElement("div",{id:`${g}-panel-${p}`,role:"tabpanel","aria-labelledby":`${g}-tab-${p}`,className:"rf0-dt-panel"},"values"===p&&e.createElement(L,{value:h}),"errors"===p&&(0===v.length?e.createElement("p",{className:"rf0-dt-empty"},"no errors"):v.map(({path:t,type:n,message:r},o)=>e.createElement("div",{key:`${t}:${o}`,className:"rf0-dt-item"},e.createElement("span",{className:"rf0-dt-item-path"},t),e.createElement("span",{className:"rf0-dt-item-msg"},r),e.createElement("span",{className:"rf0-dt-item-tag"},n)))),"touched"===p&&(0===y.length?e.createElement("p",{className:"rf0-dt-empty"},"no touched fields"):y.map(t=>e.createElement("div",{key:t,className:"rf0-dt-item rf0-dt-item--touched"},e.createElement("span",{className:"rf0-dt-item-path"},t)))),"dirty"===p&&(0===Object.keys(E).length?e.createElement("p",{className:"rf0-dt-empty"},"no dirty fields"):Object.keys(E).map(t=>e.createElement("div",{key:t,className:"rf0-dt-item rf0-dt-item--dirty"},e.createElement("span",{className:"rf0-dt-item-path"},t),e.createElement("span",{className:"rf0-dt-item-msg rf0-dt-item-msg--ok"},"changed"))))),e.createElement("p",{className:"rf0-dt-status","aria-live":"polite"},e.createElement("span",{className:w?"rf0-dt-on":void 0},"submitting ",e.createElement("b",null,String(w))),e.createElement("span",null,"submits ",e.createElement("b",null,S)),e.createElement("span",{className:A(k)},"ok"," ",e.createElement("b",null,void 0===k?"–":String(k)))),e.createElement("div",{className:"rf0-dt-actions"},e.createElement("button",{type:"button",className:"rf0-dt-action",onClick:()=>c(o,o.initialValues)},"Reset"),e.createElement("button",{type:"button",className:"rf0-dt-action",onClick:()=>m(o)},"Validate")))}export{T as Devtools};
|
|
1
|
+
import*as e from"react";import{useCallback as t,useRef as n,useSyncExternalStore as r,createContext as a,useState as o,useContext as l,useId as d}from"react";import{on as s}from"@for-fun/event-emitter";import{g as i,r as c}from"../values-DRY-a32G.mjs";import{d as f,g as p,a as m}from"../errors-CrQBddrJ.mjs";import{t as b}from"../validate-CNtuUhmk.mjs";function u(e){const t={};return function(e,t){for(const[n,r]of e.values){const a=JSON.parse(n);p(e,n,a)!==r&&t(a.join("."))}}(e,e=>{t[e]=!0}),t}function g(e){let t=f.get(e);if(t){if(t.version>0){const n=u(e);(function(e,t){const n=Object.keys(e);return n.length===Object.keys(t).length&&n.every(e=>!0===t[e])})(t.result,n)||(t.result=n),t.version=0}}else t={version:0,result:u(e)},f.set(e,t);return t.result}function x({touched:e}){return Array.from(e,e=>JSON.parse(e).join("."))}function h(e,a,o,l){const d="emitter"in e?e.emitter:e;return function(e,a,o){const l=n(null);null===l.current&&(l.current={hasValue:!1});const d=l.current,s=n(a);s.current=a;const i=n(o);i.current=o;const c=t(()=>(d.hasValue||(d.value=s.current(),d.hasValue=!0),d.value),[d]),f=t(t=>(d.hasValue=!1,e(()=>{const e=i.current;if(e&&d.hasValue){const n=s.current();if(e(d.value,n))return;return d.value=n,void t()}d.hasValue=!1,t()})),[e,d]);return r(f,c,c)}(t(e=>s(d,a,e),[d,a]),o,l)}function y(e){return h(e,"touched",x.bind(null,e))}const v=a(null);v.Provider;a(null).Provider;function E({name:t,value:n,depth:r=0}){const[a,l]=o(r<=1),d=void 0===t?null:e.createElement(e.Fragment,null,e.createElement("span",{className:"rf0-dt-key"},String(t)),e.createElement("span",{className:"rf0-dt-punct"},": "));if(null!==n&&"object"==typeof n){const t=Array.isArray(n),o=t?n.map((e,t)=>[t,e]):Object.entries(n),s=t?"[":"{",i=t?"]":"}",c=a?"":`${s}…${i} ${o.length}`;return e.createElement("div",{className:"rf0-dt-row",style:{paddingLeft:12*r}},e.createElement("button",{type:"button",className:"rf0-dt-node-toggle","aria-expanded":a,onClick:()=>l(!a)},e.createElement("span",{className:"rf0-dt-caret"},a?"▾":"▸"),d,e.createElement("span",{className:"rf0-dt-punct"},a?s:c)),a&&e.createElement(e.Fragment,null,o.map(([t,n])=>e.createElement(E,{key:String(t),name:t,value:n,depth:r+1})),e.createElement("span",{className:"rf0-dt-punct",style:{paddingLeft:12*r}},i)))}return e.createElement("span",{className:"rf0-dt-row",style:{paddingLeft:12*r,display:"block"}},d,e.createElement(k,{value:n}))}function k({value:t}){return void 0===t?e.createElement("span",{className:"rf0-dt-null"},"undefined"):null===t?e.createElement("span",{className:"rf0-dt-null"},"null"):"string"==typeof t?e.createElement("span",{className:"rf0-dt-string"},'"',t,'"'):"boolean"==typeof t?e.createElement("span",{className:"rf0-dt-boolean"},String(t)):e.createElement("span",{className:"rf0-dt-number"},String(t))}const N="react-f0rm-devtools-style";const w=["values","errors","touched","dirty"];function $(e){if(void 0!==e)return e?"rf0-dt-ok":"rf0-dt-err"}function C(e){if(null===e||"object"!=typeof e)return 1;let t=0;for(const n of Object.values(e))t+=C(n);return t}function j({form:t,position:n="top-right"}){!function(){if("undefined"==typeof document)return;if(document.getElementById(N))return;const e=document.createElement("style");e.id=N,e.textContent="\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",document.head.appendChild(e)}();const r=l(v),a=t??r;if(!a)throw new Error("<Devtools> needs a form: pass the `form` prop or render it inside a <Form> / FormProvider.");const[s,f]=o(!0),[p,u]=o("values"),x=d().replace(/[^a-zA-Z0-9-]/g,""),k=h(a,"change",i.bind(null,a)),j=h(a,"errors",m.bind(null,a)),z=y(a),S=function(e){return h(e,"change",g.bind(null,e))}(a),O=function(e){return h(e,"submitting",()=>e.isSubmitting)}(a),M=function(e){return h(e,"submitCount",()=>e.submitCount)}(a),V=h(a,"submitSuccessful",()=>a.isSubmitSuccessful);if(!s)return e.createElement("button",{type:"button",className:`rf0-dt-badge rf0-dt-badge--${n}${j.length>0?" rf0-dt-badge--has-errors":""}`,"aria-expanded":!1,"aria-label":`Open react-f0rm devtools (${j.length} errors)`,onClick:()=>f(!0)},"f0",e.createElement("span",{className:"rf0-dt-dot"}));const F={values:C(k),errors:j.length,touched:z.length,dirty:Object.keys(S).length};return e.createElement("section",{className:`rf0-dt rf0-dt--${n}`,"aria-label":"react-f0rm devtools"},e.createElement("header",{className:"rf0-dt-header"},e.createElement("span",{className:"rf0-dt-title"},"react-f0rm"),e.createElement("button",{type:"button",className:"rf0-dt-headerbtn","aria-label":"Collapse devtools",onClick:()=>f(!1)},"–")),e.createElement("div",{className:"rf0-dt-tablist",role:"tablist","aria-label":"Form state",tabIndex:-1,onKeyDown:e=>{const t={ArrowRight:1,ArrowLeft:-1}[e.key];if(!t)return;e.preventDefault();const n=w[(w.indexOf(p)+t+w.length)%w.length];u(n),document.getElementById(`${x}-tab-${n}`)?.focus()}},w.map(t=>e.createElement("button",{key:t,id:`${x}-tab-${t}`,type:"button",role:"tab",className:"rf0-dt-tab"+("errors"===t?" rf0-dt-tab--danger":""),"aria-selected":p===t,"aria-controls":`${x}-panel-${t}`,tabIndex:p===t?0:-1,onClick:()=>u(t)},t,e.createElement("span",{className:"rf0-dt-tab-count"},F[t])))),e.createElement("div",{id:`${x}-panel-${p}`,role:"tabpanel","aria-labelledby":`${x}-tab-${p}`,className:"rf0-dt-panel"},"values"===p&&e.createElement(E,{value:k}),"errors"===p&&(0===j.length?e.createElement("p",{className:"rf0-dt-empty"},"no errors"):j.map(({path:t,type:n,message:r},a)=>e.createElement("div",{key:`${t}:${a}`,className:"rf0-dt-item"},e.createElement("span",{className:"rf0-dt-item-path"},t),e.createElement("span",{className:"rf0-dt-item-msg"},r),e.createElement("span",{className:"rf0-dt-item-tag"},n)))),"touched"===p&&(0===z.length?e.createElement("p",{className:"rf0-dt-empty"},"no touched fields"):z.map(t=>e.createElement("div",{key:t,className:"rf0-dt-item rf0-dt-item--touched"},e.createElement("span",{className:"rf0-dt-item-path"},t)))),"dirty"===p&&(0===Object.keys(S).length?e.createElement("p",{className:"rf0-dt-empty"},"no dirty fields"):Object.keys(S).map(t=>e.createElement("div",{key:t,className:"rf0-dt-item rf0-dt-item--dirty"},e.createElement("span",{className:"rf0-dt-item-path"},t),e.createElement("span",{className:"rf0-dt-item-msg rf0-dt-item-msg--ok"},"changed"))))),e.createElement("p",{className:"rf0-dt-status","aria-live":"polite"},e.createElement("span",{className:O?"rf0-dt-on":void 0},"submitting ",e.createElement("b",null,String(O))),e.createElement("span",null,"submits ",e.createElement("b",null,M)),e.createElement("span",{className:$(V)},"ok"," ",e.createElement("b",null,void 0===V?"–":String(V)))),e.createElement("div",{className:"rf0-dt-actions"},e.createElement("button",{type:"button",className:"rf0-dt-action",onClick:()=>c(a,a.initialValues)},"Reset"),e.createElement("button",{type:"button",className:"rf0-dt-action",onClick:()=>b(a)},"Validate")))}export{j as Devtools};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|