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/dist/react.cjs ADDED
@@ -0,0 +1,487 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // src/react/hooks/useForm.ts
6
+
7
+ // src/core/utils/paths.ts
8
+ function splitPath(path) {
9
+ return path.split(".").filter(Boolean);
10
+ }
11
+ function getAtPath(source, path) {
12
+ const segments = splitPath(path);
13
+ let current = source;
14
+ for (const segment of segments) {
15
+ if (current === null || typeof current !== "object") {
16
+ return void 0;
17
+ }
18
+ current = current[segment];
19
+ }
20
+ return current;
21
+ }
22
+ function setAtPath(source, path, value) {
23
+ const segments = splitPath(path);
24
+ if (segments.length === 0) return source;
25
+ const [head, ...rest] = segments;
26
+ if (rest.length === 0) {
27
+ if (Object.is(source[head], value)) {
28
+ return source;
29
+ }
30
+ return { ...source, [head]: value };
31
+ }
32
+ const currentChild = source[head];
33
+ const childSource = currentChild !== null && typeof currentChild === "object" ? currentChild : {};
34
+ const nextChild = setAtPath(childSource, rest.join("."), value);
35
+ if (Object.is(currentChild, nextChild)) {
36
+ return source;
37
+ }
38
+ return { ...source, [head]: nextChild };
39
+ }
40
+ function diffPaths(prev, next, basePath = "", seen = /* @__PURE__ */ new Set()) {
41
+ if (Object.is(prev, next)) {
42
+ return [];
43
+ }
44
+ const prevIsObject = prev !== null && typeof prev === "object" && !Array.isArray(prev);
45
+ const nextIsObject = next !== null && typeof next === "object" && !Array.isArray(next);
46
+ if (!prevIsObject || !nextIsObject) {
47
+ return basePath ? [basePath] : [];
48
+ }
49
+ const keys = /* @__PURE__ */ new Set([
50
+ ...Object.keys(prev),
51
+ ...Object.keys(next)
52
+ ]);
53
+ const changed = [];
54
+ for (const key of keys) {
55
+ const childPath = basePath ? `${basePath}.${key}` : key;
56
+ if (seen.has(childPath)) continue;
57
+ seen.add(childPath);
58
+ changed.push(
59
+ ...diffPaths(
60
+ prev[key],
61
+ next[key],
62
+ childPath,
63
+ seen
64
+ )
65
+ );
66
+ }
67
+ return changed;
68
+ }
69
+
70
+ // src/core/utils/deepEqual.ts
71
+ function deepEqual(a, b) {
72
+ if (Object.is(a, b)) return true;
73
+ if (Array.isArray(a) && Array.isArray(b)) {
74
+ if (a.length !== b.length) return false;
75
+ return a.every((item, index) => deepEqual(item, b[index]));
76
+ }
77
+ const aIsPlainObject = typeof a === "object" && a !== null && !Array.isArray(a) && a.constructor === Object;
78
+ const bIsPlainObject = typeof b === "object" && b !== null && !Array.isArray(b) && b.constructor === Object;
79
+ if (aIsPlainObject && bIsPlainObject) {
80
+ const aKeys = Object.keys(a);
81
+ const bKeys = Object.keys(b);
82
+ if (aKeys.length !== bKeys.length) return false;
83
+ return aKeys.every(
84
+ (key) => deepEqual(a[key], b[key])
85
+ );
86
+ }
87
+ return false;
88
+ }
89
+
90
+ // src/core/utils/watch.ts
91
+ function isPathWithin(changedPath, watchedPath) {
92
+ if (changedPath === "" || watchedPath === "") return true;
93
+ return changedPath === watchedPath || changedPath.startsWith(`${watchedPath}.`) || watchedPath.startsWith(`${changedPath}.`);
94
+ }
95
+ function watchPath(subscribe, readValue, path, listener) {
96
+ let last = readValue();
97
+ return subscribe((changedPaths) => {
98
+ if (!changedPaths.some((changed) => isPathWithin(changed, path))) return;
99
+ const next = readValue();
100
+ if (deepEqual(last, next)) {
101
+ last = next;
102
+ return;
103
+ }
104
+ const previous = last;
105
+ last = next;
106
+ listener(next, previous);
107
+ });
108
+ }
109
+
110
+ // src/core/store/createFormSection.ts
111
+ function createFormSection(store, key) {
112
+ const prefix = `${key}.`;
113
+ const toRelative = (path) => {
114
+ if (path === key) return "";
115
+ if (path.startsWith(prefix)) return path.slice(prefix.length);
116
+ return null;
117
+ };
118
+ const toAbsolute = (relativePath) => relativePath ? `${key}.${relativePath}` : key;
119
+ return {
120
+ key,
121
+ getValues: () => getAtPath(store.getValues(), key) ?? {},
122
+ getValue: (relativePath) => getAtPath(store.getValues(), toAbsolute(relativePath)),
123
+ setValue: (relativePath, value) => {
124
+ store.setValue(toAbsolute(relativePath), value);
125
+ },
126
+ setValues: (partial) => {
127
+ const current = getAtPath(store.getValues(), key) ?? {};
128
+ store.setValue(key, { ...current, ...partial });
129
+ },
130
+ reset: () => {
131
+ const baseline = getAtPath(store.getInitialValues(), key) ?? {};
132
+ store.setValue(key, baseline);
133
+ },
134
+ getInitialValues: () => getAtPath(store.getInitialValues(), key) ?? {},
135
+ subscribe: (listener) => {
136
+ return store.subscribe((changedPaths) => {
137
+ const relevant = [];
138
+ for (const path of changedPaths) {
139
+ const relative = toRelative(path);
140
+ if (relative !== null) relevant.push(relative);
141
+ }
142
+ if (relevant.length > 0) listener(relevant);
143
+ });
144
+ },
145
+ /**
146
+ * Same semantics as `FormStore.watch`, but with section-relative paths.
147
+ * Also fires when the section's slice is replaced wholesale on the
148
+ * parent (`store.setValue(key, {...})` arrives here as a relative ""
149
+ * change) and the watched value actually changed as a result.
150
+ */
151
+ watch: (relativePath, listener) => watchPath(
152
+ (cb) => store.subscribe((changedPaths) => {
153
+ const relevant = [];
154
+ for (const path of changedPaths) {
155
+ const relative = toRelative(path);
156
+ if (relative !== null) relevant.push(relative);
157
+ }
158
+ if (relevant.length > 0) cb(relevant);
159
+ }),
160
+ () => getAtPath(store.getValues(), toAbsolute(relativePath)),
161
+ relativePath,
162
+ listener
163
+ )
164
+ };
165
+ }
166
+
167
+ // src/core/validation/validateValues.ts
168
+ function validateValues(values, schema) {
169
+ const errors = {};
170
+ const fields = schema.fields ?? {};
171
+ for (const path of Object.keys(fields)) {
172
+ const rules = fields[path];
173
+ if (!rules) continue;
174
+ const list = Array.isArray(rules) ? rules : [rules];
175
+ const value = getAtPath(values, path);
176
+ for (const rule of list) {
177
+ const error = rule(value, { path, values });
178
+ if (error) {
179
+ errors[path] = error;
180
+ break;
181
+ }
182
+ }
183
+ }
184
+ const formRules = schema.form;
185
+ if (formRules) {
186
+ const list = Array.isArray(formRules) ? formRules : [formRules];
187
+ for (const rule of list) {
188
+ const formErrors = rule(values);
189
+ if (!formErrors) continue;
190
+ for (const path of Object.keys(formErrors)) {
191
+ const message = formErrors[path];
192
+ if (message && errors[path] === void 0) {
193
+ errors[path] = message;
194
+ }
195
+ }
196
+ }
197
+ }
198
+ return { errors, isValid: Object.keys(errors).length === 0 };
199
+ }
200
+
201
+ // src/core/utils/normalize.ts
202
+ function isEvent(value) {
203
+ return typeof value === "object" && value !== null && "target" in value && typeof value.target === "object";
204
+ }
205
+ function isOptionLike(value) {
206
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && "value" in value;
207
+ }
208
+ var defaultNormalize = (input, _context) => {
209
+ if (isEvent(input)) {
210
+ const target = input.target;
211
+ if (target.type === "checkbox") {
212
+ return target.checked;
213
+ }
214
+ return target.value;
215
+ }
216
+ if (Array.isArray(input)) {
217
+ return input.map((item) => isOptionLike(item) ? item.value : item);
218
+ }
219
+ if (input instanceof Date) {
220
+ return input;
221
+ }
222
+ if (isOptionLike(input)) {
223
+ return input.value;
224
+ }
225
+ return input;
226
+ };
227
+
228
+ // src/react/hooks/useForm.ts
229
+ var EMPTY_VALIDATION_RESULT = { errors: {}, isValid: true };
230
+ function useForm(target, options = {}) {
231
+ const { validation } = options;
232
+ const scoped = react.useMemo(() => {
233
+ return options.section ? createFormSection(target, options.section) : target;
234
+ }, [target, options.section]);
235
+ const getSnapshot = react.useCallback(() => scoped.getValues(), [scoped]);
236
+ const subscribeToTarget = react.useCallback(
237
+ (onStoreChange) => scoped.subscribe(() => onStoreChange()),
238
+ [scoped]
239
+ );
240
+ const values = react.useSyncExternalStore(subscribeToTarget, getSnapshot, getSnapshot);
241
+ const scopedRef = react.useRef(scoped);
242
+ const initialRef = react.useRef(values);
243
+ if (scopedRef.current !== scoped) {
244
+ scopedRef.current = scoped;
245
+ initialRef.current = values;
246
+ }
247
+ const [touched, setTouched] = react.useState({});
248
+ const markTouched = react.useCallback((path) => {
249
+ setTouched((prev) => prev[path] ? prev : { ...prev, [path]: true });
250
+ }, []);
251
+ const setValue = react.useCallback(
252
+ (path, value) => scoped.setValue(path, value),
253
+ [scoped]
254
+ );
255
+ const setValues = react.useCallback(
256
+ (partial) => scoped.setValues(partial),
257
+ [scoped]
258
+ );
259
+ const reset = react.useCallback(() => {
260
+ scoped.reset();
261
+ initialRef.current = scoped.getValues();
262
+ setTouched({});
263
+ }, [scoped]);
264
+ const registerField = react.useCallback(
265
+ (path, fieldOptions = {}) => {
266
+ const normalize = fieldOptions.normalize ?? defaultNormalize;
267
+ const rawValue = getAtPath(values, path);
268
+ const value = rawValue === void 0 ? fieldOptions.defaultValue : rawValue;
269
+ return {
270
+ name: path,
271
+ value,
272
+ onChange: (input) => {
273
+ scoped.setValue(path, normalize(input, { path }));
274
+ },
275
+ onBlur: () => markTouched(path)
276
+ };
277
+ },
278
+ [values, scoped, markTouched]
279
+ );
280
+ const isDirty = react.useMemo(() => !deepEqual(values, initialRef.current), [values]);
281
+ const { errors, isValid } = react.useMemo(
282
+ () => validation ? validateValues(values, validation) : EMPTY_VALIDATION_RESULT,
283
+ [values, validation]
284
+ );
285
+ const validate = react.useCallback(() => {
286
+ return validateValues(scoped.getValues(), validation ?? {});
287
+ }, [scoped, validation]);
288
+ return {
289
+ values,
290
+ touched,
291
+ isDirty,
292
+ errors,
293
+ isValid,
294
+ validate,
295
+ getValue: (path) => getAtPath(values, path),
296
+ setValue,
297
+ setValues,
298
+ registerField,
299
+ reset
300
+ };
301
+ }
302
+ function useFormField(target, path, options = {}) {
303
+ const getSnapshot = react.useCallback(() => target.getValue(path), [target, path]);
304
+ const subscribe = react.useCallback(
305
+ (onChange) => target.subscribe(() => onChange()),
306
+ [target, path]
307
+ );
308
+ const rawValue = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
309
+ const value = rawValue === void 0 ? options.defaultValue : rawValue;
310
+ const normalize = options.normalize ?? defaultNormalize;
311
+ return {
312
+ name: path,
313
+ value,
314
+ onChange: (input) => {
315
+ target.setValue(path, normalize(input, { path }));
316
+ },
317
+ onBlur: () => {
318
+ }
319
+ };
320
+ }
321
+
322
+ // src/core/store/createDebouncedSync.ts
323
+ var DEFAULT_DELAY_MS = 300;
324
+ function createDebouncedSync(target, options = {}) {
325
+ const delay = options.delay ?? DEFAULT_DELAY_MS;
326
+ let timer = null;
327
+ let flushing = false;
328
+ let pending = /* @__PURE__ */ new Map();
329
+ let effectiveCache = null;
330
+ const listeners = /* @__PURE__ */ new Set();
331
+ const invalidateEffective = () => {
332
+ effectiveCache = null;
333
+ };
334
+ const effectiveValues = () => {
335
+ if (effectiveCache !== null) return effectiveCache;
336
+ if (pending.size === 0) return target.getValues();
337
+ let next = target.getValues();
338
+ for (const [path, value] of pending) {
339
+ next = setAtPath(next, path, value);
340
+ }
341
+ effectiveCache = next;
342
+ return effectiveCache;
343
+ };
344
+ const notify = (changedPaths) => {
345
+ if (changedPaths.length === 0) return;
346
+ for (const listener of listeners) {
347
+ listener(changedPaths);
348
+ }
349
+ };
350
+ const cancel = () => {
351
+ if (timer !== null) {
352
+ clearTimeout(timer);
353
+ timer = null;
354
+ }
355
+ pending = /* @__PURE__ */ new Map();
356
+ invalidateEffective();
357
+ };
358
+ const flush = () => {
359
+ if (timer !== null) {
360
+ clearTimeout(timer);
361
+ timer = null;
362
+ }
363
+ if (flushing || pending.size === 0) return;
364
+ flushing = true;
365
+ const batch = pending;
366
+ pending = /* @__PURE__ */ new Map();
367
+ invalidateEffective();
368
+ try {
369
+ let staged = target.getValues();
370
+ for (const [path, value] of batch) {
371
+ staged = setAtPath(staged, path, value);
372
+ }
373
+ target.setValues(staged);
374
+ } finally {
375
+ flushing = false;
376
+ }
377
+ };
378
+ const schedule = () => {
379
+ if (timer !== null) clearTimeout(timer);
380
+ timer = setTimeout(() => {
381
+ timer = null;
382
+ flush();
383
+ }, delay);
384
+ };
385
+ const bufferWrite = (path, value) => {
386
+ if (flushing) {
387
+ target.setValue(path, value);
388
+ return;
389
+ }
390
+ const before = effectiveValues();
391
+ pending.set(path, value);
392
+ invalidateEffective();
393
+ notify(diffPaths(before, effectiveValues()));
394
+ schedule();
395
+ };
396
+ const setValues = (partial) => {
397
+ const keys = Object.keys(partial);
398
+ if (keys.length === 0) return;
399
+ if (flushing) {
400
+ target.setValues(partial);
401
+ return;
402
+ }
403
+ const before = effectiveValues();
404
+ for (const key of keys) {
405
+ pending.set(key, partial[key]);
406
+ }
407
+ invalidateEffective();
408
+ notify(diffPaths(before, effectiveValues()));
409
+ schedule();
410
+ };
411
+ const subscribe = (listener) => {
412
+ listeners.add(listener);
413
+ return () => {
414
+ listeners.delete(listener);
415
+ };
416
+ };
417
+ target.subscribe((changedPaths) => {
418
+ invalidateEffective();
419
+ if (flushing) return;
420
+ notify(changedPaths);
421
+ });
422
+ const watch = (path, listener) => watchPath(
423
+ subscribe,
424
+ () => getAtPath(effectiveValues(), path),
425
+ path,
426
+ listener
427
+ );
428
+ return {
429
+ delay,
430
+ get pendingCount() {
431
+ return pending.size;
432
+ },
433
+ getValues: () => effectiveValues(),
434
+ getValue: (path) => getAtPath(effectiveValues(), path),
435
+ setValue: (path, value) => bufferWrite(path, value),
436
+ setValues,
437
+ reset: (nextInitialValues) => {
438
+ cancel();
439
+ target.reset(nextInitialValues);
440
+ },
441
+ // Baseline semantics, delegated straight through: what `reset()` on
442
+ // this wrapper restores to is whatever the wrapped target considers
443
+ // its initial values. Buffered writes are intentionally excluded —
444
+ // they are uncommitted edits, not a new baseline.
445
+ getInitialValues: () => target.getInitialValues(),
446
+ subscribe,
447
+ watch,
448
+ flush: () => flush(),
449
+ cancel: () => cancel()
450
+ };
451
+ }
452
+
453
+ // src/react/hooks/useDebouncedSync.ts
454
+ function useDebouncedSync(target, options = {}) {
455
+ const { delay, flushOnUnmount = true } = options;
456
+ const sync = react.useMemo(
457
+ () => createDebouncedSync(target, delay === void 0 ? {} : { delay }),
458
+ [target, delay]
459
+ );
460
+ react.useEffect(() => {
461
+ return () => {
462
+ if (flushOnUnmount) {
463
+ sync.flush();
464
+ } else {
465
+ sync.cancel();
466
+ }
467
+ };
468
+ }, [sync, flushOnUnmount]);
469
+ return sync;
470
+ }
471
+ function useWatch(target, path, listener) {
472
+ const listenerRef = react.useRef(listener);
473
+ listenerRef.current = listener;
474
+ react.useEffect(() => {
475
+ return target.watch(
476
+ path,
477
+ (value, previousValue) => listenerRef.current(value, previousValue)
478
+ );
479
+ }, [target, path]);
480
+ }
481
+
482
+ exports.useDebouncedSync = useDebouncedSync;
483
+ exports.useForm = useForm;
484
+ exports.useFormField = useFormField;
485
+ exports.useWatch = useWatch;
486
+ //# sourceMappingURL=react.cjs.map
487
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/utils/paths.ts","../src/core/utils/deepEqual.ts","../src/core/utils/watch.ts","../src/core/store/createFormSection.ts","../src/core/validation/validateValues.ts","../src/core/utils/normalize.ts","../src/react/hooks/useForm.ts","../src/react/hooks/useFormField.ts","../src/core/store/createDebouncedSync.ts","../src/react/hooks/useDebouncedSync.ts","../src/react/hooks/useWatch.ts"],"names":["useMemo","useCallback","useSyncExternalStore","useRef","useState","useEffect"],"mappings":"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","file":"react.cjs","sourcesContent":["/**\r\n * Minimal, dependency-free dot-path helpers.\r\n *\r\n * Deliberately not using lodash here (get/set/isEqual) even though the\r\n * reference application relies on it — the whole store is small enough that\r\n * shipping ~30 lines here beats pulling in a runtime dependency for a\r\n * library meant to stay near-zero-dependency (see roadmap: \"Dependency\r\n * Philosophy\").\r\n */\r\n\r\nexport function splitPath(path: string): string[] {\r\n return path.split(\".\").filter(Boolean);\r\n}\r\n\r\nexport function getAtPath(source: unknown, path: string): unknown {\r\n const segments = splitPath(path);\r\n let current: unknown = source;\r\n for (const segment of segments) {\r\n if (current === null || typeof current !== \"object\") {\r\n return undefined;\r\n }\r\n current = (current as Record<string, unknown>)[segment];\r\n }\r\n return current;\r\n}\r\n\r\n/**\r\n * Returns a new object with `value` written at `path`, cloning only the\r\n * objects along the path (structural sharing everywhere else). This is what\r\n * lets subscribers cheaply detect \"did the branch I care about change?\" via\r\n * reference equality, without deep-cloning the whole form on every keystroke.\r\n */\r\nexport function setAtPath<T extends Record<string, unknown>>(\r\n source: T,\r\n path: string,\r\n value: unknown,\r\n): T {\r\n const segments = splitPath(path);\r\n if (segments.length === 0) return source;\r\n\r\n const [head, ...rest] = segments as [string, ...string[]];\r\n\r\n if (rest.length === 0) {\r\n if (Object.is((source as Record<string, unknown>)[head], value)) {\r\n return source;\r\n }\r\n return { ...source, [head]: value };\r\n }\r\n\r\n const currentChild = (source as Record<string, unknown>)[head];\r\n const childSource =\r\n currentChild !== null && typeof currentChild === \"object\"\r\n ? (currentChild as Record<string, unknown>)\r\n : {};\r\n\r\n const nextChild = setAtPath(childSource, rest.join(\".\"), value);\r\n\r\n if (Object.is(currentChild, nextChild)) {\r\n return source;\r\n }\r\n\r\n return { ...source, [head]: nextChild };\r\n}\r\n\r\n/**\r\n * Shallow-merges `partial` into `source` at the top level, one key at a\r\n * time, reusing setAtPath so each key's structural-sharing behavior stays\r\n * consistent with single-field writes.\r\n */\r\nexport function mergeAtRoot<T extends Record<string, unknown>>(\r\n source: T,\r\n partial: Partial<T>,\r\n): T {\r\n let next: T = source;\r\n for (const key of Object.keys(partial)) {\r\n next = setAtPath(next, key, (partial as Record<string, unknown>)[key]);\r\n }\r\n return next;\r\n}\r\n\r\n/**\r\n * Returns every dot-path whose leaf value differs (by reference, via\r\n * Object.is) between `prev` and `next`, walking both objects together.\r\n * Used to compute exactly which paths to notify subscribers about.\r\n */\r\nexport function diffPaths(\r\n prev: unknown,\r\n next: unknown,\r\n basePath = \"\",\r\n seen: Set<string> = new Set(),\r\n): string[] {\r\n if (Object.is(prev, next)) {\r\n return [];\r\n }\r\n\r\n const prevIsObject = prev !== null && typeof prev === \"object\" && !Array.isArray(prev);\r\n const nextIsObject = next !== null && typeof next === \"object\" && !Array.isArray(next);\r\n\r\n if (!prevIsObject || !nextIsObject) {\r\n return basePath ? [basePath] : [];\r\n }\r\n\r\n const keys = new Set([\r\n ...Object.keys(prev as Record<string, unknown>),\r\n ...Object.keys(next as Record<string, unknown>),\r\n ]);\r\n\r\n const changed: string[] = [];\r\n for (const key of keys) {\r\n const childPath = basePath ? `${basePath}.${key}` : key;\r\n if (seen.has(childPath)) continue;\r\n seen.add(childPath);\r\n changed.push(\r\n ...diffPaths(\r\n (prev as Record<string, unknown>)[key],\r\n (next as Record<string, unknown>)[key],\r\n childPath,\r\n seen,\r\n ),\r\n );\r\n }\r\n return changed;\r\n}\r\n","/**\r\n * Structural equality for plain JSON-like form values (objects, arrays,\r\n * primitives). Non-plain values (Date, File, custom classes) fall back to\r\n * `Object.is` rather than field-by-field inspection — sufficient for dirty\r\n * tracking, since those values are typically replaced wholesale rather than\r\n * mutated in place. Revisit only if a real use case needs otherwise.\r\n */\r\nexport function deepEqual(a: unknown, b: unknown): boolean {\r\n if (Object.is(a, b)) return true;\r\n\r\n if (Array.isArray(a) && Array.isArray(b)) {\r\n if (a.length !== b.length) return false;\r\n return a.every((item, index) => deepEqual(item, b[index]));\r\n }\r\n\r\n const aIsPlainObject =\r\n typeof a === \"object\" && a !== null && !Array.isArray(a) && a.constructor === Object;\r\n const bIsPlainObject =\r\n typeof b === \"object\" && b !== null && !Array.isArray(b) && b.constructor === Object;\r\n\r\n if (aIsPlainObject && bIsPlainObject) {\r\n const aKeys = Object.keys(a as Record<string, unknown>);\r\n const bKeys = Object.keys(b as Record<string, unknown>);\r\n if (aKeys.length !== bKeys.length) return false;\r\n return aKeys.every((key) =>\r\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\r\n );\r\n }\r\n\r\n return false;\r\n}\r\n","import type { Unsubscribe, WatchListener } from \"../types/store\";\r\nimport { deepEqual } from \"./deepEqual\";\r\n\r\n/**\r\n * Whether a change reported at `changedPath` could have affected the value\r\n * at `watchedPath`. Three cases matter:\r\n *\r\n * - exact match (`changedPath === watchedPath`)\r\n * - the change is *inside* the watched subtree (`employee.name` changes\r\n * when watching `employee`)\r\n * - the watched path sits *inside* the changed subtree — an ancestor was\r\n * replaced wholesale (e.g. `store.setValue(\"employee\", {...})`), so the\r\n * value at the watched path may or may not have changed; the watcher\r\n * still needs to check\r\n *\r\n * An empty changed path means \"everything under here was replaced\" (how a\r\n * section sees `store.setValue(sectionKey, {...})`), so it matches any\r\n * watch. An empty watched path is a whole-scope watch and matches every\r\n * change.\r\n */\r\nexport function isPathWithin(changedPath: string, watchedPath: string): boolean {\r\n if (changedPath === \"\" || watchedPath === \"\") return true;\r\n return (\r\n changedPath === watchedPath ||\r\n changedPath.startsWith(`${watchedPath}.`) ||\r\n watchedPath.startsWith(`${changedPath}.`)\r\n );\r\n}\r\n\r\n/**\r\n * Shared implementation of the `watch(path, listener)` primitive used by\r\n * both `FormStore` and `FormSection` (and the debounced-sync wrapper).\r\n *\r\n * The subscriber receives the changed-paths list from whatever target it\r\n * wraps; this helper filters to changes that could affect `path`, re-reads\r\n * the current value, and only invokes the listener when the value *actually\r\n * changed* (deep-equality, consistent with `deepEqual`) — an ancestor being\r\n * replaced with the same leaf values must not fire derived-field logic.\r\n *\r\n * `readValue` is a closure so the current value is always read live — this\r\n * file never holds a reference to the store's internals itself.\r\n */\r\nexport function watchPath(\r\n subscribe: (listener: (changedPaths: readonly string[]) => void) => Unsubscribe,\r\n readValue: () => unknown,\r\n path: string,\r\n listener: WatchListener,\r\n): Unsubscribe {\r\n let last = readValue();\r\n\r\n return subscribe((changedPaths) => {\r\n if (!changedPaths.some((changed) => isPathWithin(changed, path))) return;\r\n\r\n const next = readValue();\r\n if (deepEqual(last, next)) {\r\n last = next;\r\n return;\r\n }\r\n\r\n const previous = last;\r\n last = next;\r\n listener(next, previous);\r\n });\r\n}","import type { FormStore, FormValues } from \"../types/store\";\r\nimport type { FormSection } from \"../types/section\";\r\nimport { getAtPath } from \"../utils/paths\";\r\nimport { watchPath } from \"../utils/watch\";\r\n\r\n/**\r\n * Wraps a slice of a FormStore (identified by a top-level or dot-path key)\r\n * as an independently usable FormSection.\r\n *\r\n * This is the primitive behind multi-section ERP-style forms: each section\r\n * of a form (e.g. \"employeeInfo\", \"jobInfo\", \"bankInfo\") can be built,\r\n * tested, and reasoned about as if it owned its own store, while every\r\n * write actually lands on the shared parent store — so the parent always\r\n * has the complete, merged plain object (see roadmap section 8).\r\n *\r\n * Framework-agnostic: no React import here either. `useForm(store, {\r\n * section })` (in the react entry point) is a thin hook wrapper around this.\r\n */\r\nexport function createFormSection<TSectionValues extends FormValues = FormValues>(\r\n store: FormStore<FormValues>,\r\n key: string,\r\n): FormSection<TSectionValues> {\r\n const prefix = `${key}.`;\r\n\r\n const toRelative = (path: string): string | null => {\r\n if (path === key) return \"\";\r\n if (path.startsWith(prefix)) return path.slice(prefix.length);\r\n return null;\r\n };\r\n\r\n const toAbsolute = (relativePath: string): string =>\r\n relativePath ? `${key}.${relativePath}` : key;\r\n\r\n return {\r\n key,\r\n\r\n getValues: () => (getAtPath(store.getValues(), key) ?? {}) as TSectionValues,\r\n\r\n getValue: (relativePath) => getAtPath(store.getValues(), toAbsolute(relativePath)),\r\n\r\n setValue: (relativePath, value) => {\r\n store.setValue(toAbsolute(relativePath), value);\r\n },\r\n\r\n setValues: (partial) => {\r\n const current = (getAtPath(store.getValues(), key) ?? {}) as TSectionValues;\r\n store.setValue(key, { ...current, ...partial });\r\n },\r\n\r\n reset: () => {\r\n const baseline = getAtPath(store.getInitialValues(), key) ?? {};\r\n store.setValue(key, baseline);\r\n },\r\n\r\n getInitialValues: () =>\r\n (getAtPath(store.getInitialValues(), key) ?? {}) as TSectionValues,\r\n\r\n subscribe: (listener) => {\r\n return store.subscribe((changedPaths) => {\r\n const relevant: string[] = [];\r\n for (const path of changedPaths) {\r\n const relative = toRelative(path);\r\n if (relative !== null) relevant.push(relative);\r\n }\r\n if (relevant.length > 0) listener(relevant);\r\n });\r\n },\r\n\r\n /**\r\n * Same semantics as `FormStore.watch`, but with section-relative paths.\r\n * Also fires when the section's slice is replaced wholesale on the\r\n * parent (`store.setValue(key, {...})` arrives here as a relative \"\"\r\n * change) and the watched value actually changed as a result.\r\n */\r\n watch: (relativePath, listener) =>\r\n watchPath(\r\n (cb) =>\r\n store.subscribe((changedPaths) => {\r\n const relevant: string[] = [];\r\n for (const path of changedPaths) {\r\n const relative = toRelative(path);\r\n if (relative !== null) relevant.push(relative);\r\n }\r\n if (relevant.length > 0) cb(relevant);\r\n }),\r\n () => getAtPath(store.getValues(), toAbsolute(relativePath)),\r\n relativePath,\r\n listener,\r\n ),\r\n };\r\n}\r\n","import type {\r\n FormLevelValidator,\r\n ValidationSchema,\r\n ValidationResult,\r\n Validator,\r\n} from \"../types/validation\";\r\nimport type { FormValues } from \"../types/store\";\r\nimport { getAtPath } from \"../utils/paths\";\r\n\r\n/**\r\n * Runs a validation schema against a values object and returns every\r\n * error, keyed by field path.\r\n *\r\n * Framework-agnostic and pure: no store, no React, no timers. `useForm`\r\n * calls this on every values change; anything else (a submit handler, a\r\n * draft-save guard, a section component) can call it directly with the\r\n * same schema.\r\n *\r\n * Semantics:\r\n * - Field rules run first, per path, in schema key order; the first\r\n * failing rule's message wins for that path.\r\n * - Form-level validators run after, and their errors only fill paths\r\n * that don't already have a field-level error (the per-field message is\r\n * the more specific one).\r\n * - Nested paths (\"employee.firstName\") are resolved with `getAtPath`,\r\n * consistent with how the store reads values.\r\n */\r\nexport function validateValues<TValues extends FormValues = FormValues>(\r\n values: TValues,\r\n schema: ValidationSchema,\r\n): ValidationResult {\r\n const errors: Record<string, string> = {};\r\n\r\n const fields = schema.fields ?? {};\r\n for (const path of Object.keys(fields)) {\r\n const rules = fields[path];\r\n if (!rules) continue;\r\n const list: readonly Validator[] = Array.isArray(rules) ? rules : [rules];\r\n const value = getAtPath(values, path);\r\n for (const rule of list) {\r\n const error = rule(value, { path, values });\r\n if (error) {\r\n errors[path] = error;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n const formRules = schema.form;\r\n if (formRules) {\r\n const list: readonly FormLevelValidator[] = Array.isArray(formRules)\r\n ? formRules\r\n : [formRules];\r\n for (const rule of list) {\r\n const formErrors = rule(values);\r\n if (!formErrors) continue;\r\n for (const path of Object.keys(formErrors)) {\r\n const message = formErrors[path];\r\n if (message && errors[path] === undefined) {\r\n errors[path] = message;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { errors, isValid: Object.keys(errors).length === 0 };\r\n}","import type { NormalizeContext, Normalizer } from \"../types/field\";\r\n\r\nfunction isEvent(value: unknown): value is { target: EventTarget } {\r\n return (\r\n typeof value === \"object\" &&\r\n value !== null &&\r\n \"target\" in value &&\r\n typeof (value as { target?: unknown }).target === \"object\"\r\n );\r\n}\r\n\r\nfunction isOptionLike(value: unknown): value is { value: unknown } {\r\n return (\r\n typeof value === \"object\" &&\r\n value !== null &&\r\n !Array.isArray(value) &&\r\n !(value instanceof Date) &&\r\n \"value\" in value\r\n );\r\n}\r\n\r\n/**\r\n * The default normalizer. Covers the shapes that come up repeatedly across\r\n * hand-written ERP form components:\r\n *\r\n * - native DOM change events (checkbox vs. everything else)\r\n * - `Date` objects (from date pickers) — passed through as-is; callers who\r\n * need a serialized string should supply a custom normalizer, since the\r\n * right format is app-specific (see roadmap section 13: normalization\r\n * decisions must be explicit, not silently opinionated)\r\n * - arrays of `{ value }` option objects (multi-selects)\r\n * - single `{ value }` option objects (single-selects)\r\n * - plain primitives, passed through unchanged\r\n *\r\n * This intentionally does not know about any specific UI library. A\r\n * component whose change shape doesn't match one of the above should be\r\n * wired with a custom `normalize` function via `FieldOptions`.\r\n */\r\nexport const defaultNormalize: Normalizer = (input: unknown, _context: NormalizeContext) => {\r\n if (isEvent(input)) {\r\n const target = input.target as HTMLInputElement;\r\n if (target.type === \"checkbox\") {\r\n return target.checked;\r\n }\r\n return target.value;\r\n }\r\n\r\n if (Array.isArray(input)) {\r\n return input.map((item) => (isOptionLike(item) ? item.value : item));\r\n }\r\n\r\n if (input instanceof Date) {\r\n return input;\r\n }\r\n\r\n if (isOptionLike(input)) {\r\n return input.value;\r\n }\r\n\r\n return input;\r\n};\r\n","import { useCallback, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\r\nimport type { FormStore, FormValues } from \"../../core/types/store\";\r\nimport type { SyncTarget } from \"../../core/types/sync\";\r\nimport type { ValidationSchema, ValidationResult } from \"../../core/types/validation\";\r\nimport { createFormSection } from \"../../core/store/createFormSection\";\r\nimport { validateValues } from \"../../core/validation/validateValues\";\r\nimport { defaultNormalize } from \"../../core/utils/normalize\";\r\nimport { deepEqual } from \"../../core/utils/deepEqual\";\r\nimport { getAtPath } from \"../../core/utils/paths\";\r\nimport type { FieldOptions, Normalizer, RegisteredField } from \"../../core/types/field\";\r\n\r\n/** Shared result for the no-schema case — stable identity across renders. */\r\nconst EMPTY_VALIDATION_RESULT: ValidationResult = { errors: {}, isValid: true };\r\n\r\nexport interface UseFormOptions {\r\n /**\r\n * Scopes this hook instance to a slice of the store (e.g. \"employeeInfo\"),\r\n * mirroring how independent sections of a multi-section ERP form each own\r\n * a key on the parent object. Omit to work with the whole store.\r\n */\r\n section?: string;\r\n\r\n /**\r\n * Validation schema (Phase 2): per-field rules keyed by scope-relative\r\n * dot-path, and/or form-level validators receiving the whole values\r\n * object in scope (for cross-field rules like date ranges and budget\r\n * caps). Omit for no validation (`errors` stays `{}`, `isValid` stays\r\n * `true`).\r\n */\r\n validation?: ValidationSchema;\r\n}\r\n\r\nexport interface FormApi<TValues extends FormValues = FormValues> {\r\n /** Current values in scope (whole store, or just this section). */\r\n values: TValues;\r\n\r\n /** Which field paths (relative to scope) have been blurred at least once. */\r\n touched: Record<string, boolean>;\r\n\r\n /**\r\n * Whether values in scope differ from their value when this hook instance\r\n * first observed them. See the in-source note on `initialRef` below for\r\n * the exact semantics.\r\n */\r\n isDirty: boolean;\r\n\r\n /**\r\n * Validation errors for the current values in scope, keyed by\r\n * scope-relative field path. Recomputed on every values change against\r\n * `options.validation` — and because validation runs over the values this\r\n * hook renders from, a DebouncedSync target yields per-keystroke errors\r\n * from its read-through values, not only after the debounced commit.\r\n * All paths are exposed regardless of touched state; gate display with\r\n * `touched` if you only want to show errors after a field was blurred.\r\n */\r\n errors: Record<string, string>;\r\n /** Whether `errors` is currently empty. Always `true` without a schema. */\r\n isValid: boolean;\r\n\r\n /**\r\n * Runs the validation schema against the target's CURRENT values (re-read\r\n * live, not the render snapshot) and returns the full result — for\r\n * submit-time checks or manual revalidation. Same computation\r\n * `errors`/`isValid` already reflect.\r\n */\r\n validate: () => ValidationResult;\r\n\r\n getValue: (path: string) => unknown;\r\n setValue: (path: string, value: unknown) => void;\r\n setValues: (partial: Partial<TValues>) => void;\r\n\r\n /**\r\n * Produces a `{ name, value, onChange, onBlur }` prop bag for a field,\r\n * normalizing whatever the input hands back via `onChange`. This is the\r\n * direct replacement for a hand-written `handleFieldChange` per component.\r\n */\r\n registerField: <TValue = unknown>(\r\n path: string,\r\n options?: FieldOptions<TValue>,\r\n ) => RegisteredField<TValue>;\r\n\r\n /** Resets values in scope back to baseline and clears touched state. */\r\n reset: () => void;\r\n}\r\n\r\n/**\r\n * React binding over the shared structural target surface — a FormStore, a\r\n * FormSection, or a DebouncedSync wrapper (all satisfy `SyncTarget`\r\n * structurally; that is what makes debounced buffering compose straight\r\n * into this hook).\r\n *\r\n * Subscribes via `useSyncExternalStore`, so this component re-renders on\r\n * any change within its scope (whole store, a section slice, or the sync\r\n * wrapper's read-through values). This\r\n * is the \"whole-section\" usage pattern — the direct replacement for a\r\n * component's local `useState` + manual sync-to-parent — where one\r\n * component renders many fields together, same as most existing ERP form\r\n * sections do today.\r\n *\r\n * For genuinely fine-grained, single-field re-render isolation (a separate\r\n * component per field), see `useFormField` instead — this hook intentionally\r\n * re-renders on any change in scope, matching how these forms are already\r\n * structured, rather than forcing a per-field-component rewrite to adopt it.\r\n */\r\nexport function useForm<TValues extends FormValues = FormValues>(\r\n target: SyncTarget<FormValues>,\r\n options: UseFormOptions = {},\r\n): FormApi<TValues> {\r\n const { validation } = options;\r\n\r\n // `section` scoping only makes sense when the target is the whole store;\r\n // a section of a DebouncedSync would double-prefix paths. The cast is\r\n // the one place the structural surface is narrowed back to FormStore —\r\n // createFormSection needs the store's `watch` primitive.\r\n const scoped = useMemo<SyncTarget<TValues>>(() => {\r\n return (options.section\r\n ? createFormSection(target as FormStore<FormValues>, options.section)\r\n : target) as unknown as SyncTarget<TValues>;\r\n }, [target, options.section]);\r\n\r\n const getSnapshot = useCallback(() => scoped.getValues(), [scoped]);\r\n const subscribeToTarget = useCallback(\r\n (onStoreChange: () => void) => scoped.subscribe(() => onStoreChange()),\r\n [scoped],\r\n );\r\n\r\n const values = useSyncExternalStore(subscribeToTarget, getSnapshot, getSnapshot);\r\n\r\n // Captures the values this hook instance first saw, as the baseline for\r\n // `isDirty`. Recomputed whenever `scoped` itself changes (a new target,\r\n // or a different `section` key) so switching scopes doesn't carry over a\r\n // stale baseline. This is a per-hook-instance notion of \"dirty since I\r\n // started watching,\" not a form-wide \"dirty since the app booted\" — the\r\n // common case in practice, since a form's baseline is whatever it loaded\r\n // with when the component mounted.\r\n const scopedRef = useRef(scoped);\r\n const initialRef = useRef(values);\r\n if (scopedRef.current !== scoped) {\r\n scopedRef.current = scoped;\r\n initialRef.current = values;\r\n }\r\n\r\n const [touched, setTouched] = useState<Record<string, boolean>>({});\r\n\r\n const markTouched = useCallback((path: string) => {\r\n setTouched((prev) => (prev[path] ? prev : { ...prev, [path]: true }));\r\n }, []);\r\n\r\n const setValue = useCallback(\r\n (path: string, value: unknown) => scoped.setValue(path, value),\r\n [scoped],\r\n );\r\n\r\n const setValues = useCallback(\r\n (partial: Partial<TValues>) => scoped.setValues(partial),\r\n [scoped],\r\n );\r\n\r\n const reset = useCallback(() => {\r\n scoped.reset();\r\n initialRef.current = scoped.getValues();\r\n setTouched({});\r\n }, [scoped]);\r\n\r\n const registerField = useCallback(\r\n <TValue = unknown>(\r\n path: string,\r\n fieldOptions: FieldOptions<TValue> = {},\r\n ): RegisteredField<TValue> => {\r\n const normalize: Normalizer<TValue> =\r\n fieldOptions.normalize ?? (defaultNormalize as unknown as Normalizer<TValue>);\r\n const rawValue = getAtPath(values, path);\r\n const value = (rawValue === undefined ? fieldOptions.defaultValue : rawValue) as TValue;\r\n\r\n return {\r\n name: path,\r\n value,\r\n onChange: (input: unknown) => {\r\n scoped.setValue(path, normalize(input, { path }));\r\n },\r\n onBlur: () => markTouched(path),\r\n };\r\n },\r\n [values, scoped, markTouched],\r\n );\r\n\r\n const isDirty = useMemo(() => !deepEqual(values, initialRef.current), [values]);\r\n\r\n // Phase 2: validation runs on every values change, over exactly the\r\n // values this hook renders from — so with a DebouncedSync target, errors\r\n // react to read-through (buffered) values per keystroke, and with a\r\n // section target, schema paths are section-relative. No schema means a\r\n // stable empty result, so consumers can keep `errors` in dependency\r\n // arrays without churn.\r\n const { errors, isValid } = useMemo(\r\n () => (validation ? validateValues(values, validation) : EMPTY_VALIDATION_RESULT),\r\n [values, validation],\r\n );\r\n\r\n const validate = useCallback((): ValidationResult => {\r\n return validateValues(scoped.getValues(), validation ?? {});\r\n }, [scoped, validation]);\r\n\r\n return {\r\n values,\r\n touched,\r\n isDirty,\r\n errors,\r\n isValid,\r\n validate,\r\n getValue: (path: string) => getAtPath(values, path),\r\n setValue,\r\n setValues,\r\n registerField,\r\n reset,\r\n };\r\n}\r\n","import { useCallback, useSyncExternalStore } from \"react\";\r\nimport type { SyncTarget } from \"../../core/types/sync\";\r\nimport { defaultNormalize } from \"../../core/utils/normalize\";\r\nimport type { FieldOptions, Normalizer, RegisteredField } from \"../../core/types/field\";\r\n\r\n/**\r\n * Subscribes a component to exactly one field path.\r\n *\r\n * The target may be a FormStore, a FormSection, or a DebouncedSync wrapper\r\n * — anything satisfying the shared structural SyncTarget surface, which is\r\n * what lets debounced buffering compose into per-field subscriptions.\r\n *\r\n * The subscribe callback here ignores the changed-paths list and always\r\n * asks React to re-check — but `useSyncExternalStore` only actually\r\n * re-renders the component when `getSnapshot()`'s return value differs\r\n * (via `Object.is`) from the last one. Because the store only clones\r\n * objects along the path that changed (see `setAtPath`), an update to an\r\n * unrelated field leaves this field's value referentially identical, so\r\n * React bails out without rendering.\r\n *\r\n * This is the primitive to reach for when a form is large enough that\r\n * isolating re-renders per field (rather than per section, via `useForm`)\r\n * actually matters — e.g. a field array with hundreds of rows.\r\n */\r\nexport function useFormField<TValue = unknown>(\r\n target: SyncTarget,\r\n path: string,\r\n options: FieldOptions<TValue> = {},\r\n): RegisteredField<TValue> {\r\n const getSnapshot = useCallback(() => target.getValue(path), [target, path]);\r\n const subscribe = useCallback(\r\n (onChange: () => void) => target.subscribe(() => onChange()),\r\n [target, path],\r\n );\r\n\r\n const rawValue = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\r\n const value = (rawValue === undefined ? options.defaultValue : rawValue) as TValue;\r\n\r\n const normalize: Normalizer<TValue> =\r\n options.normalize ?? (defaultNormalize as unknown as Normalizer<TValue>);\r\n\r\n return {\r\n name: path,\r\n value,\r\n onChange: (input: unknown) => {\r\n target.setValue(path, normalize(input, { path }));\r\n },\r\n onBlur: () => {\r\n // Touched-state for a field observed in isolation is tracked by the\r\n // caller if needed (e.g. local `useState`) — `useForm`'s shared\r\n // `touched` map is a whole-scope concern by design; duplicating a\r\n // second touched-tracking mechanism here would be exactly the kind\r\n // of speculative feature the roadmap says to avoid until a real case\r\n // needs it.\r\n },\r\n };\r\n}\r\n","import type {\r\n DebouncedSync,\r\n DebouncedSyncOptions,\r\n SyncTarget,\r\n} from \"../types/sync\";\r\nimport type {\r\n FieldPath,\r\n FormValues,\r\n StoreListener,\r\n Unsubscribe,\r\n WatchListener,\r\n} from \"../types/store\";\r\nimport { diffPaths, getAtPath, setAtPath } from \"../utils/paths\";\r\nimport { watchPath } from \"../utils/watch\";\r\n\r\nconst DEFAULT_DELAY_MS = 300;\r\n\r\n/**\r\n * One debounced-sync wrapper for ANY target — a whole FormStore, a section\r\n * slice, or an array row's object slice (all are SyncTargets, so there is\r\n * deliberately no separate \"array\" vs \"object\" implementation).\r\n *\r\n * Why this exists (the gap it closes):\r\n *\r\n * - Phase 1's store commits synchronously on every `setValue` — during fast\r\n * typing that is one diff+notify per keystroke hitting the parent store\r\n * and every subscriber of it. With this wrapper in front, the target sees\r\n * exactly ONE commit per quiet period, no matter how many writes happened.\r\n * - The commit itself is transactional: the whole pending batch is applied\r\n * to a staged copy of the target's values and handed over via a single\r\n * `setValues`, which the store turns into one diff + one notify. Multiple\r\n * sections syncing through their own wrappers therefore also stop\r\n * fighting over the parent on every keystroke.\r\n * - Reads are read-through and cached: `getValues`/`getValue` include the\r\n * buffered writes, so a UI rendering from the wrapper shows what the user\r\n * typed immediately — the debounce only delays the *parent commit*, not\r\n * the visible state. The snapshot caching also makes the wrapper safe to\r\n * hand to `useSyncExternalStore` directly.\r\n *\r\n * Semantics worth knowing:\r\n *\r\n * - Trailing-edge debounce: each buffered write restarts the timer.\r\n * - Last write per path wins (a Map keyed by path).\r\n * - `flush()` commits immediately (use on blur/submit); `cancel()` drops.\r\n * - Writes arriving while a flush is in flight commit straight through, so\r\n * nothing triggered synchronously by the flush notification is ever lost.\r\n * - Wrapper subscribers hear about buffered writes immediately and about\r\n * external target changes as they happen; the wrapper's own flush is NOT\r\n * re-announced (it was already announced when buffered, and the values\r\n * did not change at that point).\r\n */\r\nexport function createDebouncedSync<TValues extends FormValues = FormValues>(\r\n target: SyncTarget<TValues>,\r\n options: DebouncedSyncOptions = {},\r\n): DebouncedSync<TValues> {\r\n const delay = options.delay ?? DEFAULT_DELAY_MS;\r\n\r\n let timer: ReturnType<typeof setTimeout> | null = null;\r\n let flushing = false;\r\n let pending = new Map<FieldPath, unknown>();\r\n let effectiveCache: TValues | null = null;\r\n const listeners = new Set<StoreListener>();\r\n\r\n const invalidateEffective = () => {\r\n effectiveCache = null;\r\n };\r\n\r\n /** Target values with the pending batch applied (read-through, cached). */\r\n const effectiveValues = (): TValues => {\r\n if (effectiveCache !== null) return effectiveCache;\r\n if (pending.size === 0) return target.getValues();\r\n\r\n let next = target.getValues() as unknown as Record<string, unknown>;\r\n for (const [path, value] of pending) {\r\n next = setAtPath(next, path, value);\r\n }\r\n effectiveCache = next as TValues;\r\n return effectiveCache;\r\n };\r\n\r\n const notify = (changedPaths: readonly FieldPath[]) => {\r\n if (changedPaths.length === 0) return;\r\n for (const listener of listeners) {\r\n listener(changedPaths);\r\n }\r\n };\r\n\r\n const cancel = () => {\r\n if (timer !== null) {\r\n clearTimeout(timer);\r\n timer = null;\r\n }\r\n pending = new Map();\r\n invalidateEffective();\r\n };\r\n\r\n const flush = () => {\r\n if (timer !== null) {\r\n clearTimeout(timer);\r\n timer = null;\r\n }\r\n if (flushing || pending.size === 0) return;\r\n\r\n flushing = true;\r\n const batch = pending;\r\n pending = new Map();\r\n invalidateEffective();\r\n try {\r\n // Apply the whole batch onto a staged copy of the target's CURRENT\r\n // values, then hand the complete object over in one `setValues`. For\r\n // a store target that is one commit; for a section target it is one\r\n // `store.setValue(sectionKey, ...)` — one commit either way.\r\n let staged = target.getValues() as unknown as Record<string, unknown>;\r\n for (const [path, value] of batch) {\r\n staged = setAtPath(staged, path, value);\r\n }\r\n target.setValues(staged as Partial<TValues>);\r\n } finally {\r\n flushing = false;\r\n }\r\n };\r\n\r\n const schedule = () => {\r\n if (timer !== null) clearTimeout(timer);\r\n timer = setTimeout(() => {\r\n timer = null;\r\n flush();\r\n }, delay);\r\n };\r\n\r\n const bufferWrite = (path: FieldPath, value: unknown) => {\r\n if (flushing) {\r\n // A write arriving mid-flush (e.g. from a listener triggered by the\r\n // flush notification) must not join the batch already being applied.\r\n // Commit it straight through so it can never be dropped.\r\n target.setValue(path, value);\r\n return;\r\n }\r\n const before = effectiveValues();\r\n pending.set(path, value);\r\n invalidateEffective();\r\n notify(diffPaths(before, effectiveValues()));\r\n schedule();\r\n };\r\n\r\n const setValues = (partial: Partial<TValues>) => {\r\n const keys = Object.keys(partial);\r\n if (keys.length === 0) return;\r\n\r\n if (flushing) {\r\n target.setValues(partial);\r\n return;\r\n }\r\n\r\n const before = effectiveValues();\r\n for (const key of keys) {\r\n pending.set(key, (partial as Record<string, unknown>)[key]);\r\n }\r\n invalidateEffective();\r\n notify(diffPaths(before, effectiveValues()));\r\n schedule();\r\n };\r\n\r\n const subscribe = (listener: StoreListener): Unsubscribe => {\r\n listeners.add(listener);\r\n return () => {\r\n listeners.delete(listener);\r\n };\r\n };\r\n\r\n // Forward changes that happen behind our back (external writes, resets —\r\n // anything that is not this wrapper's own flush, which was already\r\n // announced when it was buffered).\r\n target.subscribe((changedPaths) => {\r\n invalidateEffective();\r\n if (flushing) return;\r\n notify(changedPaths);\r\n });\r\n\r\n const watch = (path: FieldPath, listener: WatchListener): Unsubscribe =>\r\n watchPath(\r\n subscribe,\r\n () => getAtPath(effectiveValues(), path),\r\n path,\r\n listener,\r\n );\r\n\r\n return {\r\n delay,\r\n\r\n get pendingCount() {\r\n return pending.size;\r\n },\r\n\r\n getValues: () => effectiveValues(),\r\n\r\n getValue: (path) => getAtPath(effectiveValues(), path),\r\n\r\n setValue: (path, value) => bufferWrite(path, value),\r\n\r\n setValues,\r\n\r\n reset: (nextInitialValues) => {\r\n cancel();\r\n target.reset(nextInitialValues);\r\n },\r\n\r\n // Baseline semantics, delegated straight through: what `reset()` on\r\n // this wrapper restores to is whatever the wrapped target considers\r\n // its initial values. Buffered writes are intentionally excluded —\r\n // they are uncommitted edits, not a new baseline.\r\n getInitialValues: () => target.getInitialValues(),\r\n\r\n subscribe,\r\n\r\n watch,\r\n\r\n flush: () => flush(),\r\n\r\n cancel: () => cancel(),\r\n };\r\n}","import { useEffect, useMemo } from \"react\";\r\nimport type { FormValues } from \"../../core/types/store\";\r\nimport type { DebouncedSync, SyncTarget } from \"../../core/types/sync\";\r\nimport { createDebouncedSync } from \"../../core/store/createDebouncedSync\";\r\n\r\nexport interface UseDebouncedSyncOptions {\r\n /**\r\n * Milliseconds to wait after the last buffered write before committing to\r\n * the target. Defaults to 300ms.\r\n */\r\n delay?: number;\r\n\r\n /**\r\n * What happens to buffered-but-uncommitted writes when the component\r\n * unmounts. Defaults to `true` (flush) so the last keystrokes before a\r\n * navigation are never lost — the exact failure mode debounced sync\r\n * would otherwise introduce. Set to `false` (cancel) when unmounting\r\n * means \"abandon this edit\" rather than \"the form went away\".\r\n */\r\n flushOnUnmount?: boolean;\r\n}\r\n\r\n/**\r\n * React binding over `createDebouncedSync`. The wrapper is memoized on\r\n * `[target, delay]`, so passing it to `useForm`/`useFormField` (it exposes\r\n * the same read/write/subscribe surface they expect) or rendering from its\r\n * read-through values stays referentially stable across re-renders.\r\n */\r\nexport function useDebouncedSync<TValues extends FormValues = FormValues>(\r\n target: SyncTarget<TValues>,\r\n options: UseDebouncedSyncOptions = {},\r\n): DebouncedSync<TValues> {\r\n const { delay, flushOnUnmount = true } = options;\r\n\r\n const sync = useMemo(\r\n () => createDebouncedSync(target, delay === undefined ? {} : { delay }),\r\n [target, delay],\r\n );\r\n\r\n useEffect(() => {\r\n return () => {\r\n if (flushOnUnmount) {\r\n sync.flush();\r\n } else {\r\n sync.cancel();\r\n }\r\n };\r\n }, [sync, flushOnUnmount]);\r\n\r\n return sync;\r\n}","import { useEffect, useRef } from \"react\";\r\nimport type { WatchListener } from \"../../core/types/store\";\r\nimport type { SyncTarget } from \"../../core/types/sync\";\r\n\r\n/**\r\n * Runs `listener(value, previousValue)` whenever the value at `path`\r\n * actually changes (deep equality). This is the React entry to the store's\r\n * `watch` primitive — the primitive derived fields (`qty × price =\r\n * lineTotal`) and cascading selects (`Country` change clears `City`) are\r\n * built on.\r\n *\r\n * The listener identity may change every render (it usually closes over\r\n * props/state); the effect only re-subscribes when `target` or `path`\r\n * changes, and always invokes the latest listener via a ref — so handlers\r\n * never see stale closures, and no subscription churn happens per render.\r\n *\r\n * The listener does not fire on mount. Initial derivations belong in\r\n * render (read the value directly) or in the code that sets up the form.\r\n */\r\nexport function useWatch(\r\n target: SyncTarget,\r\n path: string,\r\n listener: WatchListener,\r\n): void {\r\n const listenerRef = useRef(listener);\r\n listenerRef.current = listener;\r\n\r\n useEffect(() => {\r\n return target.watch(path, (value, previousValue) =>\r\n listenerRef.current(value, previousValue),\r\n );\r\n }, [target, path]);\r\n}"]}