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.cjs
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/utils/paths.ts
|
|
4
|
+
function splitPath(path) {
|
|
5
|
+
return path.split(".").filter(Boolean);
|
|
6
|
+
}
|
|
7
|
+
function getAtPath(source, path) {
|
|
8
|
+
const segments = splitPath(path);
|
|
9
|
+
let current = source;
|
|
10
|
+
for (const segment of segments) {
|
|
11
|
+
if (current === null || typeof current !== "object") {
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
current = current[segment];
|
|
15
|
+
}
|
|
16
|
+
return current;
|
|
17
|
+
}
|
|
18
|
+
function setAtPath(source, path, value) {
|
|
19
|
+
const segments = splitPath(path);
|
|
20
|
+
if (segments.length === 0) return source;
|
|
21
|
+
const [head, ...rest] = segments;
|
|
22
|
+
if (rest.length === 0) {
|
|
23
|
+
if (Object.is(source[head], value)) {
|
|
24
|
+
return source;
|
|
25
|
+
}
|
|
26
|
+
return { ...source, [head]: value };
|
|
27
|
+
}
|
|
28
|
+
const currentChild = source[head];
|
|
29
|
+
const childSource = currentChild !== null && typeof currentChild === "object" ? currentChild : {};
|
|
30
|
+
const nextChild = setAtPath(childSource, rest.join("."), value);
|
|
31
|
+
if (Object.is(currentChild, nextChild)) {
|
|
32
|
+
return source;
|
|
33
|
+
}
|
|
34
|
+
return { ...source, [head]: nextChild };
|
|
35
|
+
}
|
|
36
|
+
function mergeAtRoot(source, partial) {
|
|
37
|
+
let next = source;
|
|
38
|
+
for (const key of Object.keys(partial)) {
|
|
39
|
+
next = setAtPath(next, key, partial[key]);
|
|
40
|
+
}
|
|
41
|
+
return next;
|
|
42
|
+
}
|
|
43
|
+
function diffPaths(prev, next, basePath = "", seen = /* @__PURE__ */ new Set()) {
|
|
44
|
+
if (Object.is(prev, next)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const prevIsObject = prev !== null && typeof prev === "object" && !Array.isArray(prev);
|
|
48
|
+
const nextIsObject = next !== null && typeof next === "object" && !Array.isArray(next);
|
|
49
|
+
if (!prevIsObject || !nextIsObject) {
|
|
50
|
+
return basePath ? [basePath] : [];
|
|
51
|
+
}
|
|
52
|
+
const keys = /* @__PURE__ */ new Set([
|
|
53
|
+
...Object.keys(prev),
|
|
54
|
+
...Object.keys(next)
|
|
55
|
+
]);
|
|
56
|
+
const changed = [];
|
|
57
|
+
for (const key of keys) {
|
|
58
|
+
const childPath = basePath ? `${basePath}.${key}` : key;
|
|
59
|
+
if (seen.has(childPath)) continue;
|
|
60
|
+
seen.add(childPath);
|
|
61
|
+
changed.push(
|
|
62
|
+
...diffPaths(
|
|
63
|
+
prev[key],
|
|
64
|
+
next[key],
|
|
65
|
+
childPath,
|
|
66
|
+
seen
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return changed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/core/utils/deepEqual.ts
|
|
74
|
+
function deepEqual(a, b) {
|
|
75
|
+
if (Object.is(a, b)) return true;
|
|
76
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
77
|
+
if (a.length !== b.length) return false;
|
|
78
|
+
return a.every((item, index) => deepEqual(item, b[index]));
|
|
79
|
+
}
|
|
80
|
+
const aIsPlainObject = typeof a === "object" && a !== null && !Array.isArray(a) && a.constructor === Object;
|
|
81
|
+
const bIsPlainObject = typeof b === "object" && b !== null && !Array.isArray(b) && b.constructor === Object;
|
|
82
|
+
if (aIsPlainObject && bIsPlainObject) {
|
|
83
|
+
const aKeys = Object.keys(a);
|
|
84
|
+
const bKeys = Object.keys(b);
|
|
85
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
86
|
+
return aKeys.every(
|
|
87
|
+
(key) => deepEqual(a[key], b[key])
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/core/utils/watch.ts
|
|
94
|
+
function isPathWithin(changedPath, watchedPath) {
|
|
95
|
+
if (changedPath === "" || watchedPath === "") return true;
|
|
96
|
+
return changedPath === watchedPath || changedPath.startsWith(`${watchedPath}.`) || watchedPath.startsWith(`${changedPath}.`);
|
|
97
|
+
}
|
|
98
|
+
function watchPath(subscribe, readValue, path, listener) {
|
|
99
|
+
let last = readValue();
|
|
100
|
+
return subscribe((changedPaths) => {
|
|
101
|
+
if (!changedPaths.some((changed) => isPathWithin(changed, path))) return;
|
|
102
|
+
const next = readValue();
|
|
103
|
+
if (deepEqual(last, next)) {
|
|
104
|
+
last = next;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const previous = last;
|
|
108
|
+
last = next;
|
|
109
|
+
listener(next, previous);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/core/store/createFormStore.ts
|
|
114
|
+
function createFormStore(initialValues) {
|
|
115
|
+
let initial = initialValues;
|
|
116
|
+
let values = initialValues;
|
|
117
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
118
|
+
const subscribe = (listener) => {
|
|
119
|
+
listeners.add(listener);
|
|
120
|
+
return () => {
|
|
121
|
+
listeners.delete(listener);
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
const notify = (changedPaths) => {
|
|
125
|
+
if (changedPaths.length === 0) return;
|
|
126
|
+
for (const listener of listeners) {
|
|
127
|
+
listener(changedPaths);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const commit = (nextValues) => {
|
|
131
|
+
if (Object.is(values, nextValues)) return;
|
|
132
|
+
const changed = diffPaths(values, nextValues);
|
|
133
|
+
values = nextValues;
|
|
134
|
+
notify(changed);
|
|
135
|
+
};
|
|
136
|
+
return {
|
|
137
|
+
getValues: () => values,
|
|
138
|
+
getValue: (path) => getAtPath(values, path),
|
|
139
|
+
setValue: (path, value) => {
|
|
140
|
+
commit(setAtPath(values, path, value));
|
|
141
|
+
},
|
|
142
|
+
setValues: (partial) => {
|
|
143
|
+
commit(mergeAtRoot(values, partial));
|
|
144
|
+
},
|
|
145
|
+
reset: (nextInitialValues) => {
|
|
146
|
+
const baseline = nextInitialValues ?? initial;
|
|
147
|
+
initial = baseline;
|
|
148
|
+
commit(baseline);
|
|
149
|
+
},
|
|
150
|
+
subscribe,
|
|
151
|
+
/**
|
|
152
|
+
* Observes one path (including its whole subtree) across commits. The
|
|
153
|
+
* listener fires only when the value at `path` actually changed (deep
|
|
154
|
+
* equality — an ancestor being replaced with identical leaf values does
|
|
155
|
+
* not count). This is the primitive derived fields and cascading
|
|
156
|
+
* selects are built on: `watch("qty", ...)` / `watch("country", ...)`.
|
|
157
|
+
* It does not fire on subscribe; compute initial derivations during
|
|
158
|
+
* render from `getValues()` instead.
|
|
159
|
+
*/
|
|
160
|
+
watch: (path, listener) => watchPath(
|
|
161
|
+
subscribe,
|
|
162
|
+
() => getAtPath(values, path),
|
|
163
|
+
path,
|
|
164
|
+
listener
|
|
165
|
+
),
|
|
166
|
+
getInitialValues: () => initial
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/core/store/createFormSection.ts
|
|
171
|
+
function createFormSection(store, key) {
|
|
172
|
+
const prefix = `${key}.`;
|
|
173
|
+
const toRelative = (path) => {
|
|
174
|
+
if (path === key) return "";
|
|
175
|
+
if (path.startsWith(prefix)) return path.slice(prefix.length);
|
|
176
|
+
return null;
|
|
177
|
+
};
|
|
178
|
+
const toAbsolute = (relativePath) => relativePath ? `${key}.${relativePath}` : key;
|
|
179
|
+
return {
|
|
180
|
+
key,
|
|
181
|
+
getValues: () => getAtPath(store.getValues(), key) ?? {},
|
|
182
|
+
getValue: (relativePath) => getAtPath(store.getValues(), toAbsolute(relativePath)),
|
|
183
|
+
setValue: (relativePath, value) => {
|
|
184
|
+
store.setValue(toAbsolute(relativePath), value);
|
|
185
|
+
},
|
|
186
|
+
setValues: (partial) => {
|
|
187
|
+
const current = getAtPath(store.getValues(), key) ?? {};
|
|
188
|
+
store.setValue(key, { ...current, ...partial });
|
|
189
|
+
},
|
|
190
|
+
reset: () => {
|
|
191
|
+
const baseline = getAtPath(store.getInitialValues(), key) ?? {};
|
|
192
|
+
store.setValue(key, baseline);
|
|
193
|
+
},
|
|
194
|
+
getInitialValues: () => getAtPath(store.getInitialValues(), key) ?? {},
|
|
195
|
+
subscribe: (listener) => {
|
|
196
|
+
return store.subscribe((changedPaths) => {
|
|
197
|
+
const relevant = [];
|
|
198
|
+
for (const path of changedPaths) {
|
|
199
|
+
const relative = toRelative(path);
|
|
200
|
+
if (relative !== null) relevant.push(relative);
|
|
201
|
+
}
|
|
202
|
+
if (relevant.length > 0) listener(relevant);
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
/**
|
|
206
|
+
* Same semantics as `FormStore.watch`, but with section-relative paths.
|
|
207
|
+
* Also fires when the section's slice is replaced wholesale on the
|
|
208
|
+
* parent (`store.setValue(key, {...})` arrives here as a relative ""
|
|
209
|
+
* change) and the watched value actually changed as a result.
|
|
210
|
+
*/
|
|
211
|
+
watch: (relativePath, listener) => watchPath(
|
|
212
|
+
(cb) => store.subscribe((changedPaths) => {
|
|
213
|
+
const relevant = [];
|
|
214
|
+
for (const path of changedPaths) {
|
|
215
|
+
const relative = toRelative(path);
|
|
216
|
+
if (relative !== null) relevant.push(relative);
|
|
217
|
+
}
|
|
218
|
+
if (relevant.length > 0) cb(relevant);
|
|
219
|
+
}),
|
|
220
|
+
() => getAtPath(store.getValues(), toAbsolute(relativePath)),
|
|
221
|
+
relativePath,
|
|
222
|
+
listener
|
|
223
|
+
)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/core/store/createDebouncedSync.ts
|
|
228
|
+
var DEFAULT_DELAY_MS = 300;
|
|
229
|
+
function createDebouncedSync(target, options = {}) {
|
|
230
|
+
const delay = options.delay ?? DEFAULT_DELAY_MS;
|
|
231
|
+
let timer = null;
|
|
232
|
+
let flushing = false;
|
|
233
|
+
let pending = /* @__PURE__ */ new Map();
|
|
234
|
+
let effectiveCache = null;
|
|
235
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
236
|
+
const invalidateEffective = () => {
|
|
237
|
+
effectiveCache = null;
|
|
238
|
+
};
|
|
239
|
+
const effectiveValues = () => {
|
|
240
|
+
if (effectiveCache !== null) return effectiveCache;
|
|
241
|
+
if (pending.size === 0) return target.getValues();
|
|
242
|
+
let next = target.getValues();
|
|
243
|
+
for (const [path, value] of pending) {
|
|
244
|
+
next = setAtPath(next, path, value);
|
|
245
|
+
}
|
|
246
|
+
effectiveCache = next;
|
|
247
|
+
return effectiveCache;
|
|
248
|
+
};
|
|
249
|
+
const notify = (changedPaths) => {
|
|
250
|
+
if (changedPaths.length === 0) return;
|
|
251
|
+
for (const listener of listeners) {
|
|
252
|
+
listener(changedPaths);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
const cancel = () => {
|
|
256
|
+
if (timer !== null) {
|
|
257
|
+
clearTimeout(timer);
|
|
258
|
+
timer = null;
|
|
259
|
+
}
|
|
260
|
+
pending = /* @__PURE__ */ new Map();
|
|
261
|
+
invalidateEffective();
|
|
262
|
+
};
|
|
263
|
+
const flush = () => {
|
|
264
|
+
if (timer !== null) {
|
|
265
|
+
clearTimeout(timer);
|
|
266
|
+
timer = null;
|
|
267
|
+
}
|
|
268
|
+
if (flushing || pending.size === 0) return;
|
|
269
|
+
flushing = true;
|
|
270
|
+
const batch = pending;
|
|
271
|
+
pending = /* @__PURE__ */ new Map();
|
|
272
|
+
invalidateEffective();
|
|
273
|
+
try {
|
|
274
|
+
let staged = target.getValues();
|
|
275
|
+
for (const [path, value] of batch) {
|
|
276
|
+
staged = setAtPath(staged, path, value);
|
|
277
|
+
}
|
|
278
|
+
target.setValues(staged);
|
|
279
|
+
} finally {
|
|
280
|
+
flushing = false;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const schedule = () => {
|
|
284
|
+
if (timer !== null) clearTimeout(timer);
|
|
285
|
+
timer = setTimeout(() => {
|
|
286
|
+
timer = null;
|
|
287
|
+
flush();
|
|
288
|
+
}, delay);
|
|
289
|
+
};
|
|
290
|
+
const bufferWrite = (path, value) => {
|
|
291
|
+
if (flushing) {
|
|
292
|
+
target.setValue(path, value);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const before = effectiveValues();
|
|
296
|
+
pending.set(path, value);
|
|
297
|
+
invalidateEffective();
|
|
298
|
+
notify(diffPaths(before, effectiveValues()));
|
|
299
|
+
schedule();
|
|
300
|
+
};
|
|
301
|
+
const setValues = (partial) => {
|
|
302
|
+
const keys = Object.keys(partial);
|
|
303
|
+
if (keys.length === 0) return;
|
|
304
|
+
if (flushing) {
|
|
305
|
+
target.setValues(partial);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const before = effectiveValues();
|
|
309
|
+
for (const key of keys) {
|
|
310
|
+
pending.set(key, partial[key]);
|
|
311
|
+
}
|
|
312
|
+
invalidateEffective();
|
|
313
|
+
notify(diffPaths(before, effectiveValues()));
|
|
314
|
+
schedule();
|
|
315
|
+
};
|
|
316
|
+
const subscribe = (listener) => {
|
|
317
|
+
listeners.add(listener);
|
|
318
|
+
return () => {
|
|
319
|
+
listeners.delete(listener);
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
target.subscribe((changedPaths) => {
|
|
323
|
+
invalidateEffective();
|
|
324
|
+
if (flushing) return;
|
|
325
|
+
notify(changedPaths);
|
|
326
|
+
});
|
|
327
|
+
const watch = (path, listener) => watchPath(
|
|
328
|
+
subscribe,
|
|
329
|
+
() => getAtPath(effectiveValues(), path),
|
|
330
|
+
path,
|
|
331
|
+
listener
|
|
332
|
+
);
|
|
333
|
+
return {
|
|
334
|
+
delay,
|
|
335
|
+
get pendingCount() {
|
|
336
|
+
return pending.size;
|
|
337
|
+
},
|
|
338
|
+
getValues: () => effectiveValues(),
|
|
339
|
+
getValue: (path) => getAtPath(effectiveValues(), path),
|
|
340
|
+
setValue: (path, value) => bufferWrite(path, value),
|
|
341
|
+
setValues,
|
|
342
|
+
reset: (nextInitialValues) => {
|
|
343
|
+
cancel();
|
|
344
|
+
target.reset(nextInitialValues);
|
|
345
|
+
},
|
|
346
|
+
// Baseline semantics, delegated straight through: what `reset()` on
|
|
347
|
+
// this wrapper restores to is whatever the wrapped target considers
|
|
348
|
+
// its initial values. Buffered writes are intentionally excluded —
|
|
349
|
+
// they are uncommitted edits, not a new baseline.
|
|
350
|
+
getInitialValues: () => target.getInitialValues(),
|
|
351
|
+
subscribe,
|
|
352
|
+
watch,
|
|
353
|
+
flush: () => flush(),
|
|
354
|
+
cancel: () => cancel()
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/core/validation/validateValues.ts
|
|
359
|
+
function validateValues(values, schema) {
|
|
360
|
+
const errors = {};
|
|
361
|
+
const fields = schema.fields ?? {};
|
|
362
|
+
for (const path of Object.keys(fields)) {
|
|
363
|
+
const rules = fields[path];
|
|
364
|
+
if (!rules) continue;
|
|
365
|
+
const list = Array.isArray(rules) ? rules : [rules];
|
|
366
|
+
const value = getAtPath(values, path);
|
|
367
|
+
for (const rule of list) {
|
|
368
|
+
const error = rule(value, { path, values });
|
|
369
|
+
if (error) {
|
|
370
|
+
errors[path] = error;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const formRules = schema.form;
|
|
376
|
+
if (formRules) {
|
|
377
|
+
const list = Array.isArray(formRules) ? formRules : [formRules];
|
|
378
|
+
for (const rule of list) {
|
|
379
|
+
const formErrors = rule(values);
|
|
380
|
+
if (!formErrors) continue;
|
|
381
|
+
for (const path of Object.keys(formErrors)) {
|
|
382
|
+
const message = formErrors[path];
|
|
383
|
+
if (message && errors[path] === void 0) {
|
|
384
|
+
errors[path] = message;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return { errors, isValid: Object.keys(errors).length === 0 };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/core/utils/normalize.ts
|
|
393
|
+
function isEvent(value) {
|
|
394
|
+
return typeof value === "object" && value !== null && "target" in value && typeof value.target === "object";
|
|
395
|
+
}
|
|
396
|
+
function isOptionLike(value) {
|
|
397
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && "value" in value;
|
|
398
|
+
}
|
|
399
|
+
var defaultNormalize = (input, _context) => {
|
|
400
|
+
if (isEvent(input)) {
|
|
401
|
+
const target = input.target;
|
|
402
|
+
if (target.type === "checkbox") {
|
|
403
|
+
return target.checked;
|
|
404
|
+
}
|
|
405
|
+
return target.value;
|
|
406
|
+
}
|
|
407
|
+
if (Array.isArray(input)) {
|
|
408
|
+
return input.map((item) => isOptionLike(item) ? item.value : item);
|
|
409
|
+
}
|
|
410
|
+
if (input instanceof Date) {
|
|
411
|
+
return input;
|
|
412
|
+
}
|
|
413
|
+
if (isOptionLike(input)) {
|
|
414
|
+
return input.value;
|
|
415
|
+
}
|
|
416
|
+
return input;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
exports.createDebouncedSync = createDebouncedSync;
|
|
420
|
+
exports.createFormSection = createFormSection;
|
|
421
|
+
exports.createFormStore = createFormStore;
|
|
422
|
+
exports.deepEqual = deepEqual;
|
|
423
|
+
exports.defaultNormalize = defaultNormalize;
|
|
424
|
+
exports.diffPaths = diffPaths;
|
|
425
|
+
exports.getAtPath = getAtPath;
|
|
426
|
+
exports.isPathWithin = isPathWithin;
|
|
427
|
+
exports.mergeAtRoot = mergeAtRoot;
|
|
428
|
+
exports.setAtPath = setAtPath;
|
|
429
|
+
exports.validateValues = validateValues;
|
|
430
|
+
//# sourceMappingURL=index.cjs.map
|
|
431
|
+
//# sourceMappingURL=index.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/createFormStore.ts","../src/core/store/createFormSection.ts","../src/core/store/createDebouncedSync.ts","../src/core/validation/validateValues.ts","../src/core/utils/normalize.ts"],"names":[],"mappings":"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","file":"index.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 {\r\n FieldPath,\r\n FormStore,\r\n FormValues,\r\n StoreListener,\r\n Unsubscribe,\r\n} from \"../types/store\";\r\nimport { diffPaths, getAtPath, mergeAtRoot, setAtPath } from \"../utils/paths\";\r\nimport { watchPath } from \"../utils/watch\";\r\n\r\n/**\r\n * Creates a framework-agnostic form store.\r\n *\r\n * This file must never import React. It's the piece every other package\r\n * (react, validators, file, array) builds on top of, and it needs to stay\r\n * independently unit-testable and usable outside React entirely (see\r\n * roadmap section 7).\r\n *\r\n * Design notes (documented here because the \"why\" matters for anyone\r\n * extending this later):\r\n *\r\n * - Values are plain objects, updated immutably (see `paths.ts`), so\r\n * `getValues()` always returns a plain, serializable snapshot — no\r\n * proxies, no framework-specific wrappers.\r\n * - The store does not itself decide who re-renders. It just computes\r\n * *which dot-paths changed* on every commit and hands that list to every\r\n * subscriber. The React binding (`useForm`, in the `react` entry point)\r\n * is what turns \"did my path change?\" into a re-render decision, via\r\n * `useSyncExternalStore`. This split is what avoids a Context-based\r\n * design, where every consumer re-renders on every change regardless of\r\n * which field they read.\r\n */\r\nexport function createFormStore<TValues extends FormValues = FormValues>(\r\n initialValues: TValues,\r\n): FormStore<TValues> {\r\n let initial = initialValues;\r\n let values = initialValues;\r\n const listeners = new Set<StoreListener>();\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 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 commit = (nextValues: TValues) => {\r\n if (Object.is(values, nextValues)) return;\r\n const changed = diffPaths(values, nextValues);\r\n values = nextValues;\r\n notify(changed);\r\n };\r\n\r\n return {\r\n getValues: () => values,\r\n\r\n getValue: (path) => getAtPath(values, path),\r\n\r\n setValue: (path, value) => {\r\n commit(setAtPath(values, path, value));\r\n },\r\n\r\n setValues: (partial) => {\r\n commit(mergeAtRoot(values, partial));\r\n },\r\n\r\n reset: (nextInitialValues) => {\r\n const baseline = nextInitialValues ?? initial;\r\n initial = baseline;\r\n commit(baseline);\r\n },\r\n\r\n subscribe,\r\n\r\n /**\r\n * Observes one path (including its whole subtree) across commits. The\r\n * listener fires only when the value at `path` actually changed (deep\r\n * equality — an ancestor being replaced with identical leaf values does\r\n * not count). This is the primitive derived fields and cascading\r\n * selects are built on: `watch(\"qty\", ...)` / `watch(\"country\", ...)`.\r\n * It does not fire on subscribe; compute initial derivations during\r\n * render from `getValues()` instead.\r\n */\r\n watch: (path, listener) =>\r\n watchPath(\r\n subscribe,\r\n () => getAtPath(values, path),\r\n path,\r\n listener,\r\n ),\r\n\r\n getInitialValues: () => initial,\r\n };\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 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 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"]}
|