react-formesh 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/dist/array.cjs +4 -0
- package/dist/array.cjs.map +1 -0
- package/dist/array.d.cts +2 -0
- package/dist/array.d.ts +2 -0
- package/dist/array.js +3 -0
- package/dist/array.js.map +1 -0
- package/dist/field-CLlwd7BC.d.ts +134 -0
- package/dist/field-DvWKUaTD.d.cts +134 -0
- package/dist/file.cjs +4 -0
- package/dist/file.cjs.map +1 -0
- package/dist/file.d.cts +2 -0
- package/dist/file.d.ts +2 -0
- package/dist/file.js +3 -0
- package/dist/file.js.map +1 -0
- package/dist/index.cjs +431 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +209 -0
- package/dist/index.d.ts +209 -0
- package/dist/index.js +419 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +487 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +144 -0
- package/dist/react.d.ts +144 -0
- package/dist/react.js +482 -0
- package/dist/react.js.map +1 -0
- package/dist/validation-gIT3Xn9h.d.cts +125 -0
- package/dist/validation-gIT3Xn9h.d.ts +125 -0
- package/dist/validators.cjs +99 -0
- package/dist/validators.cjs.map +1 -0
- package/dist/validators.d.cts +20 -0
- package/dist/validators.d.ts +20 -0
- package/dist/validators.js +89 -0
- package/dist/validators.js.map +1 -0
- package/package.json +70 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { F as FormValues, d as FormStore, a as FieldPath, U as Unsubscribe, W as WatchListener, c as ValidationSchema, b as ValidationResult } from './validation-gIT3Xn9h.cjs';
|
|
2
|
+
export { e as FieldRules, f as FormLevelValidator, S as StoreListener, g as ValidationContext, V as Validator } from './validation-gIT3Xn9h.cjs';
|
|
3
|
+
import { S as SyncTarget, a as DebouncedSyncOptions, D as DebouncedSync, N as Normalizer } from './field-DvWKUaTD.cjs';
|
|
4
|
+
export { F as FieldOptions, b as NormalizeContext, R as RegisteredField } from './field-DvWKUaTD.cjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a framework-agnostic form store.
|
|
8
|
+
*
|
|
9
|
+
* This file must never import React. It's the piece every other package
|
|
10
|
+
* (react, validators, file, array) builds on top of, and it needs to stay
|
|
11
|
+
* independently unit-testable and usable outside React entirely (see
|
|
12
|
+
* roadmap section 7).
|
|
13
|
+
*
|
|
14
|
+
* Design notes (documented here because the "why" matters for anyone
|
|
15
|
+
* extending this later):
|
|
16
|
+
*
|
|
17
|
+
* - Values are plain objects, updated immutably (see `paths.ts`), so
|
|
18
|
+
* `getValues()` always returns a plain, serializable snapshot — no
|
|
19
|
+
* proxies, no framework-specific wrappers.
|
|
20
|
+
* - The store does not itself decide who re-renders. It just computes
|
|
21
|
+
* *which dot-paths changed* on every commit and hands that list to every
|
|
22
|
+
* subscriber. The React binding (`useForm`, in the `react` entry point)
|
|
23
|
+
* is what turns "did my path change?" into a re-render decision, via
|
|
24
|
+
* `useSyncExternalStore`. This split is what avoids a Context-based
|
|
25
|
+
* design, where every consumer re-renders on every change regardless of
|
|
26
|
+
* which field they read.
|
|
27
|
+
*/
|
|
28
|
+
declare function createFormStore<TValues extends FormValues = FormValues>(initialValues: TValues): FormStore<TValues>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A section-scoped listener receives dot-paths already relative to the
|
|
32
|
+
* section (the section's own key prefix is stripped before delivery).
|
|
33
|
+
*/
|
|
34
|
+
type SectionListener = (changedPaths: readonly FieldPath[]) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Members are declared with method syntax (see the note on `FormStore` for
|
|
37
|
+
* why — property-syntax function members break structural assignability of
|
|
38
|
+
* typed sections under `strictFunctionTypes`).
|
|
39
|
+
*/
|
|
40
|
+
interface FormSection<TSectionValues extends FormValues = FormValues> {
|
|
41
|
+
/** The dot-path key this section is scoped to on the parent store. */
|
|
42
|
+
readonly key: string;
|
|
43
|
+
/** Current values for just this section, as a plain object. */
|
|
44
|
+
getValues(): TSectionValues;
|
|
45
|
+
/** Reads a value at a path relative to this section. */
|
|
46
|
+
getValue(relativePath: FieldPath): unknown;
|
|
47
|
+
/** Writes a value at a path relative to this section. */
|
|
48
|
+
setValue(relativePath: FieldPath, value: unknown): void;
|
|
49
|
+
/** Shallow-merges a partial object into this section's values. */
|
|
50
|
+
setValues(partial: Partial<TSectionValues>): void;
|
|
51
|
+
/** Resets this section back to its slice of the store's initial values. */
|
|
52
|
+
reset(): void;
|
|
53
|
+
/**
|
|
54
|
+
* The slice of the parent store's initial values this section owns (i.e.
|
|
55
|
+
* what `reset()` restores to). Present so a section satisfies the same
|
|
56
|
+
* structural surface as a store — required for `SyncTarget` composition
|
|
57
|
+
* (`createDebouncedSync(section)`, `useForm(section)`, ...).
|
|
58
|
+
*/
|
|
59
|
+
getInitialValues(): TSectionValues;
|
|
60
|
+
/** Subscribes to changes within this section only (paths are relative). */
|
|
61
|
+
subscribe(listener: SectionListener): Unsubscribe;
|
|
62
|
+
/**
|
|
63
|
+
* Observes one section-relative path (including its subtree) across
|
|
64
|
+
* changes, invoking `listener(value, previousValue)` only when the value
|
|
65
|
+
* actually changed (deep equality). Returns an unsubscribe function.
|
|
66
|
+
*/
|
|
67
|
+
watch(relativePath: FieldPath, listener: WatchListener): Unsubscribe;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wraps a slice of a FormStore (identified by a top-level or dot-path key)
|
|
72
|
+
* as an independently usable FormSection.
|
|
73
|
+
*
|
|
74
|
+
* This is the primitive behind multi-section ERP-style forms: each section
|
|
75
|
+
* of a form (e.g. "employeeInfo", "jobInfo", "bankInfo") can be built,
|
|
76
|
+
* tested, and reasoned about as if it owned its own store, while every
|
|
77
|
+
* write actually lands on the shared parent store — so the parent always
|
|
78
|
+
* has the complete, merged plain object (see roadmap section 8).
|
|
79
|
+
*
|
|
80
|
+
* Framework-agnostic: no React import here either. `useForm(store, {
|
|
81
|
+
* section })` (in the react entry point) is a thin hook wrapper around this.
|
|
82
|
+
*/
|
|
83
|
+
declare function createFormSection<TSectionValues extends FormValues = FormValues>(store: FormStore<FormValues>, key: string): FormSection<TSectionValues>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One debounced-sync wrapper for ANY target — a whole FormStore, a section
|
|
87
|
+
* slice, or an array row's object slice (all are SyncTargets, so there is
|
|
88
|
+
* deliberately no separate "array" vs "object" implementation).
|
|
89
|
+
*
|
|
90
|
+
* Why this exists (the gap it closes):
|
|
91
|
+
*
|
|
92
|
+
* - Phase 1's store commits synchronously on every `setValue` — during fast
|
|
93
|
+
* typing that is one diff+notify per keystroke hitting the parent store
|
|
94
|
+
* and every subscriber of it. With this wrapper in front, the target sees
|
|
95
|
+
* exactly ONE commit per quiet period, no matter how many writes happened.
|
|
96
|
+
* - The commit itself is transactional: the whole pending batch is applied
|
|
97
|
+
* to a staged copy of the target's values and handed over via a single
|
|
98
|
+
* `setValues`, which the store turns into one diff + one notify. Multiple
|
|
99
|
+
* sections syncing through their own wrappers therefore also stop
|
|
100
|
+
* fighting over the parent on every keystroke.
|
|
101
|
+
* - Reads are read-through and cached: `getValues`/`getValue` include the
|
|
102
|
+
* buffered writes, so a UI rendering from the wrapper shows what the user
|
|
103
|
+
* typed immediately — the debounce only delays the *parent commit*, not
|
|
104
|
+
* the visible state. The snapshot caching also makes the wrapper safe to
|
|
105
|
+
* hand to `useSyncExternalStore` directly.
|
|
106
|
+
*
|
|
107
|
+
* Semantics worth knowing:
|
|
108
|
+
*
|
|
109
|
+
* - Trailing-edge debounce: each buffered write restarts the timer.
|
|
110
|
+
* - Last write per path wins (a Map keyed by path).
|
|
111
|
+
* - `flush()` commits immediately (use on blur/submit); `cancel()` drops.
|
|
112
|
+
* - Writes arriving while a flush is in flight commit straight through, so
|
|
113
|
+
* nothing triggered synchronously by the flush notification is ever lost.
|
|
114
|
+
* - Wrapper subscribers hear about buffered writes immediately and about
|
|
115
|
+
* external target changes as they happen; the wrapper's own flush is NOT
|
|
116
|
+
* re-announced (it was already announced when buffered, and the values
|
|
117
|
+
* did not change at that point).
|
|
118
|
+
*/
|
|
119
|
+
declare function createDebouncedSync<TValues extends FormValues = FormValues>(target: SyncTarget<TValues>, options?: DebouncedSyncOptions): DebouncedSync<TValues>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Runs a validation schema against a values object and returns every
|
|
123
|
+
* error, keyed by field path.
|
|
124
|
+
*
|
|
125
|
+
* Framework-agnostic and pure: no store, no React, no timers. `useForm`
|
|
126
|
+
* calls this on every values change; anything else (a submit handler, a
|
|
127
|
+
* draft-save guard, a section component) can call it directly with the
|
|
128
|
+
* same schema.
|
|
129
|
+
*
|
|
130
|
+
* Semantics:
|
|
131
|
+
* - Field rules run first, per path, in schema key order; the first
|
|
132
|
+
* failing rule's message wins for that path.
|
|
133
|
+
* - Form-level validators run after, and their errors only fill paths
|
|
134
|
+
* that don't already have a field-level error (the per-field message is
|
|
135
|
+
* the more specific one).
|
|
136
|
+
* - Nested paths ("employee.firstName") are resolved with `getAtPath`,
|
|
137
|
+
* consistent with how the store reads values.
|
|
138
|
+
*/
|
|
139
|
+
declare function validateValues<TValues extends FormValues = FormValues>(values: TValues, schema: ValidationSchema): ValidationResult;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The default normalizer. Covers the shapes that come up repeatedly across
|
|
143
|
+
* hand-written ERP form components:
|
|
144
|
+
*
|
|
145
|
+
* - native DOM change events (checkbox vs. everything else)
|
|
146
|
+
* - `Date` objects (from date pickers) — passed through as-is; callers who
|
|
147
|
+
* need a serialized string should supply a custom normalizer, since the
|
|
148
|
+
* right format is app-specific (see roadmap section 13: normalization
|
|
149
|
+
* decisions must be explicit, not silently opinionated)
|
|
150
|
+
* - arrays of `{ value }` option objects (multi-selects)
|
|
151
|
+
* - single `{ value }` option objects (single-selects)
|
|
152
|
+
* - plain primitives, passed through unchanged
|
|
153
|
+
*
|
|
154
|
+
* This intentionally does not know about any specific UI library. A
|
|
155
|
+
* component whose change shape doesn't match one of the above should be
|
|
156
|
+
* wired with a custom `normalize` function via `FieldOptions`.
|
|
157
|
+
*/
|
|
158
|
+
declare const defaultNormalize: Normalizer;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Structural equality for plain JSON-like form values (objects, arrays,
|
|
162
|
+
* primitives). Non-plain values (Date, File, custom classes) fall back to
|
|
163
|
+
* `Object.is` rather than field-by-field inspection — sufficient for dirty
|
|
164
|
+
* tracking, since those values are typically replaced wholesale rather than
|
|
165
|
+
* mutated in place. Revisit only if a real use case needs otherwise.
|
|
166
|
+
*/
|
|
167
|
+
declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
168
|
+
|
|
169
|
+
declare function getAtPath(source: unknown, path: string): unknown;
|
|
170
|
+
/**
|
|
171
|
+
* Returns a new object with `value` written at `path`, cloning only the
|
|
172
|
+
* objects along the path (structural sharing everywhere else). This is what
|
|
173
|
+
* lets subscribers cheaply detect "did the branch I care about change?" via
|
|
174
|
+
* reference equality, without deep-cloning the whole form on every keystroke.
|
|
175
|
+
*/
|
|
176
|
+
declare function setAtPath<T extends Record<string, unknown>>(source: T, path: string, value: unknown): T;
|
|
177
|
+
/**
|
|
178
|
+
* Shallow-merges `partial` into `source` at the top level, one key at a
|
|
179
|
+
* time, reusing setAtPath so each key's structural-sharing behavior stays
|
|
180
|
+
* consistent with single-field writes.
|
|
181
|
+
*/
|
|
182
|
+
declare function mergeAtRoot<T extends Record<string, unknown>>(source: T, partial: Partial<T>): T;
|
|
183
|
+
/**
|
|
184
|
+
* Returns every dot-path whose leaf value differs (by reference, via
|
|
185
|
+
* Object.is) between `prev` and `next`, walking both objects together.
|
|
186
|
+
* Used to compute exactly which paths to notify subscribers about.
|
|
187
|
+
*/
|
|
188
|
+
declare function diffPaths(prev: unknown, next: unknown, basePath?: string, seen?: Set<string>): string[];
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Whether a change reported at `changedPath` could have affected the value
|
|
192
|
+
* at `watchedPath`. Three cases matter:
|
|
193
|
+
*
|
|
194
|
+
* - exact match (`changedPath === watchedPath`)
|
|
195
|
+
* - the change is *inside* the watched subtree (`employee.name` changes
|
|
196
|
+
* when watching `employee`)
|
|
197
|
+
* - the watched path sits *inside* the changed subtree — an ancestor was
|
|
198
|
+
* replaced wholesale (e.g. `store.setValue("employee", {...})`), so the
|
|
199
|
+
* value at the watched path may or may not have changed; the watcher
|
|
200
|
+
* still needs to check
|
|
201
|
+
*
|
|
202
|
+
* An empty changed path means "everything under here was replaced" (how a
|
|
203
|
+
* section sees `store.setValue(sectionKey, {...})`), so it matches any
|
|
204
|
+
* watch. An empty watched path is a whole-scope watch and matches every
|
|
205
|
+
* change.
|
|
206
|
+
*/
|
|
207
|
+
declare function isPathWithin(changedPath: string, watchedPath: string): boolean;
|
|
208
|
+
|
|
209
|
+
export { DebouncedSync, DebouncedSyncOptions, FieldPath, type FormSection, FormStore, FormValues, Normalizer, type SectionListener, SyncTarget, Unsubscribe, ValidationResult, ValidationSchema, WatchListener, createDebouncedSync, createFormSection, createFormStore, deepEqual, defaultNormalize, diffPaths, getAtPath, isPathWithin, mergeAtRoot, setAtPath, validateValues };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { F as FormValues, d as FormStore, a as FieldPath, U as Unsubscribe, W as WatchListener, c as ValidationSchema, b as ValidationResult } from './validation-gIT3Xn9h.js';
|
|
2
|
+
export { e as FieldRules, f as FormLevelValidator, S as StoreListener, g as ValidationContext, V as Validator } from './validation-gIT3Xn9h.js';
|
|
3
|
+
import { S as SyncTarget, a as DebouncedSyncOptions, D as DebouncedSync, N as Normalizer } from './field-CLlwd7BC.js';
|
|
4
|
+
export { F as FieldOptions, b as NormalizeContext, R as RegisteredField } from './field-CLlwd7BC.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a framework-agnostic form store.
|
|
8
|
+
*
|
|
9
|
+
* This file must never import React. It's the piece every other package
|
|
10
|
+
* (react, validators, file, array) builds on top of, and it needs to stay
|
|
11
|
+
* independently unit-testable and usable outside React entirely (see
|
|
12
|
+
* roadmap section 7).
|
|
13
|
+
*
|
|
14
|
+
* Design notes (documented here because the "why" matters for anyone
|
|
15
|
+
* extending this later):
|
|
16
|
+
*
|
|
17
|
+
* - Values are plain objects, updated immutably (see `paths.ts`), so
|
|
18
|
+
* `getValues()` always returns a plain, serializable snapshot — no
|
|
19
|
+
* proxies, no framework-specific wrappers.
|
|
20
|
+
* - The store does not itself decide who re-renders. It just computes
|
|
21
|
+
* *which dot-paths changed* on every commit and hands that list to every
|
|
22
|
+
* subscriber. The React binding (`useForm`, in the `react` entry point)
|
|
23
|
+
* is what turns "did my path change?" into a re-render decision, via
|
|
24
|
+
* `useSyncExternalStore`. This split is what avoids a Context-based
|
|
25
|
+
* design, where every consumer re-renders on every change regardless of
|
|
26
|
+
* which field they read.
|
|
27
|
+
*/
|
|
28
|
+
declare function createFormStore<TValues extends FormValues = FormValues>(initialValues: TValues): FormStore<TValues>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A section-scoped listener receives dot-paths already relative to the
|
|
32
|
+
* section (the section's own key prefix is stripped before delivery).
|
|
33
|
+
*/
|
|
34
|
+
type SectionListener = (changedPaths: readonly FieldPath[]) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Members are declared with method syntax (see the note on `FormStore` for
|
|
37
|
+
* why — property-syntax function members break structural assignability of
|
|
38
|
+
* typed sections under `strictFunctionTypes`).
|
|
39
|
+
*/
|
|
40
|
+
interface FormSection<TSectionValues extends FormValues = FormValues> {
|
|
41
|
+
/** The dot-path key this section is scoped to on the parent store. */
|
|
42
|
+
readonly key: string;
|
|
43
|
+
/** Current values for just this section, as a plain object. */
|
|
44
|
+
getValues(): TSectionValues;
|
|
45
|
+
/** Reads a value at a path relative to this section. */
|
|
46
|
+
getValue(relativePath: FieldPath): unknown;
|
|
47
|
+
/** Writes a value at a path relative to this section. */
|
|
48
|
+
setValue(relativePath: FieldPath, value: unknown): void;
|
|
49
|
+
/** Shallow-merges a partial object into this section's values. */
|
|
50
|
+
setValues(partial: Partial<TSectionValues>): void;
|
|
51
|
+
/** Resets this section back to its slice of the store's initial values. */
|
|
52
|
+
reset(): void;
|
|
53
|
+
/**
|
|
54
|
+
* The slice of the parent store's initial values this section owns (i.e.
|
|
55
|
+
* what `reset()` restores to). Present so a section satisfies the same
|
|
56
|
+
* structural surface as a store — required for `SyncTarget` composition
|
|
57
|
+
* (`createDebouncedSync(section)`, `useForm(section)`, ...).
|
|
58
|
+
*/
|
|
59
|
+
getInitialValues(): TSectionValues;
|
|
60
|
+
/** Subscribes to changes within this section only (paths are relative). */
|
|
61
|
+
subscribe(listener: SectionListener): Unsubscribe;
|
|
62
|
+
/**
|
|
63
|
+
* Observes one section-relative path (including its subtree) across
|
|
64
|
+
* changes, invoking `listener(value, previousValue)` only when the value
|
|
65
|
+
* actually changed (deep equality). Returns an unsubscribe function.
|
|
66
|
+
*/
|
|
67
|
+
watch(relativePath: FieldPath, listener: WatchListener): Unsubscribe;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wraps a slice of a FormStore (identified by a top-level or dot-path key)
|
|
72
|
+
* as an independently usable FormSection.
|
|
73
|
+
*
|
|
74
|
+
* This is the primitive behind multi-section ERP-style forms: each section
|
|
75
|
+
* of a form (e.g. "employeeInfo", "jobInfo", "bankInfo") can be built,
|
|
76
|
+
* tested, and reasoned about as if it owned its own store, while every
|
|
77
|
+
* write actually lands on the shared parent store — so the parent always
|
|
78
|
+
* has the complete, merged plain object (see roadmap section 8).
|
|
79
|
+
*
|
|
80
|
+
* Framework-agnostic: no React import here either. `useForm(store, {
|
|
81
|
+
* section })` (in the react entry point) is a thin hook wrapper around this.
|
|
82
|
+
*/
|
|
83
|
+
declare function createFormSection<TSectionValues extends FormValues = FormValues>(store: FormStore<FormValues>, key: string): FormSection<TSectionValues>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One debounced-sync wrapper for ANY target — a whole FormStore, a section
|
|
87
|
+
* slice, or an array row's object slice (all are SyncTargets, so there is
|
|
88
|
+
* deliberately no separate "array" vs "object" implementation).
|
|
89
|
+
*
|
|
90
|
+
* Why this exists (the gap it closes):
|
|
91
|
+
*
|
|
92
|
+
* - Phase 1's store commits synchronously on every `setValue` — during fast
|
|
93
|
+
* typing that is one diff+notify per keystroke hitting the parent store
|
|
94
|
+
* and every subscriber of it. With this wrapper in front, the target sees
|
|
95
|
+
* exactly ONE commit per quiet period, no matter how many writes happened.
|
|
96
|
+
* - The commit itself is transactional: the whole pending batch is applied
|
|
97
|
+
* to a staged copy of the target's values and handed over via a single
|
|
98
|
+
* `setValues`, which the store turns into one diff + one notify. Multiple
|
|
99
|
+
* sections syncing through their own wrappers therefore also stop
|
|
100
|
+
* fighting over the parent on every keystroke.
|
|
101
|
+
* - Reads are read-through and cached: `getValues`/`getValue` include the
|
|
102
|
+
* buffered writes, so a UI rendering from the wrapper shows what the user
|
|
103
|
+
* typed immediately — the debounce only delays the *parent commit*, not
|
|
104
|
+
* the visible state. The snapshot caching also makes the wrapper safe to
|
|
105
|
+
* hand to `useSyncExternalStore` directly.
|
|
106
|
+
*
|
|
107
|
+
* Semantics worth knowing:
|
|
108
|
+
*
|
|
109
|
+
* - Trailing-edge debounce: each buffered write restarts the timer.
|
|
110
|
+
* - Last write per path wins (a Map keyed by path).
|
|
111
|
+
* - `flush()` commits immediately (use on blur/submit); `cancel()` drops.
|
|
112
|
+
* - Writes arriving while a flush is in flight commit straight through, so
|
|
113
|
+
* nothing triggered synchronously by the flush notification is ever lost.
|
|
114
|
+
* - Wrapper subscribers hear about buffered writes immediately and about
|
|
115
|
+
* external target changes as they happen; the wrapper's own flush is NOT
|
|
116
|
+
* re-announced (it was already announced when buffered, and the values
|
|
117
|
+
* did not change at that point).
|
|
118
|
+
*/
|
|
119
|
+
declare function createDebouncedSync<TValues extends FormValues = FormValues>(target: SyncTarget<TValues>, options?: DebouncedSyncOptions): DebouncedSync<TValues>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Runs a validation schema against a values object and returns every
|
|
123
|
+
* error, keyed by field path.
|
|
124
|
+
*
|
|
125
|
+
* Framework-agnostic and pure: no store, no React, no timers. `useForm`
|
|
126
|
+
* calls this on every values change; anything else (a submit handler, a
|
|
127
|
+
* draft-save guard, a section component) can call it directly with the
|
|
128
|
+
* same schema.
|
|
129
|
+
*
|
|
130
|
+
* Semantics:
|
|
131
|
+
* - Field rules run first, per path, in schema key order; the first
|
|
132
|
+
* failing rule's message wins for that path.
|
|
133
|
+
* - Form-level validators run after, and their errors only fill paths
|
|
134
|
+
* that don't already have a field-level error (the per-field message is
|
|
135
|
+
* the more specific one).
|
|
136
|
+
* - Nested paths ("employee.firstName") are resolved with `getAtPath`,
|
|
137
|
+
* consistent with how the store reads values.
|
|
138
|
+
*/
|
|
139
|
+
declare function validateValues<TValues extends FormValues = FormValues>(values: TValues, schema: ValidationSchema): ValidationResult;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The default normalizer. Covers the shapes that come up repeatedly across
|
|
143
|
+
* hand-written ERP form components:
|
|
144
|
+
*
|
|
145
|
+
* - native DOM change events (checkbox vs. everything else)
|
|
146
|
+
* - `Date` objects (from date pickers) — passed through as-is; callers who
|
|
147
|
+
* need a serialized string should supply a custom normalizer, since the
|
|
148
|
+
* right format is app-specific (see roadmap section 13: normalization
|
|
149
|
+
* decisions must be explicit, not silently opinionated)
|
|
150
|
+
* - arrays of `{ value }` option objects (multi-selects)
|
|
151
|
+
* - single `{ value }` option objects (single-selects)
|
|
152
|
+
* - plain primitives, passed through unchanged
|
|
153
|
+
*
|
|
154
|
+
* This intentionally does not know about any specific UI library. A
|
|
155
|
+
* component whose change shape doesn't match one of the above should be
|
|
156
|
+
* wired with a custom `normalize` function via `FieldOptions`.
|
|
157
|
+
*/
|
|
158
|
+
declare const defaultNormalize: Normalizer;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Structural equality for plain JSON-like form values (objects, arrays,
|
|
162
|
+
* primitives). Non-plain values (Date, File, custom classes) fall back to
|
|
163
|
+
* `Object.is` rather than field-by-field inspection — sufficient for dirty
|
|
164
|
+
* tracking, since those values are typically replaced wholesale rather than
|
|
165
|
+
* mutated in place. Revisit only if a real use case needs otherwise.
|
|
166
|
+
*/
|
|
167
|
+
declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
168
|
+
|
|
169
|
+
declare function getAtPath(source: unknown, path: string): unknown;
|
|
170
|
+
/**
|
|
171
|
+
* Returns a new object with `value` written at `path`, cloning only the
|
|
172
|
+
* objects along the path (structural sharing everywhere else). This is what
|
|
173
|
+
* lets subscribers cheaply detect "did the branch I care about change?" via
|
|
174
|
+
* reference equality, without deep-cloning the whole form on every keystroke.
|
|
175
|
+
*/
|
|
176
|
+
declare function setAtPath<T extends Record<string, unknown>>(source: T, path: string, value: unknown): T;
|
|
177
|
+
/**
|
|
178
|
+
* Shallow-merges `partial` into `source` at the top level, one key at a
|
|
179
|
+
* time, reusing setAtPath so each key's structural-sharing behavior stays
|
|
180
|
+
* consistent with single-field writes.
|
|
181
|
+
*/
|
|
182
|
+
declare function mergeAtRoot<T extends Record<string, unknown>>(source: T, partial: Partial<T>): T;
|
|
183
|
+
/**
|
|
184
|
+
* Returns every dot-path whose leaf value differs (by reference, via
|
|
185
|
+
* Object.is) between `prev` and `next`, walking both objects together.
|
|
186
|
+
* Used to compute exactly which paths to notify subscribers about.
|
|
187
|
+
*/
|
|
188
|
+
declare function diffPaths(prev: unknown, next: unknown, basePath?: string, seen?: Set<string>): string[];
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Whether a change reported at `changedPath` could have affected the value
|
|
192
|
+
* at `watchedPath`. Three cases matter:
|
|
193
|
+
*
|
|
194
|
+
* - exact match (`changedPath === watchedPath`)
|
|
195
|
+
* - the change is *inside* the watched subtree (`employee.name` changes
|
|
196
|
+
* when watching `employee`)
|
|
197
|
+
* - the watched path sits *inside* the changed subtree — an ancestor was
|
|
198
|
+
* replaced wholesale (e.g. `store.setValue("employee", {...})`), so the
|
|
199
|
+
* value at the watched path may or may not have changed; the watcher
|
|
200
|
+
* still needs to check
|
|
201
|
+
*
|
|
202
|
+
* An empty changed path means "everything under here was replaced" (how a
|
|
203
|
+
* section sees `store.setValue(sectionKey, {...})`), so it matches any
|
|
204
|
+
* watch. An empty watched path is a whole-scope watch and matches every
|
|
205
|
+
* change.
|
|
206
|
+
*/
|
|
207
|
+
declare function isPathWithin(changedPath: string, watchedPath: string): boolean;
|
|
208
|
+
|
|
209
|
+
export { DebouncedSync, DebouncedSyncOptions, FieldPath, type FormSection, FormStore, FormValues, Normalizer, type SectionListener, SyncTarget, Unsubscribe, ValidationResult, ValidationSchema, WatchListener, createDebouncedSync, createFormSection, createFormStore, deepEqual, defaultNormalize, diffPaths, getAtPath, isPathWithin, mergeAtRoot, setAtPath, validateValues };
|