@colisweb/rescript-toolkit 4.7.2 → 4.8.1
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/bsconfig.json +0 -1
- package/locale/fr.json +0 -5
- package/package.json +1 -2
- package/src/form/ReSchema.res +322 -0
- package/src/form/ReSchemaI18n.res +34 -0
- package/src/form/ReSchemaRegExp.res +5 -0
- package/src/form/Reform.res +108 -41
- package/src/form/Toolkit__Form.res +41 -114
package/bsconfig.json
CHANGED
package/locale/fr.json
CHANGED
|
@@ -54,11 +54,6 @@
|
|
|
54
54
|
"defaultMessage": "Seulement",
|
|
55
55
|
"message": "Seulement"
|
|
56
56
|
},
|
|
57
|
-
{
|
|
58
|
-
"id": "_471931e7",
|
|
59
|
-
"defaultMessage": "Le champ est requis.",
|
|
60
|
-
"message": "Le champ est requis."
|
|
61
|
-
},
|
|
62
57
|
{
|
|
63
58
|
"id": "_491a4e71",
|
|
64
59
|
"defaultMessage": "Tapez au moins {minSearchLength, plural, one {1 caractère} other {# caractères}}",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colisweb/rescript-toolkit",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"clean": "rescript clean",
|
|
@@ -64,7 +64,6 @@
|
|
|
64
64
|
"react-use": "17.4.0",
|
|
65
65
|
"reason-promise": "1.1.5",
|
|
66
66
|
"res-react-intl": "3.1.2",
|
|
67
|
-
"reschema": "1.3.1",
|
|
68
67
|
"rescript": "10.1.4",
|
|
69
68
|
"rescript-classnames": "6.0.0",
|
|
70
69
|
"rescript-react-update": "5.0.0",
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
module type Lenses = {
|
|
2
|
+
type field<'a>
|
|
3
|
+
type state
|
|
4
|
+
let set: (state, field<'a>, 'a) => state
|
|
5
|
+
let get: (state, field<'a>) => 'a
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type childFieldError = {
|
|
9
|
+
error: string,
|
|
10
|
+
index: int,
|
|
11
|
+
name: string,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type fieldState =
|
|
15
|
+
| Valid
|
|
16
|
+
| NestedErrors(array<childFieldError>)
|
|
17
|
+
| Error(string)
|
|
18
|
+
|
|
19
|
+
type recordValidationState<'a> =
|
|
20
|
+
| Valid
|
|
21
|
+
| Errors(array<('a, fieldState)>)
|
|
22
|
+
|
|
23
|
+
module Make = (Lenses: Lenses) => {
|
|
24
|
+
type rec field = Field(Lenses.field<'a>): field
|
|
25
|
+
|
|
26
|
+
module Validation = {
|
|
27
|
+
type rec t =
|
|
28
|
+
| Email({field: Lenses.field<string>, error: option<string>}): t
|
|
29
|
+
| NoValidation({field: Lenses.field<'a>}): t
|
|
30
|
+
| StringNonEmpty({field: Lenses.field<string>, error: option<string>}): t
|
|
31
|
+
| StringRegExp({field: Lenses.field<string>, matches: string, error: option<string>}): t
|
|
32
|
+
| StringMin({field: Lenses.field<string>, min: int, error: option<string>}): t
|
|
33
|
+
| StringMax({field: Lenses.field<string>, max: int, error: option<string>}): t
|
|
34
|
+
| IntMin({field: Lenses.field<int>, min: int, error: option<string>}): t
|
|
35
|
+
| IntMax({field: Lenses.field<int>, max: int, error: option<string>}): t
|
|
36
|
+
| FloatMin({field: Lenses.field<float>, min: float, error: option<string>}): t
|
|
37
|
+
| FloatMax({field: Lenses.field<float>, max: float, error: option<string>}): t
|
|
38
|
+
| Custom({field: Lenses.field<'a>, predicate: Lenses.state => fieldState}): t
|
|
39
|
+
| True({field: Lenses.field<bool>, error: option<string>}): t
|
|
40
|
+
| False({field: Lenses.field<bool>, error: option<string>}): t
|
|
41
|
+
| OptionNonEmpty({field: Lenses.field<option<'a>>, error: option<string>}): t
|
|
42
|
+
| ArrayNonEmpty({field: Lenses.field<array<'a>>, error: option<string>}): t
|
|
43
|
+
|
|
44
|
+
type schema = array<t>
|
|
45
|
+
|
|
46
|
+
let schema = validations => validations->Belt.Array.concatMany
|
|
47
|
+
|
|
48
|
+
let mergeValidators = validations => {
|
|
49
|
+
validations->Belt.Array.reduce([], (rules, (rule, apply)) =>
|
|
50
|
+
switch rule {
|
|
51
|
+
| None => rules
|
|
52
|
+
| Some(rule) => rules->Belt.Array.concat([rule->apply])
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let custom = (field, predicate) => [Custom({field, predicate})]
|
|
58
|
+
|
|
59
|
+
let optionNonEmpty = (~error=?, field) => [OptionNonEmpty({field, error})]
|
|
60
|
+
|
|
61
|
+
let arrayNonEmpty = (~error=?, field) => [ArrayNonEmpty({field, error})]
|
|
62
|
+
|
|
63
|
+
let true_ = (~error=?, field) => [True({field, error})]
|
|
64
|
+
|
|
65
|
+
let false_ = (~error=?, field) => [False({field, error})]
|
|
66
|
+
|
|
67
|
+
let email = (~error=?, field) => [Email({field, error})]
|
|
68
|
+
|
|
69
|
+
let nonEmpty = (~error=?, field) => [StringNonEmpty({field, error})]
|
|
70
|
+
|
|
71
|
+
let string = (~min=?, ~minError=?, ~max=?, ~maxError=?, field) => {
|
|
72
|
+
mergeValidators([
|
|
73
|
+
(
|
|
74
|
+
min,
|
|
75
|
+
min => StringMin({
|
|
76
|
+
field,
|
|
77
|
+
min,
|
|
78
|
+
error: minError,
|
|
79
|
+
}),
|
|
80
|
+
),
|
|
81
|
+
(
|
|
82
|
+
max,
|
|
83
|
+
max => StringMax({
|
|
84
|
+
field,
|
|
85
|
+
max,
|
|
86
|
+
error: maxError,
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
])
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let regExp = (~error=?, ~matches, field) => [StringRegExp({field, matches, error})]
|
|
93
|
+
|
|
94
|
+
let float = (~min=?, ~minError=?, ~max=?, ~maxError=?, field) => {
|
|
95
|
+
mergeValidators([
|
|
96
|
+
(
|
|
97
|
+
min,
|
|
98
|
+
min => FloatMin({
|
|
99
|
+
field,
|
|
100
|
+
min,
|
|
101
|
+
error: minError,
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
(
|
|
105
|
+
max,
|
|
106
|
+
max => FloatMax({
|
|
107
|
+
field,
|
|
108
|
+
max,
|
|
109
|
+
error: maxError,
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let int = (~min=?, ~minError=?, ~max=?, ~maxError=?, field) => {
|
|
116
|
+
mergeValidators([
|
|
117
|
+
(
|
|
118
|
+
min,
|
|
119
|
+
min => IntMin({
|
|
120
|
+
field,
|
|
121
|
+
min,
|
|
122
|
+
error: minError,
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
(
|
|
126
|
+
max,
|
|
127
|
+
max => IntMax({
|
|
128
|
+
field,
|
|
129
|
+
max,
|
|
130
|
+
error: maxError,
|
|
131
|
+
}),
|
|
132
|
+
),
|
|
133
|
+
])
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let validateField = (~validator, ~values, ~i18n: ReSchemaI18n.t): (field, fieldState) =>
|
|
138
|
+
switch validator {
|
|
139
|
+
| Validation.True({field, error}) =>
|
|
140
|
+
let value = Lenses.get(values, field)
|
|
141
|
+
(Field(field), value ? Valid : Error(error->Belt.Option.getWithDefault(i18n.true_())))
|
|
142
|
+
| Validation.False({field, error}) =>
|
|
143
|
+
let value = Lenses.get(values, field)
|
|
144
|
+
(
|
|
145
|
+
Field(field),
|
|
146
|
+
value == false ? Valid : Error(error->Belt.Option.getWithDefault(i18n.false_())),
|
|
147
|
+
)
|
|
148
|
+
| Validation.IntMin({field, min, error}) =>
|
|
149
|
+
let value = Lenses.get(values, field)
|
|
150
|
+
(
|
|
151
|
+
Field(field),
|
|
152
|
+
value >= min ? Valid : Error(error->Belt.Option.getWithDefault(i18n.intMin(~value, ~min))),
|
|
153
|
+
)
|
|
154
|
+
| Validation.IntMax({field, max, error}) =>
|
|
155
|
+
let value = Lenses.get(values, field)
|
|
156
|
+
|
|
157
|
+
(
|
|
158
|
+
Field(field),
|
|
159
|
+
value <= max ? Valid : Error(error->Belt.Option.getWithDefault(i18n.intMax(~value, ~max))),
|
|
160
|
+
)
|
|
161
|
+
| Validation.FloatMin({field, min, error}) =>
|
|
162
|
+
let value = Lenses.get(values, field)
|
|
163
|
+
(
|
|
164
|
+
Field(field),
|
|
165
|
+
value >= min
|
|
166
|
+
? Valid
|
|
167
|
+
: Error(error->Belt.Option.getWithDefault(i18n.floatMin(~value, ~min))),
|
|
168
|
+
)
|
|
169
|
+
| Validation.FloatMax({field, max, error}) =>
|
|
170
|
+
let value = Lenses.get(values, field)
|
|
171
|
+
(
|
|
172
|
+
Field(field),
|
|
173
|
+
Lenses.get(values, field) <= max
|
|
174
|
+
? Valid
|
|
175
|
+
: Error(error->Belt.Option.getWithDefault(i18n.floatMax(~value, ~max))),
|
|
176
|
+
)
|
|
177
|
+
| Validation.Email({field, error}) =>
|
|
178
|
+
let value = Lenses.get(values, field)
|
|
179
|
+
(
|
|
180
|
+
Field(field),
|
|
181
|
+
Js.Re.test_(ReSchemaRegExp.email, value)
|
|
182
|
+
? Valid
|
|
183
|
+
: Error(error->Belt.Option.getWithDefault(i18n.email(~value))),
|
|
184
|
+
)
|
|
185
|
+
| Validation.NoValidation({field}) => (Field(field), Valid)
|
|
186
|
+
| Validation.StringNonEmpty({field, error}) =>
|
|
187
|
+
let value = Lenses.get(values, field)
|
|
188
|
+
(
|
|
189
|
+
Field(field),
|
|
190
|
+
value === ""
|
|
191
|
+
? Error(error->Belt.Option.getWithDefault(i18n.stringNonEmpty(~value)))
|
|
192
|
+
: Valid,
|
|
193
|
+
)
|
|
194
|
+
| Validation.StringRegExp({field, matches, error}) =>
|
|
195
|
+
let value = Lenses.get(values, field)
|
|
196
|
+
(
|
|
197
|
+
Field(field),
|
|
198
|
+
Js.Re.test_(Js.Re.fromString(matches), value)
|
|
199
|
+
? Valid
|
|
200
|
+
: Error(error->Belt.Option.getWithDefault(i18n.stringRegExp(~value, ~pattern=matches))),
|
|
201
|
+
)
|
|
202
|
+
| Validation.StringMin({field, min, error}) =>
|
|
203
|
+
let value = Lenses.get(values, field)
|
|
204
|
+
(
|
|
205
|
+
Field(field),
|
|
206
|
+
Js.String.length(value) >= min
|
|
207
|
+
? Valid
|
|
208
|
+
: Error(error->Belt.Option.getWithDefault(i18n.stringMin(~value, ~min))),
|
|
209
|
+
)
|
|
210
|
+
| Validation.StringMax({field, max, error}) =>
|
|
211
|
+
let value = Lenses.get(values, field)
|
|
212
|
+
(
|
|
213
|
+
Field(field),
|
|
214
|
+
Js.String.length(value) <= max
|
|
215
|
+
? Valid
|
|
216
|
+
: Error(error->Belt.Option.getWithDefault(i18n.stringMax(~value, ~max))),
|
|
217
|
+
)
|
|
218
|
+
| Validation.Custom({field, predicate}) => (Field(field), predicate(values))
|
|
219
|
+
|
|
220
|
+
| Validation.OptionNonEmpty({field, error}) => {
|
|
221
|
+
let value = Lenses.get(values, field)
|
|
222
|
+
|
|
223
|
+
(
|
|
224
|
+
Field(field),
|
|
225
|
+
value->Option.isNone
|
|
226
|
+
? Error(error->Belt.Option.getWithDefault(i18n.stringNonEmpty(~value="")))
|
|
227
|
+
: Valid,
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
| Validation.ArrayNonEmpty({field, error}) => {
|
|
231
|
+
let value = Lenses.get(values, field)
|
|
232
|
+
|
|
233
|
+
(
|
|
234
|
+
Field(field),
|
|
235
|
+
value->Array.length == 0
|
|
236
|
+
? Error(error->Belt.Option.getWithDefault(i18n.stringNonEmpty(~value="")))
|
|
237
|
+
: Valid,
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let getFieldValidator = (~validators, ~fieldName) =>
|
|
243
|
+
validators->Belt.Array.getBy(validator =>
|
|
244
|
+
switch validator {
|
|
245
|
+
| Validation.False({field}) => Field(field) == fieldName
|
|
246
|
+
| Validation.True({field}) => Field(field) == fieldName
|
|
247
|
+
| Validation.IntMin({field}) => Field(field) == fieldName
|
|
248
|
+
| Validation.IntMax({field}) => Field(field) == fieldName
|
|
249
|
+
| Validation.FloatMin({field}) => Field(field) == fieldName
|
|
250
|
+
| Validation.FloatMax({field}) => Field(field) == fieldName
|
|
251
|
+
| Validation.Email({field}) => Field(field) == fieldName
|
|
252
|
+
| Validation.NoValidation({field}) => Field(field) == fieldName
|
|
253
|
+
| Validation.StringNonEmpty({field}) => Field(field) == fieldName
|
|
254
|
+
| Validation.StringRegExp({field}) => Field(field) == fieldName
|
|
255
|
+
| Validation.StringMin({field}) => Field(field) == fieldName
|
|
256
|
+
| Validation.StringMax({field}) => Field(field) == fieldName
|
|
257
|
+
| Validation.Custom({field}) => Field(field) == fieldName
|
|
258
|
+
| Validation.OptionNonEmpty({field}) => Field(field) == fieldName
|
|
259
|
+
| Validation.ArrayNonEmpty({field}) => Field(field) == fieldName
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
let getFieldValidators = (~validators, ~fieldName) =>
|
|
264
|
+
validators->Belt.Array.keep(validator =>
|
|
265
|
+
switch validator {
|
|
266
|
+
| Validation.False({field}) => Field(field) == fieldName
|
|
267
|
+
| Validation.True({field}) => Field(field) == fieldName
|
|
268
|
+
| Validation.IntMin({field}) => Field(field) == fieldName
|
|
269
|
+
| Validation.IntMax({field}) => Field(field) == fieldName
|
|
270
|
+
| Validation.FloatMin({field}) => Field(field) == fieldName
|
|
271
|
+
| Validation.FloatMax({field}) => Field(field) == fieldName
|
|
272
|
+
| Validation.Email({field}) => Field(field) == fieldName
|
|
273
|
+
| Validation.NoValidation({field}) => Field(field) == fieldName
|
|
274
|
+
| Validation.StringNonEmpty({field}) => Field(field) == fieldName
|
|
275
|
+
| Validation.StringRegExp({field}) => Field(field) == fieldName
|
|
276
|
+
| Validation.StringMin({field}) => Field(field) == fieldName
|
|
277
|
+
| Validation.StringMax({field}) => Field(field) == fieldName
|
|
278
|
+
| Validation.Custom({field}) => Field(field) == fieldName
|
|
279
|
+
| Validation.OptionNonEmpty({field}) => Field(field) == fieldName
|
|
280
|
+
| Validation.ArrayNonEmpty({field}) => Field(field) == fieldName
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
let validateOne = (
|
|
285
|
+
~field: field,
|
|
286
|
+
~values,
|
|
287
|
+
~i18n=ReSchemaI18n.default,
|
|
288
|
+
schema: Validation.schema,
|
|
289
|
+
) => {
|
|
290
|
+
getFieldValidators(~validators=schema, ~fieldName=field)
|
|
291
|
+
->Belt.Array.map(validator => validateField(~validator, ~values, ~i18n))
|
|
292
|
+
->Belt.Array.getBy(validation => {
|
|
293
|
+
switch validation {
|
|
294
|
+
| (_, Error(_)) => true
|
|
295
|
+
| _ => false
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let validateFields = (~fields, ~values, ~i18n, schema: Validation.schema) => {
|
|
301
|
+
Belt.Array.map(fields, field =>
|
|
302
|
+
getFieldValidator(~validators=schema, ~fieldName=field)->Belt.Option.map(validator =>
|
|
303
|
+
validateField(~validator, ~values, ~i18n)
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let validate = (~i18n=ReSchemaI18n.default, values: Lenses.state, schema: Validation.schema) => {
|
|
309
|
+
let validationList =
|
|
310
|
+
schema->Belt.Array.map(validator => validateField(~validator, ~values, ~i18n))
|
|
311
|
+
|
|
312
|
+
let errors = validationList->Belt.Array.keepMap(((field, fieldState)) =>
|
|
313
|
+
switch fieldState {
|
|
314
|
+
| Error(_) as e => Some((field, e))
|
|
315
|
+
| NestedErrors(_) as e => Some((field, e))
|
|
316
|
+
| _ => None
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
Belt.Array.length(errors) > 0 ? Errors(errors) : Valid
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
type t = {
|
|
2
|
+
false_: unit => string,
|
|
3
|
+
true_: unit => string,
|
|
4
|
+
intMin: (~value: int, ~min: int) => string,
|
|
5
|
+
intMax: (~value: int, ~max: int) => string,
|
|
6
|
+
floatMin: (~value: float, ~min: float) => string,
|
|
7
|
+
floatMax: (~value: float, ~max: float) => string,
|
|
8
|
+
email: (~value: string) => string,
|
|
9
|
+
stringNonEmpty: (~value: string) => string,
|
|
10
|
+
stringRegExp: (~value: string, ~pattern: string) => string,
|
|
11
|
+
stringMin: (~value: string, ~min: int) => string,
|
|
12
|
+
stringMax: (~value: string, ~max: int) => string,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let default = {
|
|
16
|
+
false_: () => "This value should be false",
|
|
17
|
+
true_: () => "This value should be true",
|
|
18
|
+
intMin: (~value as _value, ~min) =>
|
|
19
|
+
"This value must be greater than or equal to " ++ string_of_int(min),
|
|
20
|
+
intMax: (~value as _value, ~max) =>
|
|
21
|
+
"This value must be less than or equal to " ++ string_of_int(max),
|
|
22
|
+
floatMin: (~value as _value, ~min) =>
|
|
23
|
+
"This value must be greater than or equal to " ++ Js.Float.toString(min),
|
|
24
|
+
floatMax: (~value as _value, ~max) =>
|
|
25
|
+
"This value must be less than or equal to " ++ Js.Float.toString(max),
|
|
26
|
+
email: (~value) => value ++ " is not a valid email",
|
|
27
|
+
stringNonEmpty: (~value as _) => "String must not be empty",
|
|
28
|
+
stringRegExp: (~value as _value, ~pattern) =>
|
|
29
|
+
"This value must match the following: /" ++ pattern ++ "/",
|
|
30
|
+
stringMin: (~value as _value, ~min) =>
|
|
31
|
+
"This value must be at least " ++ string_of_int(min) ++ " characters",
|
|
32
|
+
stringMax: (~value as _value, ~max) =>
|
|
33
|
+
"This value must be at most " ++ string_of_int(max) ++ " characters",
|
|
34
|
+
}
|
package/src/form/Reform.res
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
open ReactIntl
|
|
2
|
+
|
|
1
3
|
module type Config = {
|
|
2
4
|
type field<'a>
|
|
3
5
|
type state
|
|
@@ -5,9 +7,13 @@ module type Config = {
|
|
|
5
7
|
let get: (state, field<'a>) => 'a
|
|
6
8
|
type error
|
|
7
9
|
}
|
|
10
|
+
|
|
11
|
+
type childFieldError = ReSchema.childFieldError
|
|
12
|
+
|
|
8
13
|
type fieldState =
|
|
9
14
|
| Pristine
|
|
10
15
|
| Valid
|
|
16
|
+
| NestedErrors(array<ReSchema.childFieldError>)
|
|
11
17
|
| Error(string)
|
|
12
18
|
type formState<'error> =
|
|
13
19
|
| Pristine
|
|
@@ -53,6 +59,7 @@ module Make = (Config: Config) => {
|
|
|
53
59
|
state: state,
|
|
54
60
|
getFieldState: field => fieldState,
|
|
55
61
|
getFieldError: field => option<string>,
|
|
62
|
+
getNestedFieldError: (field, int) => array<childFieldError>,
|
|
56
63
|
handleChange: 'a. (Config.field<'a>, 'a) => unit,
|
|
57
64
|
arrayPush: 'a. (Config.field<array<'a>>, 'a) => unit,
|
|
58
65
|
arrayUpdateByIndex: 'a. (~field: Config.field<array<'a>>, ~index: int, 'a) => unit,
|
|
@@ -92,20 +99,23 @@ module Make = (Config: Config) => {
|
|
|
92
99
|
| OnDemand
|
|
93
100
|
|
|
94
101
|
let getInitialFieldsState: Validation.schema => array<(field, fieldState)> = schema => {
|
|
95
|
-
|
|
96
|
-
validators->Belt.Array.map(validator =>
|
|
102
|
+
schema->Belt.Array.map(validator =>
|
|
97
103
|
switch validator {
|
|
98
|
-
| Validation.IntMin(field
|
|
99
|
-
| Validation.
|
|
100
|
-
| Validation.
|
|
101
|
-
| Validation.
|
|
102
|
-
| Validation.
|
|
103
|
-
| Validation.
|
|
104
|
-
| Validation.
|
|
105
|
-
| Validation.
|
|
106
|
-
| Validation.
|
|
107
|
-
| Validation.
|
|
108
|
-
| Validation.
|
|
104
|
+
| Validation.IntMin({field}) => (ReSchema.Field(field), (Pristine: fieldState))
|
|
105
|
+
| Validation.True({field}) => (Field(field), Pristine)
|
|
106
|
+
| Validation.False({field}) => (Field(field), Pristine)
|
|
107
|
+
| Validation.IntMax({field}) => (Field(field), Pristine)
|
|
108
|
+
| Validation.FloatMin({field}) => (Field(field), Pristine)
|
|
109
|
+
| Validation.FloatMax({field}) => (Field(field), Pristine)
|
|
110
|
+
| Validation.Email({field}) => (Field(field), Pristine)
|
|
111
|
+
| Validation.NoValidation({field}) => (Field(field), Pristine)
|
|
112
|
+
| Validation.StringNonEmpty({field}) => (Field(field), Pristine)
|
|
113
|
+
| Validation.StringRegExp({field}) => (Field(field), Pristine)
|
|
114
|
+
| Validation.StringMin({field}) => (Field(field), Pristine)
|
|
115
|
+
| Validation.StringMax({field}) => (Field(field), Pristine)
|
|
116
|
+
| Validation.Custom({field}) => (Field(field), Pristine)
|
|
117
|
+
| Validation.OptionNonEmpty({field}) => (Field(field), Pristine)
|
|
118
|
+
| Validation.ArrayNonEmpty({field}) => (Field(field), Pristine)
|
|
109
119
|
}
|
|
110
120
|
)
|
|
111
121
|
}
|
|
@@ -126,10 +136,30 @@ module Make = (Config: Config) => {
|
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
module
|
|
139
|
+
module FormProvider = {
|
|
130
140
|
let make = React.Context.provider(formContext)
|
|
131
141
|
}
|
|
132
142
|
|
|
143
|
+
module Provider = {
|
|
144
|
+
@react.component
|
|
145
|
+
let make = (~id=?, ~className=?, ~children, ~form) => {
|
|
146
|
+
<FormProvider value={form}>
|
|
147
|
+
<form
|
|
148
|
+
?id
|
|
149
|
+
?className
|
|
150
|
+
onSubmit={event => {
|
|
151
|
+
ReactEvent.Synthetic.preventDefault(event)
|
|
152
|
+
form.submit()
|
|
153
|
+
}}>
|
|
154
|
+
children
|
|
155
|
+
<Toolkit__Ui_VisuallyHidden>
|
|
156
|
+
<input type_="submit" hidden=true />
|
|
157
|
+
</Toolkit__Ui_VisuallyHidden>
|
|
158
|
+
</form>
|
|
159
|
+
</FormProvider>
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
133
163
|
module Field = {
|
|
134
164
|
@react.component
|
|
135
165
|
let make = (
|
|
@@ -146,13 +176,16 @@ module Make = (Config: Config) => {
|
|
|
146
176
|
|
|
147
177
|
let use = (
|
|
148
178
|
~initialState,
|
|
149
|
-
~schema: Validation.schema,
|
|
179
|
+
~schema=[]: Validation.schema,
|
|
150
180
|
~onSubmit,
|
|
151
181
|
~onSubmitFail=ignore,
|
|
152
|
-
~i18n
|
|
153
|
-
~validationStrategy=
|
|
182
|
+
~i18n=?,
|
|
183
|
+
~validationStrategy=OnDemand,
|
|
154
184
|
(),
|
|
155
185
|
) => {
|
|
186
|
+
let intl = useIntl()
|
|
187
|
+
let i18n = i18n->Option.getWithDefault(Toolkit__FormValidationFunctions.i18n(intl))
|
|
188
|
+
|
|
156
189
|
let (state, send) = ReactUpdate.useReducerWithMapState(
|
|
157
190
|
(state, action) => {
|
|
158
191
|
switch action {
|
|
@@ -165,6 +198,13 @@ module Make = (Config: Config) => {
|
|
|
165
198
|
state: self.state,
|
|
166
199
|
raiseSubmitFailed: error => self.send(RaiseSubmitFailed(error)),
|
|
167
200
|
})
|
|
201
|
+
->Promise.tap(x =>
|
|
202
|
+
switch x {
|
|
203
|
+
| Ok(_) => self.send(SetFormState(SubmitSucceed))
|
|
204
|
+
| Error(error) => self.send(SetFormState(SubmitFailed(Some(error))))
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
->ignore
|
|
168
208
|
|
|
169
209
|
None
|
|
170
210
|
},
|
|
@@ -180,26 +220,27 @@ module Make = (Config: Config) => {
|
|
|
180
220
|
| ValidateField(field) =>
|
|
181
221
|
SideEffects(
|
|
182
222
|
self => {
|
|
183
|
-
let fieldState =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
223
|
+
let fieldState = ReSchema.validateOne(
|
|
224
|
+
~field,
|
|
225
|
+
~values=self.state.values,
|
|
226
|
+
~i18n,
|
|
227
|
+
schema,
|
|
228
|
+
)
|
|
229
|
+
let newFieldState: fieldState = switch fieldState {
|
|
230
|
+
| None => Valid
|
|
231
|
+
| Some(fieldState) =>
|
|
232
|
+
switch fieldState {
|
|
187
233
|
| (_, Error(message)) => Error(message)
|
|
234
|
+
| (_, NestedErrors(errors)) => NestedErrors(errors)
|
|
188
235
|
| (_, Valid) => Valid
|
|
189
236
|
}
|
|
190
|
-
|
|
237
|
+
}
|
|
191
238
|
|
|
192
239
|
let newFieldsState =
|
|
193
240
|
state.fieldsState
|
|
194
|
-
->Belt.Array.keep(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
->Belt.Array.concat(
|
|
198
|
-
switch newFieldState {
|
|
199
|
-
| Some(fieldState) => [(field, fieldState)]
|
|
200
|
-
| None => []
|
|
201
|
-
},
|
|
202
|
-
)
|
|
241
|
+
->Belt.Array.keep(((value, _)) => value != field)
|
|
242
|
+
->Belt.Array.concat([(field, newFieldState)])
|
|
243
|
+
|
|
203
244
|
self.send(SetFieldsState(newFieldsState))
|
|
204
245
|
None
|
|
205
246
|
},
|
|
@@ -207,23 +248,38 @@ module Make = (Config: Config) => {
|
|
|
207
248
|
| ValidateForm(submit) =>
|
|
208
249
|
SideEffects(
|
|
209
250
|
self => {
|
|
210
|
-
let recordState =
|
|
251
|
+
let recordState = ReSchema.validate(~i18n, self.state.values, schema)
|
|
211
252
|
|
|
212
253
|
switch recordState {
|
|
213
|
-
| Valid =>
|
|
214
|
-
|
|
215
|
-
|
|
254
|
+
| Valid => {
|
|
255
|
+
let newFieldsState: array<(field, fieldState)> =
|
|
256
|
+
self.state.fieldsState->Belt.Array.map(((field, _)) => (
|
|
257
|
+
field,
|
|
258
|
+
(Valid: fieldState),
|
|
259
|
+
))
|
|
260
|
+
self.send(SetFieldsState(newFieldsState))
|
|
261
|
+
submit ? self.send(Submit) : ()
|
|
262
|
+
}
|
|
216
263
|
| Errors(erroredFields) =>
|
|
217
|
-
let newFieldsState =
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
264
|
+
let newFieldsState: array<(field, fieldState)> = erroredFields->Belt.Array.map(((
|
|
265
|
+
field,
|
|
266
|
+
err,
|
|
267
|
+
)) => (
|
|
268
|
+
field,
|
|
269
|
+
switch err {
|
|
270
|
+
| Error(e) => Error(e)
|
|
271
|
+
| NestedErrors(e) => NestedErrors(e)
|
|
272
|
+
| Valid => Valid
|
|
273
|
+
},
|
|
274
|
+
))
|
|
222
275
|
self.send(SetFieldsState(newFieldsState))
|
|
223
276
|
submit
|
|
224
277
|
? onSubmitFail({
|
|
225
278
|
send: self.send,
|
|
226
|
-
state:
|
|
279
|
+
state: {
|
|
280
|
+
...self.state,
|
|
281
|
+
fieldsState: newFieldsState,
|
|
282
|
+
},
|
|
227
283
|
raiseSubmitFailed: error => self.send(RaiseSubmitFailed(error)),
|
|
228
284
|
})
|
|
229
285
|
: ()
|
|
@@ -328,10 +384,19 @@ module Make = (Config: Config) => {
|
|
|
328
384
|
x =>
|
|
329
385
|
switch x {
|
|
330
386
|
| Error(error) => Some(error)
|
|
387
|
+
| NestedErrors(_errors) => None
|
|
331
388
|
| _ => None
|
|
332
389
|
}
|
|
333
390
|
)
|
|
334
391
|
|
|
392
|
+
let getNestedFieldError = (field, index) =>
|
|
393
|
+
switch getFieldState(field) {
|
|
394
|
+
| NestedErrors(errors) => errors->Array.keep(error => error.index === index)
|
|
395
|
+
| Pristine
|
|
396
|
+
| Valid
|
|
397
|
+
| Error(_) => []
|
|
398
|
+
}
|
|
399
|
+
|
|
335
400
|
let validateFields = (fields: array<field>) => {
|
|
336
401
|
let fieldsValidated = ReSchema.validateFields(~fields, ~values=state.values, ~i18n, schema)
|
|
337
402
|
|
|
@@ -354,6 +419,7 @@ module Make = (Config: Config) => {
|
|
|
354
419
|
switch newFieldStateValidated {
|
|
355
420
|
| Valid => [(field, (Valid: fieldState))]
|
|
356
421
|
| Error(message) => [(field, Error(message))]
|
|
422
|
+
| NestedErrors(message) => [(field, NestedErrors(message))]
|
|
357
423
|
}
|
|
358
424
|
|
|
359
425
|
| None => []
|
|
@@ -386,6 +452,7 @@ module Make = (Config: Config) => {
|
|
|
386
452
|
shouldValidate ? send(FieldChangeValue(field, value)) : send(SetFieldValue(field, value)),
|
|
387
453
|
getFieldState,
|
|
388
454
|
getFieldError,
|
|
455
|
+
getNestedFieldError,
|
|
389
456
|
handleChange: (field, value) => send(FieldChangeValue(field, value)),
|
|
390
457
|
arrayPush: (field, value) => send(FieldArrayAdd(field, value)),
|
|
391
458
|
arrayUpdateByIndex: (~field, ~index, value) =>
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
open ReactIntl
|
|
2
2
|
|
|
3
|
-
module Helpers = {
|
|
4
|
-
let handleChange = (handleChange, event) => handleChange(ReactEvent.Form.target(event)["value"])
|
|
5
|
-
|
|
6
|
-
let handleSubmit = (handleSubmit, event) => {
|
|
7
|
-
ReactEvent.Synthetic.preventDefault(event)
|
|
8
|
-
handleSubmit()
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
module Msg = {
|
|
13
|
-
@@intl.messages
|
|
14
|
-
let optionNonEmpty = {defaultMessage: "Le champ est requis."}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
3
|
module ErrorMessage = {
|
|
18
4
|
@react.component
|
|
19
5
|
let make = (~error=?, ~className="") =>
|
|
@@ -33,83 +19,12 @@ module type Config = {
|
|
|
33
19
|
}
|
|
34
20
|
|
|
35
21
|
module Make = (StateLenses: Config) => {
|
|
36
|
-
|
|
37
|
-
include Reform.Make(StateLenses)
|
|
38
|
-
|
|
39
|
-
let use = (
|
|
40
|
-
~initialState,
|
|
41
|
-
~schema=Validation.Schema([]),
|
|
42
|
-
~onSubmit=?,
|
|
43
|
-
~onSubmitFail=ignore,
|
|
44
|
-
~validationStrategy=OnDemand,
|
|
45
|
-
(),
|
|
46
|
-
): api => {
|
|
47
|
-
let intl = useIntl()
|
|
48
|
-
|
|
49
|
-
use(
|
|
50
|
-
~initialState,
|
|
51
|
-
~schema,
|
|
52
|
-
~onSubmit=form => {
|
|
53
|
-
onSubmit
|
|
54
|
-
->Option.map(onSubmit =>
|
|
55
|
-
onSubmit(form)->Promise.tap(x =>
|
|
56
|
-
switch x {
|
|
57
|
-
| Ok(_) => form.send(SetFormState(SubmitSucceed))
|
|
58
|
-
| Error(error) => form.send(SetFormState(SubmitFailed(Some(error))))
|
|
59
|
-
}
|
|
60
|
-
)
|
|
61
|
-
)
|
|
62
|
-
->ignore
|
|
63
|
-
},
|
|
64
|
-
~onSubmitFail,
|
|
65
|
-
~i18n=Toolkit__FormValidationFunctions.i18n(intl),
|
|
66
|
-
~validationStrategy,
|
|
67
|
-
(),
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
module CustomValidation = {
|
|
73
|
-
let optionNonEmpty = (intl, lense) => Form.Validation.Custom(
|
|
74
|
-
lense,
|
|
75
|
-
values =>
|
|
76
|
-
values->StateLenses.get(lense)->Option.isNone
|
|
77
|
-
? Error(intl->Intl.formatMessage(Msg.optionNonEmpty))
|
|
78
|
-
: Valid,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
let arrayNonEmpty = (intl, lense) => Form.Validation.Custom(
|
|
82
|
-
lense,
|
|
83
|
-
values =>
|
|
84
|
-
values->StateLenses.get(lense)->Array.length == 0
|
|
85
|
-
? Error(intl->Intl.formatMessage(Msg.optionNonEmpty))
|
|
86
|
-
: Valid,
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
module Wrapper = {
|
|
91
|
-
@react.component
|
|
92
|
-
let make = (~id=?, ~className=?, ~children) => {
|
|
93
|
-
let form = Form.useFormContext()
|
|
94
|
-
<form
|
|
95
|
-
?id
|
|
96
|
-
?className
|
|
97
|
-
onSubmit={event => {
|
|
98
|
-
ReactEvent.Synthetic.preventDefault(event)
|
|
99
|
-
form.submit()
|
|
100
|
-
}}>
|
|
101
|
-
children
|
|
102
|
-
<Toolkit__Ui_VisuallyHidden>
|
|
103
|
-
<input type_="submit" hidden=true />
|
|
104
|
-
</Toolkit__Ui_VisuallyHidden>
|
|
105
|
-
</form>
|
|
106
|
-
}
|
|
107
|
-
}
|
|
22
|
+
include Reform.Make(StateLenses)
|
|
108
23
|
|
|
109
24
|
module RadioGroup = {
|
|
110
25
|
@react.component
|
|
111
26
|
let make = (~field, ~elements, ~inline=?) =>
|
|
112
|
-
<
|
|
27
|
+
<Field
|
|
113
28
|
field
|
|
114
29
|
render={({handleChange, error, value}) => <>
|
|
115
30
|
<Toolkit__Ui_RadioGroup
|
|
@@ -148,7 +63,7 @@ module Make = (StateLenses: Config) => {
|
|
|
148
63
|
let (showPassword, setShowPassword) = React.useState(() => false)
|
|
149
64
|
let isPasswordType = type_->Option.mapWithDefault(false, type_ => type_ === "password")
|
|
150
65
|
|
|
151
|
-
<
|
|
66
|
+
<Field
|
|
152
67
|
field
|
|
153
68
|
key={showPassword->string_of_bool}
|
|
154
69
|
render={({handleChange, error, value, validate, state}) => {
|
|
@@ -256,7 +171,7 @@ module Make = (StateLenses: Config) => {
|
|
|
256
171
|
}
|
|
257
172
|
`)
|
|
258
173
|
|
|
259
|
-
<
|
|
174
|
+
<Field
|
|
260
175
|
field
|
|
261
176
|
render={({handleChange, error, value, validate, state}) => {
|
|
262
177
|
let isInvalid = error->Option.isSome
|
|
@@ -330,7 +245,7 @@ module Make = (StateLenses: Config) => {
|
|
|
330
245
|
~timeIntervals=?,
|
|
331
246
|
~inline=?,
|
|
332
247
|
) =>
|
|
333
|
-
<
|
|
248
|
+
<Field
|
|
334
249
|
field
|
|
335
250
|
render={({handleChange, error, value, validate, state}) => {
|
|
336
251
|
let isInvalid = error->Option.isSome
|
|
@@ -399,12 +314,12 @@ module Make = (StateLenses: Config) => {
|
|
|
399
314
|
~isOptional=?,
|
|
400
315
|
~className=?,
|
|
401
316
|
) =>
|
|
402
|
-
<
|
|
317
|
+
<Field
|
|
403
318
|
field
|
|
404
319
|
render={({handleChange, error, value, validate, state}) => {
|
|
405
320
|
let isInvalid = error->Option.isSome
|
|
406
321
|
|
|
407
|
-
let onChange =
|
|
322
|
+
let onChange = event => handleChange(ReactEvent.Form.target(event)["value"])
|
|
408
323
|
|
|
409
324
|
let onBlur = _ =>
|
|
410
325
|
switch state {
|
|
@@ -446,7 +361,7 @@ module Make = (StateLenses: Config) => {
|
|
|
446
361
|
module Checkbox = {
|
|
447
362
|
@react.component
|
|
448
363
|
let make = (~field, ~label, ~name=?, ~disabled=?, ~className=?) =>
|
|
449
|
-
<
|
|
364
|
+
<Field
|
|
450
365
|
field
|
|
451
366
|
render={({handleChange, error, value}) => <>
|
|
452
367
|
<Toolkit__Ui_Checkbox
|
|
@@ -477,7 +392,7 @@ module Make = (StateLenses: Config) => {
|
|
|
477
392
|
~isOptional=?,
|
|
478
393
|
~className=?,
|
|
479
394
|
) => {
|
|
480
|
-
<
|
|
395
|
+
<Field
|
|
481
396
|
field
|
|
482
397
|
render={({handleChange, error, value, validate, state}) => {
|
|
483
398
|
let isInvalid = error->Option.isSome
|
|
@@ -539,7 +454,7 @@ module Make = (StateLenses: Config) => {
|
|
|
539
454
|
~isOptional=?,
|
|
540
455
|
~className=?,
|
|
541
456
|
) => {
|
|
542
|
-
<
|
|
457
|
+
<Field
|
|
543
458
|
field
|
|
544
459
|
render={({handleChange, error, value, validate, state}) => {
|
|
545
460
|
let isInvalid = error->Option.isSome
|
|
@@ -604,7 +519,7 @@ module Make = (StateLenses: Config) => {
|
|
|
604
519
|
~valueFormat,
|
|
605
520
|
~autoFocus=?,
|
|
606
521
|
) => {
|
|
607
|
-
<
|
|
522
|
+
<Field
|
|
608
523
|
field
|
|
609
524
|
render={({handleChange, error, value}) => {
|
|
610
525
|
let isInvalid = error->Option.isSome
|
|
@@ -654,7 +569,7 @@ module Make = (StateLenses: Config) => {
|
|
|
654
569
|
~dropdownClassName=?,
|
|
655
570
|
~itemClassName=?,
|
|
656
571
|
) => {
|
|
657
|
-
<
|
|
572
|
+
<Field
|
|
658
573
|
field
|
|
659
574
|
render={({handleChange, error, value, validate, state}) => {
|
|
660
575
|
let onClose = _ => {
|
|
@@ -705,7 +620,7 @@ module Make = (StateLenses: Config) => {
|
|
|
705
620
|
~switchClassName=?,
|
|
706
621
|
~labelClassName=?,
|
|
707
622
|
) =>
|
|
708
|
-
<
|
|
623
|
+
<Field
|
|
709
624
|
field
|
|
710
625
|
render={({handleChange, value}) => <>
|
|
711
626
|
<Toolkit__Ui_Switch
|
|
@@ -731,6 +646,7 @@ module Make = (StateLenses: Config) => {
|
|
|
731
646
|
~containerClassName,
|
|
732
647
|
~placeholder,
|
|
733
648
|
~disabledBefore: option<Js.Date.t>=?,
|
|
649
|
+
~revalidate,
|
|
734
650
|
) => {
|
|
735
651
|
let (date, setDate) = React.useState((): option<Js.Date.t> => value)
|
|
736
652
|
let intl = useIntl()
|
|
@@ -800,6 +716,7 @@ module Make = (StateLenses: Config) => {
|
|
|
800
716
|
type_="button"
|
|
801
717
|
disabled={date->Option.isNone}
|
|
802
718
|
onClick={_ => {
|
|
719
|
+
revalidate()
|
|
803
720
|
setDate(_ => None)
|
|
804
721
|
}}>
|
|
805
722
|
<FormattedMessage defaultMessage={"Réinitialiser"} />
|
|
@@ -810,6 +727,7 @@ module Make = (StateLenses: Config) => {
|
|
|
810
727
|
disabled={date->Option.isNone}
|
|
811
728
|
onClick={_ => {
|
|
812
729
|
disclosure.hide()
|
|
730
|
+
revalidate()
|
|
813
731
|
handleChange(date)
|
|
814
732
|
}}>
|
|
815
733
|
<FormattedMessage defaultMessage={"Choisir"} />
|
|
@@ -831,23 +749,32 @@ module Make = (StateLenses: Config) => {
|
|
|
831
749
|
~placeholder=?,
|
|
832
750
|
~disabledBefore=?,
|
|
833
751
|
) => {
|
|
834
|
-
<
|
|
752
|
+
<Field
|
|
835
753
|
field
|
|
836
|
-
render={({handleChange, value, error}) =>
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
754
|
+
render={({handleChange, value, error, state, validate}) => {
|
|
755
|
+
let revalidate = () => {
|
|
756
|
+
switch state {
|
|
757
|
+
| Pristine => ()
|
|
758
|
+
| _ => validate()
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
<>
|
|
763
|
+
{switch label {
|
|
764
|
+
| None => React.null
|
|
765
|
+
| Some(label) =>
|
|
766
|
+
<Toolkit__Ui_Label
|
|
767
|
+
htmlFor=id
|
|
768
|
+
optionalMessage={isOptional->Option.getWithDefault(false)
|
|
769
|
+
? <FormattedMessage defaultMessage="(Optionnel)" />
|
|
770
|
+
: React.null}>
|
|
771
|
+
label
|
|
772
|
+
</Toolkit__Ui_Label>
|
|
773
|
+
}}
|
|
774
|
+
<Picker value handleChange containerClassName placeholder ?disabledBefore revalidate />
|
|
775
|
+
<ErrorMessage ?error />
|
|
776
|
+
</>
|
|
777
|
+
}}
|
|
851
778
|
/>
|
|
852
779
|
}
|
|
853
780
|
}
|