1- import type { ValidationError } from './errors'
1+ import type { ValidationError , ValidationErrorPath } from './errors'
22import type { Field } from './field/type'
33import type { JsfObjectSchema , JsfSchema , NonBooleanJsfSchema , SchemaValue } from './types'
4+ import type { ValidationOptions } from './validation/schema'
45import { getErrorMessage } from './errors/messages'
56import { buildFieldObject } from './field/object'
67import { validateSchema } from './validation/schema'
78import { isObjectValue } from './validation/util'
9+ import { updateFieldVisibility } from './visibility'
10+
11+ export { ValidationOptions } from './validation/schema'
812
913interface FormResult {
1014 fields : Field [ ]
@@ -26,6 +30,39 @@ export interface ValidationResult {
2630 formErrors ?: FormErrors
2731}
2832
33+ /**
34+ * Remove composition keywords and their indices as well as conditional keywords from the path
35+ * @param path - The path to clean
36+ * @returns The cleaned path
37+ * @example
38+ * ```ts
39+ * cleanErrorPath(['some_object','allOf', 0, 'then', 'field'])
40+ * // ['some_object', 'field']
41+ * ```
42+ */
43+ function cleanErrorPath ( path : ValidationErrorPath ) : ValidationErrorPath {
44+ const result : ValidationErrorPath = [ ]
45+
46+ for ( let i = 0 ; i < path . length ; i ++ ) {
47+ const segment = path [ i ]
48+
49+ if ( [ 'allOf' , 'anyOf' , 'oneOf' ] . includes ( segment as string ) ) {
50+ if ( i + 1 < path . length && typeof path [ i + 1 ] === 'number' ) {
51+ i ++
52+ }
53+ continue
54+ }
55+
56+ if ( segment === 'then' || segment === 'else' ) {
57+ continue
58+ }
59+
60+ result . push ( segment )
61+ }
62+
63+ return result
64+ }
65+
2966/**
3067 * Transform validation errors into an object with the field names as keys and the error messages as values.
3168 * For nested fields, creates a nested object structure rather than using dot notation.
@@ -56,61 +93,32 @@ function validationErrorsToFormErrors(errors: ValidationErrorWithMessage[]): For
5693 return result
5794 }
5895
59- // For conditional validation branches (then/else), show the error at the field level
60- const thenElseIndex = Math . max ( path . indexOf ( 'then' ) , path . indexOf ( 'else' ) )
61- if ( thenElseIndex !== - 1 ) {
62- const fieldName = path [ thenElseIndex + 1 ]
63- if ( fieldName ) {
64- result [ fieldName ] = error . message
65- return result
66- }
67- }
68-
69- // For allOf/anyOf/oneOf validation errors, show the error at the field level
70- const compositionKeywords = [ 'allOf' , 'anyOf' , 'oneOf' ]
71- const compositionIndex = compositionKeywords . reduce ( ( index , keyword ) => {
72- const keywordIndex = path . indexOf ( keyword )
73- return keywordIndex !== - 1 ? keywordIndex : index
74- } , - 1 )
75-
76- if ( compositionIndex !== - 1 ) {
77- // Get the field path before the composition keyword
78- const fieldPath = path . slice ( 0 , compositionIndex )
79- if ( fieldPath . length > 0 ) {
80- let current = result
81-
82- // Process all segments except the last one
83- fieldPath . slice ( 0 , - 1 ) . forEach ( ( segment ) => {
84- if ( ! ( segment in current ) || typeof current [ segment ] === 'string' ) {
85- current [ segment ] = { }
86- }
87- current = current [ segment ] as FormErrors
88- } )
89-
90- // Set the message at the last segment
91- const lastSegment = fieldPath [ fieldPath . length - 1 ]
92- current [ lastSegment ] = error . message
93- return result
94- }
95- }
96+ // Clean the path to remove intermediate composition structures
97+ const cleanedPath = cleanErrorPath ( path )
9698
97- // For all other paths, recursively build the nested structure
99+ // For all paths, recursively build the nested structure
98100 let current = result
99101
100102 // Process all segments except the last one (which will hold the message)
101- path . slice ( 0 , - 1 ) . forEach ( ( segment ) => {
103+ cleanedPath . slice ( 0 , - 1 ) . forEach ( ( segment ) => {
102104 // If this segment doesn't exist yet or is currently a string (from a previous error),
103105 // initialize it as an object
104106 if ( ! ( segment in current ) || typeof current [ segment ] === 'string' ) {
105107 current [ segment ] = { }
106108 }
107109
108- current = current [ segment ]
110+ current = current [ segment ] as FormErrors
109111 } )
110112
111113 // Set the message at the final level
112- const lastSegment = path [ path . length - 1 ]
113- current [ lastSegment ] = error . message
114+ if ( cleanedPath . length > 0 ) {
115+ const lastSegment = cleanedPath [ cleanedPath . length - 1 ]
116+ current [ lastSegment ] = error . message
117+ }
118+ else {
119+ // Fallback for unexpected path structures
120+ result [ '' ] = error . message
121+ }
114122
115123 return result
116124 } , { } )
@@ -257,9 +265,6 @@ function validate(value: SchemaValue, schema: JsfSchema, options: ValidationOpti
257265 const result : ValidationResult = { }
258266 const errors = validateSchema ( value , schema , options )
259267
260- // console.log(errors)
261-
262- // Apply custom error messages before converting to form errors
263268 const errorsWithMessages = addErrorMessages ( value , schema , errors )
264269 const processedErrors = applyCustomErrorMessages ( errorsWithMessages , schema )
265270
@@ -272,42 +277,34 @@ function validate(value: SchemaValue, schema: JsfSchema, options: ValidationOpti
272277 return result
273278}
274279
275- export interface ValidationOptions {
276- /**
277- * A null value will be treated as undefined.
278- * That means that when validating a null value, against a non-required field that is not of type 'null' or ['null']
279- * the validation will succeed instead of returning a type error.
280- * @default false
281- */
282- treatNullAsUndefined ?: boolean
283- }
284-
285280export interface CreateHeadlessFormOptions {
286281 initialValues ?: SchemaValue
287282 validationOptions ?: ValidationOptions
288283}
289284
290285function buildFields ( params : { schema : JsfObjectSchema } ) : Field [ ] {
291286 const { schema } = params
292- return buildFieldObject ( schema , 'root' , true ) . fields || [ ]
287+ const fields = buildFieldObject ( schema , 'root' , true ) . fields || [ ]
288+ return fields
293289}
294290
295291export function createHeadlessForm (
296292 schema : JsfObjectSchema ,
297293 options : CreateHeadlessFormOptions = { } ,
298294) : FormResult {
299- const errors = validateSchema ( options . initialValues , schema , options . validationOptions )
300- const errorsWithMessages = addErrorMessages ( options . initialValues , schema , errors )
301- const validationResult = validationErrorsToFormErrors ( errorsWithMessages )
302- const isError = validationResult !== null
295+ const initialValues = options . initialValues || { }
296+ const fields = buildFields ( { schema } )
297+ updateFieldVisibility ( fields , initialValues , schema )
298+ const isError = false
303299
304300 const handleValidation = ( value : SchemaValue ) => {
305301 const result = validate ( value , schema , options . validationOptions )
302+ updateFieldVisibility ( fields , value , schema , options . validationOptions )
306303 return result
307304 }
308305
309306 return {
310- fields : buildFields ( { schema } ) ,
307+ fields,
311308 isError,
312309 error : null ,
313310 handleValidation,
0 commit comments