react-f0rm 0.7.0 → 0.9.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../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":["'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 createForm, {\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, Options, Name} from '../form';\nimport type {FieldPath, PathValueOf} from '../types';\nimport createPath from '../path';\nimport type {Path} from '../path';\nimport {isEqual} 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 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}. */\ninterface WatchCache<T> {\n hasValue: boolean;\n value?: T;\n}\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 */\nfunction useWatchCore<T>(\n subscribeFactory: (invalidate: () => void) => () => void,\n getter: () => T\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\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 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 */\nexport function useWatch<T>(\n emitter: EventEmitter,\n event: string,\n getter: () => T\n): T {\n const subscribeFactory = useCallback(\n (invalidate: () => void) => on(emitter, event, invalidate),\n [emitter, event]\n );\n return useWatchCore(subscribeFactory, getter);\n}\n\n/**\n * Get field value state\n */\nexport function useValue<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | Name = Name\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, removeField,\n // setInitialValues) still sync everything.\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> | Name = Name\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> | Name = Name\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> | Name = Name\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.emitter, '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.emitter, '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.emitter, 'touched', getTouchedFields.bind(null, form));\n}\n\nexport function useHasErrors(form: Form): boolean {\n return useWatch(form.emitter, 'errors', hasErrors.bind(null, form));\n}\n\nexport function useIsSubmitting(form: Form): boolean {\n return useWatch(form.emitter, 'submitting', () => form.isSubmitting);\n}\n\nexport function useSubmitCount(form: Form): number {\n return useWatch(form.emitter, 'submitCount', () => form.submitCount);\n}\n","import {createContext, createElement, useContext, type ReactNode} from 'react';\nimport {\n useFieldCore,\n type UseFieldOptions,\n type UseFieldResult\n} from './hooks/field';\nimport {useFieldArrayCore, type UseFieldArrayResult} from './hooks/fieldArray';\nimport type {Form} from './form';\nimport type {Name} from './path';\nimport type {FieldPath} from './types';\n\nexport const FormContext = createContext<any>(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` / `useFormContext` hooks that resolve\n * 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<TPath extends FieldPath<TValues> | Name = Name>(\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 // 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 };\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 */\ninterface JsonNodeProps {\n /** Property name (or array index) rendering before the value. */\n name?: string | number;\n /** Value to render. */\n value: unknown;\n /** Current nesting depth (root is 0). */\n depth?: number;\n}\n\n/**\n * One line of the tree: either a collapsible container row\n * (`▸ key: {`) or a leaf (`key: value`).\n */\nfunction JsonNode({name, value, depth = 0}: JsonNodeProps) {\n const [open, setOpen] = useState(depth <= DEFAULT_OPEN_DEPTH);\n\n const label =\n name === undefined ? null : (\n <>\n <span className=\"rf0-dt-key\">{String(name)}</span>\n <span className=\"rf0-dt-punct\">: </span>\n </>\n );\n\n if (value !== null && typeof value === 'object') {\n const isArray = Array.isArray(value);\n const entries: Array<[string | number, unknown]> = isArray\n ? (value as unknown[]).map((v, i) => [i, v])\n : Object.entries(value as Record<string, unknown>);\n const openBracket = isArray ? '[' : '{';\n const closeBracket = isArray ? ']' : '}';\n const summary = open\n ? ''\n : `${openBracket}…${closeBracket} ${entries.length}`;\n\n return (\n <div className=\"rf0-dt-row\" style={{paddingLeft: depth * 12}}>\n <button\n type=\"button\"\n className=\"rf0-dt-node-toggle\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n <span className=\"rf0-dt-caret\">{open ? '▾' : '▸'}</span>\n {label}\n <span className=\"rf0-dt-punct\">{open ? openBracket : summary}</span>\n </button>\n {open && (\n <>\n {entries.map(([k, v]) => (\n <JsonNode key={String(k)} name={k} value={v} depth={depth + 1} />\n ))}\n <span className=\"rf0-dt-punct\" style={{paddingLeft: depth * 12}}>\n {closeBracket}\n </span>\n </>\n )}\n </div>\n );\n }\n\n return (\n <span\n className=\"rf0-dt-row\"\n style={{paddingLeft: depth * 12, display: 'block'}}\n >\n {label}\n <Primitive value={value} />\n </span>\n );\n}\n\n/** Render a primitive leaf with terminal-style type coloring. */\nfunction Primitive({value}: {value: unknown}): ReactNode {\n if (value === undefined)\n return <span className=\"rf0-dt-null\">undefined</span>;\n if (value === null) return <span className=\"rf0-dt-null\">null</span>;\n if (typeof value === 'string')\n return <span className=\"rf0-dt-string\">&quot;{value}&quot;</span>;\n if (typeof value === 'boolean')\n return <span className=\"rf0-dt-boolean\">{String(value)}</span>;\n return <span className=\"rf0-dt-number\">{String(value)}</span>;\n}\n\nexport default JsonNode;\n","/**\n * Stylesheet for the Devtools panel.\n *\n * Zero runtime dependencies by design: a single CSS string injected once\n * into <head> (idempotent across module reloads and multiple bundles).\n *\n * Aesthetic: instrument panel / terminal — near-black layers, monospace\n * stack, dense rows, hairline borders. Semantic colors only: error red,\n * success green, neutral gray, with one dim amber accent for the active\n * tab indicator and the collapsed badge.\n */\n\nconst CSS = `\n.rf0-dt {\n position: fixed;\n z-index: 2147483000;\n box-sizing: border-box;\n width: 308px;\n max-width: calc(100vw - 16px);\n max-height: min(70vh, 560px);\n display: flex;\n flex-direction: column;\n font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',\n Menlo, Consolas, 'Liberation Mono', monospace;\n font-size: 11px;\n line-height: 1.45;\n color: #c7d0dc;\n background: #0c1017;\n border: 1px solid #1f2735;\n border-radius: 4px;\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.4);\n}\n.rf0-dt *,\n.rf0-dt-badge * {\n box-sizing: border-box;\n}\n.rf0-dt--top-right { top: 8px; right: 8px; }\n.rf0-dt--bottom-right { bottom: 8px; right: 8px; }\n.rf0-dt--top-left { top: 8px; left: 8px; }\n.rf0-dt--bottom-left { bottom: 8px; left: 8px; }\n\n/* ---- header -------------------------------------------------------- */\n.rf0-dt-header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 6px 5px 9px;\n border-bottom: 1px solid #1f2735;\n background: #10151e;\n}\n.rf0-dt-title {\n flex: 1;\n min-width: 0;\n color: #8b96a5;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.rf0-dt-title::before {\n content: '';\n display: inline-block;\n width: 6px;\n height: 6px;\n margin-right: 6px;\n border-radius: 50%;\n background: #e2b93b;\n vertical-align: 1px;\n}\n.rf0-dt-headerbtn {\n border: 1px solid transparent;\n border-radius: 3px;\n padding: 1px 5px;\n color: #8b96a5;\n background: transparent;\n font: inherit;\n font-size: 10px;\n cursor: pointer;\n}\n.rf0-dt-headerbtn:hover { color: #dce3ec; border-color: #2a3547; }\n.rf0-dt-headerbtn:focus-visible,\n.rf0-dt-tab:focus-visible,\n.rf0-dt-action:focus-visible,\n.rf0-dt-badge:focus-visible,\n.rf0-dt-node-toggle:focus-visible {\n outline: 1px solid #e2b93b;\n outline-offset: 1px;\n}\n\n/* ---- tabs ---------------------------------------------------------- */\n.rf0-dt-tablist {\n display: flex;\n border-bottom: 1px solid #1f2735;\n background: #0e131b;\n}\n.rf0-dt-tab {\n flex: 1;\n padding: 4px 2px 5px;\n border: 0;\n border-bottom: 2px solid transparent;\n background: transparent;\n color: #6b7686;\n font: inherit;\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n cursor: pointer;\n white-space: nowrap;\n}\n.rf0-dt-tab:hover { color: #aab5c4; }\n.rf0-dt-tab[aria-selected='true'] {\n color: #e7edf4;\n border-bottom-color: #e2b93b;\n}\n.rf0-dt-tab-count {\n margin-left: 3px;\n color: inherit;\n opacity: 0.75;\n}\n.rf0-dt-tab--danger[aria-selected='true'] {\n border-bottom-color: #f0647c;\n}\n\n/* ---- panels -------------------------------------------------------- */\n.rf0-dt-panel {\n flex: 1;\n min-height: 84px;\n overflow: auto;\n padding: 6px 8px;\n scrollbar-width: thin;\n scrollbar-color: #2a3547 transparent;\n}\n.rf0-dt-empty {\n padding: 10px 2px;\n color: #4d5766;\n font-style: italic;\n}\n\n/* json tree */\n.rf0-dt-row {\n display: block;\n white-space: pre;\n tab-size: 2;\n}\n.rf0-dt-node-toggle {\n border: 0;\n padding: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n white-space: pre;\n}\n.rf0-dt-node-toggle:hover .rf0-dt-key { color: #dce3ec; }\n.rf0-dt-caret {\n display: inline-block;\n width: 1.2em;\n color: #4d5766;\n}\n.rf0-dt-key { color: #8b96a5; }\n.rf0-dt-punct { color: #4d5766; }\n.rf0-dt-string { color: #8fd68a; }\n.rf0-dt-number { color: #e2b93b; }\n.rf0-dt-boolean { color: #6fb3d9; }\n.rf0-dt-null { color: #55607080; font-style: italic; }\n\n/* errors / touched / dirty lists */\n.rf0-dt-item {\n padding: 3px 2px;\n border-bottom: 1px dotted #1a2230;\n display: flex;\n gap: 8px;\n align-items: baseline;\n}\n.rf0-dt-item:last-child { border-bottom: 0; }\n.rf0-dt-item-path {\n color: #aab5c4;\n word-break: break-all;\n}\n.rf0-dt-item-msg {\n color: #f0647c;\n word-break: break-word;\n}\n.rf0-dt-item-msg--ok { color: #8fd68a; }\n.rf0-dt-item-tag {\n flex: none;\n color: #4d5766;\n font-size: 10px;\n}\n.rf0-dt-item--touched .rf0-dt-item-path { color: #6fb3d9; }\n.rf0-dt-item--dirty .rf0-dt-item-path { color: #e2b93b; }\n\n/* ---- submit status + actions --------------------------------------- */\n.rf0-dt-status {\n display: flex;\n gap: 10px;\n padding: 4px 9px;\n border-top: 1px solid #1f2735;\n background: #10151e;\n color: #6b7686;\n font-size: 10px;\n letter-spacing: 0.04em;\n white-space: nowrap;\n overflow: hidden;\n}\n.rf0-dt-status b { color: #aab5c4; font-weight: 400; }\n.rf0-dt-status .rf0-dt-on { color: #e2b93b; }\n.rf0-dt-status .rf0-dt-ok { color: #8fd68a; }\n.rf0-dt-status .rf0-dt-err { color: #f0647c; }\n.rf0-dt-actions {\n display: flex;\n gap: 6px;\n padding: 6px 8px;\n border-top: 1px solid #1f2735;\n background: #10151e;\n}\n.rf0-dt-action {\n flex: 1;\n padding: 3px 0;\n border: 1px solid #2a3547;\n border-radius: 3px;\n background: #151b26;\n color: #c7d0dc;\n font: inherit;\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n cursor: pointer;\n}\n.rf0-dt-action:hover { border-color: #3b4a61; background: #1a2230; color: #e7edf4; }\n.rf0-dt-action:active { transform: translateY(1px); }\n\n/* ---- collapsed badge ----------------------------------------------- */\n.rf0-dt-badge {\n position: fixed;\n z-index: 2147483000;\n width: 26px;\n height: 26px;\n border: 1px solid #2a3547;\n border-radius: 50%;\n background: #0c1017;\n color: #e2b93b;\n font: inherit;\n font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',\n Menlo, Consolas, 'Liberation Mono', monospace;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.02em;\n cursor: pointer;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);\n}\n.rf0-dt-badge:hover { border-color: #e2b93b; }\n.rf0-dt-badge--top-right { top: 10px; right: 10px; }\n.rf0-dt-badge--bottom-right { bottom: 10px; right: 10px; }\n.rf0-dt-badge--top-left { top: 10px; left: 10px; }\n.rf0-dt-badge--bottom-left { bottom: 10px; left: 10px; }\n.rf0-dt-badge .rf0-dt-dot {\n position: absolute;\n top: 3px;\n right: 3px;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: #f0647c;\n display: none;\n}\n.rf0-dt-badge--has-errors .rf0-dt-dot { display: block; }\n`;\n\nconst STYLE_ID = 'react-f0rm-devtools-style';\n\n/**\n * Inject the panel stylesheet into <head>. Idempotent: repeated calls\n * (module reloads, HMR, multiple Devtools mounts) never duplicate the\n * <style> element. No-ops outside a DOM environment (SSR).\n */\nexport function injectDevtoolsStyles(): void {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = CSS;\n document.head.appendChild(style);\n}\n\ninjectDevtoolsStyles();\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 './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 interface 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 const contextForm = useContext(FormContext);\n const f = 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.emitter, 'change', getValues.bind(null, f));\n const errors = useWatch<FieldErrorEntry[]>(\n f.emitter,\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.emitter,\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":["process","env","NODE_ENV","shimModule","exports","React","require$$0","objectIs","Object","is","x","y","useState","useEffect","useLayoutEffect","useDebugValue","checkIfSnapshotChanged","inst","latestGetSnapshot","getSnapshot","value","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","emitter","event","getter","subscribeFactory","cacheRef","useRef","current","hasValue","cache","getterRef","useCallback","notify","useWatchCore","invalidate","on","FormContext","createContext","Provider","JsonNode","name","depth","open","setOpen","label","Fragment","className","String","isArray","Array","entries","map","v","i","openBracket","closeBracket","summary","length","style","paddingLeft","type","onClick","k","key","display","Primitive","STYLE_ID","getElementById","id","textContent","head","appendChild","injectDevtoolsStyles","TABS","submitStatusClass","isSubmitSuccessful","countLeaves","count","values","Devtools","form","position","contextForm","useContext","f","tab","setTab","idPrefix","useId","replace","getValues","bind","errors","getErrors","touched","getTouchedFields","useTouchedFields","dirty","getDirtyFields","useDirtyFields","isSubmitting","useIsSubmitting","submitCount","useSubmitCount","counts","keys","role","tabIndex","onKeyDown","e","delta","ArrowRight","ArrowLeft","preventDefault","next","indexOf","focus","JsonTree","path","message","index","reset","initialValues","trigger"],"mappings":"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","x_google_ignoreList":[0,1,2]}
1
+ {"version":3,"file":"index.mjs","sources":["../../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":["'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 createForm, {\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, Options, Name} from '../form';\nimport type {FieldPath, PathValueOf} from '../types';\nimport createPath from '../path';\nimport type {Path} from '../path';\nimport {isEqual} 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 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}. */\ninterface WatchCache<T> {\n hasValue: boolean;\n value?: T;\n}\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 */\nfunction useWatchCore<T>(\n subscribeFactory: (invalidate: () => void) => () => void,\n getter: () => T\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\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 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 */\nexport function useWatch<T>(\n emitter: EventEmitter,\n event: string,\n getter: () => T\n): T {\n const subscribeFactory = useCallback(\n (invalidate: () => void) => on(emitter, event, invalidate),\n [emitter, event]\n );\n return useWatchCore(subscribeFactory, getter);\n}\n\n/**\n * Get field value state\n */\nexport function useValue<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | Name = Name\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, removeField,\n // setInitialValues) still sync everything.\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> | Name = Name\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> | Name = Name\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> | Name = Name\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.emitter, '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.emitter, '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.emitter, 'touched', getTouchedFields.bind(null, form));\n}\n\nexport function useHasErrors(form: Form): boolean {\n return useWatch(form.emitter, 'errors', hasErrors.bind(null, form));\n}\n\nexport function useIsSubmitting(form: Form): boolean {\n return useWatch(form.emitter, 'submitting', () => form.isSubmitting);\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.emitter, 'submitCount', () => form.submitCount);\n}\n","import {createContext, createElement, useContext, type ReactNode} from 'react';\nimport {\n useFieldCore,\n type UseFieldOptions,\n type UseFieldResult\n} from './hooks/field';\nimport {useFieldArrayCore, type UseFieldArrayResult} from './hooks/fieldArray';\nimport type {Form} from './form';\nimport type {Name} from './path';\nimport type {FieldPath} from './types';\n\nexport const FormContext = createContext<any>(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` / `useFormContext` hooks that resolve\n * 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<TPath extends FieldPath<TValues> | Name = Name>(\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 // 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 };\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 */\ninterface JsonNodeProps {\n /** Property name (or array index) rendering before the value. */\n name?: string | number;\n /** Value to render. */\n value: unknown;\n /** Current nesting depth (root is 0). */\n depth?: number;\n}\n\n/**\n * One line of the tree: either a collapsible container row\n * (`▸ key: {`) or a leaf (`key: value`).\n */\nfunction JsonNode({name, value, depth = 0}: JsonNodeProps) {\n const [open, setOpen] = useState(depth <= DEFAULT_OPEN_DEPTH);\n\n const label =\n name === undefined ? null : (\n <>\n <span className=\"rf0-dt-key\">{String(name)}</span>\n <span className=\"rf0-dt-punct\">: </span>\n </>\n );\n\n if (value !== null && typeof value === 'object') {\n const isArray = Array.isArray(value);\n const entries: Array<[string | number, unknown]> = isArray\n ? (value as unknown[]).map((v, i) => [i, v])\n : Object.entries(value as Record<string, unknown>);\n const openBracket = isArray ? '[' : '{';\n const closeBracket = isArray ? ']' : '}';\n const summary = open\n ? ''\n : `${openBracket}…${closeBracket} ${entries.length}`;\n\n return (\n <div className=\"rf0-dt-row\" style={{paddingLeft: depth * 12}}>\n <button\n type=\"button\"\n className=\"rf0-dt-node-toggle\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n <span className=\"rf0-dt-caret\">{open ? '▾' : '▸'}</span>\n {label}\n <span className=\"rf0-dt-punct\">{open ? openBracket : summary}</span>\n </button>\n {open && (\n <>\n {entries.map(([k, v]) => (\n <JsonNode key={String(k)} name={k} value={v} depth={depth + 1} />\n ))}\n <span className=\"rf0-dt-punct\" style={{paddingLeft: depth * 12}}>\n {closeBracket}\n </span>\n </>\n )}\n </div>\n );\n }\n\n return (\n <span\n className=\"rf0-dt-row\"\n style={{paddingLeft: depth * 12, display: 'block'}}\n >\n {label}\n <Primitive value={value} />\n </span>\n );\n}\n\n/** Render a primitive leaf with terminal-style type coloring. */\nfunction Primitive({value}: {value: unknown}): ReactNode {\n if (value === undefined)\n return <span className=\"rf0-dt-null\">undefined</span>;\n if (value === null) return <span className=\"rf0-dt-null\">null</span>;\n if (typeof value === 'string')\n return <span className=\"rf0-dt-string\">&quot;{value}&quot;</span>;\n if (typeof value === 'boolean')\n return <span className=\"rf0-dt-boolean\">{String(value)}</span>;\n return <span className=\"rf0-dt-number\">{String(value)}</span>;\n}\n\nexport default JsonNode;\n","/**\n * Stylesheet for the Devtools panel.\n *\n * Zero runtime dependencies by design: a single CSS string injected once\n * into <head> (idempotent across module reloads and multiple bundles).\n *\n * Aesthetic: instrument panel / terminal — near-black layers, monospace\n * stack, dense rows, hairline borders. Semantic colors only: error red,\n * success green, neutral gray, with one dim amber accent for the active\n * tab indicator and the collapsed badge.\n */\n\nconst CSS = `\n.rf0-dt {\n position: fixed;\n z-index: 2147483000;\n box-sizing: border-box;\n width: 308px;\n max-width: calc(100vw - 16px);\n max-height: min(70vh, 560px);\n display: flex;\n flex-direction: column;\n font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',\n Menlo, Consolas, 'Liberation Mono', monospace;\n font-size: 11px;\n line-height: 1.45;\n color: #c7d0dc;\n background: #0c1017;\n border: 1px solid #1f2735;\n border-radius: 4px;\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.4);\n}\n.rf0-dt *,\n.rf0-dt-badge * {\n box-sizing: border-box;\n}\n.rf0-dt--top-right { top: 8px; right: 8px; }\n.rf0-dt--bottom-right { bottom: 8px; right: 8px; }\n.rf0-dt--top-left { top: 8px; left: 8px; }\n.rf0-dt--bottom-left { bottom: 8px; left: 8px; }\n\n/* ---- header -------------------------------------------------------- */\n.rf0-dt-header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 6px 5px 9px;\n border-bottom: 1px solid #1f2735;\n background: #10151e;\n}\n.rf0-dt-title {\n flex: 1;\n min-width: 0;\n color: #8b96a5;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.rf0-dt-title::before {\n content: '';\n display: inline-block;\n width: 6px;\n height: 6px;\n margin-right: 6px;\n border-radius: 50%;\n background: #e2b93b;\n vertical-align: 1px;\n}\n.rf0-dt-headerbtn {\n border: 1px solid transparent;\n border-radius: 3px;\n padding: 1px 5px;\n color: #8b96a5;\n background: transparent;\n font: inherit;\n font-size: 10px;\n cursor: pointer;\n}\n.rf0-dt-headerbtn:hover { color: #dce3ec; border-color: #2a3547; }\n.rf0-dt-headerbtn:focus-visible,\n.rf0-dt-tab:focus-visible,\n.rf0-dt-action:focus-visible,\n.rf0-dt-badge:focus-visible,\n.rf0-dt-node-toggle:focus-visible {\n outline: 1px solid #e2b93b;\n outline-offset: 1px;\n}\n\n/* ---- tabs ---------------------------------------------------------- */\n.rf0-dt-tablist {\n display: flex;\n border-bottom: 1px solid #1f2735;\n background: #0e131b;\n}\n.rf0-dt-tab {\n flex: 1;\n padding: 4px 2px 5px;\n border: 0;\n border-bottom: 2px solid transparent;\n background: transparent;\n color: #6b7686;\n font: inherit;\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n cursor: pointer;\n white-space: nowrap;\n}\n.rf0-dt-tab:hover { color: #aab5c4; }\n.rf0-dt-tab[aria-selected='true'] {\n color: #e7edf4;\n border-bottom-color: #e2b93b;\n}\n.rf0-dt-tab-count {\n margin-left: 3px;\n color: inherit;\n opacity: 0.75;\n}\n.rf0-dt-tab--danger[aria-selected='true'] {\n border-bottom-color: #f0647c;\n}\n\n/* ---- panels -------------------------------------------------------- */\n.rf0-dt-panel {\n flex: 1;\n min-height: 84px;\n overflow: auto;\n padding: 6px 8px;\n scrollbar-width: thin;\n scrollbar-color: #2a3547 transparent;\n}\n.rf0-dt-empty {\n padding: 10px 2px;\n color: #4d5766;\n font-style: italic;\n}\n\n/* json tree */\n.rf0-dt-row {\n display: block;\n white-space: pre;\n tab-size: 2;\n}\n.rf0-dt-node-toggle {\n border: 0;\n padding: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n white-space: pre;\n}\n.rf0-dt-node-toggle:hover .rf0-dt-key { color: #dce3ec; }\n.rf0-dt-caret {\n display: inline-block;\n width: 1.2em;\n color: #4d5766;\n}\n.rf0-dt-key { color: #8b96a5; }\n.rf0-dt-punct { color: #4d5766; }\n.rf0-dt-string { color: #8fd68a; }\n.rf0-dt-number { color: #e2b93b; }\n.rf0-dt-boolean { color: #6fb3d9; }\n.rf0-dt-null { color: #55607080; font-style: italic; }\n\n/* errors / touched / dirty lists */\n.rf0-dt-item {\n padding: 3px 2px;\n border-bottom: 1px dotted #1a2230;\n display: flex;\n gap: 8px;\n align-items: baseline;\n}\n.rf0-dt-item:last-child { border-bottom: 0; }\n.rf0-dt-item-path {\n color: #aab5c4;\n word-break: break-all;\n}\n.rf0-dt-item-msg {\n color: #f0647c;\n word-break: break-word;\n}\n.rf0-dt-item-msg--ok { color: #8fd68a; }\n.rf0-dt-item-tag {\n flex: none;\n color: #4d5766;\n font-size: 10px;\n}\n.rf0-dt-item--touched .rf0-dt-item-path { color: #6fb3d9; }\n.rf0-dt-item--dirty .rf0-dt-item-path { color: #e2b93b; }\n\n/* ---- submit status + actions --------------------------------------- */\n.rf0-dt-status {\n display: flex;\n gap: 10px;\n padding: 4px 9px;\n border-top: 1px solid #1f2735;\n background: #10151e;\n color: #6b7686;\n font-size: 10px;\n letter-spacing: 0.04em;\n white-space: nowrap;\n overflow: hidden;\n}\n.rf0-dt-status b { color: #aab5c4; font-weight: 400; }\n.rf0-dt-status .rf0-dt-on { color: #e2b93b; }\n.rf0-dt-status .rf0-dt-ok { color: #8fd68a; }\n.rf0-dt-status .rf0-dt-err { color: #f0647c; }\n.rf0-dt-actions {\n display: flex;\n gap: 6px;\n padding: 6px 8px;\n border-top: 1px solid #1f2735;\n background: #10151e;\n}\n.rf0-dt-action {\n flex: 1;\n padding: 3px 0;\n border: 1px solid #2a3547;\n border-radius: 3px;\n background: #151b26;\n color: #c7d0dc;\n font: inherit;\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n cursor: pointer;\n}\n.rf0-dt-action:hover { border-color: #3b4a61; background: #1a2230; color: #e7edf4; }\n.rf0-dt-action:active { transform: translateY(1px); }\n\n/* ---- collapsed badge ----------------------------------------------- */\n.rf0-dt-badge {\n position: fixed;\n z-index: 2147483000;\n width: 26px;\n height: 26px;\n border: 1px solid #2a3547;\n border-radius: 50%;\n background: #0c1017;\n color: #e2b93b;\n font: inherit;\n font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',\n Menlo, Consolas, 'Liberation Mono', monospace;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.02em;\n cursor: pointer;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);\n}\n.rf0-dt-badge:hover { border-color: #e2b93b; }\n.rf0-dt-badge--top-right { top: 10px; right: 10px; }\n.rf0-dt-badge--bottom-right { bottom: 10px; right: 10px; }\n.rf0-dt-badge--top-left { top: 10px; left: 10px; }\n.rf0-dt-badge--bottom-left { bottom: 10px; left: 10px; }\n.rf0-dt-badge .rf0-dt-dot {\n position: absolute;\n top: 3px;\n right: 3px;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: #f0647c;\n display: none;\n}\n.rf0-dt-badge--has-errors .rf0-dt-dot { display: block; }\n`;\n\nconst STYLE_ID = 'react-f0rm-devtools-style';\n\n/**\n * Inject the panel stylesheet into <head>. Idempotent: repeated calls\n * (module reloads, HMR, multiple Devtools mounts) never duplicate the\n * <style> element. No-ops outside a DOM environment (SSR).\n */\nexport function injectDevtoolsStyles(): void {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = CSS;\n document.head.appendChild(style);\n}\n\ninjectDevtoolsStyles();\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 './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 interface 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 const contextForm = useContext(FormContext);\n const f = 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.emitter, 'change', getValues.bind(null, f));\n const errors = useWatch<FieldErrorEntry[]>(\n f.emitter,\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.emitter,\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":["process","env","NODE_ENV","shimModule","exports","React","require$$0","objectIs","Object","is","x","y","useState","useEffect","useLayoutEffect","useDebugValue","checkIfSnapshotChanged","inst","latestGetSnapshot","getSnapshot","value","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","emitter","event","getter","subscribeFactory","cacheRef","useRef","current","hasValue","cache","getterRef","useCallback","notify","useWatchCore","invalidate","on","FormContext","createContext","Provider","JsonNode","name","depth","open","setOpen","label","Fragment","className","String","isArray","Array","entries","map","v","i","openBracket","closeBracket","summary","length","style","paddingLeft","type","onClick","k","key","display","Primitive","STYLE_ID","getElementById","id","textContent","head","appendChild","injectDevtoolsStyles","TABS","submitStatusClass","isSubmitSuccessful","countLeaves","count","values","Devtools","form","position","contextForm","useContext","f","tab","setTab","idPrefix","useId","replace","getValues","bind","errors","getErrors","touched","getTouchedFields","useTouchedFields","dirty","getDirtyFields","useDirtyFields","isSubmitting","useIsSubmitting","submitCount","useSubmitCount","counts","keys","role","tabIndex","onKeyDown","e","delta","ArrowRight","ArrowLeft","preventDefault","next","indexOf","focus","JsonTree","path","message","index","reset","initialValues","trigger"],"mappings":"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","x_google_ignoreList":[0,1,2]}
@@ -96,6 +96,24 @@ type ValidationOutcome<T> = {
96
96
  * branded {@link ValidationOutcome} whose `values` become the form's
97
97
  * parsedValues baseline. */
98
98
  type ValidateResult<T> = Record<string, any> | ValidationOutcome<T> | Promise<Record<string, any> | ValidationOutcome<T>>;
99
+ /** Context passed to a form-level `validate` function's second argument.
100
+ * `signal` aborts as soon as the round is superseded — a newer round
101
+ * started (which only happens under a positive `validateDebounce`, where
102
+ * kicks merge into windows) — so async validators can cancel their
103
+ * underlying work instead of racing a stale result home. Stale results
104
+ * are dropped independently by the round gate, so validators that ignore
105
+ * the signal stay correct too; the same contract field-level validators
106
+ * get through their own `meta`. */
107
+ type FormValidateMeta<T extends Record<string, any> = any> = {
108
+ form: Form<T>;
109
+ signal: AbortSignal;
110
+ };
111
+ /** Form-level validator: receives all values (plus {@link
112
+ * FormValidateMeta} as an optional second argument) and returns a
113
+ * {@link ValidateResult} — sync or async — or `undefined`/nothing when
114
+ * valid (the runtime skips falsy results, so implicit-return callbacks
115
+ * type-check). */
116
+ type FormValidateFn<T extends Record<string, any> = any> = (values: T, meta: FormValidateMeta<T>) => ValidateResult<T> | undefined;
99
117
  interface Form<T extends Record<string, any> = any> {
100
118
  emitter: EventEmitter;
101
119
  mode: ValidationMode;
@@ -127,7 +145,12 @@ interface Form<T extends Record<string, any> = any> {
127
145
  * until `reset`/`setInitialValues` clears it. Never affects dirty
128
146
  * state — that compares live edits against initialValues only. */
129
147
  parsedValues: T | undefined;
130
- validate?: (values: T) => ValidateResult<T>;
148
+ /** Form-level validator, seeded from {@link Options.validate}. May
149
+ * receive a second {@link FormValidateMeta} argument. */
150
+ validate?: FormValidateFn<T>;
151
+ /** Delay in milliseconds before the form-level `validate` runs; seeded
152
+ * from {@link Options.validateDebounce} and fixed at create time. */
153
+ validateDebounce?: number;
131
154
  isSubmitting: boolean;
132
155
  submitCount: number;
133
156
  isSubmitSuccessful: boolean | undefined;
@@ -156,7 +179,17 @@ type Options<T extends Record<string, any> = any> = {
156
179
  * (the schema's parsed output) becomes the form's parsedValues baseline
157
180
  * that {@link getValues} layers over initialValues.
158
181
  */
159
- validate?: (values: T) => ValidateResult<T>;
182
+ validate?: FormValidateFn<T>;
183
+ /**
184
+ * Milliseconds to debounce the form-level `validate`: kicks from
185
+ * `trigger`/`ensureValidate`/submit inside the window merge into one
186
+ * run, and while the timer is pending the form counts as validating,
187
+ * so `trigger` and submit wait the window out — the same contract the
188
+ * per-field `validateDebounce` gives field validators. The merged run
189
+ * reads the values current when its timer fires. Defaults to `0`
190
+ * (validate runs immediately, exactly as before this option existed).
191
+ */
192
+ validateDebounce?: number;
160
193
  /** Start the form with every bound field disabled — the flag bound
161
194
  * fields OR with their own `disabled` option (a field cannot opt out
162
195
  * of a disabled form). Toggle later with {@link setDisabled}.
@@ -204,18 +237,23 @@ declare function getValue<T extends Record<string, any> = any, P extends FieldPa
204
237
  * @param path
205
238
  */
206
239
  declare function getValueByPath({ initialValues, parsedValues, values, deleted }: Form, path: Path): any;
207
- /** Options accepted by {@link setValue} / {@link setValueByPath}. Every flag
208
- * defaults to `false`; omitting the options object entirely keeps the plain
209
- * set-value behavior (no validation, no touched marking). */
240
+ /** Options accepted by {@link setValue} / {@link setValueByPath} / {@link
241
+ * changeValue} / {@link changeValueByPath}. `shouldValidate`/`shouldTouch`
242
+ * default to `false`; omitting the options object entirely keeps the plain
243
+ * set-value behavior (no validation, no touched marking, dirty stays
244
+ * derived). */
210
245
  interface SetFieldOptions {
211
246
  /** Run the field's registered validator (if any) after the value lands,
212
247
  * same as triggering that single field. Defaults to `false`. */
213
248
  shouldValidate?: boolean;
214
249
  /** Mark the field as touched. Defaults to `false`. */
215
250
  shouldTouch?: boolean;
216
- /** Reserved for a future manual dirty marker. Dirty state is currently
217
- * derived from comparing values against initialValues, so this flag is
218
- * accepted but does nothing. Defaults to `false`. */
251
+ /** Land the value as a commit instead of an edit: the value becomes the
252
+ * field's dirty-comparison baseline, so `getDirtyFields`/`isDirty`/
253
+ * `getFieldState().isDirty` read the field as clean, and a later write
254
+ * dirties it only by differing from the new baseline. `true` (or
255
+ * omitting the flag) keeps the default derived behavior — dirty while
256
+ * the live value differs from initialValues. */
219
257
  shouldDirty?: boolean;
220
258
  }
221
259
  /**
@@ -255,18 +293,26 @@ declare function setValueByPath(form: Form, path: Path, value: any, options?: Se
255
293
  * ignoring any mode. Functional updaters are the caller's to evaluate
256
294
  * ({@link getValue}).
257
295
  *
296
+ * `options` carries the same {@link SetFieldOptions}: on the fallback path
297
+ * (no mounted field) they forward to {@link setValueByPath} wholesale,
298
+ * while on the mounted-field path only `shouldDirty: false` applies — the
299
+ * write lands as a commit while the field's own mode gating keeps driving
300
+ * validation, which is the point of this channel.
301
+ *
258
302
  * @param form
259
303
  * @param name
260
304
  * @param value
305
+ * @param options
261
306
  */
262
- declare function changeValue<T extends Record<string, any> = any, P extends FieldPath<T> | Name = Name>(form: Form<T>, name: P, value: PathValueOf<T, P>): void;
307
+ declare function changeValue<T extends Record<string, any> = any, P extends FieldPath<T> | Name = Name>(form: Form<T>, name: P, value: PathValueOf<T, P>, options?: SetFieldOptions): void;
263
308
  /**
264
309
  * Set a field value as a user change, by parsed path
265
310
  * @param form
266
311
  * @param path
267
312
  * @param value
313
+ * @param options
268
314
  */
269
- declare function changeValueByPath(form: Form, path: Path, value: any): void;
315
+ declare function changeValueByPath(form: Form, path: Path, value: any, options?: SetFieldOptions): void;
270
316
  /**
271
317
  * Get field error
272
318
  * @param form
@@ -643,5 +689,5 @@ interface SetFocusOptions {
643
689
  */
644
690
  declare function setFocus(form: Form, name: Name, options?: SetFocusOptions): void;
645
691
 
646
- export { setInitialValues as $, getTouchedFields as A, getValue as B, getValueByPath as C, getValues as D, handleSubmit as E, hasErrors as G, hasTouched as I, hasTouchedByPath as J, incrementSubmitCount as K, isDirty as L, isTouched as M, removeField as Q, removeFieldByPath as T, reset as U, resetField as W, setDisabled as X, setError as Y, setErrorByPath as Z, setFocus as _, setIsSubmitting as a0, setServerErrors as a1, setSubmitSuccessful as a2, setTouched as a3, setTouchedByPath as a4, setValidatingByPath as a5, setValue as a6, setValueByPath as a7, trigger as a8, unsetValidatingByPath as a9, validate as aa, VALIDATION_OUTCOME as k, changeValue as n, changeValueByPath as o, clearErrors as p, create as q, ensureValidate as r, getDirtyFields as s, getError as t, getErrorByPath as u, getErrors as v, getFieldErrors as w, getFieldErrorsByPath as x, getFieldState as y, getFirstError as z };
647
- export type { Form as F, HandleSubmitOptions as H, Name as N, Options as O, PathValueOf as P, ReValidateMode as R, SetFieldOptions as S, ValidationMode as V, FieldPath as a, FieldError as b, Path as c, FieldErrorEntry as d, FieldState as e, PathValue as f, ResetFieldOptions as g, ResetOptions as h, SetFocusOptions as i, SetServerErrorsOptions as j, ValidateResult as l, ValidationOutcome as m };
692
+ export { setErrorByPath as $, getFieldState as A, getFirstError as B, getTouchedFields as C, getValue as D, getValueByPath as E, getValues as G, handleSubmit as I, hasErrors as J, hasTouched as K, hasTouchedByPath as L, incrementSubmitCount as M, isDirty as Q, isTouched as T, removeField as U, removeFieldByPath as W, reset as X, resetField as Y, setDisabled as Z, setError as _, setFocus as a0, setInitialValues as a1, setIsSubmitting as a2, setServerErrors as a3, setSubmitSuccessful as a4, setTouched as a5, setTouchedByPath as a6, setValidatingByPath as a7, setValue as a8, setValueByPath as a9, trigger as aa, unsetValidatingByPath as ab, validate as ac, VALIDATION_OUTCOME as m, changeValue as p, changeValueByPath as q, clearErrors as r, create as s, ensureValidate as t, getDirtyFields as u, getError as v, getErrorByPath as w, getErrors as x, getFieldErrors as y, getFieldErrorsByPath as z };
693
+ export type { Form as F, HandleSubmitOptions as H, Name as N, Options as O, PathValueOf as P, ReValidateMode as R, SetFieldOptions as S, ValidationMode as V, FieldPath as a, FieldError as b, Path as c, FieldErrorEntry as d, FieldState as e, FormValidateFn as f, FormValidateMeta as g, PathValue as h, ResetFieldOptions as i, ResetOptions as j, SetFocusOptions as k, SetServerErrorsOptions as l, ValidateResult as n, ValidationOutcome as o };
@@ -0,0 +1,2 @@
1
+ "use strict";function e(e,t){for(var r=arguments.length,n=new Array(r>2?r-2:0),o=2;o<r;o++)n[o-2]=arguments[o];(e.get(t)||[]).forEach(function(e){return e.apply(void 0,n)})}function t(e,t,r){var n=function(e,t){var r=e.get(t);if(r)return r;var n=new Set;return e.set(t,n),n}(e,t);return n.add(r),function(){return n.delete(r)}}const r=new Map;function n(e){if(Array.isArray(e))return e;const t=r.get(e);if(t)return t;const n=function(e){const t=[];let r="";const n=()=>{t.push(r),r=""};for(let o=0;o<e.length;o++){const s=e[o];if("."===s)""!==r&&n();else if("["===s){""!==r&&n();const s=e[o+1];if('"'===s||"'"===s){const r=e.indexOf(s,o+2);if(-1===r)throw new TypeError(`Unterminated quote in path: ${e}`);if("]"!==e[r+1])throw new TypeError(`Expected "]" after quoted segment in path: ${e}`);t.push(e.slice(o+2,r)),o=r+1}else{const r=e.indexOf("]",o+1);if(-1===r)throw new TypeError(`Unterminated bracket in path: ${e}`);const n=e.slice(o+1,r);t.push(/^-?\d+$/.test(n)?Number(n):n),o=r}}else r+=s}""===r&&0!==t.length||n();return t}(e);return r.set(e,n),n}function o(e,t){if(!t.length||null==e)return e;const[r,...n]=t;if(n.length){const t=o(e[r],n);return t===e[r]?e:s(e,[r],t)}if(Array.isArray(e)){if(!(r in e))return e;const t=e.slice();return delete t[r],t}if("object"!=typeof e||!(r in e))return e;const i={...e};return delete i[r],i}function s(e,t,r){if(!t.length)return r;const[n,...o]=t;if("number"==typeof n){const t=Array.isArray(e)?e.slice():[];return t[n]=s(t[n],o,r),t}return{...e,[n]:s(e&&e[n],o,r)}}function i(e,t,r,n){if(!t.length)return r;let o=e,s=null,i="";for(let u=0;u<t.length;u++){const c=t[u];if(!n.has(o)){let t;t="number"==typeof c?Array.isArray(o)?o.slice():[]:{...o},n.add(t),0===u?e=t:s[i]=t,o=t}u===t.length-1?o[c]=r:(s=o,i=c,o=o[c])}return e}function u(e){const t=n(e);return{value:t,key:JSON.stringify(t)}}const c=Symbol("validation-outcome");function a(e){const{initialValues:t,parsedValues:r,values:n,deleted:s}=e,u=new Set;let c=r??t;for(const[e,t]of n)c=i(c,JSON.parse(e),t,u);for(const e of s)c=o(c,JSON.parse(e));return c}function l(t,r,n,o){const{emitter:s,values:i,deleted:u}=t;i.set(r.key,n),function(e,{key:t}){if(!e.size)return;for(const r of e)(r===t||r.startsWith(`${t.slice(0,-1)},`)||t.startsWith(`${r.slice(0,-1)},`))&&e.delete(r)}(u,r),function(e,{key:t}){const r=d.get(e);if(!r?.size)return;const n=`${t.slice(0,-1)},`;for(const e of r.keys())e.startsWith(n)&&r.delete(e)}(t,r),p(t),e(s,"change",r)}function f(t,r,n){!function({emitter:t,errors:r},n,o){const s=function(e){if("string"==typeof e)return e?[{type:"custom",message:e}]:void 0;if(y(e))return[e];if(!e)return;const t=[];return e.forEach(e=>{"string"==typeof e&&e?t.push({type:"custom",message:e}):y(e)&&t.push(e)}),t.length?t:void 0}(o);s?r.set(n.key,s):r.delete(n.key);e(t,"errors",n)}(t,u(r),n)}const d=new WeakMap;function g(e,t,r){const n=d.get(e);return n?.has(t)?n.get(t):(o=e.initialValues,r.reduce((e,t)=>{if(null!=e)return e[t]},o));var o}const m=new WeakMap;function p(e){const t=m.get(e);t&&t.version++}function v(e){const t={};return function(e,t){for(const[r,n]of e.values){const o=JSON.parse(r);g(e,r,o)!==n&&t(o.join("."))}}(e,e=>{t[e]=!0}),t}function y(e){return!!e&&"object"==typeof e&&"string"==typeof e.type&&"string"==typeof e.message}function h(e,t,r=[]){Object.entries(t).forEach(([t,o])=>{const s=[...r,...n(t)];"string"==typeof o?o&&f(e,s,o):Array.isArray(o)||y(o)?f(e,s,o):o&&"object"==typeof o&&h(e,o,s)})}function w(t,r){if(r){if("object"==typeof r&&c in r){const n=r;return n.errors&&h(t,n.errors),void function(t,r){void 0!==r&&r!==t.parsedValues&&(t.parsedValues=r,e(t.emitter,"change"))}(t,n.values)}h(t,r)}}const b="__form_validate__";const k=Symbol("form-validate-settled"),A=new WeakMap;function O(t,r,n,o){if(r.round!==n)return;if(r.round=null,null!==r.timer)return;r.marked&&(r.marked=!1,t.validating.delete(b),e(t.emitter,"validating"));const s=r.waiters;r.waiters=[];for(const e of s)o===k?e.resolve():e.reject(o)}exports.VALIDATION_OUTCOME=c,exports.getDirtyFields=function(e){let t=m.get(e);if(t){if(t.version>0){const r=v(e);(function(e,t){const r=Object.keys(e);return r.length===Object.keys(t).length&&r.every(e=>!0===t[e])})(t.result,r)||(t.result=r),t.version=0}}else t={version:0,result:v(e)},m.set(e,t);return t.result},exports.getErrors=function({errors:e}){const t=[];for(const[r,n]of e){const e=JSON.parse(r).join(".");for(const{type:r,message:o}of n)t.push({path:e,type:r,message:o})}return t},exports.getTouchedFields=function({touched:e}){return Array.from(e,e=>JSON.parse(e).join("."))},exports.getValues=a,exports.on=t,exports.reset=function(t,r,n){const o=[];t.initialValues=r,t.parsedValues=void 0,function(t){const{emitter:r,errors:n}=t;n.clear(),e(r,"errors")}(t);const{emitter:s,touched:i,values:c,deleted:a,validating:f}=t;c.clear(),a.clear(),function(e){const t=d.get(e);t&&t.clear()}(t),i.clear(),f.clear(),t.isSubmitting=!1,t.submitCount=0,t.isSubmitSuccessful=void 0,p(t);for(const{key:e,value:r}of o)l(t,u(e),r);e(s,"change"),e(s,"touched"),e(s,"validating"),e(s,"submitting"),e(s,"submitCount"),e(s,"submitSuccessful"),e(s,"reset")},exports.trigger=async function(r,n){const o=()=>{return e=r.emitter,n="validating",o=()=>function(e){for(const t of e.validating)if(t!==b)return!1;return!0}(r),s=()=>!1,new Promise((r,i)=>{if(s())return void i();if(o())return void r();const u=t(e,n,()=>{if(s())return u(),void i();o()&&(u(),r())})});var e,n,o,s};return r.validators.forEach(e=>e()),await o(),r.validate&&await function(t){const r=t.validate;if(!r)return Promise.resolve();const n=t.validateDebounce??0;if(n<=0){const e=new AbortController;return Promise.resolve(r(a(t),{form:t,signal:e.signal})).then(e=>{w(t,e)})}const o=function(e){let t=A.get(e);t||(t={timer:null,controller:null,round:null,marked:!1,waiters:[]},A.set(e,t));return t}(t);null!==o.timer?clearTimeout(o.timer):(o.marked=!0,t.validating.add(b),e(t.emitter,"validating"));return o.timer=setTimeout(()=>{o.timer=null;const e=o.round={};(function(e,t,r){const n=e.validate;if(!n)return Promise.resolve();t.controller?.abort();const o=t.controller=new AbortController;let s;try{s=Promise.resolve(n(a(e),{form:e,signal:o.signal}))}catch(e){s=Promise.reject(e)}return s.then(n=>{t.round===r&&w(e,n)},e=>{if(t.round===r)throw e})})(t,o,e).then(()=>O(t,o,e,k),r=>O(t,o,e,r))},n),new Promise((e,t)=>{o.waiters.push({resolve:e,reject:t})})}(r),!function({errors:e}){return e.size>0}(r)};
2
+ //# sourceMappingURL=form-DbDDJ8bt.cjs.js.map