react-f0rm 0.6.1 → 0.8.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;
@@ -113,6 +131,13 @@ interface Form<T extends Record<string, any> = any> {
113
131
  errors: Map<string, FieldError[]>;
114
132
  touched: Set<string>;
115
133
  validators: Map<string, () => void>;
134
+ /** Change handlers published by mounted fields (`useField`): each is the
135
+ * field's own onChange — the write plus its mode/reValidateMode-gated
136
+ * validation kick. Path-based writes with user-change semantics
137
+ * ({@link changeValueByPath}) route through the registered handler; the
138
+ * effective per-field mode and live-error view live inside the field's
139
+ * closure, so this map is the only channel that can reproduce them. */
140
+ changeHandlers: Map<string, (value: any) => void>;
116
141
  validating: Set<string>;
117
142
  /** Parsed values from the last successful schema validation: the
118
143
  * schema's complete output tree (coerced/transformed values included).
@@ -120,7 +145,12 @@ interface Form<T extends Record<string, any> = any> {
120
145
  * until `reset`/`setInitialValues` clears it. Never affects dirty
121
146
  * state — that compares live edits against initialValues only. */
122
147
  parsedValues: T | undefined;
123
- 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;
124
154
  isSubmitting: boolean;
125
155
  submitCount: number;
126
156
  isSubmitSuccessful: boolean | undefined;
@@ -149,7 +179,17 @@ type Options<T extends Record<string, any> = any> = {
149
179
  * (the schema's parsed output) becomes the form's parsedValues baseline
150
180
  * that {@link getValues} layers over initialValues.
151
181
  */
152
- 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;
153
193
  /** Start the form with every bound field disabled — the flag bound
154
194
  * fields OR with their own `disabled` option (a field cannot opt out
155
195
  * of a disabled form). Toggle later with {@link setDisabled}.
@@ -227,6 +267,39 @@ declare function setValue<T extends Record<string, any> = any, P extends FieldPa
227
267
  * @param options
228
268
  */
229
269
  declare function setValueByPath(form: Form, path: Path, value: any, options?: SetFieldOptions): void;
270
+ /**
271
+ * Set a field value as a user change.
272
+ *
273
+ * The write routes through the field's own onChange when one is mounted
274
+ * (registered by `useField`), so it fires exactly the validation a user
275
+ * typing into the field would fire: the field's effective `mode`
276
+ * (per-field override included) and the form's `reValidateMode`. With no
277
+ * mounted field on the path it degrades to a plain value set
278
+ * ({@link setValue}).
279
+ *
280
+ * This is the channel for component-library bridges that hand a control a
281
+ * plain setter bound to a field — they cannot rebuild the gating from
282
+ * public form state, because the per-field mode override and the
283
+ * live-error view that gates `reValidateMode` live inside the field's
284
+ * onChange closure.
285
+ *
286
+ * Contrast {@link setValue}: that is the imperative channel — its
287
+ * `shouldValidate` option kicks the field's validator unconditionally,
288
+ * ignoring any mode. Functional updaters are the caller's to evaluate
289
+ * ({@link getValue}).
290
+ *
291
+ * @param form
292
+ * @param name
293
+ * @param value
294
+ */
295
+ 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;
296
+ /**
297
+ * Set a field value as a user change, by parsed path
298
+ * @param form
299
+ * @param path
300
+ * @param value
301
+ */
302
+ declare function changeValueByPath(form: Form, path: Path, value: any): void;
230
303
  /**
231
304
  * Get field error
232
305
  * @param form
@@ -603,5 +676,5 @@ interface SetFocusOptions {
603
676
  */
604
677
  declare function setFocus(form: Form, name: Name, options?: SetFocusOptions): void;
605
678
 
606
- export { setServerErrors as $, getValueByPath as A, getValues as B, handleSubmit as C, hasErrors as D, hasTouched as E, hasTouchedByPath as G, incrementSubmitCount as I, isDirty as J, isTouched as K, removeField as L, removeFieldByPath as M, reset as Q, resetField as T, setDisabled as U, setError as W, setErrorByPath as X, setFocus as Y, setInitialValues as Z, setIsSubmitting as _, setSubmitSuccessful as a0, setTouched as a1, setTouchedByPath as a2, setValidatingByPath as a3, setValue as a4, setValueByPath as a5, trigger as a6, unsetValidatingByPath as a7, validate as a8, VALIDATION_OUTCOME as k, clearErrors as n, create as o, ensureValidate as p, getDirtyFields as q, getError as r, getErrorByPath as s, getErrors as t, getFieldErrors as u, getFieldErrorsByPath as v, getFieldState as w, getFirstError as x, getTouchedFields as y, getValue as z };
607
- 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 };
679
+ 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 };
680
+ 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 i=e[o];if("."===i)""!==r&&n();else if("["===i){""!==r&&n();const i=e[o+1];if('"'===i||"'"===i){const r=e.indexOf(i,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+=i}""===r&&0!==t.length||n();return t}(e);return r.set(e,n),n}function o(e,t){return t.reduce((e,t)=>{if(null!=e)return e[t]},e)}function i(e,t){if(!t.length||null==e)return e;const[r,...n]=t;if(n.length){const t=i(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 o={...e};return delete o[r],o}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 u(e,t,r,n){if(!t.length)return r;let o=e,i=null,s="";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:i[s]=t,o=t}u===t.length-1?o[c]=r:(i=o,s=c,o=o[c])}return e}function c(e){const t=n(e);return{value:t,key:JSON.stringify(t)}}const a=Symbol("validation-outcome");function l(e){const{initialValues:t,parsedValues:r,values:n,deleted:o}=e,s=new Set;let c=r??t;for(const[e,t]of n)c=u(c,JSON.parse(e),t,s);for(const e of o)c=i(c,JSON.parse(e));return c}function f(t,r,n,o){const{emitter:i,values:s,deleted:u}=t;s.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),g(t),e(i,"change",r)}function d(t,r,n){!function({emitter:t,errors:r},n,o){const i=function(e){if("string"==typeof e)return e?[{type:"custom",message:e}]:void 0;if(v(e))return[e];if(!e)return;const t=[];return e.forEach(e=>{"string"==typeof e&&e?t.push({type:"custom",message:e}):v(e)&&t.push(e)}),t.length?t:void 0}(o);i?r.set(n.key,i):r.delete(n.key);e(t,"errors",n)}(t,c(r),n)}const m=new WeakMap;function g(e){const t=m.get(e);t&&t.version++}function p(e){const t={};return function({initialValues:e,values:t},r){for(const[n,i]of t){const t=JSON.parse(n);o(e,t)!==i&&r(t.join("."))}}(e,e=>{t[e]=!0}),t}function v(e){return!!e&&"object"==typeof e&&"string"==typeof e.type&&"string"==typeof e.message}function y(e,t,r=[]){Object.entries(t).forEach(([t,o])=>{const i=[...r,...n(t)];"string"==typeof o?o&&d(e,i,o):Array.isArray(o)||v(o)?d(e,i,o):o&&"object"==typeof o&&y(e,o,i)})}function h(t,r){if(r){if("object"==typeof r&&a in r){const n=r;return n.errors&&y(t,n.errors),void function(t,r){void 0!==r&&r!==t.parsedValues&&(t.parsedValues=r,e(t.emitter,"change"))}(t,n.values)}y(t,r)}}const b="__form_validate__";const w=Symbol("form-validate-settled"),A=new WeakMap;function k(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 i=r.waiters;r.waiters=[];for(const e of i)o===w?e.resolve():e.reject(o)}exports.VALIDATION_OUTCOME=a,exports.getDirtyFields=function(e){let t=m.get(e);if(t){if(t.version>0){const r=p(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:p(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=l,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:i,touched:s,values:u,deleted:a,validating:l}=t;u.clear(),a.clear(),s.clear(),l.clear(),t.isSubmitting=!1,t.submitCount=0,t.isSubmitSuccessful=void 0,g(t);for(const{key:e,value:r}of o)f(t,c(e),r);e(i,"change"),e(i,"touched"),e(i,"validating"),e(i,"submitting"),e(i,"submitCount"),e(i,"submitSuccessful"),e(i,"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),i=()=>!1,new Promise((r,s)=>{if(i())return void s();if(o())return void r();const u=t(e,n,()=>{if(i())return u(),void s();o()&&(u(),r())})});var e,n,o,i};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(l(t),{form:t,signal:e.signal})).then(e=>{h(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 i;try{i=Promise.resolve(n(l(e),{form:e,signal:o.signal}))}catch(e){i=Promise.reject(e)}return i.then(n=>{t.round===r&&h(e,n)},e=>{if(t.round===r)throw e})})(t,o,e).then(()=>k(t,o,e,w),r=>k(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-DnM4ONxf.cjs.js.map