@@ -3,8 +3,10 @@ import isequal from 'lodash.isequal'
33import get from 'lodash.get'
44import set from 'lodash.set'
55import { ValidationCallback , Config , NamedInputEvent , SimpleValidationErrors , ValidationErrors , Validator as TValidator , ValidatorListeners , ValidationConfig } from './types'
6- import { client } from './client'
6+ import { client , isFile } from './client'
77import { isAxiosError } from 'axios'
8+ import omit from 'lodash.omit'
9+ import merge from 'lodash.merge'
810
911export const createValidator = ( callback : ValidationCallback , initialData : Record < string , unknown > = { } ) : TValidator => {
1012 /**
@@ -16,6 +18,11 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
1618 validatingChanged : [ ] ,
1719 }
1820
21+ /**
22+ * Validate files state.
23+ */
24+ let validateFiles = false
25+
1926 /**
2027 * Processing validation state.
2128 */
@@ -57,8 +64,14 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
5764
5865 listeners . errorsChanged . forEach ( callback => callback ( ) )
5966 }
67+ }
6068
61- setTouched ( [ ...touched , ...Object . keys ( errors ) ] )
69+ const forgetError = ( name : string | NamedInputEvent ) => {
70+ const newErrors = { ...errors }
71+
72+ delete newErrors [ resolveName ( name ) ]
73+
74+ setErrors ( newErrors )
6275 }
6376
6477 /**
@@ -96,11 +109,11 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
96109 */
97110 const createValidator = ( ) => debounce ( ( ) => {
98111 callback ( {
99- get : ( url , data = { } , config = { } ) => client . get ( url , data , resolveConfig ( config , data ) ) ,
100- post : ( url , data = { } , config = { } ) => client . post ( url , data , resolveConfig ( config , data ) ) ,
101- patch : ( url , data = { } , config = { } ) => client . patch ( url , data , resolveConfig ( config , data ) ) ,
102- put : ( url , data = { } , config = { } ) => client . put ( url , data , resolveConfig ( config , data ) ) ,
103- delete : ( url , data = { } , config = { } ) => client . delete ( url , data , resolveConfig ( config , data ) ) ,
112+ get : ( url , data = { } , config = { } ) => client . get ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
113+ post : ( url , data = { } , config = { } ) => client . post ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
114+ patch : ( url , data = { } , config = { } ) => client . patch ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
115+ put : ( url , data = { } , config = { } ) => client . put ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
116+ delete : ( url , data = { } , config = { } ) => client . delete ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
104117 } )
105118 . catch ( error => isAxiosError ( error ) ? null : Promise . reject ( error ) )
106119 } , debounceTimeoutDuration , { leading : true , trailing : true } )
@@ -113,61 +126,69 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
113126 /**
114127 * Resolve the configuration.
115128 */
116- const resolveConfig = ( config : ValidationConfig , data : Record < string , unknown > = { } ) : Config => ( {
117- ...config ,
118- timeout : config . timeout ?? 5000 ,
119- validate : config . validate
120- ? config . validate
121- : touched ,
122- onValidationError : ( response , axiosError ) => {
123- setErrors ( response . data . errors )
124-
125- return config . onValidationError
126- ? config . onValidationError ( response , axiosError )
127- : Promise . reject ( axiosError )
128- } ,
129- onPrecognitionSuccess : ( response ) => {
130- setErrors ( { } )
131-
132- return config . onPrecognitionSuccess
133- ? config . onPrecognitionSuccess ( response )
134- : response
135- } ,
136- onBefore : ( ) => {
137- const beforeValidationResult = ( config . onBeforeValidation ?? ( ( newRequest , oldRequest ) => {
138- return ! isequal ( newRequest , oldRequest )
139- } ) ) ( { data } , { data : oldData } )
140-
141- if ( beforeValidationResult === false ) {
142- return false
143- }
144-
145- const beforeResult = ( config . onBefore || ( ( ) => true ) ) ( )
146-
147- if ( beforeResult === false ) {
148- return false
149- }
150-
151- oldData = data
152-
153- return true
154- } ,
155- onStart : ( ) => {
156- setValidating ( true ) ;
157-
158- ( config . onStart ?? ( ( ) => null ) ) ( )
159- } ,
160- onFinish : ( ) => {
161- setValidating ( false ) ;
162-
163- ( config . onFinish ?? ( ( ) => null ) ) ( )
164- } ,
165- } )
129+ const resolveConfig = ( config : ValidationConfig , data : Record < string , unknown > = { } ) : Config => {
130+ const validate = Array . from ( config . validate ?? touched )
131+
132+ return {
133+ ...config ,
134+ validate,
135+ timeout : config . timeout ?? 5000 ,
136+ onValidationError : ( response , axiosError ) => {
137+ setErrors ( merge ( omit ( { ...errors } , validate ) , response . data . errors ) )
138+
139+ return config . onValidationError
140+ ? config . onValidationError ( response , axiosError )
141+ : Promise . reject ( axiosError )
142+ } ,
143+ onPrecognitionSuccess : ( response ) => {
144+ setErrors ( omit ( { ...errors } , validate ) )
145+
146+ return config . onPrecognitionSuccess
147+ ? config . onPrecognitionSuccess ( response )
148+ : response
149+ } ,
150+ onBefore : ( ) => {
151+ const beforeValidationResult = ( config . onBeforeValidation ?? ( ( newRequest , oldRequest ) => {
152+ return ! isequal ( newRequest , oldRequest )
153+ } ) ) ( { data } , { data : oldData } )
154+
155+ if ( beforeValidationResult === false ) {
156+ return false
157+ }
158+
159+ const beforeResult = ( config . onBefore || ( ( ) => true ) ) ( )
160+
161+ if ( beforeResult === false ) {
162+ return false
163+ }
164+
165+ oldData = data
166+
167+ return true
168+ } ,
169+ onStart : ( ) => {
170+ setValidating ( true ) ;
171+
172+ ( config . onStart ?? ( ( ) => null ) ) ( )
173+ } ,
174+ onFinish : ( ) => {
175+ setValidating ( false ) ;
176+
177+ ( config . onFinish ?? ( ( ) => null ) ) ( )
178+ } ,
179+ }
180+ }
166181
167182 /**
168183 * Validate the given input.
169184 */
170185 const validate = ( name : string | NamedInputEvent , value : unknown ) => {
186+ if ( isFile ( value ) && ! validateFiles ) {
187+ console . warn ( 'Precognition file validation is not active. Call the "validateFiles" function on your form to enable it.' )
188+
189+ return
190+ }
191+
171192 name = resolveName ( name )
172193
173194 if ( get ( oldData , name ) !== value ) {
@@ -181,6 +202,13 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
181202 validator ( )
182203 }
183204
205+ /**
206+ * Parse the validated data.
207+ */
208+ const parseData = ( data : Record < string , unknown > ) : Record < string , unknown > => validateFiles === false
209+ ? forgetFiles ( data )
210+ : data
211+
184212 /**
185213 * The form validator instance.
186214 */
@@ -200,6 +228,11 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
200228
201229 return this
202230 } ,
231+ forgetError ( name ) {
232+ forgetError ( name )
233+
234+ return this
235+ } ,
203236 reset ( ...names ) {
204237 if ( names . length === 0 ) {
205238 setTouched ( [ ] )
@@ -227,11 +260,19 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
227260 on ( event , callback ) {
228261 listeners [ event ] . push ( callback )
229262
263+ return this
264+ } ,
265+ validateFiles ( ) {
266+ validateFiles = true
267+
230268 return this
231269 } ,
232270 }
233271}
234272
273+ /**
274+ * Normalise the validation errors as Inertia formatted errors.
275+ */
235276export const toSimpleValidationErrors = ( errors : ValidationErrors | SimpleValidationErrors ) : SimpleValidationErrors => {
236277 return Object . keys ( errors ) . reduce ( ( carry , key ) => ( {
237278 ...carry ,
@@ -241,16 +282,57 @@ export const toSimpleValidationErrors = (errors: ValidationErrors|SimpleValidati
241282 } ) , { } )
242283}
243284
285+ /**
286+ * Normalise the validation errors as Laravel formatted errors.
287+ */
244288export const toValidationErrors = ( errors : ValidationErrors | SimpleValidationErrors ) : ValidationErrors => {
245289 return Object . keys ( errors ) . reduce ( ( carry , key ) => ( {
246290 ...carry ,
247291 [ key ] : typeof errors [ key ] === 'string' ? [ errors [ key ] ] : errors [ key ] ,
248292 } ) , { } )
249293}
250294
295+ /**
296+ * Resolve the input's "name" attribute.
297+ */
251298export const resolveName = ( name : string | NamedInputEvent ) : string => {
252299 return typeof name !== 'string'
253300 ? name . target . name
254301 : name
255302}
256303
304+ /**
305+ * Forget any files from the payload.
306+ */
307+ const forgetFiles = ( data : Record < string , unknown > ) : Record < string , unknown > => {
308+ const newData = { ...data }
309+
310+ Object . keys ( newData ) . forEach ( name => {
311+ const value = newData [ name ]
312+
313+ if ( value === null ) {
314+ return
315+ }
316+
317+ if ( isFile ( value ) ) {
318+ delete newData [ name ]
319+
320+ return
321+ }
322+
323+ if ( Array . isArray ( value ) ) {
324+ newData [ name ] = value . filter ( isFile )
325+
326+ return
327+ }
328+
329+ if ( typeof value === 'object' ) {
330+ // @ts -expect-error
331+ newData [ name ] = forgetFiles ( newData [ name ] )
332+
333+ return
334+ }
335+ } )
336+
337+ return newData
338+ }
0 commit comments