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.
@@ -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 };
@@ -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 };