@4riders/reform 3.0.24

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +266 -0
  3. package/dist/index.d.ts +2715 -0
  4. package/dist/index.es.js +1715 -0
  5. package/dist/index.es.js.map +1 -0
  6. package/package.json +70 -0
  7. package/src/index.ts +90 -0
  8. package/src/reform/ArrayHelper.ts +164 -0
  9. package/src/reform/Form.tsx +81 -0
  10. package/src/reform/FormManager.ts +494 -0
  11. package/src/reform/Reform.ts +15 -0
  12. package/src/reform/components/BaseCheckboxField.tsx +72 -0
  13. package/src/reform/components/BaseDateField.tsx +84 -0
  14. package/src/reform/components/BaseRadioField.tsx +72 -0
  15. package/src/reform/components/BaseSelectField.tsx +103 -0
  16. package/src/reform/components/BaseTextAreaField.tsx +87 -0
  17. package/src/reform/components/BaseTextField.tsx +135 -0
  18. package/src/reform/components/InputHTMLProps.tsx +89 -0
  19. package/src/reform/observers/observer.ts +131 -0
  20. package/src/reform/observers/observerPath.ts +327 -0
  21. package/src/reform/observers/useObservers.ts +232 -0
  22. package/src/reform/useForm.ts +246 -0
  23. package/src/reform/useFormContext.tsx +37 -0
  24. package/src/reform/useFormField.ts +75 -0
  25. package/src/reform/useRender.ts +12 -0
  26. package/src/yop/MessageProvider.ts +204 -0
  27. package/src/yop/Metadata.ts +304 -0
  28. package/src/yop/ObjectsUtil.ts +811 -0
  29. package/src/yop/TypesUtil.ts +148 -0
  30. package/src/yop/ValidationContext.ts +207 -0
  31. package/src/yop/Yop.ts +430 -0
  32. package/src/yop/constraints/CommonConstraints.ts +124 -0
  33. package/src/yop/constraints/Constraint.ts +135 -0
  34. package/src/yop/constraints/MinMaxConstraints.ts +53 -0
  35. package/src/yop/constraints/OneOfConstraint.ts +40 -0
  36. package/src/yop/constraints/TestConstraint.ts +176 -0
  37. package/src/yop/decorators/array.ts +157 -0
  38. package/src/yop/decorators/boolean.ts +69 -0
  39. package/src/yop/decorators/date.ts +73 -0
  40. package/src/yop/decorators/email.ts +66 -0
  41. package/src/yop/decorators/file.ts +69 -0
  42. package/src/yop/decorators/id.ts +35 -0
  43. package/src/yop/decorators/ignored.ts +40 -0
  44. package/src/yop/decorators/instance.ts +110 -0
  45. package/src/yop/decorators/number.ts +73 -0
  46. package/src/yop/decorators/string.ts +90 -0
  47. package/src/yop/decorators/test.ts +41 -0
  48. package/src/yop/decorators/time.ts +112 -0
