Skip to content

Commit 788f42d

Browse files
committed
Get rid of the ValidatorGenerators concept
(Accept validators as validator output instead)
1 parent 77a69c6 commit 788f42d

File tree

6 files changed

+90
-56
lines changed

6 files changed

+90
-56
lines changed

src/components/ui-plus-behavior/input/useDateField.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export type DateFieldControls = Omit<InputFieldControls<Date>, 'placeholder'> &
4444
}
4545

4646
export function useDateField(props: DateFieldProps): DateFieldControls {
47-
const { initialValue = new Date(), validatorsGenerator, validators, type = 'datetime-local' } = props
47+
const { initialValue = new Date(), validators, type = 'datetime-local' } = props
4848

4949
const [minDate, tooEarlyMessage] = expandCoupledData(props.minDate, [
5050
undefined,
@@ -61,20 +61,21 @@ export function useDateField(props: DateFieldProps): DateFieldControls {
6161
{
6262
...props,
6363
initialValue,
64-
validators: undefined,
65-
validatorsGenerator: value => [
66-
// Check minimum date
67-
minDate
68-
? () => (value.getTime() < minDate.getTime() ? getDateMessage(tooEarlyMessage, minDate) : undefined)
69-
: undefined,
64+
validators: [
65+
// Check minimum date if configured
66+
value =>
67+
!!minDate && value.getTime() < minDate.getTime()
68+
? getDateMessage(tooEarlyMessage, minDate)
69+
: undefined,
7070

71-
// Check maximum date
72-
maxDate
73-
? () => (value.getTime() > maxDate.getTime() ? getDateMessage(tooLateMessage, maxDate) : undefined)
74-
: undefined,
71+
// Check maximum date if configured
72+
value =>
73+
!!maxDate && value.getTime() > maxDate.getTime()
74+
? getDateMessage(tooLateMessage, maxDate)
75+
: undefined,
7576

7677
// Any custom validators
77-
...getAsArray(validatorsGenerator ? validatorsGenerator(value) : validators),
78+
...getAsArray(validators),
7879
],
7980
},
8081
{

src/components/ui-plus-behavior/input/useInputField.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
checkMessagesForProblems,
1717
camelToTitleCase,
1818
MessageMaybeAtLocation,
19+
SimpleValidatorOutput,
1920
} from './util'
2021
import { MarkdownCode } from '../../ui/markdown'
2122

@@ -64,8 +65,6 @@ export type InputFieldProps<DataType> = {
6465
*/
6566
required?: CoupledData
6667

67-
validatorsGenerator?: (values: DataType) => ValidatorBundle<DataType>
68-
6968
/**
7069
* Validators to apply to values
7170
*/
@@ -296,7 +295,6 @@ export function useInputFieldInternal<DataType>(
296295
initialValue,
297296
cleanUp,
298297
validators = noValidators,
299-
validatorsGenerator,
300298
containerClassName,
301299
expandHorizontally = true,
302300
validateOnChange = false,
@@ -457,14 +455,11 @@ export function useInputFieldInternal<DataType>(
457455
},
458456
}
459457

460-
// Identify the user-configured validators to use
461-
const realValidators = getAsArray(
462-
validatorsGenerator ? validatorsGenerator(cleanValue) : validators
463-
).filter((v): v is ValidatorFunction<DataType> => !!v)
458+
const validatorsToUse = [...getAsArray(validators)]
464459

465-
// Go through all the validators
466-
for (const validator of realValidators) {
467-
// Do we have anything to worry about from this validator?
460+
for (let i = 0; i < validatorsToUse.length; i++) {
461+
const validator = validatorsToUse[i]
462+
if (!validator) continue
468463
try {
469464
const validatorReport =
470465
hasError ||
@@ -473,7 +468,17 @@ export function useInputFieldInternal<DataType>(
473468
? [] // If we already have an error, don't even bother with any more validators
474469
: await validator(cleanValue, { ...validatorControls, isStillFresh }, params.reason) // Execute the current validators
475470

476-
getAsArray(validatorReport) // Maybe we have a single report, maybe an array. Receive it as an array.
471+
// Maybe we have a single report, maybe an array. Receive it as an array.
472+
const reports = getAsArray(validatorReport)
473+
474+
// Handle the recursive reports
475+
reports
476+
.filter((report): report is ValidatorFunction<DataType> => typeof report === 'function')
477+
.forEach(validator => validatorsToUse.push(validator))
478+
479+
// Handle the simple reports
480+
reports
481+
.filter((report): report is SimpleValidatorOutput => typeof report !== 'function')
477482
.map(report => wrapValidatorOutput(report, 'root', 'error')) // Wrap single strings to proper reports
478483
.forEach(message => {
479484
// Go through all the reports
@@ -516,7 +521,6 @@ export function useInputFieldInternal<DataType>(
516521
requiredMessage,
517522
validateEmptyOnChange,
518523
validators,
519-
validatorsGenerator,
520524
visible,
521525
]
522526
)

src/components/ui-plus-behavior/input/useOneOfField.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ export function useNullableOneOfField<DataType extends HasToString>(
128128
placeholder,
129129
canSelectPlaceholder = true,
130130
validators,
131-
validatorsGenerator,
132131
onValueChange,
133132
choices: realChoices,
134133
...rest
@@ -162,9 +161,6 @@ export function useNullableOneOfField<DataType extends HasToString>(
162161
},
163162
required: expandCoupledData(props.required, [false, 'Please select an option!']),
164163
validators: validators as ValidatorBundle<InternalDataType<DataType>>,
165-
validatorsGenerator: validatorsGenerator as (
166-
values: InternalDataType<DataType>
167-
) => ValidatorBundle<InternalDataType<DataType>>,
168164
},
169165
{
170166
isEmpty: v => v === PLEASE_SELECT || isEmpty(v as DataType),

src/components/ui-plus-behavior/input/useTextField.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function useTextField(
6363
const props = (
6464
typeof rawProps === 'string' ? { name: rawProps, description, required } : rawProps
6565
) as TextFieldProps
66-
const { initialValue = '', validatorsGenerator, validators, autoFocus = false, onEnter, hideInput } = props
66+
const { initialValue = '', validators, autoFocus = false, onEnter, hideInput } = props
6767

6868
const [minLength, tooShortMessage] = expandCoupledData(props.minLength, [
6969
1,
@@ -80,27 +80,21 @@ export function useTextField(
8080
{
8181
...props,
8282
initialValue,
83-
validators: undefined,
8483
cleanUp: props.cleanUp ?? ((s: string) => s.trim()),
85-
validatorsGenerator: value => [
86-
// Check minimum length
87-
minLength
88-
? () =>
89-
value !== '' && value.length < minLength!
90-
? `tooShort: ${getNumberMessage(tooShortMessage, minLength)} (Currently: ${value.length})`
91-
: undefined
92-
: undefined,
84+
validators: [
85+
// Check minimum length, if configured
86+
value =>
87+
!!minLength && value !== '' && value.length < minLength!
88+
? `tooShort: ${getNumberMessage(tooShortMessage, minLength)} (Currently: ${value.length})`
89+
: undefined,
9390

94-
// Check maximum length
95-
maxLength
96-
? () =>
97-
value !== '' && value.length > maxLength!
98-
? `tooLong: ${getNumberMessage(tooLongMessage, maxLength)} (Currently: ${value.length})`
99-
: undefined
100-
: undefined,
91+
// Check maximum length, if configured
92+
value =>
93+
!!maxLength && value !== '' && value.length > maxLength!
94+
? `tooLong: ${getNumberMessage(tooLongMessage, maxLength)} (Currently: ${value.length})`
95+
: undefined,
10196

102-
// Any custom validators
103-
...getAsArray(validatorsGenerator ? validatorsGenerator(value) : validators),
97+
...getAsArray(validators),
10498
],
10599
},
106100
{

src/components/ui-plus-behavior/input/util/validationMessages.ts

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@ export type MessageAtLocation = FieldMessage & { location: string }
2121
/**
2222
* Validators can return their findings on various detail levels
2323
*/
24-
export type ValidatorOutput = MessageMaybeAtLocation | MarkdownCode | undefined
24+
export type SimpleValidatorOutput = MessageMaybeAtLocation | MarkdownCode | undefined
25+
26+
/**
27+
* Validators can return their findings on various detail levels
28+
*/
29+
export type RecursiveValidatorOutput<DataType> = ValidatorFunction<DataType>
2530

2631
/**
2732
* Flush out validator messages with all details
2833
*/
2934
export const wrapValidatorOutput = (
30-
output: ValidatorOutput,
35+
output: SimpleValidatorOutput,
3136
defaultLocation: string,
3237
defaultLevel: FieldMessageType
3338
): MessageAtLocation | undefined => {
@@ -95,22 +100,56 @@ export const getDateMessage = (template: DateMessageTemplate, date: Date): strin
95100
* It should return undefined if everything is fine,
96101
* or return the found issues.
97102
*/
98-
export type SyncValidatorFunction<DataType> = (
103+
export type SyncSimpleValidatorFunction<DataType> = (
104+
value: DataType,
105+
controls: ValidatorControls,
106+
reason: string
107+
) => SingleOrArray<SimpleValidatorOutput>
108+
109+
/**
110+
* An asynchronous validator function
111+
*
112+
* It should return undefined if everything is fine,
113+
* or return the found issues.
114+
*/
115+
export type AsyncSimpleValidatorFunction<DataType> = (
116+
value: DataType,
117+
controls: ValidatorControls,
118+
reason: string
119+
) => Promise<SingleOrArray<SimpleValidatorOutput>>
120+
121+
export type SimpleValidatorFunction<DataType> =
122+
| SyncSimpleValidatorFunction<DataType>
123+
| AsyncSimpleValidatorFunction<DataType>
124+
125+
/**
126+
* A synchronous validator function
127+
*
128+
* It should return undefined if everything is fine,
129+
* or return the found issues.
130+
*/
131+
export type SyncRecursiveValidatorFunction<DataType> = (
99132
value: DataType,
100133
controls: ValidatorControls,
101134
reason: string
102-
) => SingleOrArray<ValidatorOutput>
135+
) => SingleOrArray<RecursiveValidatorOutput<DataType>>
103136

104137
/**
105138
* An asynchronous validator function
106139
*
107140
* It should return undefined if everything is fine,
108141
* or return the found issues.
109142
*/
110-
export type AsyncValidatorFunction<DataType> = (
143+
export type AsyncRecursiveValidatorFunction<DataType> = (
111144
value: DataType,
112145
controls: ValidatorControls,
113146
reason: string
114-
) => Promise<SingleOrArray<ValidatorOutput>>
147+
) => Promise<SingleOrArray<RecursiveValidatorOutput<DataType>>>
148+
149+
export type RecursiveValidatorFunction<DataType> =
150+
| SyncRecursiveValidatorFunction<DataType>
151+
| AsyncRecursiveValidatorFunction<DataType>
115152

116-
export type ValidatorFunction<DataType> = SyncValidatorFunction<DataType> | AsyncValidatorFunction<DataType>
153+
export type ValidatorFunction<DataType> =
154+
| SimpleValidatorFunction<DataType>
155+
| RecursiveValidatorFunction<DataType>

src/components/ui-plus-behavior/input/validation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { InputFieldControls, ValidationReason } from './useInputField'
2-
import { AsyncValidatorFunction, getAsArray, SingleOrArray, sleep } from './util'
2+
import { AsyncSimpleValidatorFunction, getAsArray, SingleOrArray, sleep } from './util'
33
import { LabelProps } from './useLabel'
44

55
export type FieldLike<DataType> = Readonly<
@@ -61,7 +61,7 @@ export const doFieldsHaveAnError = (fields: FieldArrayConfiguration): boolean =>
6161
.filter(field => field.visible)
6262
.some(field => field.hasProblems)
6363

64-
const mockValidator: AsyncValidatorFunction<unknown> = async (_value, controls) => {
64+
const mockValidator: AsyncSimpleValidatorFunction<unknown> = async (_value, controls) => {
6565
const { isStillFresh } = controls
6666
if (isStillFresh && !isStillFresh()) return undefined
6767
await sleep(500)

0 commit comments

Comments
 (0)