dalila 1.9.13 → 1.9.14
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/form/form-types.d.ts +38 -1
- package/dist/form/form.js +374 -36
- package/dist/form/index.d.ts +1 -0
- package/dist/form/index.js +1 -0
- package/dist/form/schema-adapters.d.ts +14 -0
- package/dist/form/schema-adapters.js +304 -0
- package/package.json +1 -1
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
* Type utilities for path-based access
|
|
3
3
|
*/
|
|
4
4
|
export type FieldErrors = Record<string, string>;
|
|
5
|
+
export interface SchemaValidationIssue {
|
|
6
|
+
path?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SchemaValidationResult<T = unknown> {
|
|
10
|
+
value?: T;
|
|
11
|
+
issues?: SchemaValidationIssue[];
|
|
12
|
+
formError?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface FormSchemaAdapter<T = unknown> {
|
|
15
|
+
/**
|
|
16
|
+
* Validate full form data.
|
|
17
|
+
*/
|
|
18
|
+
validate(data: unknown): SchemaValidationResult<T> | Promise<SchemaValidationResult<T>>;
|
|
19
|
+
/**
|
|
20
|
+
* Optional field-level validation. Useful for validateOn blur/change.
|
|
21
|
+
*/
|
|
22
|
+
validateField?(path: string, value: unknown, data: unknown): SchemaValidationResult<T> | Promise<SchemaValidationResult<T>>;
|
|
23
|
+
/**
|
|
24
|
+
* Optional fallback mapper for unknown schema errors.
|
|
25
|
+
*/
|
|
26
|
+
mapErrors?(error: unknown): SchemaValidationIssue[] | {
|
|
27
|
+
formError?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
5
30
|
export interface FormSubmitContext {
|
|
6
31
|
signal: AbortSignal;
|
|
7
32
|
}
|
|
@@ -23,7 +48,14 @@ export interface FormOptions<T> {
|
|
|
23
48
|
validate?: (data: T) => FieldErrors | {
|
|
24
49
|
fieldErrors?: FieldErrors;
|
|
25
50
|
formError?: string;
|
|
26
|
-
} | void
|
|
51
|
+
} | void | Promise<FieldErrors | {
|
|
52
|
+
fieldErrors?: FieldErrors;
|
|
53
|
+
formError?: string;
|
|
54
|
+
} | void>;
|
|
55
|
+
/**
|
|
56
|
+
* Optional schema adapter (zod/valibot/yup/custom).
|
|
57
|
+
*/
|
|
58
|
+
schema?: FormSchemaAdapter<T>;
|
|
27
59
|
/**
|
|
28
60
|
* When to run validation:
|
|
29
61
|
* - "submit" (default): only on submit
|
|
@@ -114,6 +146,11 @@ export interface Form<T> {
|
|
|
114
146
|
* Create or get a field array
|
|
115
147
|
*/
|
|
116
148
|
fieldArray<TItem = unknown>(path: string): FieldArray<TItem>;
|
|
149
|
+
/**
|
|
150
|
+
* Watch a specific path and run callback when its value changes.
|
|
151
|
+
* Returns an idempotent unsubscribe function.
|
|
152
|
+
*/
|
|
153
|
+
watch(path: string, fn: (next: unknown, prev: unknown) => void): () => void;
|
|
117
154
|
}
|
|
118
155
|
export interface FieldArrayItem<T = unknown> {
|
|
119
156
|
key: string;
|
package/dist/form/form.js
CHANGED
|
@@ -275,6 +275,7 @@ export function createForm(options = {}) {
|
|
|
275
275
|
// Registry
|
|
276
276
|
const fieldRegistry = new Map();
|
|
277
277
|
const fieldArrayRegistry = new Map();
|
|
278
|
+
const pathWatchers = new Set();
|
|
278
279
|
// Form element reference
|
|
279
280
|
let formElement = null;
|
|
280
281
|
// Submit abort controller
|
|
@@ -282,6 +283,94 @@ export function createForm(options = {}) {
|
|
|
282
283
|
// Default values
|
|
283
284
|
let defaultValues = {};
|
|
284
285
|
let defaultsInitialized = false;
|
|
286
|
+
function getCurrentSnapshot(preferFieldArrayPaths) {
|
|
287
|
+
const snapshot = formElement
|
|
288
|
+
? (options.parse ?? parseFormData)(formElement, new FormData(formElement))
|
|
289
|
+
: {};
|
|
290
|
+
// Field arrays may exist before DOM render; overlay them when DOM snapshot
|
|
291
|
+
// has no value for that path, or when callers explicitly prefer array state
|
|
292
|
+
// (useful during reorder mutations before DOM patches apply).
|
|
293
|
+
for (const [path, array] of fieldArrayRegistry) {
|
|
294
|
+
const hasDomValue = getNestedValue(snapshot, path) !== undefined;
|
|
295
|
+
const shouldPreferArrayState = preferFieldArrayPaths?.has(path) === true;
|
|
296
|
+
if (hasDomValue && !shouldPreferArrayState)
|
|
297
|
+
continue;
|
|
298
|
+
const rows = array.fields().map((item) => item.value);
|
|
299
|
+
setNestedValue(snapshot, path, rows);
|
|
300
|
+
}
|
|
301
|
+
return snapshot;
|
|
302
|
+
}
|
|
303
|
+
function readPathValue(path, preferFieldArrayPaths) {
|
|
304
|
+
return getNestedValue(getCurrentSnapshot(preferFieldArrayPaths), path);
|
|
305
|
+
}
|
|
306
|
+
function isPathRelated(pathA, pathB) {
|
|
307
|
+
if (pathA === pathB)
|
|
308
|
+
return true;
|
|
309
|
+
if (pathA.startsWith(`${pathB}.`) || pathA.startsWith(`${pathB}[`))
|
|
310
|
+
return true;
|
|
311
|
+
if (pathB.startsWith(`${pathA}.`) || pathB.startsWith(`${pathA}[`))
|
|
312
|
+
return true;
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
function emitWatchCallback(callback, next, prev) {
|
|
316
|
+
try {
|
|
317
|
+
callback(next, prev);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
if (typeof console !== 'undefined') {
|
|
321
|
+
console.error('[Dalila] Error in form.watch callback:', err);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function isPlainObject(value) {
|
|
326
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
327
|
+
}
|
|
328
|
+
function areValuesEqual(a, b) {
|
|
329
|
+
if (Object.is(a, b))
|
|
330
|
+
return true;
|
|
331
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
332
|
+
if (a.length !== b.length)
|
|
333
|
+
return false;
|
|
334
|
+
for (let i = 0; i < a.length; i++) {
|
|
335
|
+
if (!areValuesEqual(a[i], b[i]))
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
341
|
+
const aKeys = Object.keys(a);
|
|
342
|
+
const bKeys = Object.keys(b);
|
|
343
|
+
if (aKeys.length !== bKeys.length)
|
|
344
|
+
return false;
|
|
345
|
+
for (const key of aKeys) {
|
|
346
|
+
if (!(key in b))
|
|
347
|
+
return false;
|
|
348
|
+
if (!areValuesEqual(a[key], b[key]))
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
function notifyWatchers(changedPath, opts = {}) {
|
|
356
|
+
if (pathWatchers.size === 0)
|
|
357
|
+
return;
|
|
358
|
+
const preferFieldArrayPaths = opts.preferFieldArrayPaths != null
|
|
359
|
+
? new Set(opts.preferFieldArrayPaths)
|
|
360
|
+
: opts.preferFieldArrayPath
|
|
361
|
+
? new Set([opts.preferFieldArrayPath])
|
|
362
|
+
: undefined;
|
|
363
|
+
for (const watcher of pathWatchers) {
|
|
364
|
+
if (changedPath && !isPathRelated(changedPath, watcher.path))
|
|
365
|
+
continue;
|
|
366
|
+
const next = readPathValue(watcher.path, preferFieldArrayPaths);
|
|
367
|
+
if (areValuesEqual(next, watcher.lastValue))
|
|
368
|
+
continue;
|
|
369
|
+
const prev = watcher.lastValue;
|
|
370
|
+
watcher.lastValue = next;
|
|
371
|
+
emitWatchCallback(watcher.callback, next, prev);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
285
374
|
// Initialize defaults
|
|
286
375
|
(async () => {
|
|
287
376
|
try {
|
|
@@ -317,6 +406,189 @@ export function createForm(options = {}) {
|
|
|
317
406
|
// Validation mode
|
|
318
407
|
const validateOn = options.validateOn ?? 'submit';
|
|
319
408
|
let hasSubmitted = false;
|
|
409
|
+
let validationRunSeq = 0;
|
|
410
|
+
let formValidationRunId = 0;
|
|
411
|
+
let formInvalidationId = 0;
|
|
412
|
+
const latestFieldValidationByPath = new Map();
|
|
413
|
+
function beginValidation(path) {
|
|
414
|
+
if (path) {
|
|
415
|
+
// Field-level validation should invalidate pending full-form validations,
|
|
416
|
+
// but keep other field paths independent.
|
|
417
|
+
formInvalidationId += 1;
|
|
418
|
+
const runId = ++validationRunSeq;
|
|
419
|
+
latestFieldValidationByPath.set(path, runId);
|
|
420
|
+
return {
|
|
421
|
+
kind: 'path',
|
|
422
|
+
path,
|
|
423
|
+
runId,
|
|
424
|
+
formRunIdAtStart: formValidationRunId,
|
|
425
|
+
formInvalidationIdAtStart: formInvalidationId,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// Full-form validation invalidates previous full-form and field-level runs.
|
|
429
|
+
formValidationRunId += 1;
|
|
430
|
+
latestFieldValidationByPath.clear();
|
|
431
|
+
const runId = ++validationRunSeq;
|
|
432
|
+
return {
|
|
433
|
+
kind: 'form',
|
|
434
|
+
runId,
|
|
435
|
+
formRunIdAtStart: formValidationRunId,
|
|
436
|
+
formInvalidationIdAtStart: formInvalidationId,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function isValidationCurrent(token) {
|
|
440
|
+
if (token.kind === 'form') {
|
|
441
|
+
return token.formRunIdAtStart === formValidationRunId &&
|
|
442
|
+
token.formInvalidationIdAtStart === formInvalidationId;
|
|
443
|
+
}
|
|
444
|
+
if (!token.path)
|
|
445
|
+
return false;
|
|
446
|
+
return token.formRunIdAtStart === formValidationRunId &&
|
|
447
|
+
latestFieldValidationByPath.get(token.path) === token.runId;
|
|
448
|
+
}
|
|
449
|
+
function isPromiseLike(value) {
|
|
450
|
+
return !!value && typeof value.then === 'function';
|
|
451
|
+
}
|
|
452
|
+
function mergeValidationOutcomes(outcomes) {
|
|
453
|
+
const mergedFieldErrors = {};
|
|
454
|
+
let formError = undefined;
|
|
455
|
+
let value;
|
|
456
|
+
let hasValue = false;
|
|
457
|
+
for (const outcome of outcomes) {
|
|
458
|
+
for (const [path, message] of Object.entries(outcome.fieldErrors)) {
|
|
459
|
+
if (!(path in mergedFieldErrors)) {
|
|
460
|
+
mergedFieldErrors[path] = message;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (formError == null && typeof outcome.formError === 'string' && outcome.formError.length > 0) {
|
|
464
|
+
formError = outcome.formError;
|
|
465
|
+
}
|
|
466
|
+
if (outcome.hasValue) {
|
|
467
|
+
hasValue = true;
|
|
468
|
+
value = outcome.value;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return { fieldErrors: mergedFieldErrors, formError, value, hasValue };
|
|
472
|
+
}
|
|
473
|
+
function normalizeLegacyValidationResult(result) {
|
|
474
|
+
if (!result || typeof result !== 'object') {
|
|
475
|
+
return { fieldErrors: {} };
|
|
476
|
+
}
|
|
477
|
+
if ('fieldErrors' in result || 'formError' in result) {
|
|
478
|
+
const typedResult = result;
|
|
479
|
+
return {
|
|
480
|
+
fieldErrors: typedResult.fieldErrors ?? {},
|
|
481
|
+
formError: typedResult.formError,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
return { fieldErrors: result };
|
|
485
|
+
}
|
|
486
|
+
function normalizeSchemaValidationResult(result) {
|
|
487
|
+
const fieldErrors = {};
|
|
488
|
+
let formError = result.formError;
|
|
489
|
+
const hasValue = Object.prototype.hasOwnProperty.call(result, 'value');
|
|
490
|
+
for (const issue of result.issues ?? []) {
|
|
491
|
+
if (issue.path && !(issue.path in fieldErrors)) {
|
|
492
|
+
fieldErrors[issue.path] = issue.message;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (!issue.path && formError == null) {
|
|
496
|
+
formError = issue.message;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return { fieldErrors, formError, value: result.value, hasValue };
|
|
500
|
+
}
|
|
501
|
+
function runLegacyValidation(data) {
|
|
502
|
+
if (!options.validate)
|
|
503
|
+
return { fieldErrors: {} };
|
|
504
|
+
const result = options.validate(data);
|
|
505
|
+
if (isPromiseLike(result)) {
|
|
506
|
+
return result
|
|
507
|
+
.then((resolved) => normalizeLegacyValidationResult(resolved))
|
|
508
|
+
.catch((error) => ({
|
|
509
|
+
fieldErrors: {},
|
|
510
|
+
formError: error instanceof Error ? error.message : 'Validation failed',
|
|
511
|
+
}));
|
|
512
|
+
}
|
|
513
|
+
return normalizeLegacyValidationResult(result);
|
|
514
|
+
}
|
|
515
|
+
function runSchemaValidation(data, path) {
|
|
516
|
+
if (!options.schema)
|
|
517
|
+
return { fieldErrors: {} };
|
|
518
|
+
const schema = options.schema;
|
|
519
|
+
const execute = () => {
|
|
520
|
+
if (path && schema.validateField) {
|
|
521
|
+
return schema.validateField(path, getNestedValue(data, path), data);
|
|
522
|
+
}
|
|
523
|
+
return schema.validate(data);
|
|
524
|
+
};
|
|
525
|
+
const normalizeMappedError = (error) => {
|
|
526
|
+
const mapped = schema.mapErrors?.(error);
|
|
527
|
+
if (Array.isArray(mapped)) {
|
|
528
|
+
if (mapped.length === 0) {
|
|
529
|
+
return {
|
|
530
|
+
fieldErrors: {},
|
|
531
|
+
formError: error instanceof Error ? error.message : 'Schema validation failed',
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
return normalizeSchemaValidationResult({ issues: mapped });
|
|
535
|
+
}
|
|
536
|
+
if (mapped && typeof mapped === 'object') {
|
|
537
|
+
const mappedFormError = mapped.formError;
|
|
538
|
+
if (typeof mappedFormError === 'string' && mappedFormError.length > 0) {
|
|
539
|
+
return {
|
|
540
|
+
fieldErrors: {},
|
|
541
|
+
formError: mappedFormError,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
fieldErrors: {},
|
|
546
|
+
formError: error instanceof Error ? error.message : 'Schema validation failed',
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
fieldErrors: {},
|
|
551
|
+
formError: error instanceof Error ? error.message : 'Schema validation failed',
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
try {
|
|
555
|
+
const result = execute();
|
|
556
|
+
if (isPromiseLike(result)) {
|
|
557
|
+
return result
|
|
558
|
+
.then((resolved) => normalizeSchemaValidationResult(resolved))
|
|
559
|
+
.catch((error) => normalizeMappedError(error));
|
|
560
|
+
}
|
|
561
|
+
return normalizeSchemaValidationResult(result);
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
return normalizeMappedError(error);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function applyValidationOutcome(outcome, path) {
|
|
568
|
+
if (path && options.schema?.validateField) {
|
|
569
|
+
errors.update((prev) => {
|
|
570
|
+
const next = {};
|
|
571
|
+
for (const [key, message] of Object.entries(prev)) {
|
|
572
|
+
if (!isPathRelated(key, path)) {
|
|
573
|
+
next[key] = message;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
for (const [key, message] of Object.entries(outcome.fieldErrors)) {
|
|
577
|
+
if (isPathRelated(key, path)) {
|
|
578
|
+
next[key] = message;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return next;
|
|
582
|
+
});
|
|
583
|
+
formErrorSignal.set(outcome.formError ?? null);
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
errors.set(outcome.fieldErrors);
|
|
587
|
+
formErrorSignal.set(outcome.formError ?? null);
|
|
588
|
+
}
|
|
589
|
+
return Object.keys(outcome.fieldErrors).length === 0 &&
|
|
590
|
+
!(typeof outcome.formError === 'string' && outcome.formError.length > 0);
|
|
591
|
+
}
|
|
320
592
|
// Error Management
|
|
321
593
|
function setError(path, message) {
|
|
322
594
|
errors.update((prev) => ({ ...prev, [path]: message }));
|
|
@@ -346,6 +618,26 @@ export function createForm(options = {}) {
|
|
|
346
618
|
function formError() {
|
|
347
619
|
return formErrorSignal();
|
|
348
620
|
}
|
|
621
|
+
function watch(path, fn) {
|
|
622
|
+
const watcher = {
|
|
623
|
+
path,
|
|
624
|
+
callback: fn,
|
|
625
|
+
lastValue: readPathValue(path),
|
|
626
|
+
};
|
|
627
|
+
pathWatchers.add(watcher);
|
|
628
|
+
let active = true;
|
|
629
|
+
const unsubscribe = () => {
|
|
630
|
+
if (!active)
|
|
631
|
+
return;
|
|
632
|
+
active = false;
|
|
633
|
+
pathWatchers.delete(watcher);
|
|
634
|
+
};
|
|
635
|
+
const ownerScope = getCurrentScope();
|
|
636
|
+
if (ownerScope) {
|
|
637
|
+
ownerScope.onCleanup(unsubscribe);
|
|
638
|
+
}
|
|
639
|
+
return unsubscribe;
|
|
640
|
+
}
|
|
349
641
|
// Touched / Dirty Management
|
|
350
642
|
function touched(path) {
|
|
351
643
|
return touchedSet().has(path);
|
|
@@ -372,38 +664,53 @@ export function createForm(options = {}) {
|
|
|
372
664
|
return next;
|
|
373
665
|
});
|
|
374
666
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const typedResult = result;
|
|
387
|
-
if (typedResult.fieldErrors) {
|
|
388
|
-
errors.set(typedResult.fieldErrors);
|
|
389
|
-
}
|
|
390
|
-
if (typedResult.formError) {
|
|
391
|
-
formErrorSignal.set(typedResult.formError);
|
|
392
|
-
}
|
|
393
|
-
// Check if fieldErrors is empty (not just truthy)
|
|
394
|
-
// Validator can return { fieldErrors: {}, formError: null } which is valid
|
|
395
|
-
const hasFieldErrors = typedResult.fieldErrors && Object.keys(typedResult.fieldErrors).length > 0;
|
|
396
|
-
const hasFormError = typedResult.formError && typedResult.formError.length > 0;
|
|
397
|
-
return !hasFieldErrors && !hasFormError;
|
|
667
|
+
function resolveValidationOutcome(data, opts = {}) {
|
|
668
|
+
if (!options.schema && !options.validate) {
|
|
669
|
+
return { fieldErrors: {}, value: data, hasValue: true };
|
|
670
|
+
}
|
|
671
|
+
const runLegacyAfterSchema = (schemaOutcome) => {
|
|
672
|
+
if (!options.validate)
|
|
673
|
+
return schemaOutcome;
|
|
674
|
+
const legacyInput = schemaOutcome.hasValue ? schemaOutcome.value : data;
|
|
675
|
+
const legacyOutcome = runLegacyValidation(legacyInput);
|
|
676
|
+
if (!isPromiseLike(legacyOutcome)) {
|
|
677
|
+
return mergeValidationOutcomes([schemaOutcome, legacyOutcome]);
|
|
398
678
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
679
|
+
return Promise.resolve(legacyOutcome).then((resolvedLegacy) => mergeValidationOutcomes([schemaOutcome, resolvedLegacy]));
|
|
680
|
+
};
|
|
681
|
+
if (options.schema) {
|
|
682
|
+
const schemaOutcome = runSchemaValidation(data, opts.path);
|
|
683
|
+
if (!isPromiseLike(schemaOutcome)) {
|
|
684
|
+
return runLegacyAfterSchema(schemaOutcome);
|
|
404
685
|
}
|
|
686
|
+
return Promise.resolve(schemaOutcome).then((resolvedSchema) => runLegacyAfterSchema(resolvedSchema));
|
|
687
|
+
}
|
|
688
|
+
return runLegacyValidation(data);
|
|
689
|
+
}
|
|
690
|
+
function finalizeValidation(token, sourceData, outcome, opts = {}) {
|
|
691
|
+
if (!isValidationCurrent(token)) {
|
|
692
|
+
return { isValid: false, data: sourceData };
|
|
693
|
+
}
|
|
694
|
+
const isValid = applyValidationOutcome(outcome, opts.path);
|
|
695
|
+
const data = outcome.hasValue ? outcome.value : sourceData;
|
|
696
|
+
return { isValid, data };
|
|
697
|
+
}
|
|
698
|
+
// Validation
|
|
699
|
+
function validate(data, opts = {}) {
|
|
700
|
+
const token = beginValidation(opts.path);
|
|
701
|
+
const outcome = resolveValidationOutcome(data, opts);
|
|
702
|
+
if (!isPromiseLike(outcome)) {
|
|
703
|
+
return finalizeValidation(token, data, outcome, opts).isValid;
|
|
704
|
+
}
|
|
705
|
+
return outcome.then((resolved) => finalizeValidation(token, data, resolved, opts).isValid);
|
|
706
|
+
}
|
|
707
|
+
function validateForSubmit(data) {
|
|
708
|
+
const token = beginValidation();
|
|
709
|
+
const outcome = resolveValidationOutcome(data);
|
|
710
|
+
if (!isPromiseLike(outcome)) {
|
|
711
|
+
return finalizeValidation(token, data, outcome);
|
|
405
712
|
}
|
|
406
|
-
return
|
|
713
|
+
return outcome.then((resolved) => finalizeValidation(token, data, resolved));
|
|
407
714
|
}
|
|
408
715
|
// Field Registry
|
|
409
716
|
function _registerField(path, element) {
|
|
@@ -423,16 +730,25 @@ export function createForm(options = {}) {
|
|
|
423
730
|
const fd = new FormData(formElement);
|
|
424
731
|
const parser = options.parse ?? parseFormData;
|
|
425
732
|
const data = parser(formElement, fd);
|
|
426
|
-
validate(data);
|
|
733
|
+
const result = validate(data, { path: currentPath });
|
|
734
|
+
if (isPromiseLike(result)) {
|
|
735
|
+
void result.catch((err) => {
|
|
736
|
+
if (typeof console !== 'undefined') {
|
|
737
|
+
console.error('[Dalila] Field validation failed:', err);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
427
741
|
}
|
|
428
742
|
};
|
|
429
743
|
element.addEventListener('blur', handleBlur);
|
|
430
744
|
// Setup change handler for dirty + validation
|
|
431
745
|
const handleChange = () => {
|
|
432
|
-
if (!defaultsInitialized)
|
|
433
|
-
return;
|
|
434
746
|
// Use dynamic path lookup instead of captured path
|
|
435
747
|
const currentPath = getCurrentPath();
|
|
748
|
+
if (!defaultsInitialized) {
|
|
749
|
+
notifyWatchers(currentPath);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
436
752
|
const input = element;
|
|
437
753
|
const defaultValue = getNestedValue(defaultValues, currentPath);
|
|
438
754
|
let currentValue;
|
|
@@ -487,8 +803,16 @@ export function createForm(options = {}) {
|
|
|
487
803
|
const fd = new FormData(formElement);
|
|
488
804
|
const parser = options.parse ?? parseFormData;
|
|
489
805
|
const data = parser(formElement, fd);
|
|
490
|
-
validate(data);
|
|
806
|
+
const result = validate(data, { path: currentPath });
|
|
807
|
+
if (isPromiseLike(result)) {
|
|
808
|
+
void result.catch((err) => {
|
|
809
|
+
if (typeof console !== 'undefined') {
|
|
810
|
+
console.error('[Dalila] Field validation failed:', err);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
}
|
|
491
814
|
}
|
|
815
|
+
notifyWatchers(currentPath);
|
|
492
816
|
};
|
|
493
817
|
element.addEventListener('change', handleChange);
|
|
494
818
|
element.addEventListener('input', handleChange);
|
|
@@ -525,13 +849,14 @@ export function createForm(options = {}) {
|
|
|
525
849
|
const parser = options.parse ?? parseFormData;
|
|
526
850
|
const data = parser(form, fd);
|
|
527
851
|
// Validate
|
|
528
|
-
const
|
|
852
|
+
const validation = await Promise.resolve(validateForSubmit(data));
|
|
853
|
+
const isValid = validation.isValid;
|
|
529
854
|
if (!isValid) {
|
|
530
855
|
focus(); // Focus first error
|
|
531
856
|
return;
|
|
532
857
|
}
|
|
533
858
|
// Call handler
|
|
534
|
-
await handler(data, { signal });
|
|
859
|
+
await handler(validation.data, { signal });
|
|
535
860
|
// If aborted, don't do anything
|
|
536
861
|
if (signal.aborted)
|
|
537
862
|
return;
|
|
@@ -672,6 +997,7 @@ export function createForm(options = {}) {
|
|
|
672
997
|
submitController?.abort();
|
|
673
998
|
submitController = null;
|
|
674
999
|
submittingSignal.set(false);
|
|
1000
|
+
notifyWatchers(undefined, { preferFieldArrayPaths: fieldArrayRegistry.keys() });
|
|
675
1001
|
}
|
|
676
1002
|
// Field Arrays
|
|
677
1003
|
function fieldArray(path) {
|
|
@@ -683,11 +1009,14 @@ export function createForm(options = {}) {
|
|
|
683
1009
|
const array = createFieldArray(path, {
|
|
684
1010
|
form: formElement,
|
|
685
1011
|
scope,
|
|
1012
|
+
onMutate: () => notifyWatchers(path, { preferFieldArrayPath: path }),
|
|
686
1013
|
// Pass meta-state signals for remapping on reorder
|
|
687
1014
|
errors,
|
|
688
1015
|
touchedSet,
|
|
689
1016
|
dirtySet,
|
|
690
1017
|
});
|
|
1018
|
+
// Register before hydration so watch reads can see this array immediately.
|
|
1019
|
+
fieldArrayRegistry.set(path, array);
|
|
691
1020
|
// Initialize from defaultValues if available
|
|
692
1021
|
// When d-array is first rendered, it should start with values from defaultValues
|
|
693
1022
|
if (defaultsInitialized) {
|
|
@@ -696,7 +1025,6 @@ export function createForm(options = {}) {
|
|
|
696
1025
|
array.replace(initialValue);
|
|
697
1026
|
}
|
|
698
1027
|
}
|
|
699
|
-
fieldArrayRegistry.set(path, array);
|
|
700
1028
|
return array;
|
|
701
1029
|
}
|
|
702
1030
|
// Getters
|
|
@@ -711,6 +1039,7 @@ export function createForm(options = {}) {
|
|
|
711
1039
|
}
|
|
712
1040
|
function _setFormElement(form) {
|
|
713
1041
|
formElement = form;
|
|
1042
|
+
notifyWatchers();
|
|
714
1043
|
}
|
|
715
1044
|
// Cleanup on scope disposal
|
|
716
1045
|
if (scope) {
|
|
@@ -718,6 +1047,7 @@ export function createForm(options = {}) {
|
|
|
718
1047
|
submitController?.abort();
|
|
719
1048
|
fieldRegistry.clear();
|
|
720
1049
|
fieldArrayRegistry.clear();
|
|
1050
|
+
pathWatchers.clear();
|
|
721
1051
|
});
|
|
722
1052
|
}
|
|
723
1053
|
return {
|
|
@@ -733,6 +1063,7 @@ export function createForm(options = {}) {
|
|
|
733
1063
|
submitting,
|
|
734
1064
|
submitCount,
|
|
735
1065
|
focus,
|
|
1066
|
+
watch,
|
|
736
1067
|
_registerField,
|
|
737
1068
|
_getFormElement,
|
|
738
1069
|
_setFormElement,
|
|
@@ -844,6 +1175,7 @@ function createFieldArray(basePath, options) {
|
|
|
844
1175
|
newKeys.forEach((key, i) => next.set(key, items[i]));
|
|
845
1176
|
return next;
|
|
846
1177
|
});
|
|
1178
|
+
options.onMutate?.();
|
|
847
1179
|
}
|
|
848
1180
|
function remove(key) {
|
|
849
1181
|
const removeIndex = _getIndex(key);
|
|
@@ -905,6 +1237,7 @@ function createFieldArray(basePath, options) {
|
|
|
905
1237
|
remapMetaState(oldIndices, newIndices);
|
|
906
1238
|
}
|
|
907
1239
|
}
|
|
1240
|
+
options.onMutate?.();
|
|
908
1241
|
}
|
|
909
1242
|
function removeAt(index) {
|
|
910
1243
|
if (index < 0 || index >= keys().length)
|
|
@@ -939,6 +1272,7 @@ function createFieldArray(basePath, options) {
|
|
|
939
1272
|
next.set(key, value);
|
|
940
1273
|
return next;
|
|
941
1274
|
});
|
|
1275
|
+
options.onMutate?.();
|
|
942
1276
|
}
|
|
943
1277
|
function move(fromIndex, toIndex) {
|
|
944
1278
|
const len = keys().length;
|
|
@@ -974,6 +1308,7 @@ function createFieldArray(basePath, options) {
|
|
|
974
1308
|
next.splice(toIndex, 0, item);
|
|
975
1309
|
return next;
|
|
976
1310
|
});
|
|
1311
|
+
options.onMutate?.();
|
|
977
1312
|
}
|
|
978
1313
|
function swap(indexA, indexB) {
|
|
979
1314
|
const len = keys().length;
|
|
@@ -988,6 +1323,7 @@ function createFieldArray(basePath, options) {
|
|
|
988
1323
|
[next[indexA], next[indexB]] = [next[indexB], next[indexA]];
|
|
989
1324
|
return next;
|
|
990
1325
|
});
|
|
1326
|
+
options.onMutate?.();
|
|
991
1327
|
}
|
|
992
1328
|
function replace(newValues) {
|
|
993
1329
|
const newKeys = newValues.map(() => generateKey());
|
|
@@ -1027,6 +1363,7 @@ function createFieldArray(basePath, options) {
|
|
|
1027
1363
|
}
|
|
1028
1364
|
keys.set(newKeys);
|
|
1029
1365
|
values.set(new Map(newKeys.map((key, i) => [key, newValues[i]])));
|
|
1366
|
+
options.onMutate?.();
|
|
1030
1367
|
}
|
|
1031
1368
|
function update(key, value) {
|
|
1032
1369
|
values.update((prev) => {
|
|
@@ -1034,6 +1371,7 @@ function createFieldArray(basePath, options) {
|
|
|
1034
1371
|
next.set(key, value);
|
|
1035
1372
|
return next;
|
|
1036
1373
|
});
|
|
1374
|
+
options.onMutate?.();
|
|
1037
1375
|
}
|
|
1038
1376
|
function updateAt(index, value) {
|
|
1039
1377
|
if (index < 0 || index >= keys().length)
|
package/dist/form/index.d.ts
CHANGED
package/dist/form/index.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FormSchemaAdapter } from "./form-types.js";
|
|
2
|
+
interface ValibotRuntime {
|
|
3
|
+
safeParseAsync?: (schema: unknown, input: unknown) => Promise<unknown>;
|
|
4
|
+
safeParse?: (schema: unknown, input: unknown) => unknown;
|
|
5
|
+
parseAsync?: (schema: unknown, input: unknown) => Promise<unknown>;
|
|
6
|
+
parse?: (schema: unknown, input: unknown) => unknown;
|
|
7
|
+
}
|
|
8
|
+
declare global {
|
|
9
|
+
var valibot: ValibotRuntime | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function zodAdapter<T = unknown>(schema: unknown): FormSchemaAdapter<T>;
|
|
12
|
+
export declare function valibotAdapter<T = unknown>(schema: unknown, runtime?: ValibotRuntime): FormSchemaAdapter<T>;
|
|
13
|
+
export declare function yupAdapter<T = unknown>(schema: unknown): FormSchemaAdapter<T>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
let resolvedValibotRuntime;
|
|
2
|
+
let pendingValibotRuntime = null;
|
|
3
|
+
let attemptedValibotModuleImport = false;
|
|
4
|
+
function isPathRelated(pathA, pathB) {
|
|
5
|
+
if (pathA === pathB)
|
|
6
|
+
return true;
|
|
7
|
+
if (pathA.startsWith(`${pathB}.`) || pathA.startsWith(`${pathB}[`))
|
|
8
|
+
return true;
|
|
9
|
+
if (pathB.startsWith(`${pathA}.`) || pathB.startsWith(`${pathA}[`))
|
|
10
|
+
return true;
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
function normalizePathSegment(segment) {
|
|
14
|
+
if (typeof segment === "string" || typeof segment === "number")
|
|
15
|
+
return segment;
|
|
16
|
+
if (!segment || typeof segment !== "object")
|
|
17
|
+
return null;
|
|
18
|
+
if ("key" in segment) {
|
|
19
|
+
const key = segment.key;
|
|
20
|
+
if (typeof key === "string" || typeof key === "number")
|
|
21
|
+
return key;
|
|
22
|
+
}
|
|
23
|
+
if ("path" in segment) {
|
|
24
|
+
const path = segment.path;
|
|
25
|
+
if (typeof path === "string" || typeof path === "number")
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function normalizePath(path) {
|
|
31
|
+
if (typeof path === "string")
|
|
32
|
+
return path;
|
|
33
|
+
if (typeof path === "number")
|
|
34
|
+
return `[${path}]`;
|
|
35
|
+
if (!Array.isArray(path))
|
|
36
|
+
return undefined;
|
|
37
|
+
let out = "";
|
|
38
|
+
for (const segment of path) {
|
|
39
|
+
const normalized = normalizePathSegment(segment);
|
|
40
|
+
if (normalized == null)
|
|
41
|
+
continue;
|
|
42
|
+
if (typeof normalized === "number") {
|
|
43
|
+
out += `[${normalized}]`;
|
|
44
|
+
}
|
|
45
|
+
else if (out.length === 0) {
|
|
46
|
+
out += normalized;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
out += `.${normalized}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out.length > 0 ? out : undefined;
|
|
53
|
+
}
|
|
54
|
+
function filterIssuesByPath(issues, path) {
|
|
55
|
+
if (!issues || issues.length === 0)
|
|
56
|
+
return [];
|
|
57
|
+
return issues.filter((issue) => issue.path && isPathRelated(issue.path, path));
|
|
58
|
+
}
|
|
59
|
+
function normalizeAdapterFailure(error, map) {
|
|
60
|
+
const issues = map(error);
|
|
61
|
+
if (issues.length > 0) {
|
|
62
|
+
return { issues };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
issues: [],
|
|
66
|
+
formError: error instanceof Error ? error.message : "Schema validation failed",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function mapZodIssues(error) {
|
|
70
|
+
const issues = error?.issues;
|
|
71
|
+
if (!Array.isArray(issues))
|
|
72
|
+
return [];
|
|
73
|
+
const normalized = [];
|
|
74
|
+
for (const issue of issues) {
|
|
75
|
+
if (!issue || typeof issue !== "object")
|
|
76
|
+
continue;
|
|
77
|
+
const message = issue.message;
|
|
78
|
+
if (typeof message !== "string" || message.length === 0)
|
|
79
|
+
continue;
|
|
80
|
+
normalized.push({
|
|
81
|
+
path: normalizePath(issue.path),
|
|
82
|
+
message,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return normalized;
|
|
86
|
+
}
|
|
87
|
+
function mapValibotIssues(error) {
|
|
88
|
+
const issues = error?.issues;
|
|
89
|
+
if (!Array.isArray(issues))
|
|
90
|
+
return [];
|
|
91
|
+
const normalized = [];
|
|
92
|
+
for (const issue of issues) {
|
|
93
|
+
if (!issue || typeof issue !== "object")
|
|
94
|
+
continue;
|
|
95
|
+
const message = issue.message;
|
|
96
|
+
if (typeof message !== "string" || message.length === 0)
|
|
97
|
+
continue;
|
|
98
|
+
normalized.push({
|
|
99
|
+
path: normalizePath(issue.path),
|
|
100
|
+
message,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return normalized;
|
|
104
|
+
}
|
|
105
|
+
async function runSafeParseSchema(schema, data) {
|
|
106
|
+
const s = schema;
|
|
107
|
+
if (typeof s.safeParseAsync === "function") {
|
|
108
|
+
const result = await s.safeParseAsync(data);
|
|
109
|
+
const success = result?.success === true;
|
|
110
|
+
if (success) {
|
|
111
|
+
return { ok: true, value: result.data };
|
|
112
|
+
}
|
|
113
|
+
return { ok: false, error: result.error };
|
|
114
|
+
}
|
|
115
|
+
if (typeof s.safeParse === "function") {
|
|
116
|
+
const result = s.safeParse(data);
|
|
117
|
+
const success = result?.success === true;
|
|
118
|
+
if (success) {
|
|
119
|
+
return { ok: true, value: result.data };
|
|
120
|
+
}
|
|
121
|
+
return { ok: false, error: result.error };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
if (typeof s.parseAsync === "function") {
|
|
125
|
+
return { ok: true, value: await s.parseAsync(data) };
|
|
126
|
+
}
|
|
127
|
+
if (typeof s.parse === "function") {
|
|
128
|
+
return { ok: true, value: s.parse(data) };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return { ok: false, error };
|
|
133
|
+
}
|
|
134
|
+
throw new Error("Invalid schema adapter input: expected safeParse/parse methods.");
|
|
135
|
+
}
|
|
136
|
+
async function resolveValibotRuntime(runtime) {
|
|
137
|
+
if (runtime)
|
|
138
|
+
return runtime;
|
|
139
|
+
const globalRuntime = globalThis.valibot;
|
|
140
|
+
if (globalRuntime) {
|
|
141
|
+
resolvedValibotRuntime = globalRuntime;
|
|
142
|
+
return globalRuntime;
|
|
143
|
+
}
|
|
144
|
+
if (resolvedValibotRuntime)
|
|
145
|
+
return resolvedValibotRuntime;
|
|
146
|
+
if (attemptedValibotModuleImport)
|
|
147
|
+
return null;
|
|
148
|
+
if (!pendingValibotRuntime) {
|
|
149
|
+
attemptedValibotModuleImport = true;
|
|
150
|
+
pendingValibotRuntime = (async () => {
|
|
151
|
+
try {
|
|
152
|
+
const modName = "valibot";
|
|
153
|
+
const imported = await import(modName);
|
|
154
|
+
const moduleRuntime = imported;
|
|
155
|
+
resolvedValibotRuntime = moduleRuntime;
|
|
156
|
+
return moduleRuntime;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
pendingValibotRuntime = null;
|
|
163
|
+
}
|
|
164
|
+
})();
|
|
165
|
+
}
|
|
166
|
+
return pendingValibotRuntime;
|
|
167
|
+
}
|
|
168
|
+
async function runValibotParse(schema, data, runtime) {
|
|
169
|
+
const resolvedRuntime = await resolveValibotRuntime(runtime);
|
|
170
|
+
if (resolvedRuntime) {
|
|
171
|
+
if (typeof resolvedRuntime.safeParseAsync === "function") {
|
|
172
|
+
const result = await resolvedRuntime.safeParseAsync(schema, data);
|
|
173
|
+
const success = result?.success === true;
|
|
174
|
+
if (success) {
|
|
175
|
+
return { ok: true, value: result.output ?? result.data };
|
|
176
|
+
}
|
|
177
|
+
return { ok: false, error: result.issues ? { issues: result.issues } : result.error };
|
|
178
|
+
}
|
|
179
|
+
if (typeof resolvedRuntime.safeParse === "function") {
|
|
180
|
+
const result = resolvedRuntime.safeParse(schema, data);
|
|
181
|
+
const success = result?.success === true;
|
|
182
|
+
if (success) {
|
|
183
|
+
return { ok: true, value: result.output ?? result.data };
|
|
184
|
+
}
|
|
185
|
+
return { ok: false, error: result.issues ? { issues: result.issues } : result.error };
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
if (typeof resolvedRuntime.parseAsync === "function") {
|
|
189
|
+
return { ok: true, value: await resolvedRuntime.parseAsync(schema, data) };
|
|
190
|
+
}
|
|
191
|
+
if (typeof resolvedRuntime.parse === "function") {
|
|
192
|
+
return { ok: true, value: resolvedRuntime.parse(schema, data) };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return { ok: false, error };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
return await runSafeParseSchema(schema, data);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
throw new Error("Invalid valibot adapter input: expected valibot safeParse(schema, input) runtime or schema parse methods.");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export function zodAdapter(schema) {
|
|
207
|
+
function mapErrors(error) {
|
|
208
|
+
return mapZodIssues(error);
|
|
209
|
+
}
|
|
210
|
+
const runValidate = async (data) => {
|
|
211
|
+
const result = await runSafeParseSchema(schema, data);
|
|
212
|
+
if (result.ok) {
|
|
213
|
+
return { value: result.value, issues: [] };
|
|
214
|
+
}
|
|
215
|
+
return normalizeAdapterFailure(result.error, mapErrors);
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
validate: runValidate,
|
|
219
|
+
async validateField(path, _value, data) {
|
|
220
|
+
const full = await runValidate(data);
|
|
221
|
+
return { ...full, issues: filterIssuesByPath(full.issues, path) };
|
|
222
|
+
},
|
|
223
|
+
mapErrors,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
export function valibotAdapter(schema, runtime) {
|
|
227
|
+
function mapErrors(error) {
|
|
228
|
+
return mapValibotIssues(error);
|
|
229
|
+
}
|
|
230
|
+
const runValidate = async (data) => {
|
|
231
|
+
const result = await runValibotParse(schema, data, runtime);
|
|
232
|
+
if (result.ok) {
|
|
233
|
+
return { value: result.value, issues: [] };
|
|
234
|
+
}
|
|
235
|
+
return normalizeAdapterFailure(result.error, mapErrors);
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
validate: runValidate,
|
|
239
|
+
async validateField(path, _value, data) {
|
|
240
|
+
const full = await runValidate(data);
|
|
241
|
+
return { ...full, issues: filterIssuesByPath(full.issues, path) };
|
|
242
|
+
},
|
|
243
|
+
mapErrors,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function mapYupError(error) {
|
|
247
|
+
const err = error;
|
|
248
|
+
const mapped = [];
|
|
249
|
+
if (Array.isArray(err.inner) && err.inner.length > 0) {
|
|
250
|
+
for (const item of err.inner) {
|
|
251
|
+
if (!item || typeof item.message !== "string")
|
|
252
|
+
continue;
|
|
253
|
+
mapped.push({ path: typeof item.path === "string" ? item.path : undefined, message: item.message });
|
|
254
|
+
}
|
|
255
|
+
return mapped;
|
|
256
|
+
}
|
|
257
|
+
if (typeof err.message === "string" && err.message.length > 0) {
|
|
258
|
+
mapped.push({
|
|
259
|
+
path: typeof err.path === "string" ? err.path : undefined,
|
|
260
|
+
message: err.message,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return mapped;
|
|
264
|
+
}
|
|
265
|
+
export function yupAdapter(schema) {
|
|
266
|
+
const s = schema;
|
|
267
|
+
function mapErrors(error) {
|
|
268
|
+
return mapYupError(error);
|
|
269
|
+
}
|
|
270
|
+
const runValidate = async (data) => {
|
|
271
|
+
if (typeof s.validate !== "function") {
|
|
272
|
+
throw new Error("Invalid yup schema adapter input: expected validate().");
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const value = await s.validate(data, { abortEarly: false });
|
|
276
|
+
return { value: value, issues: [] };
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
return normalizeAdapterFailure(error, mapErrors);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
return {
|
|
283
|
+
validate: runValidate,
|
|
284
|
+
async validateField(path, _value, data) {
|
|
285
|
+
if (typeof s.validateAt === "function") {
|
|
286
|
+
try {
|
|
287
|
+
await s.validateAt(path, data, { abortEarly: false });
|
|
288
|
+
return { issues: [] };
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const normalized = normalizeAdapterFailure(error, mapErrors);
|
|
292
|
+
const formLevelIssue = (normalized.issues ?? []).find((issue) => !issue.path);
|
|
293
|
+
return {
|
|
294
|
+
formError: normalized.formError ?? formLevelIssue?.message,
|
|
295
|
+
issues: filterIssuesByPath(normalized.issues, path),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const full = await runValidate(data);
|
|
300
|
+
return { ...full, issues: filterIssuesByPath(full.issues, path) };
|
|
301
|
+
},
|
|
302
|
+
mapErrors,
|
|
303
|
+
};
|
|
304
|
+
}
|