keycloakify 6.12.7-rc.0 → 6.12.7
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/lib/pages/shared/UserProfileCommons.d.ts +46 -4
- package/lib/pages/shared/UserProfileCommons.js +336 -15
- package/lib/pages/shared/UserProfileCommons.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/useFormValidationSlice.d.ts +30 -28
- package/lib/useFormValidationSlice.js +104 -102
- package/lib/useFormValidationSlice.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/pages/shared/UserProfileCommons.tsx +510 -36
- package/src/lib/useFormValidationSlice.tsx +174 -172
@@ -1,13 +1,15 @@
|
|
1
|
-
import
|
1
|
+
import "../../tools/Array.prototype.every";
|
2
|
+
import React, { useEffect, useMemo, useReducer, Fragment } from "react";
|
2
3
|
import type { KcProps } from "../../KcProps";
|
3
|
-
import type { Attribute } from "../../getKcContext
|
4
|
+
import type { KcContextBase, Validators, Attribute } from "../../getKcContext";
|
4
5
|
import { clsx } from "../../tools/clsx";
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import type { I18nBase } from "../../i18n";
|
6
|
+
import { useConstCallback } from "../../tools/useConstCallback";
|
7
|
+
import { emailRegexp } from "../../tools/emailRegExp";
|
8
|
+
import type { I18nBase, MessageKeyBase } from "../../i18n";
|
9
|
+
import { id } from "tsafe/id";
|
8
10
|
|
9
11
|
export type UserProfileFormFieldsProps = {
|
10
|
-
kcContext: Parameters<typeof
|
12
|
+
kcContext: Parameters<typeof useFormValidation>[0]["kcContext"];
|
11
13
|
i18n: I18nBase;
|
12
14
|
} & KcProps &
|
13
15
|
Partial<Record<"BeforeField" | "AfterField", (props: { attribute: Attribute }) => JSX.Element | null>> & {
|
@@ -26,9 +28,9 @@ export function UserProfileFormFields({
|
|
26
28
|
|
27
29
|
const {
|
28
30
|
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
29
|
-
|
31
|
+
formValidationDispatch,
|
30
32
|
attributesWithPassword
|
31
|
-
} =
|
33
|
+
} = useFormValidation({
|
32
34
|
kcContext,
|
33
35
|
i18n
|
34
36
|
});
|
@@ -37,29 +39,6 @@ export function UserProfileFormFields({
|
|
37
39
|
onIsFormSubmittableValueChange(isFormSubmittable);
|
38
40
|
}, [isFormSubmittable]);
|
39
41
|
|
40
|
-
const onChangeFactory = useCallbackFactory(
|
41
|
-
(
|
42
|
-
[name]: [string],
|
43
|
-
[
|
44
|
-
{
|
45
|
-
target: { value }
|
46
|
-
}
|
47
|
-
]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>]
|
48
|
-
) =>
|
49
|
-
formValidationReducer({
|
50
|
-
"action": "update value",
|
51
|
-
name,
|
52
|
-
"newValue": value
|
53
|
-
})
|
54
|
-
);
|
55
|
-
|
56
|
-
const onBlurFactory = useCallbackFactory(([name]: [string]) =>
|
57
|
-
formValidationReducer({
|
58
|
-
"action": "focus lost",
|
59
|
-
name
|
60
|
-
})
|
61
|
-
);
|
62
|
-
|
63
42
|
let currentGroup = "";
|
64
43
|
|
65
44
|
return (
|
@@ -108,8 +87,19 @@ export function UserProfileFormFields({
|
|
108
87
|
<select
|
109
88
|
id={attribute.name}
|
110
89
|
name={attribute.name}
|
111
|
-
onChange={
|
112
|
-
|
90
|
+
onChange={event =>
|
91
|
+
formValidationDispatch({
|
92
|
+
"action": "update value",
|
93
|
+
"name": attribute.name,
|
94
|
+
"newValue": event.target.value
|
95
|
+
})
|
96
|
+
}
|
97
|
+
onBlur={() =>
|
98
|
+
formValidationDispatch({
|
99
|
+
"action": "focus lost",
|
100
|
+
"name": attribute.name
|
101
|
+
})
|
102
|
+
}
|
113
103
|
value={value}
|
114
104
|
>
|
115
105
|
{options.options.map(option => (
|
@@ -135,12 +125,23 @@ export function UserProfileFormFields({
|
|
135
125
|
id={attribute.name}
|
136
126
|
name={attribute.name}
|
137
127
|
value={value}
|
138
|
-
onChange={
|
128
|
+
onChange={event =>
|
129
|
+
formValidationDispatch({
|
130
|
+
"action": "update value",
|
131
|
+
"name": attribute.name,
|
132
|
+
"newValue": event.target.value
|
133
|
+
})
|
134
|
+
}
|
135
|
+
onBlur={() =>
|
136
|
+
formValidationDispatch({
|
137
|
+
"action": "focus lost",
|
138
|
+
"name": attribute.name
|
139
|
+
})
|
140
|
+
}
|
139
141
|
className={clsx(props.kcInputClass)}
|
140
142
|
aria-invalid={displayableErrors.length !== 0}
|
141
143
|
disabled={attribute.readOnly}
|
142
144
|
autoComplete={attribute.autocomplete}
|
143
|
-
onBlur={onBlurFactory(attribute.name)}
|
144
145
|
/>
|
145
146
|
);
|
146
147
|
})()}
|
@@ -166,7 +167,6 @@ export function UserProfileFormFields({
|
|
166
167
|
})()}
|
167
168
|
</div>
|
168
169
|
</div>
|
169
|
-
|
170
170
|
{AfterField && <AfterField attribute={attribute} />}
|
171
171
|
</Fragment>
|
172
172
|
);
|
@@ -174,3 +174,477 @@ export function UserProfileFormFields({
|
|
174
174
|
</>
|
175
175
|
);
|
176
176
|
}
|
177
|
+
|
178
|
+
/**
|
179
|
+
* NOTE: The attributesWithPassword returned is actually augmented with
|
180
|
+
* artificial password related attributes only if kcContext.passwordRequired === true
|
181
|
+
*/
|
182
|
+
export function useFormValidation(params: {
|
183
|
+
kcContext: {
|
184
|
+
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
185
|
+
profile: {
|
186
|
+
attributes: Attribute[];
|
187
|
+
};
|
188
|
+
passwordRequired?: boolean;
|
189
|
+
realm: { registrationEmailAsUsername: boolean };
|
190
|
+
};
|
191
|
+
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
192
|
+
passwordValidators?: Validators;
|
193
|
+
i18n: I18nBase;
|
194
|
+
}) {
|
195
|
+
const {
|
196
|
+
kcContext,
|
197
|
+
passwordValidators = {
|
198
|
+
"length": {
|
199
|
+
"ignore.empty.value": true,
|
200
|
+
"min": "4"
|
201
|
+
}
|
202
|
+
},
|
203
|
+
i18n
|
204
|
+
} = params;
|
205
|
+
|
206
|
+
const attributesWithPassword = useMemo(
|
207
|
+
() =>
|
208
|
+
!kcContext.passwordRequired
|
209
|
+
? kcContext.profile.attributes
|
210
|
+
: (() => {
|
211
|
+
const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username";
|
212
|
+
|
213
|
+
return kcContext.profile.attributes.reduce<Attribute[]>(
|
214
|
+
(prev, curr) => [
|
215
|
+
...prev,
|
216
|
+
...(curr.name !== name
|
217
|
+
? [curr]
|
218
|
+
: [
|
219
|
+
curr,
|
220
|
+
id<Attribute>({
|
221
|
+
"name": "password",
|
222
|
+
"displayName": id<`\${${MessageKeyBase}}`>("${password}"),
|
223
|
+
"required": true,
|
224
|
+
"readOnly": false,
|
225
|
+
"validators": passwordValidators,
|
226
|
+
"annotations": {},
|
227
|
+
"groupAnnotations": {},
|
228
|
+
"autocomplete": "new-password"
|
229
|
+
}),
|
230
|
+
id<Attribute>({
|
231
|
+
"name": "password-confirm",
|
232
|
+
"displayName": id<`\${${MessageKeyBase}}`>("${passwordConfirm}"),
|
233
|
+
"required": true,
|
234
|
+
"readOnly": false,
|
235
|
+
"validators": {
|
236
|
+
"_compareToOther": {
|
237
|
+
"name": "password",
|
238
|
+
"ignore.empty.value": true,
|
239
|
+
"shouldBe": "equal",
|
240
|
+
"error-message": id<`\${${MessageKeyBase}}`>("${invalidPasswordConfirmMessage}")
|
241
|
+
}
|
242
|
+
},
|
243
|
+
"annotations": {},
|
244
|
+
"groupAnnotations": {},
|
245
|
+
"autocomplete": "new-password"
|
246
|
+
})
|
247
|
+
])
|
248
|
+
],
|
249
|
+
[]
|
250
|
+
);
|
251
|
+
})(),
|
252
|
+
[kcContext, passwordValidators]
|
253
|
+
);
|
254
|
+
|
255
|
+
const { getErrors } = useGetErrors({
|
256
|
+
"kcContext": {
|
257
|
+
"messagesPerField": kcContext.messagesPerField,
|
258
|
+
"profile": {
|
259
|
+
"attributes": attributesWithPassword
|
260
|
+
}
|
261
|
+
},
|
262
|
+
i18n
|
263
|
+
});
|
264
|
+
|
265
|
+
const initialInternalState = useMemo(
|
266
|
+
() =>
|
267
|
+
Object.fromEntries(
|
268
|
+
attributesWithPassword
|
269
|
+
.map(attribute => ({
|
270
|
+
attribute,
|
271
|
+
"errors": getErrors({
|
272
|
+
"name": attribute.name,
|
273
|
+
"fieldValueByAttributeName": Object.fromEntries(
|
274
|
+
attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }])
|
275
|
+
)
|
276
|
+
})
|
277
|
+
}))
|
278
|
+
.map(({ attribute, errors }) => [
|
279
|
+
attribute.name,
|
280
|
+
{
|
281
|
+
"value": attribute.value ?? "",
|
282
|
+
errors,
|
283
|
+
"doDisplayPotentialErrorMessages": errors.length !== 0
|
284
|
+
}
|
285
|
+
])
|
286
|
+
),
|
287
|
+
[attributesWithPassword]
|
288
|
+
);
|
289
|
+
|
290
|
+
type InternalState = typeof initialInternalState;
|
291
|
+
|
292
|
+
const [formValidationInternalState, formValidationDispatch] = useReducer(
|
293
|
+
(
|
294
|
+
state: InternalState,
|
295
|
+
params:
|
296
|
+
| {
|
297
|
+
action: "update value";
|
298
|
+
name: string;
|
299
|
+
newValue: string;
|
300
|
+
}
|
301
|
+
| {
|
302
|
+
action: "focus lost";
|
303
|
+
name: string;
|
304
|
+
}
|
305
|
+
): InternalState => ({
|
306
|
+
...state,
|
307
|
+
[params.name]: {
|
308
|
+
...state[params.name],
|
309
|
+
...(() => {
|
310
|
+
switch (params.action) {
|
311
|
+
case "focus lost":
|
312
|
+
return { "doDisplayPotentialErrorMessages": true };
|
313
|
+
case "update value":
|
314
|
+
return {
|
315
|
+
"value": params.newValue,
|
316
|
+
"errors": getErrors({
|
317
|
+
"name": params.name,
|
318
|
+
"fieldValueByAttributeName": {
|
319
|
+
...state,
|
320
|
+
[params.name]: { "value": params.newValue }
|
321
|
+
}
|
322
|
+
})
|
323
|
+
};
|
324
|
+
}
|
325
|
+
})()
|
326
|
+
}
|
327
|
+
}),
|
328
|
+
initialInternalState
|
329
|
+
);
|
330
|
+
|
331
|
+
const formValidationState = useMemo(
|
332
|
+
() => ({
|
333
|
+
"fieldStateByAttributeName": Object.fromEntries(
|
334
|
+
Object.entries(formValidationInternalState).map(([name, { value, errors, doDisplayPotentialErrorMessages }]) => [
|
335
|
+
name,
|
336
|
+
{ value, "displayableErrors": doDisplayPotentialErrorMessages ? errors : [] }
|
337
|
+
])
|
338
|
+
),
|
339
|
+
"isFormSubmittable": Object.entries(formValidationInternalState).every(
|
340
|
+
([name, { value, errors }]) =>
|
341
|
+
errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required)
|
342
|
+
)
|
343
|
+
}),
|
344
|
+
[formValidationInternalState, attributesWithPassword]
|
345
|
+
);
|
346
|
+
|
347
|
+
return {
|
348
|
+
formValidationState,
|
349
|
+
formValidationDispatch,
|
350
|
+
attributesWithPassword
|
351
|
+
};
|
352
|
+
}
|
353
|
+
|
354
|
+
/** Expect to be used in a component wrapped within a <I18nProvider> */
|
355
|
+
function useGetErrors(params: {
|
356
|
+
kcContext: {
|
357
|
+
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
358
|
+
profile: {
|
359
|
+
attributes: { name: string; value?: string; validators: Validators }[];
|
360
|
+
};
|
361
|
+
};
|
362
|
+
i18n: I18nBase;
|
363
|
+
}) {
|
364
|
+
const { kcContext, i18n } = params;
|
365
|
+
|
366
|
+
const {
|
367
|
+
messagesPerField,
|
368
|
+
profile: { attributes }
|
369
|
+
} = kcContext;
|
370
|
+
|
371
|
+
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
|
372
|
+
|
373
|
+
const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
|
374
|
+
const { name, fieldValueByAttributeName } = params;
|
375
|
+
|
376
|
+
const { value } = fieldValueByAttributeName[name];
|
377
|
+
|
378
|
+
const { value: defaultValue, validators } = attributes.find(attribute => attribute.name === name)!;
|
379
|
+
|
380
|
+
block: {
|
381
|
+
if (defaultValue !== value) {
|
382
|
+
break block;
|
383
|
+
}
|
384
|
+
|
385
|
+
let doesErrorExist: boolean;
|
386
|
+
|
387
|
+
try {
|
388
|
+
doesErrorExist = messagesPerField.existsError(name);
|
389
|
+
} catch {
|
390
|
+
break block;
|
391
|
+
}
|
392
|
+
|
393
|
+
if (!doesErrorExist) {
|
394
|
+
break block;
|
395
|
+
}
|
396
|
+
|
397
|
+
const errorMessageStr = messagesPerField.get(name);
|
398
|
+
|
399
|
+
return [
|
400
|
+
{
|
401
|
+
"validatorName": undefined,
|
402
|
+
errorMessageStr,
|
403
|
+
"errorMessage": <span key={0}>{errorMessageStr}</span>
|
404
|
+
}
|
405
|
+
];
|
406
|
+
}
|
407
|
+
|
408
|
+
const errors: {
|
409
|
+
errorMessage: JSX.Element;
|
410
|
+
errorMessageStr: string;
|
411
|
+
validatorName: keyof Validators | undefined;
|
412
|
+
}[] = [];
|
413
|
+
|
414
|
+
scope: {
|
415
|
+
const validatorName = "length";
|
416
|
+
|
417
|
+
const validator = validators[validatorName];
|
418
|
+
|
419
|
+
if (validator === undefined) {
|
420
|
+
break scope;
|
421
|
+
}
|
422
|
+
|
423
|
+
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
|
424
|
+
|
425
|
+
if (ignoreEmptyValue && value === "") {
|
426
|
+
break scope;
|
427
|
+
}
|
428
|
+
|
429
|
+
if (max !== undefined && value.length > parseInt(max)) {
|
430
|
+
const msgArgs = ["error-invalid-length-too-long", max] as const;
|
431
|
+
|
432
|
+
errors.push({
|
433
|
+
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
434
|
+
"errorMessageStr": msgStr(...msgArgs),
|
435
|
+
validatorName
|
436
|
+
});
|
437
|
+
}
|
438
|
+
|
439
|
+
if (min !== undefined && value.length < parseInt(min)) {
|
440
|
+
const msgArgs = ["error-invalid-length-too-short", min] as const;
|
441
|
+
|
442
|
+
errors.push({
|
443
|
+
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
444
|
+
"errorMessageStr": msgStr(...msgArgs),
|
445
|
+
validatorName
|
446
|
+
});
|
447
|
+
}
|
448
|
+
}
|
449
|
+
|
450
|
+
scope: {
|
451
|
+
const validatorName = "_compareToOther";
|
452
|
+
|
453
|
+
const validator = validators[validatorName];
|
454
|
+
|
455
|
+
if (validator === undefined) {
|
456
|
+
break scope;
|
457
|
+
}
|
458
|
+
|
459
|
+
const { "ignore.empty.value": ignoreEmptyValue = false, name: otherName, shouldBe, "error-message": errorMessageKey } = validator;
|
460
|
+
|
461
|
+
if (ignoreEmptyValue && value === "") {
|
462
|
+
break scope;
|
463
|
+
}
|
464
|
+
|
465
|
+
const { value: otherValue } = fieldValueByAttributeName[otherName];
|
466
|
+
|
467
|
+
const isValid = (() => {
|
468
|
+
switch (shouldBe) {
|
469
|
+
case "different":
|
470
|
+
return otherValue !== value;
|
471
|
+
case "equal":
|
472
|
+
return otherValue === value;
|
473
|
+
}
|
474
|
+
})();
|
475
|
+
|
476
|
+
if (isValid) {
|
477
|
+
break scope;
|
478
|
+
}
|
479
|
+
|
480
|
+
const msgArg = [
|
481
|
+
errorMessageKey ??
|
482
|
+
id<MessageKeyBase>(
|
483
|
+
(() => {
|
484
|
+
switch (shouldBe) {
|
485
|
+
case "equal":
|
486
|
+
return "shouldBeEqual";
|
487
|
+
case "different":
|
488
|
+
return "shouldBeDifferent";
|
489
|
+
}
|
490
|
+
})()
|
491
|
+
),
|
492
|
+
otherName,
|
493
|
+
name,
|
494
|
+
shouldBe
|
495
|
+
] as const;
|
496
|
+
|
497
|
+
errors.push({
|
498
|
+
validatorName,
|
499
|
+
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArg)}</Fragment>,
|
500
|
+
"errorMessageStr": advancedMsgStr(...msgArg)
|
501
|
+
});
|
502
|
+
}
|
503
|
+
|
504
|
+
scope: {
|
505
|
+
const validatorName = "pattern";
|
506
|
+
|
507
|
+
const validator = validators[validatorName];
|
508
|
+
|
509
|
+
if (validator === undefined) {
|
510
|
+
break scope;
|
511
|
+
}
|
512
|
+
|
513
|
+
const { "ignore.empty.value": ignoreEmptyValue = false, pattern, "error-message": errorMessageKey } = validator;
|
514
|
+
|
515
|
+
if (ignoreEmptyValue && value === "") {
|
516
|
+
break scope;
|
517
|
+
}
|
518
|
+
|
519
|
+
if (new RegExp(pattern).test(value)) {
|
520
|
+
break scope;
|
521
|
+
}
|
522
|
+
|
523
|
+
const msgArgs = [errorMessageKey ?? id<MessageKeyBase>("shouldMatchPattern"), pattern] as const;
|
524
|
+
|
525
|
+
errors.push({
|
526
|
+
validatorName,
|
527
|
+
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
|
528
|
+
"errorMessageStr": advancedMsgStr(...msgArgs)
|
529
|
+
});
|
530
|
+
}
|
531
|
+
|
532
|
+
scope: {
|
533
|
+
if ([...errors].reverse()[0]?.validatorName === "pattern") {
|
534
|
+
break scope;
|
535
|
+
}
|
536
|
+
|
537
|
+
const validatorName = "email";
|
538
|
+
|
539
|
+
const validator = validators[validatorName];
|
540
|
+
|
541
|
+
if (validator === undefined) {
|
542
|
+
break scope;
|
543
|
+
}
|
544
|
+
|
545
|
+
const { "ignore.empty.value": ignoreEmptyValue = false } = validator;
|
546
|
+
|
547
|
+
if (ignoreEmptyValue && value === "") {
|
548
|
+
break scope;
|
549
|
+
}
|
550
|
+
|
551
|
+
if (emailRegexp.test(value)) {
|
552
|
+
break scope;
|
553
|
+
}
|
554
|
+
|
555
|
+
const msgArgs = [id<MessageKeyBase>("invalidEmailMessage")] as const;
|
556
|
+
|
557
|
+
errors.push({
|
558
|
+
validatorName,
|
559
|
+
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
560
|
+
"errorMessageStr": msgStr(...msgArgs)
|
561
|
+
});
|
562
|
+
}
|
563
|
+
|
564
|
+
scope: {
|
565
|
+
const validatorName = "integer";
|
566
|
+
|
567
|
+
const validator = validators[validatorName];
|
568
|
+
|
569
|
+
if (validator === undefined) {
|
570
|
+
break scope;
|
571
|
+
}
|
572
|
+
|
573
|
+
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
|
574
|
+
|
575
|
+
if (ignoreEmptyValue && value === "") {
|
576
|
+
break scope;
|
577
|
+
}
|
578
|
+
|
579
|
+
const intValue = parseInt(value);
|
580
|
+
|
581
|
+
if (isNaN(intValue)) {
|
582
|
+
const msgArgs = ["mustBeAnInteger"] as const;
|
583
|
+
|
584
|
+
errors.push({
|
585
|
+
validatorName,
|
586
|
+
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
587
|
+
"errorMessageStr": msgStr(...msgArgs)
|
588
|
+
});
|
589
|
+
|
590
|
+
break scope;
|
591
|
+
}
|
592
|
+
|
593
|
+
if (max !== undefined && intValue > parseInt(max)) {
|
594
|
+
const msgArgs = ["error-number-out-of-range-too-big", max] as const;
|
595
|
+
|
596
|
+
errors.push({
|
597
|
+
validatorName,
|
598
|
+
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
599
|
+
"errorMessageStr": msgStr(...msgArgs)
|
600
|
+
});
|
601
|
+
|
602
|
+
break scope;
|
603
|
+
}
|
604
|
+
|
605
|
+
if (min !== undefined && intValue < parseInt(min)) {
|
606
|
+
const msgArgs = ["error-number-out-of-range-too-small", min] as const;
|
607
|
+
|
608
|
+
errors.push({
|
609
|
+
validatorName,
|
610
|
+
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
611
|
+
"errorMessageStr": msgStr(...msgArgs)
|
612
|
+
});
|
613
|
+
|
614
|
+
break scope;
|
615
|
+
}
|
616
|
+
}
|
617
|
+
|
618
|
+
scope: {
|
619
|
+
const validatorName = "options";
|
620
|
+
|
621
|
+
const validator = validators[validatorName];
|
622
|
+
|
623
|
+
if (validator === undefined) {
|
624
|
+
break scope;
|
625
|
+
}
|
626
|
+
|
627
|
+
if (value === "") {
|
628
|
+
break scope;
|
629
|
+
}
|
630
|
+
|
631
|
+
if (validator.options.indexOf(value) >= 0) {
|
632
|
+
break scope;
|
633
|
+
}
|
634
|
+
|
635
|
+
const msgArgs = [id<MessageKeyBase>("notAValidOption")] as const;
|
636
|
+
|
637
|
+
errors.push({
|
638
|
+
validatorName,
|
639
|
+
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
|
640
|
+
"errorMessageStr": advancedMsgStr(...msgArgs)
|
641
|
+
});
|
642
|
+
}
|
643
|
+
|
644
|
+
//TODO: Implement missing validators.
|
645
|
+
|
646
|
+
return errors;
|
647
|
+
});
|
648
|
+
|
649
|
+
return { getErrors };
|
650
|
+
}
|