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