@@ -0,0 +1,69 @@
1
+ import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
2
+ import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
3
+ import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
4
+ import { isFile, isNumber } from "../TypesUtil"
5
+ import { InternalValidationContext } from "../ValidationContext"
6
+ import { fieldValidationDecorator, Groups } from "../Metadata"
7
+
8
+ /**
9
+ * Type for a file value, which can be a File object, null, or undefined.
10
+ * @ignore
11
+ */
12
+ export type FileValue = File | null | undefined
13
+
14
+ /**
15
+ * Interface for file field constraints, combining common, min/max, and test constraints.
16
+ * @template Value - The type of the file value.
17
+ * @template Parent - The type of the parent object.
18
+ * @see {@link CommonConstraints}
19
+ * @see {@link MinMaxConstraints}
20
+ * @see {@link TestConstraint}
21
+ * @category Property Decorators
22
+ */
23
+ export interface FileConstraints<Value extends FileValue, Parent> extends
24
+ CommonConstraints<Value, Parent>,
25
+ MinMaxConstraints<Value, number, Parent>,
26
+ TestConstraint<Value, Parent> {
27
+ }
28
+
29
+ /**
30
+ * Validates a file field against its constraints.
31
+ * @template Value - The type of the file value.
32
+ * @template Parent - The type of the parent object.
33
+ * @param context - The validation context.
34
+ * @param constraints - The file constraints to validate.
35
+ * @returns True if all constraints pass, false otherwise.
36
+ * @ignore
37
+ */
38
+ function validateFile<Value extends FileValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: FileConstraints<Value, Parent>) {
39
+ return (
40
+ validateTypeConstraint(context, isFile, "file") &&
41
+ validateMinMaxConstraints(context, constraints, isNumber, (value, min) => value.size >= min, (value, max) => value.size <= max) &&
42
+ validateTestConstraint(context, constraints)
43
+ )
44
+ }
45
+
46
+ /**
47
+ * Decorator for applying validation rules to a File field.
48
+ *
49
+ * Example usage:
50
+ * ```tsx
51
+ * class Person {
52
+ * @file({ required: true, min: [1024, "Picture size must be at least 1KB"] })
53
+ * profilePicture: File | null = null
54
+ * }
55
+ * const form = useForm(Person, ...)
56
+ *
57
+ * // the file decorator can also be used as a function to allow standalone validation:
58
+ * Yop.validate(null, file({ required: true })) // error: "Required field"
59
+ * ```
60
+ * @template Value - The type of the file value.
61
+ * @template Parent - The type of the parent object.
62
+ * @param constraints - The file constraints to apply.
63
+ * @param groups - Optional validation groups.
64
+ * @returns A field decorator function with validation.
65
+ * @category Property Decorators
66
+ */
67
+ export function file<Value extends FileValue, Parent>(constraints?: FileConstraints<Value, Parent>, groups?: Groups<FileConstraints<Value, Parent>>) {
68
+ return fieldValidationDecorator("file", constraints ?? {}, groups, validateFile, isNumber)
69
+ }
@@ -0,0 +1,35 @@
1
+ import { Constructor } from "../TypesUtil"
2
+ import { Yop } from "../Yop"
3
+ import { ArrayConstraints } from "./array"
4
+ import { InstanceConstraints } from "./instance"
5
+
6
+ /**
7
+ * Class decorator to register a class with a unique identifier in the Yop registry. It can be used when you need to reference
8
+ * classes by an identifier to prevent circular references.
9
+ *
10
+ * Example usage:
11
+ * ```tsx
12
+ * @id("Person")
13
+ * class Person {
14
+ *
15
+ * @instance({ of: "Person" }) // circular reference to itself using the class id
16
+ * friend: Person | null = null
17
+ *
18
+ * @array({ of: "Person" }) // circular reference to itself using the class id
19
+ * friends: Person[] | null = null
20
+ * }
21
+ * ```
22
+ *
23
+ * @template Type - The type of the class instance.
24
+ * @template Class - The constructor type of the class.
25
+ * @param id - The unique identifier for the class.
26
+ * @returns A class decorator function that registers the class in the Yop registry.
27
+ * @see {@link InstanceConstraints}
28
+ * @see {@link ArrayConstraints}
29
+ * @category Class Decorators
30
+ */
31
+ export function id<Type extends object, Class extends Constructor<Type>>(id: string) {
32
+ return function decorateClass(target: Class, _: ClassDecoratorContext<Class>) {
33
+ Yop.registerClass(id, target)
34
+ }
35
+ }
@@ -0,0 +1,40 @@
1
+ import { InternalCommonConstraints } from "../constraints/CommonConstraints"
2
+ import { Constraint } from "../constraints/Constraint"
3
+ import { fieldDecorator, Groups } from "../Metadata"
4
+
5
+ /**
6
+ * Field decorator to mark a field as ignored for validation.
7
+ *
8
+ * Example usage:
9
+ * ```tsx
10
+ * class Person {
11
+ * @string({ required: true, match: /^[A-Za-z]+$/ })
12
+ * name: string | null = null
13
+ * }
14
+ * class Anonymous extends Person {
15
+ * @ignored() // ignore all validation constraints on `name`
16
+ * override name: string | null = null
17
+ * }
18
+ * ```
19
+ *
20
+ * @template Parent - The type of the parent object.
21
+ * @param ignored - The constraint or boolean indicating if the field should be ignored (default: true).
22
+ * @param groups - Optional groups with their own ignored constraints.
23
+ * @returns A field decorator function that marks a field as ignored for validation.
24
+ * @category Property Decorators
25
+ */
26
+ export function ignored<Parent>(ignored: Constraint<any, boolean, Parent> = true, groups?: Groups<Constraint<any, boolean, Parent>>) {
27
+ return fieldDecorator<Parent, any>(field => {
28
+ field.ignored = ignored
29
+
30
+ if (groups != null) {
31
+ field.groups ??= {}
32
+ for (const [name, constraint] of Object.entries(groups)) {
33
+ if (field.groups?.[name] != null)
34
+ field.groups[name].ignored = constraint
35
+ else
36
+ field.groups[name] = { ignored: constraint } as InternalCommonConstraints
37
+ }
38
+ }
39
+ })
40
+ }
@@ -0,0 +1,110 @@
1
+ import { CommonConstraints, InternalCommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
2
+ import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
3
+ import { fieldValidationDecorator, Groups, InternalClassConstraints, validateClass } from "../Metadata"
4
+ import { defineLazyProperty } from "../ObjectsUtil"
5
+ import { ClassConstructor, isObject } from "../TypesUtil"
6
+ import { InternalValidationContext } from "../ValidationContext"
7
+ import { validationSymbol, Yop } from "../Yop"
8
+ import { id } from "./id"
9
+
10
+ /**
11
+ * Type for values that can be validated as instances (objects, null, or undefined).
12
+ * @ignore
13
+ */
14
+ export type InstanceValue = object | null | undefined
15
+
16
+
17
+ /**
18
+ * Constraints for validating an instance of a class. Inherits common and test constraints.
19
+ * @template Value - The type of the instance value.
20
+ * @template Parent - The type of the parent object.
21
+ * @property of - A class constructor, a function returning a constructor, or a class id to validate the instance against.
22
+ * @see {@link id}
23
+ * @see {@link CommonConstraints}
24
+ * @see {@link TestConstraint}
25
+ * @category Property Decorators
26
+ */
27
+ export interface InstanceConstraints<Value extends InstanceValue, Parent> extends
28
+ CommonConstraints<Value, Parent>,
29
+ TestConstraint<Value, Parent> {
30
+ /**
31
+ * A class constructor, a function returning a constructor, or a class id (see {@link id}) to validate the instance against.
32
+ */
33
+ of: ClassConstructor<NoInfer<Value>> | (() => ClassConstructor<NoInfer<Value>>) | string
34
+ }
35
+
36
+ /**
37
+ * Traverses the constraints of a class instance for validation.
38
+ * @param context - The validation context.
39
+ * @param constraints - The instance constraints.
40
+ * @param key - The property key to traverse.
41
+ * @param traverseNullish - Whether to traverse nullish values.
42
+ * @returns A tuple of the internal constraints and the value.
43
+ * @ignore
44
+ */
45
+ function traverseInstance<Value extends InstanceValue, Parent>(
46
+ context: InternalValidationContext<Value, Parent>,
47
+ constraints: InstanceConstraints<Value, Parent>,
48
+ key: string | number,
49
+ traverseNullish?: boolean
50
+ ): readonly [InternalCommonConstraints | undefined, any] {
51
+ if (constraints.of == null)
52
+ return [undefined, undefined] as const
53
+ const classConstraints = (constraints.of as any)[Symbol.metadata]?.[validationSymbol] as InternalClassConstraints | undefined
54
+ if (classConstraints == null)
55
+ return [undefined, undefined] as const
56
+ return classConstraints.traverse!(context as InternalValidationContext<never, never>, classConstraints, key, traverseNullish)
57
+ }
58
+
59
+ /**
60
+ * Validates that a value is an instance of the specified class and meets all constraints.
61
+ * @param context - The validation context.
62
+ * @param constraints - The instance constraints.
63
+ * @returns True if valid, false otherwise.
64
+ * @ignore
65
+ */
66
+ function validateInstance<Value extends InstanceValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: InstanceConstraints<Value, Parent>) {
67
+ if (!validateTypeConstraint(context, isObject, "object") ||
68
+ !validateTestConstraint(context, constraints) ||
69
+ constraints.of == null)
70
+ return false
71
+
72
+ const classConstraints = (constraints.of as any)[Symbol.metadata]?.[validationSymbol] as InternalClassConstraints | undefined
73
+ return classConstraints == null || validateClass(context as InternalValidationContext<{ [x: string]: any }>, classConstraints)
74
+ }
75
+
76
+ /**
77
+ * The kind string used to identify instance constraints.
78
+ * @ignore
79
+ */
80
+ export const instanceKind = "instance"
81
+
82
+ /**
83
+ * Decorator for validating a field value as an instance of a specified class. The `of` property must be set to a custom
84
+ * class constructor, not a built-in object type like String or Date.
85
+ *
86
+ * Example usage:
87
+ * ```tsx
88
+ * class Person {
89
+ * @instance({ of: Dog, required: true })
90
+ * dog: Dog | null = null
91
+ * }
92
+ * const form = useForm(Person, ...)
93
+ *
94
+ * // the instance decorator can also be used as a function to allow standalone validation:
95
+ * Yop.validate(new Person(), instance({ of: Person })) // error: dog is a "Required field"
96
+ * ```
97
+ * @template Value - The type of the instance value.
98
+ * @template Parent - The type of the parent object.
99
+ * @param constraints - The instance constraints.
100
+ * @param groups - Optional validation groups.
101
+ * @returns A field decorator that stores the instance constraints and validation function in the class metadata.
102
+ * @category Property Decorators
103
+ */
104
+ export function instance<Value extends InstanceValue, Parent>(constraints?: InstanceConstraints<Value, Parent>, groups?: Groups<InstanceConstraints<Value, Parent>>) {
105
+ if (typeof constraints?.of === "string" || (typeof constraints?.of === "function" && constraints.of.prototype == null)) {
106
+ const of = constraints.of
107
+ defineLazyProperty(constraints, "of", (_this) => Yop.resolveClass(of))
108
+ }
109
+ return fieldValidationDecorator(instanceKind, constraints ?? ({} as InstanceConstraints<Value, Parent>), groups, validateInstance, undefined, traverseInstance)
110
+ }
@@ -0,0 +1,73 @@
1
+ import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
2
+ import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
3
+ import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
4
+ import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
5
+ import { isNumber, isNumberArray } from "../TypesUtil"
6
+ import { InternalValidationContext } from "../ValidationContext"
7
+ import { fieldValidationDecorator, Groups } from "../Metadata"
8
+
9
+ /**
10
+ * Type for a number value, which can be a number, null, or undefined.
11
+ * @ignore
12
+ */
13
+ export type NumberValue = number | null | undefined
14
+
15
+ /**
16
+ * Interface for number field constraints, combining common, min/max, oneOf, and test constraints.
17
+ * @template Value - The type of the number value.
18
+ * @template Parent - The type of the parent object.
19
+ * @see {@link CommonConstraints}
20
+ * @see {@link MinMaxConstraints}
21
+ * @see {@link OneOfConstraint}
22
+ * @see {@link TestConstraint}
23
+ * @category Property Decorators
24
+ */
25
+ export interface NumberConstraints<Value extends NumberValue, Parent> extends
26
+ CommonConstraints<Value, Parent>,
27
+ MinMaxConstraints<Value, number, Parent>,
28
+ OneOfConstraint<Value, Parent>,
29
+ TestConstraint<Value, Parent> {
30
+ }
31
+
32
+ /**
33
+ * Validates a number field against its constraints.
34
+ * @template Value - The type of the number value.
35
+ * @template Parent - The type of the parent object.
36
+ * @param context - The validation context.
37
+ * @param constraints - The number constraints to validate.
38
+ * @returns True if all constraints pass, false otherwise.
39
+ * @ignore
40
+ */
41
+ function validateNumber<Value extends NumberValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: NumberConstraints<Value, Parent>) {
42
+ return (
43
+ validateTypeConstraint(context, isNumber, "number") &&
44
+ validateMinMaxConstraints(context, constraints, isNumber, (value, min) => value >= min, (value, max) => value <= max) &&
45
+ validateOneOfConstraint(context, constraints, isNumberArray) &&
46
+ validateTestConstraint(context, constraints)
47
+ )
48
+ }
49
+
50
+ /**
51
+ * Decorator for applying validation rules to a number field.
52
+ *
53
+ * Example usage:
54
+ * ```tsx
55
+ * class Person {
56
+ * @number({ required: true, min: 0 })
57
+ * age: number | null = null
58
+ * }
59
+ * const form = useForm(Person, ...)
60
+ *
61
+ * // the number decorator can also be used as a function to allow standalone validation:
62
+ * Yop.validate(-1, number({ required: true, min: 0 })) // error: "Must be greater or equal to 0"
63
+ * ```
64
+ * @template Value - The type of the number value.
65
+ * @template Parent - The type of the parent object.
66
+ * @param constraints - The number constraints to apply.
67
+ * @param groups - Optional validation groups.
68
+ * @returns A field decorator function with validation.
69
+ * @category Property Decorators
70
+ */
71
+ export function number<Value extends NumberValue, Parent>(constraints?: NumberConstraints<Value, Parent>, groups?: Groups<NumberConstraints<Value, Parent>>) {
72
+ return fieldValidationDecorator("number", constraints ?? {}, groups, validateNumber, isNumber)
73
+ }
@@ -0,0 +1,90 @@
1
+ import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
2
+ import { Constraint, Message, validateConstraint } from "../constraints/Constraint"
3
+ import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
4
+ import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
5
+ import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
6
+ import { isNumber, isRegExp, isString, isStringArray } from "../TypesUtil"
7
+ import { InternalValidationContext, ValuedContext } from "../ValidationContext"
8
+ import { fieldValidationDecorator, Groups } from "../Metadata"
9
+
10
+ /**
11
+ * Type for a string value, which can be a string, null, or undefined.
12
+ * @ignore
13
+ */
14
+ export type StringValue = string | null | undefined
15
+
16
+ /**
17
+ * Interface for string field constraints, combining common, min/max, oneOf, test, and match constraints.
18
+ * @template Value - The type of the string value.
19
+ * @template Parent - The type of the parent object.
20
+ * @property match - Constraint for matching a regular expression.
21
+ * @see {@link CommonConstraints}
22
+ * @see {@link MinMaxConstraints}
23
+ * @see {@link OneOfConstraint}
24
+ * @see {@link TestConstraint}
25
+ * @category Property Decorators
26
+ */
27
+ export interface StringConstraints<Value extends StringValue, Parent> extends
28
+ CommonConstraints<Value, Parent>,
29
+ MinMaxConstraints<Value, number, Parent>,
30
+ OneOfConstraint<Value, Parent>,
31
+ TestConstraint<Value, Parent> {
32
+ /**
33
+ * Constraint for matching a regular expression. The constraint value can be a RegExp, a function that returns a RegExp.
34
+ */
35
+ match?: Constraint<NonNullable<Value>, RegExp, Parent>
36
+ }
37
+
38
+ /**
39
+ * Validates a string field against its constraints.
40
+ * @template Value - The type of the string value.
41
+ * @template Parent - The type of the parent object.
42
+ * @param context - The validation context.
43
+ * @param constraints - The string constraints to validate.
44
+ * @param defaultRegexp - Optional default regular expression for matching.
45
+ * @param defaultMatchMessage - Optional default error message for match failures.
46
+ * @param type - Optional type name for error reporting.
47
+ * @returns True if all constraints pass, false otherwise.
48
+ * @ignore
49
+ */
50
+ export function validateString<Value extends StringValue, Parent>(
51
+ context: InternalValidationContext<Value, Parent>,
52
+ constraints: StringConstraints<Value, Parent>,
53
+ defaultRegexp?: RegExp,
54
+ defaultMatchMessage?: Message<Value, Parent>,
55
+ type?: string
56
+ ) {
57
+ return (
58
+ validateTypeConstraint(context, isString, type ?? "string") &&
59
+ validateMinMaxConstraints(context, constraints, isNumber, (value, min) => value.length >= min, (value, max) => value.length <= max) &&
60
+ validateConstraint(context as ValuedContext<Value, Parent>, constraints, "match", isRegExp, (value, re) => re.test(value), defaultRegexp, defaultMatchMessage) &&
61
+ validateOneOfConstraint(context, constraints, isStringArray) &&
62
+ validateTestConstraint(context, constraints)
63
+ )
64
+ }
65
+
66
+ /**
67
+ * Decorator for applying validation rules to a string field. A required string field can be an empty string, but neither `null` nor `undefined`.
68
+ * To enforce non-empty strings, use the `min` constraint with a value of 1.
69
+ *
70
+ * Example usage:
71
+ * ```tsx
72
+ * class Person {
73
+ * @string({ required: true, min: 1 })
74
+ * name: string | null = null
75
+ * }
76
+ * const form = useForm(Person, ...)
77
+ *
78
+ * // the string decorator can also be used as a function to allow standalone validation:
79
+ * Yop.validate("", string({ required: true, min: 1 })) // error: "Minimum 1 character"
80
+ * ```
81
+ * @template Value - The type of the string value.
82
+ * @template Parent - The type of the parent object.
83
+ * @param constraints - The string constraints to apply.
84
+ * @param groups - Optional validation groups.
85
+ * @returns A field decorator function with validation.
86
+ * @category Property Decorators
87
+ */
88
+ export function string<Value extends StringValue, Parent>(constraints?: StringConstraints<Value, Parent>, groups?: Groups<StringConstraints<Value, Parent>>) {
89
+ return fieldValidationDecorator("string", constraints ?? {}, groups, validateString, isNumber)
90
+ }
@@ -0,0 +1,41 @@
1
+ import { TestConstraintFunction } from "../constraints/TestConstraint"
2
+ import { initClassConstraints } from "../Metadata"
3
+ import { Constructor } from "../TypesUtil"
4
+
5
+ /**
6
+ * Utility type to extract the instance type from a constructor.
7
+ * @template Class - The constructor type.
8
+ * @category Class Decorators
9
+ */
10
+ export type InstanceType<Class> = Class extends Constructor<infer Type> ? Type : never
11
+
12
+ /**
13
+ * Class decorator to add a test constraint function to a class for validation. The test function will be called with the
14
+ * class instance after all field validations have passed, allowing for complex custom validation logic that depends on
15
+ * the entire object state.
16
+ *
17
+ * Example usage:
18
+ * ```tsx
19
+ * @test(credentials => credentials.password === credentials.confirmPassword ? true : "Passwords do not match")
20
+ * class Credentials {
21
+ * @string({ required: true, min: 8 })
22
+ * username: string | null = null
23
+ * @string({ required: true, min: 8, test: checkPasswordStrength })
24
+ * password: string | null = null
25
+ * @string({ required: true, min: 8 })
26
+ * confirmPassword: string | null = null
27
+ * }
28
+ * const form = useForm(Credentials, ...)
29
+ * ```
30
+ * @template Class - The constructor type of the class.
31
+ * @param test - The test constraint function to apply to the class instance.
32
+ * @returns A class decorator function that sets the test constraint.
33
+ * @see {@link TestConstraintFunction}
34
+ * @category Class Decorators
35
+ */
36
+ export function test<Class extends Constructor>(test: TestConstraintFunction<InstanceType<Class>>) {
37
+ return function decorateClass(_: Class, context: ClassDecoratorContext<Class>) {
38
+ const classConstraints = initClassConstraints(context.metadata)
39
+ classConstraints.test = test
40
+ }
41
+ }
@@ -0,0 +1,112 @@
1
+ import { InternalValidationContext } from "../ValidationContext"
2
+ import { fieldValidationDecorator, Groups } from "../Metadata"
3
+ import { StringValue } from "./string"
4
+ import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
5
+ import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
6
+ import { isFunction, isString, isStringArray } from "../TypesUtil"
7
+ import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
8
+ import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
9
+ import { Message } from "../constraints/Constraint"
10
+
11
+ /**
12
+ * Interface for time field constraints, combining common, min/max, oneOf, and test constraints.
13
+ * @template Value - The type of the string value.
14
+ * @template Parent - The type of the parent object.
15
+ * @property formatError - Optional custom error message for invalid time format.
16
+ * @see {@link CommonConstraints}
17
+ * @see {@link MinMaxConstraints}
18
+ * @see {@link OneOfConstraint}
19
+ * @see {@link TestConstraint}
20
+ * @category Property Decorators
21
+ */
22
+ export interface TimeConstraints<Value extends StringValue, Parent> extends
23
+ CommonConstraints<Value, Parent>,
24
+ MinMaxConstraints<Value, string, Parent>,
25
+ OneOfConstraint<Value, Parent>,
26
+ TestConstraint<Value, Parent> {
27
+ /**
28
+ * Optional custom error message for invalid time format. `formatError` can be a {@link Message} or a function that returns a {@link Message}.
29
+ * @see {@link timeRegex}
30
+ */
31
+ formatError?: Message<Value, Parent>
32
+ }
33
+
34
+ /**
35
+ * Regular expression for validating time strings in the format HH:mm[:ss[.sss]].
36
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#time_strings
37
+ * @ignore
38
+ */
39
+ export const timeRegex = /^([01][0-9]|2[0-3]):([0-5][0-9])(?::([0-5][0-9])(?:\.([0-9]{1,3}))?)?$/
40
+
41
+ /**
42
+ * Converts a time string (HH:mm[:ss[.sss]]) to milliseconds since midnight.
43
+ * @param time - The time string to convert.
44
+ * @returns The number of milliseconds since midnight, or undefined if invalid.
45
+ * @ignore
46
+ */
47
+ export function timeToMillis(time: string) {
48
+ const matches = timeRegex.exec(time)
49
+ return (
50
+ matches != null ?
51
+ (+matches[1] * 3600 * 1000) + (+matches[2] * 60 * 1000) + (+(matches[3] ?? 0) * 1000) + (+(matches[4] ?? 0)) :
52
+ undefined
53
+ )
54
+ }
55
+
56
+ /**
57
+ * Maximum number of milliseconds in a day (23:59:59.999).
58
+ * @ignore
59
+ */
60
+ const MAX_MILLIS = (24 * 3600 * 1000) - 1
61
+
62
+ /**
63
+ * Validates a time field against its constraints.
64
+ * @template Value - The type of the string value.
65
+ * @template Parent - The type of the parent object.
66
+ * @param context - The validation context.
67
+ * @param constraints - The time constraints to validate.
68
+ * @returns True if all constraints pass, false otherwise.
69
+ * @ignore
70
+ */
71
+ export function validateTime<Value extends StringValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: TimeConstraints<Value, Parent>) {
72
+ if (!validateTypeConstraint(context, isString, "time"))
73
+ return false
74
+
75
+ const millis = timeToMillis(context.value!)
76
+ if (millis == null) {
77
+ const message = isFunction(constraints.formatError) ? constraints.formatError(context) : constraints.formatError
78
+ return context.setStatus("match", timeRegex, message) == null
79
+ }
80
+
81
+ return (
82
+ validateMinMaxConstraints(context, constraints, isString, (_, min) => millis >= (timeToMillis(min) ?? 0), (_, max) => millis <= (timeToMillis(max) ?? MAX_MILLIS)) &&
83
+ validateOneOfConstraint(context, constraints, isStringArray) &&
84
+ validateTestConstraint(context, constraints)
85
+ )
86
+ }
87
+
88
+ /**
89
+ * Decorator for applying validation rules to a time field. A valid time value must be a string in the format HH:mm[:ss[.sss]] (24-hour clock).
90
+ *
91
+ * Example usage:
92
+ * ```tsx
93
+ * class Person {
94
+ * @time({ required: true, formatError: "Invalid wake up time format", max: "18:00" })
95
+ * wakeUpTime: string | null = null
96
+ * }
97
+ * const form = useForm(Person, ...)
98
+ *
99
+ * // the time decorator can also be used as a function to allow standalone validation:
100
+ * Yop.validate("00:00", time({ min: "01:00" })) // error: "Must be after or equal to 01:00"
101
+ * ```
102
+ * @template Value - The type of the string value.
103
+ * @template Parent - The type of the parent object.
104
+ * @param constraints - The time constraints to apply.
105
+ * @param groups - Optional validation groups.
106
+ * @returns A field decorator function with validation.
107
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#time_strings
108
+ * @category Property Decorators
109
+ */
110
+ export function time<Value extends StringValue, Parent>(constraints?: TimeConstraints<Value, Parent>, groups?: Groups<TimeConstraints<Value, Parent>>) {
111
+ return fieldValidationDecorator("time", constraints ?? {}, groups, validateTime, isString)
112
+ }