@colisweb/rescript-toolkit 4.7.1 → 4.8.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/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 +102 -40
- package/src/form/Toolkit__Form.res +41 -114
- package/src/ui/Toolkit__Ui_Radio.res +5 -5
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.0",
|
|
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
|
@@ -5,9 +5,13 @@ module type Config = {
|
|
|
5
5
|
let get: (state, field<'a>) => 'a
|
|
6
6
|
type error
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
type childFieldError = ReSchema.childFieldError
|
|
10
|
+
|
|
8
11
|
type fieldState =
|
|
9
12
|
| Pristine
|
|
10
13
|
| Valid
|
|
14
|
+
| NestedErrors(array<ReSchema.childFieldError>)
|
|
11
15
|
| Error(string)
|
|
12
16
|
type formState<'error> =
|
|
13
17
|
| Pristine
|
|
@@ -53,6 +57,7 @@ module Make = (Config: Config) => {
|
|
|
53
57
|
state: state,
|
|
54
58
|
getFieldState: field => fieldState,
|
|
55
59
|
getFieldError: field => option<string>,
|
|
60
|
+
getNestedFieldError: (field, int) => array<childFieldError>,
|
|
56
61
|
handleChange: 'a. (Config.field<'a>, 'a) => unit,
|
|
57
62
|
arrayPush: 'a. (Config.field<array<'a>>, 'a) => unit,
|
|
58
63
|
arrayUpdateByIndex: 'a. (~field: Config.field<array<'a>>, ~index: int, 'a) => unit,
|
|
@@ -92,20 +97,23 @@ module Make = (Config: Config) => {
|
|
|
92
97
|
| OnDemand
|
|
93
98
|
|
|
94
99
|
let getInitialFieldsState: Validation.schema => array<(field, fieldState)> = schema => {
|
|
95
|
-
|
|
96
|
-
validators->Belt.Array.map(validator =>
|
|
100
|
+
schema->Belt.Array.map(validator =>
|
|
97
101
|
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.
|
|
102
|
+
| Validation.IntMin({field}) => (ReSchema.Field(field), (Pristine: fieldState))
|
|
103
|
+
| Validation.True({field}) => (Field(field), Pristine)
|
|
104
|
+
| Validation.False({field}) => (Field(field), Pristine)
|
|
105
|
+
| Validation.IntMax({field}) => (Field(field), Pristine)
|
|
106
|
+
| Validation.FloatMin({field}) => (Field(field), Pristine)
|
|
107
|
+
| Validation.FloatMax({field}) => (Field(field), Pristine)
|
|
108
|
+
| Validation.Email({field}) => (Field(field), Pristine)
|
|
109
|
+
| Validation.NoValidation({field}) => (Field(field), Pristine)
|
|
110
|
+
| Validation.StringNonEmpty({field}) => (Field(field), Pristine)
|
|
111
|
+
| Validation.StringRegExp({field}) => (Field(field), Pristine)
|
|
112
|
+
| Validation.StringMin({field}) => (Field(field), Pristine)
|
|
113
|
+
| Validation.StringMax({field}) => (Field(field), Pristine)
|
|
114
|
+
| Validation.Custom({field}) => (Field(field), Pristine)
|
|
115
|
+
| Validation.OptionNonEmpty({field}) => (Field(field), Pristine)
|
|
116
|
+
| Validation.ArrayNonEmpty({field}) => (Field(field), Pristine)
|
|
109
117
|
}
|
|
110
118
|
)
|
|
111
119
|
}
|
|
@@ -126,10 +134,30 @@ module Make = (Config: Config) => {
|
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
136
|
|
|
129
|
-
module
|
|
137
|
+
module FormProvider = {
|
|
130
138
|
let make = React.Context.provider(formContext)
|
|
131
139
|
}
|
|
132
140
|
|
|
141
|
+
module Provider = {
|
|
142
|
+
@react.component
|
|
143
|
+
let make = (~id=?, ~className=?, ~children, ~form) => {
|
|
144
|
+
<FormProvider value={form}>
|
|
145
|
+
<form
|
|
146
|
+
?id
|
|
147
|
+
?className
|
|
148
|
+
onSubmit={event => {
|
|
149
|
+
ReactEvent.Synthetic.preventDefault(event)
|
|
150
|
+
form.submit()
|
|
151
|
+
}}>
|
|
152
|
+
children
|
|
153
|
+
<Toolkit__Ui_VisuallyHidden>
|
|
154
|
+
<input type_="submit" hidden=true />
|
|
155
|
+
</Toolkit__Ui_VisuallyHidden>
|
|
156
|
+
</form>
|
|
157
|
+
</FormProvider>
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
133
161
|
module Field = {
|
|
134
162
|
@react.component
|
|
135
163
|
let make = (
|
|
@@ -146,11 +174,11 @@ module Make = (Config: Config) => {
|
|
|
146
174
|
|
|
147
175
|
let use = (
|
|
148
176
|
~initialState,
|
|
149
|
-
~schema: Validation.schema,
|
|
177
|
+
~schema=[]: Validation.schema,
|
|
150
178
|
~onSubmit,
|
|
151
179
|
~onSubmitFail=ignore,
|
|
152
180
|
~i18n=ReSchemaI18n.default,
|
|
153
|
-
~validationStrategy=
|
|
181
|
+
~validationStrategy=OnDemand,
|
|
154
182
|
(),
|
|
155
183
|
) => {
|
|
156
184
|
let (state, send) = ReactUpdate.useReducerWithMapState(
|
|
@@ -165,6 +193,13 @@ module Make = (Config: Config) => {
|
|
|
165
193
|
state: self.state,
|
|
166
194
|
raiseSubmitFailed: error => self.send(RaiseSubmitFailed(error)),
|
|
167
195
|
})
|
|
196
|
+
->Promise.tap(x =>
|
|
197
|
+
switch x {
|
|
198
|
+
| Ok(_) => self.send(SetFormState(SubmitSucceed))
|
|
199
|
+
| Error(error) => self.send(SetFormState(SubmitFailed(Some(error))))
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
->ignore
|
|
168
203
|
|
|
169
204
|
None
|
|
170
205
|
},
|
|
@@ -180,26 +215,27 @@ module Make = (Config: Config) => {
|
|
|
180
215
|
| ValidateField(field) =>
|
|
181
216
|
SideEffects(
|
|
182
217
|
self => {
|
|
183
|
-
let fieldState =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
218
|
+
let fieldState = ReSchema.validateOne(
|
|
219
|
+
~field,
|
|
220
|
+
~values=self.state.values,
|
|
221
|
+
~i18n,
|
|
222
|
+
schema,
|
|
223
|
+
)
|
|
224
|
+
let newFieldState: fieldState = switch fieldState {
|
|
225
|
+
| None => Valid
|
|
226
|
+
| Some(fieldState) =>
|
|
227
|
+
switch fieldState {
|
|
187
228
|
| (_, Error(message)) => Error(message)
|
|
229
|
+
| (_, NestedErrors(errors)) => NestedErrors(errors)
|
|
188
230
|
| (_, Valid) => Valid
|
|
189
231
|
}
|
|
190
|
-
|
|
232
|
+
}
|
|
191
233
|
|
|
192
234
|
let newFieldsState =
|
|
193
235
|
state.fieldsState
|
|
194
|
-
->Belt.Array.keep(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
->Belt.Array.concat(
|
|
198
|
-
switch newFieldState {
|
|
199
|
-
| Some(fieldState) => [(field, fieldState)]
|
|
200
|
-
| None => []
|
|
201
|
-
},
|
|
202
|
-
)
|
|
236
|
+
->Belt.Array.keep(((value, _)) => value != field)
|
|
237
|
+
->Belt.Array.concat([(field, newFieldState)])
|
|
238
|
+
|
|
203
239
|
self.send(SetFieldsState(newFieldsState))
|
|
204
240
|
None
|
|
205
241
|
},
|
|
@@ -207,23 +243,38 @@ module Make = (Config: Config) => {
|
|
|
207
243
|
| ValidateForm(submit) =>
|
|
208
244
|
SideEffects(
|
|
209
245
|
self => {
|
|
210
|
-
let recordState =
|
|
246
|
+
let recordState = ReSchema.validate(~i18n, self.state.values, schema)
|
|
211
247
|
|
|
212
248
|
switch recordState {
|
|
213
|
-
| Valid =>
|
|
214
|
-
|
|
215
|
-
|
|
249
|
+
| Valid => {
|
|
250
|
+
let newFieldsState: array<(field, fieldState)> =
|
|
251
|
+
self.state.fieldsState->Belt.Array.map(((field, _)) => (
|
|
252
|
+
field,
|
|
253
|
+
(Valid: fieldState),
|
|
254
|
+
))
|
|
255
|
+
self.send(SetFieldsState(newFieldsState))
|
|
256
|
+
submit ? self.send(Submit) : ()
|
|
257
|
+
}
|
|
216
258
|
| Errors(erroredFields) =>
|
|
217
|
-
let newFieldsState =
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
259
|
+
let newFieldsState: array<(field, fieldState)> = erroredFields->Belt.Array.map(((
|
|
260
|
+
field,
|
|
261
|
+
err,
|
|
262
|
+
)) => (
|
|
263
|
+
field,
|
|
264
|
+
switch err {
|
|
265
|
+
| Error(e) => Error(e)
|
|
266
|
+
| NestedErrors(e) => NestedErrors(e)
|
|
267
|
+
| Valid => Valid
|
|
268
|
+
},
|
|
269
|
+
))
|
|
222
270
|
self.send(SetFieldsState(newFieldsState))
|
|
223
271
|
submit
|
|
224
272
|
? onSubmitFail({
|
|
225
273
|
send: self.send,
|
|
226
|
-
state:
|
|
274
|
+
state: {
|
|
275
|
+
...self.state,
|
|
276
|
+
fieldsState: newFieldsState,
|
|
277
|
+
},
|
|
227
278
|
raiseSubmitFailed: error => self.send(RaiseSubmitFailed(error)),
|
|
228
279
|
})
|
|
229
280
|
: ()
|
|
@@ -328,10 +379,19 @@ module Make = (Config: Config) => {
|
|
|
328
379
|
x =>
|
|
329
380
|
switch x {
|
|
330
381
|
| Error(error) => Some(error)
|
|
382
|
+
| NestedErrors(_errors) => None
|
|
331
383
|
| _ => None
|
|
332
384
|
}
|
|
333
385
|
)
|
|
334
386
|
|
|
387
|
+
let getNestedFieldError = (field, index) =>
|
|
388
|
+
switch getFieldState(field) {
|
|
389
|
+
| NestedErrors(errors) => errors->Array.keep(error => error.index === index)
|
|
390
|
+
| Pristine
|
|
391
|
+
| Valid
|
|
392
|
+
| Error(_) => []
|
|
393
|
+
}
|
|
394
|
+
|
|
335
395
|
let validateFields = (fields: array<field>) => {
|
|
336
396
|
let fieldsValidated = ReSchema.validateFields(~fields, ~values=state.values, ~i18n, schema)
|
|
337
397
|
|
|
@@ -354,6 +414,7 @@ module Make = (Config: Config) => {
|
|
|
354
414
|
switch newFieldStateValidated {
|
|
355
415
|
| Valid => [(field, (Valid: fieldState))]
|
|
356
416
|
| Error(message) => [(field, Error(message))]
|
|
417
|
+
| NestedErrors(message) => [(field, NestedErrors(message))]
|
|
357
418
|
}
|
|
358
419
|
|
|
359
420
|
| None => []
|
|
@@ -386,6 +447,7 @@ module Make = (Config: Config) => {
|
|
|
386
447
|
shouldValidate ? send(FieldChangeValue(field, value)) : send(SetFieldValue(field, value)),
|
|
387
448
|
getFieldState,
|
|
388
449
|
getFieldError,
|
|
450
|
+
getNestedFieldError,
|
|
389
451
|
handleChange: (field, value) => send(FieldChangeValue(field, value)),
|
|
390
452
|
arrayPush: (field, value) => send(FieldArrayAdd(field, value)),
|
|
391
453
|
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
|
}
|
|
@@ -45,13 +45,13 @@ let make = (
|
|
|
45
45
|
| #lg => "w-10 h-10"
|
|
46
46
|
},
|
|
47
47
|
])}>
|
|
48
|
-
<ReactIcons.
|
|
48
|
+
<ReactIcons.FaCircle
|
|
49
49
|
className="transform transition-all ease-in-out"
|
|
50
50
|
size={switch size {
|
|
51
|
-
| #xs =>
|
|
52
|
-
| #sm =>
|
|
53
|
-
| #md =>
|
|
54
|
-
| #lg =>
|
|
51
|
+
| #xs => 8
|
|
52
|
+
| #sm => 12
|
|
53
|
+
| #md => 18
|
|
54
|
+
| #lg => 24
|
|
55
55
|
}}
|
|
56
56
|
/>
|
|
57
57
|
</span>
|