@echojs-ecosystem/form 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,867 @@
1
+ import { signal, effect } from '@echojs-ecosystem/reactivity';
2
+
3
+ // src/primitives/field.ts
4
+
5
+ // src/primitives/field-persist.ts
6
+ var fieldPersistable = (field) => ({
7
+ value: () => field.$value.peek(),
8
+ set: (next) => field.set(next),
9
+ subscribe(listener) {
10
+ let prev = field.$value.peek();
11
+ return field.$value.subscribe(() => {
12
+ const next = field.$value.peek();
13
+ listener(next, prev);
14
+ prev = next;
15
+ });
16
+ }
17
+ });
18
+ var fieldArrayPersistable = (fieldArray) => ({
19
+ value: () => [...fieldArray.$items.peek()],
20
+ set: (next) => fieldArray.replace([...next]),
21
+ subscribe(listener) {
22
+ let prev = [...fieldArray.$items.peek()];
23
+ return fieldArray.$items.subscribe(() => {
24
+ const next = [...fieldArray.$items.peek()];
25
+ listener(next, prev);
26
+ prev = next;
27
+ });
28
+ }
29
+ });
30
+ var attachFieldPersist = (field) => {
31
+ const { value, subscribe } = fieldPersistable(field);
32
+ const target = Object.assign(field, { value, subscribe });
33
+ target.extend = (extension) => {
34
+ const result = extension(fieldPersistable(target));
35
+ Object.assign(target, result);
36
+ return target;
37
+ };
38
+ return target;
39
+ };
40
+ var attachFieldArrayPersist = (fieldArray) => {
41
+ const { value, set, subscribe } = fieldArrayPersistable(fieldArray);
42
+ const target = Object.assign(fieldArray, { value, set, subscribe });
43
+ target.extend = (extension) => {
44
+ const result = extension(fieldArrayPersistable(target));
45
+ Object.assign(target, result);
46
+ return target;
47
+ };
48
+ return target;
49
+ };
50
+
51
+ // src/primitives/field.ts
52
+ var defaultMeta = () => ({
53
+ dirty: false,
54
+ touched: false,
55
+ focused: false,
56
+ errors: []
57
+ });
58
+ var createField = (initial) => {
59
+ let binding;
60
+ const $value = signal(initial);
61
+ const $meta = signal(defaultMeta());
62
+ const setValue = (next) => {
63
+ $value.set(next);
64
+ $meta.update((prev) => ({ ...prev, dirty: true, errors: [] }));
65
+ };
66
+ const focus = () => {
67
+ $meta.update((prev) => ({ ...prev, focused: true }));
68
+ };
69
+ const blur = () => {
70
+ $meta.update((prev) => ({ ...prev, focused: false, touched: true }));
71
+ };
72
+ const bind = () => {
73
+ if (binding) return binding;
74
+ const isBool = typeof initial === "boolean";
75
+ const onInputText = (event) => {
76
+ if (isBool) return;
77
+ const raw = event.currentTarget.value;
78
+ setValue(raw);
79
+ };
80
+ const onChangeText = (event) => {
81
+ if (isBool) {
82
+ const checked = event.currentTarget.checked;
83
+ setValue(checked);
84
+ return;
85
+ }
86
+ const raw = event.currentTarget.value;
87
+ setValue(raw);
88
+ };
89
+ const onFocus = () => focus();
90
+ const onBlur = () => blur();
91
+ binding = { onInputText, onChangeText, onFocus, onBlur };
92
+ return binding;
93
+ };
94
+ const validate = () => [];
95
+ const validateAsync = async () => [];
96
+ const core = {
97
+ $value,
98
+ $meta,
99
+ value: () => $value.value(),
100
+ meta: () => $meta.value(),
101
+ get handlers() {
102
+ return bind();
103
+ },
104
+ set: (next) => setValue(next),
105
+ reset: (next) => {
106
+ $value.set(next !== void 0 ? next : initial);
107
+ $meta.set(defaultMeta());
108
+ },
109
+ focus,
110
+ blur,
111
+ touch: () => $meta.update((prev) => ({ ...prev, touched: true })),
112
+ bind,
113
+ validate,
114
+ validateAsync,
115
+ clearErrors: () => $meta.update((prev) => ({ ...prev, errors: [] }))
116
+ };
117
+ return attachFieldPersist(core);
118
+ };
119
+ var createFieldArray = (initial = []) => {
120
+ const initialSnapshot = [...initial];
121
+ const $items = signal([...initial]);
122
+ const append = (item) => {
123
+ $items.update((prev) => [...prev, item]);
124
+ };
125
+ const prepend = (item) => {
126
+ $items.update((prev) => [item, ...prev]);
127
+ };
128
+ const removeAt = (index) => {
129
+ $items.update((prev) => prev.filter((_, i) => i !== index));
130
+ };
131
+ const move = (from, to) => {
132
+ $items.update((prev) => {
133
+ if (from === to) return prev;
134
+ if (from < 0 || to < 0) return prev;
135
+ if (from >= prev.length || to >= prev.length) return prev;
136
+ const next = prev.slice();
137
+ const [item] = next.splice(from, 1);
138
+ next.splice(to, 0, item);
139
+ return next;
140
+ });
141
+ };
142
+ const updateAt = (index, fn) => {
143
+ $items.update((prev) => prev.map((x, i) => i === index ? fn(x) : x));
144
+ };
145
+ const replace = (next) => {
146
+ $items.set([...next]);
147
+ };
148
+ const reset = () => {
149
+ $items.set([...initialSnapshot]);
150
+ };
151
+ const core = {
152
+ $items,
153
+ append,
154
+ prepend,
155
+ removeAt,
156
+ move,
157
+ updateAt,
158
+ replace,
159
+ reset
160
+ };
161
+ return attachFieldArrayPersist(core);
162
+ };
163
+
164
+ // src/primitives/nested-field-array-ops.ts
165
+ var buildOps = (fieldArray, cfg) => {
166
+ const removeAt = (index) => {
167
+ fieldArray.removeAt(index);
168
+ };
169
+ const children = cfg.children;
170
+ const hasChildren = !!children && Object.keys(children).length > 0;
171
+ const at = hasChildren ? (index) => {
172
+ const row = fieldArray.$items.value()[index];
173
+ if (!row) return void 0;
174
+ const slice = {};
175
+ for (const key of Object.keys(children)) {
176
+ const childCfg = children[key];
177
+ if (!childCfg) continue;
178
+ const childArr = row[key];
179
+ slice[key] = buildOps(childArr, childCfg);
180
+ }
181
+ return slice;
182
+ } : (_index) => void 0;
183
+ return {
184
+ append: (item) => {
185
+ fieldArray.append(item ?? cfg.newRow());
186
+ },
187
+ removeAt,
188
+ remove: removeAt,
189
+ at
190
+ };
191
+ };
192
+ var defineNestedFieldArrayOps = (fieldArray, cfg) => buildOps(fieldArray, cfg);
193
+
194
+ // src/primitives/fieldSet.ts
195
+ var createFieldSet = (fields) => {
196
+ const validate = () => {
197
+ const out = {};
198
+ for (const [key, value] of Object.entries(fields)) {
199
+ if (value && typeof value.validate === "function") {
200
+ out[key] = value.validate();
201
+ }
202
+ }
203
+ return out;
204
+ };
205
+ const reset = () => {
206
+ for (const value of Object.values(fields)) {
207
+ if (value && typeof value.reset === "function") {
208
+ value.reset();
209
+ }
210
+ }
211
+ };
212
+ return { fields, validate, reset };
213
+ };
214
+
215
+ // src/validation/flatten.ts
216
+ var pathKey = (path) => (path ?? []).map((segment) => typeof segment === "symbol" ? String(segment) : String(segment)).join(".");
217
+ var flattenFieldErrors = (issues) => {
218
+ const out = {};
219
+ for (const issue of issues) {
220
+ const key = pathKey(issue.path);
221
+ const resolvedKey = key.length ? key : "$root";
222
+ const arr = out[resolvedKey] ?? [];
223
+ arr.push(issue.message);
224
+ out[resolvedKey] = arr;
225
+ }
226
+ return out;
227
+ };
228
+
229
+ // src/validation/standard-schema.ts
230
+ var asRecord = (v) => typeof v === "object" && v !== null ? v : null;
231
+ var isIssueLikeArray = (v) => Array.isArray(v) && v.every(
232
+ (x) => x && typeof x === "object" && typeof x.message === "string"
233
+ );
234
+ var normalizeStandardSchemaPathSegments = (path) => Array.from(path ?? []).map((segment) => {
235
+ if (typeof segment === "string" || typeof segment === "number") return String(segment);
236
+ if (typeof segment === "symbol") return String(segment);
237
+ if (segment && typeof segment === "object" && "key" in segment) {
238
+ const key = segment.key;
239
+ return typeof key === "symbol" ? String(key) : String(key);
240
+ }
241
+ return String(segment);
242
+ });
243
+ var normalizeStandardSchemaIssues = (result) => {
244
+ if (typeof result === "string") return [{ message: result }];
245
+ if (Array.isArray(result) && result.every((x) => typeof x === "string")) {
246
+ return result.map((message2) => ({ message: message2 }));
247
+ }
248
+ if (Array.isArray(result) && isIssueLikeArray(result)) {
249
+ return Array.from(result).map((i) => ({
250
+ message: i.message,
251
+ path: normalizeStandardSchemaPathSegments(i.path ?? [])
252
+ }));
253
+ }
254
+ const rec = asRecord(result);
255
+ if (!rec) return [];
256
+ const directIssues = rec.issues ?? rec.errors ?? rec.issue;
257
+ if (Array.isArray(directIssues) && isIssueLikeArray(directIssues)) {
258
+ return Array.from(directIssues).map((i) => ({
259
+ message: i.message,
260
+ path: normalizeStandardSchemaPathSegments(i.path ?? [])
261
+ }));
262
+ }
263
+ if ("value" in rec && !("issues" in rec)) return [];
264
+ const message = rec.message;
265
+ if (typeof message === "string") return [{ message }];
266
+ return [];
267
+ };
268
+ var standardSchemaIssuesForUnknown = async (schema, value) => {
269
+ const raw = schema["~standard"].validate(value);
270
+ const result = raw instanceof Promise ? await raw : raw;
271
+ const rec = typeof result === "object" && result !== null ? result : null;
272
+ const issuesUnknown = rec && Object.prototype.hasOwnProperty.call(rec, "issues") ? rec.issues : void 0;
273
+ if (Array.isArray(issuesUnknown) && issuesUnknown.every((issue) => issue && typeof issue.message === "string")) {
274
+ const issues2 = Array.from(
275
+ issuesUnknown
276
+ ).map((issue) => ({
277
+ message: issue.message,
278
+ path: normalizeStandardSchemaPathSegments(issue.path ?? [])
279
+ }));
280
+ return issues2.length > 0 ? { ok: false, issues: issues2 } : { ok: true, issues: [] };
281
+ }
282
+ const issues = normalizeStandardSchemaIssues(result);
283
+ return issues.length > 0 ? { ok: false, issues } : { ok: true, issues: [] };
284
+ };
285
+ var standardSchemaIssuesForUnknownSync = (schema, value) => {
286
+ const raw = schema["~standard"].validate(value);
287
+ if (raw instanceof Promise) {
288
+ throw new Error(
289
+ "[@echojs-ecosystem/form] Schema validation returned a Promise. Use field.validateAsync() / form.validateAsync(), or wrap with async handling."
290
+ );
291
+ }
292
+ const rec = typeof raw === "object" && raw !== null ? raw : null;
293
+ const issuesUnknown = rec && Object.prototype.hasOwnProperty.call(rec, "issues") ? rec.issues : void 0;
294
+ if (Array.isArray(issuesUnknown) && issuesUnknown.every((issue) => issue && typeof issue.message === "string")) {
295
+ const issues2 = Array.from(
296
+ issuesUnknown
297
+ ).map((issue) => ({
298
+ message: issue.message,
299
+ path: normalizeStandardSchemaPathSegments(issue.path ?? [])
300
+ }));
301
+ return issues2.length > 0 ? { ok: false, issues: issues2 } : { ok: true, issues: [] };
302
+ }
303
+ const issues = normalizeStandardSchemaIssues(raw);
304
+ return issues.length > 0 ? { ok: false, issues } : { ok: true, issues: [] };
305
+ };
306
+
307
+ // src/primitives/collect-form-value.ts
308
+ var isPlainObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
309
+ var isFieldNode = (value) => isPlainObject(value) && typeof value.$value?.value === "function" && typeof value.set === "function" && typeof value.bind === "function";
310
+ var isFieldArrayNode = (value) => isPlainObject(value) && typeof value.$items?.value === "function" && typeof value.append === "function";
311
+ var isFieldSetNode = (value) => isPlainObject(value) && isPlainObject(value.fields) && typeof value.validate === "function" && typeof value.reset === "function" && !isFieldNode(value);
312
+ var collectFormValueFromFields = (node) => {
313
+ if (node == null) return node;
314
+ if (isFieldNode(node)) return node.$value.value();
315
+ if (isFieldArrayNode(node)) {
316
+ const raw = node.$items.value();
317
+ if (!Array.isArray(raw)) return [];
318
+ return raw.map((row) => collectFormValueFromFields(row));
319
+ }
320
+ if (isFieldSetNode(node)) return collectFormValueFromFields(node.fields);
321
+ if (isPlainObject(node)) {
322
+ const out = {};
323
+ for (const [key, value] of Object.entries(node)) {
324
+ out[key] = collectFormValueFromFields(value);
325
+ }
326
+ return out;
327
+ }
328
+ return node;
329
+ };
330
+
331
+ // src/primitives/hydrate.ts
332
+ var isPlainObject2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
333
+ var isField = (v) => isPlainObject2(v) && typeof v.set === "function" && typeof v.$value?.value === "function";
334
+ var isFieldSetNode2 = (v) => isPlainObject2(v) && isPlainObject2(v.fields);
335
+ var isFieldArrayNode2 = (v) => isPlainObject2(v) && typeof v.$items?.value === "function" && typeof v.append === "function";
336
+ var readItems = (arr) => {
337
+ const v = arr.$items.value();
338
+ return Array.isArray(v) ? v : [];
339
+ };
340
+ var hydrateFormFields = (fields, partial, fieldArrayFactories) => {
341
+ if (!isPlainObject2(partial)) return;
342
+ for (const [key, raw] of Object.entries(partial)) {
343
+ if (!(key in fields)) continue;
344
+ const node = fields[key];
345
+ hydrateNode(node, raw, fieldArrayFactories?.[key]);
346
+ }
347
+ };
348
+ var hydrateNode = (node, partial, rowFactory) => {
349
+ if (isField(node)) {
350
+ node.set(partial);
351
+ return;
352
+ }
353
+ if (isFieldSetNode2(node)) {
354
+ if (!isPlainObject2(partial)) return;
355
+ hydrateFormFields(node.fields, partial, void 0);
356
+ return;
357
+ }
358
+ if (isFieldArrayNode2(node)) {
359
+ if (!Array.isArray(partial)) return;
360
+ const target = partial;
361
+ const arr = node;
362
+ while (readItems(arr).length > target.length) {
363
+ arr.removeAt(readItems(arr).length - 1);
364
+ }
365
+ while (readItems(arr).length < target.length) {
366
+ if (!rowFactory) break;
367
+ arr.append(rowFactory());
368
+ }
369
+ const rows = readItems(arr);
370
+ for (let i = 0; i < target.length && i < rows.length; i++) {
371
+ hydrateNode(rows[i], target[i], void 0);
372
+ }
373
+ return;
374
+ }
375
+ if (isPlainObject2(node) && isPlainObject2(partial)) {
376
+ hydrateFormFields(node, partial, void 0);
377
+ }
378
+ };
379
+
380
+ // src/primitives/validation-tree.ts
381
+ var isPlainObject3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
382
+ var hasValidateSync = (value) => isPlainObject3(value) && typeof value.validate === "function";
383
+ var hasValidateAsync = (value) => isPlainObject3(value) && typeof value.validateAsync === "function";
384
+ var maybeFieldSetNode = (value) => isPlainObject3(value) && isPlainObject3(value.fields);
385
+ var maybeFieldArrayNode = (value) => isPlainObject3(value) && typeof value.$items?.value === "function";
386
+ var deepValidateSync = (root) => {
387
+ if (maybeFieldArrayNode(root)) {
388
+ const items = root.$items.value();
389
+ if (!Array.isArray(items)) return items;
390
+ return items.map((row) => deepValidateSync(row));
391
+ }
392
+ if (maybeFieldSetNode(root)) {
393
+ const out2 = {};
394
+ for (const [key, child] of Object.entries(root.fields)) {
395
+ out2[key] = deepValidateSync(child);
396
+ }
397
+ return out2;
398
+ }
399
+ if (hasValidateSync(root)) return root.validate();
400
+ if (!isPlainObject3(root)) return root;
401
+ const out = {};
402
+ for (const [key, child] of Object.entries(root)) {
403
+ out[key] = deepValidateSync(child);
404
+ }
405
+ return out;
406
+ };
407
+ var deepValidateAsync = async (root) => {
408
+ if (maybeFieldArrayNode(root)) {
409
+ const items = root.$items.value();
410
+ if (!Array.isArray(items)) return items;
411
+ const entries2 = items.map((row, i) => deepValidateAsync(row).then((v) => [i, v]));
412
+ const resolved2 = await Promise.all(entries2);
413
+ return resolved2.sort((a, b) => a[0] - b[0]).map(([, v]) => v);
414
+ }
415
+ if (maybeFieldSetNode(root)) {
416
+ const entries2 = Object.entries(root.fields).map(
417
+ ([key, child]) => deepValidateAsync(child).then((v) => [key, v])
418
+ );
419
+ const resolved2 = await Promise.all(entries2);
420
+ return Object.fromEntries(resolved2);
421
+ }
422
+ if (hasValidateAsync(root)) return root.validateAsync();
423
+ if (hasValidateSync(root) && !hasValidateAsync(root)) return root.validate();
424
+ if (!isPlainObject3(root)) return root;
425
+ const entries = Object.entries(root).map(
426
+ ([key, child]) => deepValidateAsync(child).then((v) => [key, v])
427
+ );
428
+ const resolved = await Promise.all(entries);
429
+ return Object.fromEntries(resolved);
430
+ };
431
+ var deepReset = (root) => {
432
+ if (isPlainObject3(root) && typeof root.reset === "function") {
433
+ root.reset();
434
+ return;
435
+ }
436
+ if (maybeFieldArrayNode(root)) {
437
+ const items = root.$items.value();
438
+ if (Array.isArray(items)) for (const row of items) deepReset(row);
439
+ return;
440
+ }
441
+ if (maybeFieldSetNode(root)) {
442
+ for (const child of Object.values(root.fields)) deepReset(child);
443
+ return;
444
+ }
445
+ if (!isPlainObject3(root)) return;
446
+ for (const child of Object.values(root)) deepReset(child);
447
+ };
448
+
449
+ // src/primitives/create-form.ts
450
+ var isPlainObject4 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
451
+ function createForm(fields, opts) {
452
+ const $submitting = signal(false);
453
+ const $submitCount = signal(0);
454
+ const $errors = signal(void 0);
455
+ const $schemaErrors = signal(void 0);
456
+ const resolvedFields = fields;
457
+ const rootSchema = opts.validationSchema ?? opts.schema;
458
+ const validationOn = opts.validationOn ?? "manual";
459
+ if (opts.defaultValues && Object.keys(opts.defaultValues).length > 0) {
460
+ hydrateFormFields(
461
+ resolvedFields,
462
+ opts.defaultValues,
463
+ opts.fieldArrayFactories
464
+ );
465
+ }
466
+ if (opts.defaultAsyncValues) {
467
+ void opts.defaultAsyncValues().then((extra) => {
468
+ hydrateFormFields(
469
+ resolvedFields,
470
+ extra
471
+ );
472
+ });
473
+ }
474
+ const snapshotValue = () => opts.getValue ? opts.getValue() : collectFormValueFromFields(resolvedFields);
475
+ const validate = () => {
476
+ if (opts.validate) return opts.validate(resolvedFields);
477
+ return deepValidateSync(resolvedFields);
478
+ };
479
+ const validateAsync = async () => {
480
+ const getFieldTree = (async () => {
481
+ if (opts.validateAsync) return await opts.validateAsync(resolvedFields);
482
+ if (opts.validate) return opts.validate(resolvedFields);
483
+ return await deepValidateAsync(resolvedFields);
484
+ });
485
+ if (!rootSchema) return await getFieldTree();
486
+ const value = snapshotValue();
487
+ const res = await standardSchemaIssuesForUnknown(rootSchema, value);
488
+ const schemaFlat = flattenFieldErrors(res.ok ? [] : res.issues);
489
+ const rootSchemaErrors = Object.keys(schemaFlat).length > 0 ? { $schema: schemaFlat } : {};
490
+ return { ...await getFieldTree(), ...rootSchemaErrors };
491
+ };
492
+ const updateErrorsSignals = (errs) => {
493
+ $errors.set(errs);
494
+ const flat = errs.$schema;
495
+ $schemaErrors.set(flat && Object.keys(flat).length ? flat : void 0);
496
+ };
497
+ const runValidation = async () => {
498
+ const errs = await validateAsync();
499
+ updateErrorsSignals(errs);
500
+ return errs;
501
+ };
502
+ const trackValidationTriggers = (node) => {
503
+ if (node == null) return;
504
+ if (isPlainObject4(node) && node.$value && node.$meta) {
505
+ if (validationOn === "onChange" || validationOn === "all") node.$value.value();
506
+ if (validationOn === "onBlur" || validationOn === "all") node.$meta.value().touched;
507
+ if (validationOn === "onFocus" || validationOn === "all") node.$meta.value().focused;
508
+ return;
509
+ }
510
+ if (isPlainObject4(node) && typeof node.$items?.value === "function") {
511
+ const items = node.$items.value();
512
+ if (Array.isArray(items)) for (const row of items) trackValidationTriggers(row);
513
+ return;
514
+ }
515
+ if (isPlainObject4(node) && isPlainObject4(node.fields)) {
516
+ for (const child of Object.values(node.fields)) trackValidationTriggers(child);
517
+ return;
518
+ }
519
+ if (!isPlainObject4(node)) return;
520
+ for (const child of Object.values(node)) trackValidationTriggers(child);
521
+ };
522
+ if (validationOn !== "manual") {
523
+ effect(() => {
524
+ trackValidationTriggers(resolvedFields);
525
+ void runValidation();
526
+ });
527
+ }
528
+ const reset = () => {
529
+ deepReset(resolvedFields);
530
+ };
531
+ const getValue = () => snapshotValue();
532
+ const hydrate = (partial) => {
533
+ hydrateFormFields(
534
+ resolvedFields,
535
+ partial
536
+ );
537
+ };
538
+ const submit = async (handler) => {
539
+ $submitCount.update((n) => n + 1);
540
+ const errors = await runValidation();
541
+ const hasErrors = (obj) => {
542
+ if (obj == null) return false;
543
+ if (Array.isArray(obj)) {
544
+ if (obj.length === 0) return false;
545
+ return obj.some(hasErrors);
546
+ }
547
+ if (typeof obj === "object") {
548
+ const entries = Object.values(obj);
549
+ return entries.some(hasErrors);
550
+ }
551
+ if (typeof obj === "string") return obj.length > 0;
552
+ return false;
553
+ };
554
+ if (hasErrors(errors)) {
555
+ return { ok: false, errors };
556
+ }
557
+ $submitting.set(true);
558
+ try {
559
+ const value = getValue();
560
+ await handler(value);
561
+ return { ok: true, value };
562
+ } catch (error) {
563
+ return { ok: false, errors: { submit: error } };
564
+ } finally {
565
+ $submitting.set(false);
566
+ }
567
+ };
568
+ const form = {
569
+ displayName: opts.name,
570
+ fields: resolvedFields,
571
+ $submitting,
572
+ $submitCount,
573
+ $errors,
574
+ $schemaErrors,
575
+ arrayActions: {},
576
+ validate,
577
+ validateAsync,
578
+ reset,
579
+ hydrate,
580
+ submit
581
+ };
582
+ if (opts.arrayActions) {
583
+ form.arrayActions = opts.arrayActions(form);
584
+ }
585
+ return form;
586
+ }
587
+
588
+ // src/primitives/generate-append-to-array.ts
589
+ var isPlainObject5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
590
+ var getFieldArray = (node) => {
591
+ if (!isPlainObject5(node)) throw new Error("generateAppendToArray: expected FieldArray node");
592
+ if (!("$items" in node) || typeof node.append !== "function") {
593
+ throw new Error("generateAppendToArray: path does not resolve to a FieldArray");
594
+ }
595
+ return node;
596
+ };
597
+ var generateAppendToArray = (form, makeRow, path) => {
598
+ const segments = path.split(".").filter(Boolean);
599
+ if (segments.length === 0) throw new Error("generateAppendToArray: empty path");
600
+ return (...indices) => {
601
+ if (indices.length !== Math.max(0, segments.length - 1)) {
602
+ throw new Error(
603
+ `generateAppendToArray: expected ${Math.max(0, segments.length - 1)} indices, got ${indices.length}`
604
+ );
605
+ }
606
+ let cursor = form.fields;
607
+ for (let index = 0; index < segments.length; index++) {
608
+ const key = segments[index];
609
+ const isTarget = index === segments.length - 1;
610
+ const next = cursor?.[key];
611
+ if (isTarget) {
612
+ getFieldArray(next).append(makeRow());
613
+ return;
614
+ }
615
+ const arr = getFieldArray(next);
616
+ const idx = indices[index];
617
+ const items = arr.$items.value();
618
+ const row = items[idx];
619
+ cursor = row;
620
+ }
621
+ };
622
+ };
623
+
624
+ // src/primitives/generate-remove-from-array.ts
625
+ var isPlainObject6 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
626
+ var getFieldArray2 = (node) => {
627
+ if (!isPlainObject6(node)) throw new Error("generateRemoveFromArray: expected FieldArray node");
628
+ if (!("$items" in node) || typeof node.removeAt !== "function") {
629
+ throw new Error("generateRemoveFromArray: path does not resolve to a FieldArray");
630
+ }
631
+ return node;
632
+ };
633
+ var generateRemoveFromArray = (form, path) => {
634
+ const segments = path.split(".").filter(Boolean);
635
+ if (segments.length === 0) throw new Error("generateRemoveFromArray: empty path");
636
+ return (...indices) => {
637
+ if (indices.length !== segments.length) {
638
+ throw new Error(
639
+ `generateRemoveFromArray: expected ${segments.length} indices, got ${indices.length}`
640
+ );
641
+ }
642
+ let cursor = form.fields;
643
+ for (let index = 0; index < segments.length; index++) {
644
+ const key = segments[index];
645
+ const isTarget = index === segments.length - 1;
646
+ const next = cursor?.[key];
647
+ const arr = getFieldArray2(next);
648
+ if (isTarget) {
649
+ const removeIndex = indices[index];
650
+ arr.removeAt(removeIndex);
651
+ return;
652
+ }
653
+ const parentIndex = indices[index];
654
+ const items = arr.$items.value();
655
+ const row = items[parentIndex];
656
+ cursor = row;
657
+ }
658
+ };
659
+ };
660
+
661
+ // src/primitives/array-generator.ts
662
+ var arrayGenerator = {
663
+ append(form, makeRow, path) {
664
+ return generateAppendToArray(form, makeRow, path);
665
+ },
666
+ remove(form, path) {
667
+ return generateRemoveFromArray(form, path);
668
+ }
669
+ };
670
+
671
+ // src/primitives/field-kit.ts
672
+ var isPlainObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
673
+ var asRecord2 = (v) => v;
674
+ var createFieldKit = (seed) => {
675
+ const str = (key, _schema, parent, _extra) => {
676
+ const src = asRecord2(parent ?? seed);
677
+ const raw = src[key];
678
+ const initial = String(raw ?? "");
679
+ return createField(initial);
680
+ };
681
+ const num = (key, _schema, parent, _extra) => {
682
+ const src = asRecord2(parent ?? seed);
683
+ const raw = src[key];
684
+ const n = typeof raw === "number" ? raw : Number.parseFloat(String(raw));
685
+ const initial = Number.isFinite(n) ? n : 0;
686
+ return createField(initial);
687
+ };
688
+ const bool = (key, _schema, parent, _extra) => {
689
+ const src = asRecord2(parent ?? seed);
690
+ const raw = src[key];
691
+ const initial = Boolean(raw);
692
+ return createField(initial);
693
+ };
694
+ function pick(key, _schema, parent, fallback, _extra) {
695
+ const src = asRecord2(parent ?? seed);
696
+ const raw = src[key];
697
+ const options = _schema.options;
698
+ const initial = typeof raw === "string" && options.includes(raw) ? raw : fallback;
699
+ return createField(initial);
700
+ }
701
+ const list = (key, rowFactory, parent) => {
702
+ const src = asRecord2(parent ?? seed);
703
+ const raw = src[key];
704
+ const arr = Array.isArray(raw) ? raw : [];
705
+ return createFieldArray(
706
+ arr.map((item, index) => rowFactory(isPlainObject7(item) ? item : {}, index))
707
+ );
708
+ };
709
+ return { str, num, bool, pick, list };
710
+ };
711
+
712
+ // src/validation/flatten-validation.ts
713
+ var flattenValidationErrors = (node, prefix = "") => {
714
+ const out = {};
715
+ const join = (path) => prefix.length ? `${prefix}.${path}` : path;
716
+ const joinIdx = (index) => prefix.length ? `${prefix}.${index}` : String(index);
717
+ if (node == null) return out;
718
+ if (typeof node === "string") {
719
+ if (node.trim().length > 0) out[prefix.length ? prefix : "$root"] = [node];
720
+ return out;
721
+ }
722
+ if (Array.isArray(node)) {
723
+ if (node.length === 0) return out;
724
+ if (node.every((value) => typeof value === "string")) {
725
+ const msgs = node.filter((x) => typeof x === "string" && x.length > 0);
726
+ if (msgs.length > 0) out[prefix.length ? prefix : "$root"] = msgs;
727
+ return out;
728
+ }
729
+ for (let i = 0; i < node.length; i++) {
730
+ Object.assign(out, flattenValidationErrors(node[i], joinIdx(i)));
731
+ }
732
+ return out;
733
+ }
734
+ if (typeof node === "object") {
735
+ for (const [key, value] of Object.entries(node)) {
736
+ Object.assign(out, flattenValidationErrors(value, join(key)));
737
+ }
738
+ return out;
739
+ }
740
+ return out;
741
+ };
742
+ var filterRootSchemaErrorsDeferredToFieldErrors = (fieldFlat, schemaFlat) => {
743
+ const fieldKeysWithErrors = Object.entries(fieldFlat).filter(([, msgs]) => msgs.length > 0).map(([key]) => key);
744
+ const filtered = {};
745
+ for (const [schemaKey, msgs] of Object.entries(schemaFlat)) {
746
+ if (!msgs.length) continue;
747
+ const blocked = fieldKeysWithErrors.some((fk) => {
748
+ if (fk === schemaKey) return true;
749
+ if (fk.startsWith(`${schemaKey}.`)) return true;
750
+ if (schemaKey.startsWith(`${fk}.`)) return true;
751
+ return false;
752
+ });
753
+ if (!blocked) filtered[schemaKey] = msgs;
754
+ }
755
+ return filtered;
756
+ };
757
+
758
+ // src/bindings/hyperdom.ts
759
+ var getBinding = (field) => field.handlers;
760
+ var readFieldRef = (field) => field.value();
761
+ var writeFieldRef = (field, next) => {
762
+ field.set(next);
763
+ };
764
+ var parseNumericInput = (raw) => {
765
+ const n = Number.parseFloat(raw);
766
+ return Number.isFinite(n) ? n : 0;
767
+ };
768
+ var controlledStringValue = (field) => {
769
+ return () => {
770
+ const v = readFieldRef(field);
771
+ if (v == null) return "";
772
+ return String(v);
773
+ };
774
+ };
775
+ var controlledNumericStringValue = (field, whenInvalid) => {
776
+ return () => {
777
+ const v = readFieldRef(field);
778
+ if (v == null || typeof v === "number" && Number.isNaN(v)) {
779
+ return whenInvalid === "empty" ? "" : "0";
780
+ }
781
+ return String(v);
782
+ };
783
+ };
784
+ var textLikeHandlers = (b) => ({
785
+ onInput: b.onInputText,
786
+ onChange: b.onChangeText,
787
+ onFocus: b.onFocus,
788
+ onBlur: b.onBlur
789
+ });
790
+ var domNumericInputHandlers = (field) => {
791
+ const sync = (e) => {
792
+ writeFieldRef(field, parseNumericInput(e.currentTarget.value));
793
+ };
794
+ return { onInput: sync, onChange: sync };
795
+ };
796
+ var bindField = (field, opts) => {
797
+ const b = getBinding(field);
798
+ switch (opts.variant) {
799
+ case "text":
800
+ case "email":
801
+ case "password":
802
+ case "search":
803
+ case "url": {
804
+ const base = { type: opts.variant, ...textLikeHandlers(b) };
805
+ if (!opts.controlledValue) return base;
806
+ return { ...base, value: controlledStringValue(field) };
807
+ }
808
+ case "textarea": {
809
+ const base = { ...textLikeHandlers(b) };
810
+ if (!opts.controlledValue) return base;
811
+ return { ...base, value: controlledStringValue(field) };
812
+ }
813
+ case "checkbox": {
814
+ return {
815
+ type: "checkbox",
816
+ checked: () => Boolean(readFieldRef(field)),
817
+ onChange: b.onChangeText,
818
+ onFocus: b.onFocus,
819
+ onBlur: b.onBlur
820
+ };
821
+ }
822
+ case "select": {
823
+ const onChange = (e) => {
824
+ writeFieldRef(field, e.currentTarget.value);
825
+ };
826
+ const base = {
827
+ onChange,
828
+ onFocus: b.onFocus,
829
+ onBlur: b.onBlur
830
+ };
831
+ if (opts.controlledValue === false) return base;
832
+ return {
833
+ ...base,
834
+ value: controlledStringValue(field)
835
+ };
836
+ }
837
+ case "number": {
838
+ const numeric = domNumericInputHandlers(field);
839
+ const base = {
840
+ type: "number",
841
+ ...numeric,
842
+ onFocus: b.onFocus,
843
+ onBlur: b.onBlur
844
+ };
845
+ if (!opts.controlledValue) return base;
846
+ return { ...base, value: controlledNumericStringValue(field, "empty") };
847
+ }
848
+ case "range": {
849
+ const numeric = domNumericInputHandlers(field);
850
+ const base = {
851
+ type: "range",
852
+ min: opts.min,
853
+ max: opts.max,
854
+ step: opts.step,
855
+ ...numeric,
856
+ onFocus: b.onFocus,
857
+ onBlur: b.onBlur
858
+ };
859
+ if (!opts.controlledValue) return base;
860
+ return { ...base, value: controlledNumericStringValue(field, "zero") };
861
+ }
862
+ }
863
+ };
864
+
865
+ export { arrayGenerator, bindField, collectFormValueFromFields, createField, createFieldArray, createFieldKit, createFieldSet, createForm, defineNestedFieldArrayOps, filterRootSchemaErrorsDeferredToFieldErrors, flattenFieldErrors, flattenValidationErrors, generateAppendToArray, generateRemoveFromArray, hydrateFormFields, normalizeStandardSchemaIssues, normalizeStandardSchemaPathSegments, standardSchemaIssuesForUnknown, standardSchemaIssuesForUnknownSync };
866
+ //# sourceMappingURL=index.js.map
867
+ //# sourceMappingURL=index.js.map