@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,246 @@
1
+ import { useMemo } from "react"
2
+ import { instance } from "../yop/decorators/instance"
3
+ import { Path } from "../yop/ObjectsUtil"
4
+ import { isPromise } from "../yop/TypesUtil"
5
+ import { Group } from "../yop/ValidationContext"
6
+ import { FormManager, InternalFormManager } from "./FormManager"
7
+ import { useRender } from "./useRender"
8
+ import { useObservers } from "./observers/useObservers"
9
+
10
+ /**
11
+ * Configuration options for the useForm hook.
12
+ * @template T - The type of the form values.
13
+ * @category Form Management
14
+ */
15
+ export type FormConfig<T extends object | any[] | null | undefined> = {
16
+
17
+ /**
18
+ * Initial values for the form. Can be an object, a function returning an object, or a function returning a promise that
19
+ * resolves to an object. Initial values are cloned and stored internally the first time they are neither `null` nor `undefined`.
20
+ * If a function is provided, it will be called on the first render and whenever the config changes, allowing for dynamic
21
+ * initial values. If the function returns a promise, the form will be in a pending state until the promise resolves, at
22
+ * which point the initial values will be set and the form will re-render.
23
+ *
24
+ * @see {@link FormManager.initialValuesPending}
25
+ * @see {@link FormConfig.initialValuesConverter}
26
+ */
27
+ readonly initialValues?: T | (() => T) | (() => Promise<T>) | null
28
+
29
+ /**
30
+ * Converter function for initial values. This function is called with the initial values whenever they become neither
31
+ * `null` nor `undefined`. It allows for transformation or normalization of the initial values before they are set in the form.
32
+ * @param values - The initial values.
33
+ * @returns The transformed initial values.
34
+ */
35
+ readonly initialValuesConverter?: (values: T) => T
36
+
37
+ /**
38
+ * The validation schema for the form. This can be a schema object created with the `instance` or `array` decorator.
39
+ * It defines the rules for validating the form values.
40
+ *
41
+ * Example usage:
42
+ * ```tsx
43
+ * const form = useForm({
44
+ * initialValues: new Person(),
45
+ * validationSchema: instance({ of: Person, required: true }),
46
+ * })
47
+ * ```
48
+ */
49
+ readonly validationSchema?: ((_: unknown, context: ClassFieldDecoratorContext<unknown, T>) => void)
50
+
51
+ /**
52
+ * Path or paths to validate. This can be a single path string or an array of path strings. If specified, only the values
53
+ * at these paths will be validated. If not specified, the entire form values will be validated.
54
+ */
55
+ readonly validationPath?: string | string[]
56
+
57
+ /**
58
+ * Validation groups to use during validation. This can be a single group or an array of groups. If specified, the validation
59
+ * rules associated with these groups will be applied. If not specified, only the validation rules of the default group will
60
+ * be applied.
61
+ *
62
+ * For example:
63
+ * ```tsx
64
+ * // will apply validation rules of "group1":
65
+ * validationGroups: "group1"
66
+ * // will apply validation rules of "group1" and "group2":
67
+ * validationGroups: ["group1", "group2"]
68
+ * // will apply validation rules of the default group and "group2":
69
+ * validationGroups: [undefined, "group2"]
70
+ * ```
71
+ */
72
+ readonly validationGroups?: Group
73
+
74
+ /**
75
+ * Function to determine if a path should be ignored during validation. This function is called with the path being
76
+ * validated and the form manager instance. If it returns `true`, the path will be ignored and no validation will be
77
+ * performed for it.
78
+ * @param path - The path being validated.
79
+ * @param form - The form manager instance.
80
+ * @returns `true` if the path should be ignored, `false` otherwise.
81
+ */
82
+ readonly ignore?: (path: Path, form: FormManager<T>) => boolean
83
+
84
+ /**
85
+ * Function to determine if the form can be submitted. This function is called with the form manager instance when a submit
86
+ * is attempted. If it returns `true`, the form will be submitted and the `onSubmit` callback will be called. If it returns
87
+ * `false`, the submit will be aborted and the `onSubmit` callback will not be called.
88
+ * @param form - The form manager instance.
89
+ * @returns `true` if the form can be submitted, `false` otherwise.
90
+ */
91
+ readonly submitGuard?: (form: FormManager<T>) => boolean
92
+
93
+ /**
94
+ * Callback for form submission. This function is called with the form manager instance when the form is submitted and the
95
+ * `submitGuard` (if provided) returns `true`. It is responsible for handling the form submission logic, such as sending
96
+ * the form values to a server or updating application state. The {@link FormManager.submitting} is automatically set to
97
+ * `true` while this function is called. It is the responsibility of the caller to set it back to `false` when the
98
+ * submission process is complete (see {@link FormManager.setSubmitting}).
99
+ *
100
+ * Example usage:
101
+ * ```tsx
102
+ * const form = useForm({
103
+ * initialValues: new Person(),
104
+ * validationSchema: instance({ of: Person, required: true }),
105
+ * onSubmit: (form) => {
106
+ * console.log("Form submitted with values:", form.values)
107
+ * try {
108
+ * // perform submission logic here
109
+ * }
110
+ * finally {
111
+ * form.setSubmitting(false)
112
+ * }
113
+ * }
114
+ * })
115
+ * ```
116
+ * @param form - The form manager instance.
117
+ * @returns void
118
+ */
119
+ readonly onSubmit?: (form: FormManager<T>) => void
120
+
121
+ /**
122
+ * Whether to dispatch events for observer propagation. If `true`, when a value changes, an event will be dispatched that can
123
+ * be listened to by observers to react to value changes. Default is `true`.
124
+ */
125
+ readonly dispatchEvent?: boolean
126
+ }
127
+
128
+ /**
129
+ * Type for a class constructor of a model.
130
+ * @category Form Management
131
+ */
132
+ export type Model<T> = new (...args: any) => NonNullable<T>
133
+
134
+
135
+ /**
136
+ * ## First overload signature
137
+ *
138
+ * React hook to create and manage a form with all configuration options available in {@link FormConfig}. This overload allows for the most flexible
139
+ * usage of the `useForm` hook, with full control over initial values, validation schema, submission logic, and more.
140
+ *
141
+ * However, it doesn't register automatically observers listeners, and you need to use the {@link useObservers} hook manually to register observers on
142
+ * the form manager instance, as shown in the example below.
143
+ *
144
+ * Example usage:
145
+ * ```tsx
146
+ * const form = useForm({
147
+ * initialValues: new Person(),
148
+ * validationSchema: instance({ of: Person, required: true }),
149
+ * onSubmit: (form) => {
150
+ * console.log("Form submitted with values:", form.values)
151
+ * form.setSubmitting(false)
152
+ * }
153
+ * })
154
+ * // Optional, if observers are used in the form:
155
+ * useObservers(Person, form)
156
+ * ```
157
+ * @overload
158
+ * @template T - The type of the form values.
159
+ * @param config - The form configuration object.
160
+ * @param deps - Optional dependency list for memoization of the form manager.
161
+ * @returns The form manager instance.
162
+ * @category Form Management
163
+ */
164
+ export function useForm<T extends object | null | undefined>(config: FormConfig<T>, deps?: React.DependencyList): FormManager<T>
165
+
166
+ /**
167
+ * ## Second overload signature
168
+ *
169
+ * React hook to create and manage a form with validation, and automatic observer support. This overload allows for a simpler syntax. The initial values
170
+ * will be created by instantiating the provided model class, and the validation schema will be automatically generated using the `instance` decorator with
171
+ * the provided model class and `required: true`.
172
+ *
173
+ * There is no need to use {@link useObservers} here, observers will be automatically registered on the form manager instance
174
+ * for the provided model class. The code example below is strictly equivalent to the one in the other overload signature,
175
+ * but with a simpler syntax.
176
+ *
177
+ * Example usage:
178
+ * ```tsx
179
+ * const form = useForm(Person, (form) => {
180
+ * console.log("Form submitted with values:", form.values)
181
+ * form.setSubmitting(false)
182
+ * })
183
+ * ```
184
+ *
185
+ * @overload
186
+ * @template T - The type of the form values.
187
+ * @param model - The model class constructor.
188
+ * @param onSubmit - Callback for form submission.
189
+ * @param deps - Optional dependency list for memoization of the form manager.
190
+ * @returns The form manager instance.
191
+ * @category Form Management
192
+ */
193
+ export function useForm<T extends object | null | undefined>(model: Model<T>, onSubmit: (form: FormManager<T>) => void, deps?: React.DependencyList): FormManager<T>
194
+
195
+ /*
196
+ * Implementation of the useForm hook. Handles both config and model overloads, supports async initial values,
197
+ * and manages form state, validation, and observer eventing. See the overload signatures for usage details.
198
+ */
199
+ export function useForm(configOrModel: any, onSubmitOrDeps?: any, deps: React.DependencyList = []) {
200
+
201
+ const model = typeof configOrModel === "function" ? configOrModel : undefined
202
+ const render = useRender()
203
+
204
+ deps = Array.isArray(onSubmitOrDeps) ? onSubmitOrDeps : deps
205
+ const manager = useMemo(() => {
206
+ const newManager = new InternalFormManager(render)
207
+
208
+ if (typeof configOrModel === "function") {
209
+ configOrModel = {
210
+ initialValues: new configOrModel(),
211
+ validationSchema: instance({ of: configOrModel, required: true }),
212
+ onSubmit: onSubmitOrDeps as ((form: FormManager<any>) => void),
213
+ }
214
+ }
215
+ else if (typeof configOrModel.initialValues === "function") {
216
+ let initialValues = configOrModel.initialValues()
217
+ if (isPromise(initialValues)) {
218
+ newManager.initialValuesPending = true
219
+ initialValues.then((value: any) => {
220
+ setTimeout(() => {
221
+ configOrModel = { ...newManager.config, initialValues: value }
222
+ newManager.onRender(configOrModel)
223
+ newManager.commitInitialValues()
224
+ newManager.initialValuesPending = false
225
+ render()
226
+ }, 0)
227
+ })
228
+ initialValues = newManager.initialValues
229
+ }
230
+ configOrModel = { ...configOrModel, initialValues }
231
+ }
232
+
233
+ newManager.onRender(configOrModel)
234
+ return newManager
235
+ }, deps)
236
+
237
+ // We need this code to normalize configOrModel when useMemo doesn't re-run.
238
+ if (typeof configOrModel === "function")
239
+ configOrModel = { ...manager.config, onSubmit: onSubmitOrDeps as ((form: FormManager<any>) => void) }
240
+ else if (typeof configOrModel.initialValues === "function")
241
+ configOrModel = { ...configOrModel, initialValues: manager.initialValues }
242
+
243
+ manager.onRender(configOrModel)
244
+ useObservers(model, manager)
245
+ return manager
246
+ }
@@ -0,0 +1,37 @@
1
+ import React from "react"
2
+ import { FormManager } from "./FormManager"
3
+ import { Form } from "./Form"
4
+
5
+ /**
6
+ * React context for providing a FormManager instance to descendant components.
7
+ * @ignore
8
+ */
9
+ export const FormContext = React.createContext<FormManager<unknown> | null>(null)
10
+
11
+ /**
12
+ * React hook to access the current {@link FormManager} from context. This hook should be used within a component that is a descendant of a {@link Form} component,
13
+ * which provides the FormManager via context. The generic type parameter `T` can be used to specify the type of the form values managed by the FormManager,
14
+ * allowing for type-safe access to form values.
15
+ *
16
+ * Example usage:
17
+ * ```tsx
18
+ * function MyFormComponent() {
19
+ * const form = useFormContext<MyFormValues>()
20
+ * // use form to access values, statuses, etc.
21
+ * }
22
+ *
23
+ * const form = useForm(MyFormModel, onSubmit)
24
+ * return (
25
+ * <Form form={form} autoComplete="off" noValidate disabled={form.submitting}>
26
+ * <MyFormComponent />
27
+ * </Form>
28
+ * )
29
+ * ```
30
+ *
31
+ * @template T - The type of the form values managed by the FormManager.
32
+ * @returns The {@link FormManager} instance from context.
33
+ * @category Form Management
34
+ */
35
+ export function useFormContext<T = unknown>() {
36
+ return React.useContext(FormContext)! as FormManager<T>
37
+ }
@@ -0,0 +1,75 @@
1
+ import { useRef } from "react";
2
+ import { isPromise } from "../yop/TypesUtil";
3
+ import { ValidationStatus } from "../yop/ValidationContext";
4
+ import { ResolvedConstraints } from "../yop/Yop";
5
+ import { FormManager } from "./FormManager";
6
+ import { useFormContext } from "./useFormContext";
7
+ import { useRender } from "./useRender";
8
+
9
+ /**
10
+ * Represents the state of a form field, including value, validation, and metadata.
11
+ * @template Value - The type of the field value.
12
+ * @template MinMax - The type for min/max constraints.
13
+ * @template Root - The type of the root form values.
14
+ * @category Form Management
15
+ */
16
+ export type FieldState<Value, MinMax, Root = any> = {
17
+ /** The current value of the field. */
18
+ value: Value | undefined
19
+ /** Whether the field has been touched. */
20
+ touched: boolean
21
+ /** The validation status of the field, if any. See {@link ValidationStatus} for details. */
22
+ status?: ValidationStatus
23
+ /** The form manager instance. See {@link FormManager} for details. */
24
+ form: FormManager<Root>
25
+ /** Function to trigger a re-render of the component that called {@link useFormField} to get this field state. */
26
+ render: () => void
27
+ /** The resolved constraints for the field, if any. See {@link ResolvedConstraints} for details. */
28
+ constraints?: ResolvedConstraints<MinMax>
29
+ }
30
+
31
+ /**
32
+ * React hook to access and manage the state of a form field, including value, validation status, and constraints.
33
+ * Handles async validation and triggers re-renders as needed.
34
+ *
35
+ * Example usage:
36
+ * ```tsx
37
+ * function MyTextField(props: { path: string }) {
38
+ * const { constraints, status, value, form } = useFormField<string, number>(props.path)
39
+ * // render input with value, display validation status, etc.
40
+ * }
41
+ * ```
42
+ *
43
+ * @template Value - The type of the field value.
44
+ * @template MinMax - The type for min/max constraints.
45
+ * @template Root - The type of the root form values.
46
+ * @param name - The field name or path.
47
+ * @param unsafeMetadata - Whether to use unsafe metadata for constraints.
48
+ * @returns The current state of the field. See {@link FieldState} for details.
49
+ * @category Form Management
50
+ */
51
+ export function useFormField<Value, MinMax, Root = any>(name: string, unsafeMetadata = false): FieldState<Value, MinMax, Root> {
52
+ const render = useRender()
53
+ const form = useFormContext<Root>()
54
+ const promiseRef = useRef<Promise<unknown>>(undefined)
55
+
56
+ const status = form.statuses.get(name)
57
+ if (status?.level === "pending" && isPromise(status.constraint) && promiseRef.current !== status.constraint) {
58
+ promiseRef.current = status.constraint
59
+ status.constraint.finally(() => {
60
+ if (promiseRef.current === status.constraint) {
61
+ form.updateAsyncStatus(name)
62
+ form.render()
63
+ }
64
+ })
65
+ }
66
+
67
+ return {
68
+ value: form.getValue<Value>(name),
69
+ touched: form.isTouched(name),
70
+ status,
71
+ form,
72
+ render,
73
+ constraints: form.constraintsAt(name, unsafeMetadata),
74
+ }
75
+ }
@@ -0,0 +1,12 @@
1
+ import { useReducer } from "react"
2
+
3
+ /**
4
+ * React hook that returns a function to force a component re-render.
5
+ * Useful for triggering updates in custom hooks or non-stateful logic.
6
+ *
7
+ * @returns A function that, when called, forces the component to re-render.
8
+ * @category Form Management
9
+ */
10
+ export function useRender(): () => void {
11
+ return useReducer(() => ({}), {})[1] as () => void
12
+ }
@@ -0,0 +1,204 @@
1
+ import { ConstraintMessage } from "./constraints/Constraint";
2
+ import { InternalValidationContext, Level, ValidationContext } from "./ValidationContext";
3
+
4
+ /**
5
+ * Interface for providing localized validation messages.
6
+ * @category Localization
7
+ */
8
+ export interface MessageProvider {
9
+ /**
10
+ * The locale identifier (e.g., 'en-US', 'fr-FR').
11
+ */
12
+ readonly locale: string
13
+
14
+ /**
15
+ * Returns a localized message for a given validation context and code.
16
+ * @param context - The validation context.
17
+ * @param code - The message code.
18
+ * @param constraint - The constraint value.
19
+ * @param message - An optional custom message.
20
+ * @param level - The validation level (e.g., 'error', 'pending').
21
+ * @returns The resolved message.
22
+ */
23
+ getMessage(context: InternalValidationContext<unknown>, code: string, constraint: any, message: ConstraintMessage | undefined, level: Level): ConstraintMessage
24
+ }
25
+
26
+ /**
27
+ * Formats a value for display in a localized message, handling numbers, dates, and arrays.
28
+ * @param value - The value to format.
29
+ * @param numberFormat - The number formatter.
30
+ * @param dateFormat - The date formatter.
31
+ * @param listFormat - The list formatter.
32
+ * @returns The formatted string.
33
+ * @ignore
34
+ */
35
+ function format(value: any, numberFormat: Intl.NumberFormat, dateFormat: Intl.DateTimeFormat, listFormat: Intl.ListFormat): string {
36
+ return (
37
+ typeof value === "number" ? numberFormat.format(value) :
38
+ value instanceof Date ? dateFormat.format(value) :
39
+ Array.isArray(value) ? listFormat.format(value.map(item => format(item, numberFormat, dateFormat, listFormat))) :
40
+ String(value)
41
+ )
42
+ }
43
+
44
+ /**
45
+ * Properties passed to a message function for formatting.
46
+ * @category Localization
47
+ */
48
+ export type MessageProps = {
49
+ context: ValidationContext<unknown>
50
+ code: string
51
+ constraint: {
52
+ raw: any
53
+ formatted: string
54
+ plural?: Intl.LDMLPluralRule
55
+ }
56
+ level: Level
57
+ }
58
+
59
+ /**
60
+ * Function type for generating a message from message properties.
61
+ * @see {@link MessageProps}
62
+ * @category Localization
63
+ */
64
+ export type MessageFunction = (props: MessageProps) => string
65
+
66
+ /**
67
+ * Basic implementation of MessageProvider for localized validation messages.
68
+ * @category Localization
69
+ */
70
+ export class BasicMessageProvider implements MessageProvider {
71
+
72
+ private readonly numberFormat: Intl.NumberFormat
73
+ private readonly dateFormat: Intl.DateTimeFormat
74
+ private readonly listFormat: Intl.ListFormat
75
+ private readonly pluralRules: Intl.PluralRules
76
+
77
+ /**
78
+ * Map of message codes to message functions.
79
+ */
80
+ readonly messages: Map<string, MessageFunction>
81
+
82
+ /**
83
+ * Creates a new BasicMessageProvider for a given locale and message entries.
84
+ * @param locale - The locale identifier.
85
+ * @param entries - Optional array of [code, message function] pairs.
86
+ */
87
+ constructor(readonly locale: string, entries?: (readonly [string, MessageFunction])[]) {
88
+ this.numberFormat = new Intl.NumberFormat(this.locale)
89
+ this.dateFormat = new Intl.DateTimeFormat(this.locale)
90
+ this.listFormat = new Intl.ListFormat(this.locale, { type: "disjunction" })
91
+ this.pluralRules = new Intl.PluralRules(this.locale)
92
+
93
+ this.messages = new Map<string, MessageFunction>(entries)
94
+ }
95
+
96
+ /**
97
+ * @inheritdoc
98
+ */
99
+ getMessage(context: InternalValidationContext<unknown>, code: string, constraint: any, message: ConstraintMessage | undefined, level: Level): ConstraintMessage {
100
+ if (message != null)
101
+ return message
102
+
103
+ const messageFunction = this.messages.get(`${ context.kind }.${ code }`) ?? this.messages.get(code)
104
+ if (messageFunction == null)
105
+ return `Unexpected error: ${ context.kind }.${ code }`
106
+
107
+ return messageFunction({
108
+ context,
109
+ code,
110
+ constraint: {
111
+ raw: constraint,
112
+ formatted: format(constraint, this.numberFormat, this.dateFormat, this.listFormat),
113
+ plural: typeof constraint === "number" ? this.pluralRules.select(constraint) : undefined
114
+ },
115
+ level
116
+ })
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Returns the plural suffix 's' if the plural rule is not 'one'.
122
+ * @param plural - The plural rule.
123
+ * @returns 's' if plural, otherwise an empty string.
124
+ * @ignore
125
+ */
126
+ function s(plural?: Intl.LDMLPluralRule): string {
127
+ return plural == null || plural === "one" ? "" : "s"
128
+ }
129
+
130
+ /**
131
+ * English (US) message provider for validation messages.
132
+ * @category Localization
133
+ */
134
+ export const messageProvider_en_US = new BasicMessageProvider("en-US", [
135
+ ["string.min", ({ constraint }) => `Minimum ${ constraint.formatted } character${ s(constraint.plural) }`],
136
+ ["string.max", ({ constraint }) => `Maximum ${ constraint.formatted } character${ s(constraint.plural) }`],
137
+ ["string.match", () => "Invalid format"],
138
+
139
+ ["email.min", ({ constraint }) => `Minimum ${ constraint.formatted } character${ s(constraint.plural) }`],
140
+ ["email.max", ({ constraint }) => `Maximum ${ constraint.formatted } character${ s(constraint.plural) }`],
141
+ ["email.match", () => "Invalid email format"],
142
+
143
+ ["time.min", ({ constraint }) => `Must be after or equal to ${ constraint.formatted }`],
144
+ ["time.max", ({ constraint }) => `Must be before or equal to ${ constraint.formatted }`],
145
+ ["time.match", () => "Invalid time format"],
146
+
147
+ ["number.min", ({ constraint }) => `Must be greater or equal to ${ constraint.formatted }`],
148
+ ["number.max", ({ constraint }) => `Must be less or equal to ${ constraint.formatted }`],
149
+
150
+ ["date.min", ({ constraint }) => `Date must be greater or equal to ${ constraint.formatted }`],
151
+ ["date.max", ({ constraint }) => `Date must be less or equal to ${ constraint.formatted }`],
152
+
153
+ ["file.min", ({ constraint }) => `File must have a size of at least ${ constraint.formatted } byte${ s(constraint.plural) }`],
154
+ ["file.max", ({ constraint }) => `File must have a size of at most ${ constraint.formatted } byte${ s(constraint.plural) }`],
155
+
156
+ ["array.min", ({ constraint }) => `At least ${ constraint.formatted } element${ s(constraint.plural) }`],
157
+ ["array.max", ({ constraint }) => `At most ${ constraint.formatted } element${ s(constraint.plural) }`],
158
+
159
+ ["type", ({ constraint }) => `Wrong value type (expected ${ constraint.raw })`],
160
+ ["test", ({ level }) => level === "pending" ? "Pending..." : level === "error" ? "Invalid value" : ""],
161
+ ["oneOf", ({ constraint }) => `Must be one of: ${ constraint.formatted }`],
162
+ ["exists", () => "Required field"],
163
+ ["defined", () => "Required field"],
164
+ ["notnull", () => "Required field"],
165
+ ["required", () => "Required field"]
166
+ ])
167
+
168
+ /**
169
+ * French (FR) message provider for validation messages.
170
+ * @category Localization
171
+ */
172
+ export const messageProvider_fr_FR = new BasicMessageProvider("fr-FR", [
173
+ ["string.min", ({ constraint }) => `Minimum ${ constraint.formatted } caractère${ s(constraint.plural) }`],
174
+ ["string.max", ({ constraint }) => `Maximum ${ constraint.formatted } caractère${ s(constraint.plural) }`],
175
+ ["string.match", () => "Format incorrect"],
176
+
177
+ ["email.min", ({ constraint }) => `Minimum ${ constraint.formatted } caractère${ s(constraint.plural) }`],
178
+ ["email.max", ({ constraint }) => `Maximum ${ constraint.formatted } caractère${ s(constraint.plural) }`],
179
+ ["email.match", () => "Format d'email incorrect"],
180
+
181
+ ["time.min", ({ constraint }) => `Doit être antérieur ou égal à ${ constraint.formatted }`],
182
+ ["time.max", ({ constraint }) => `Doit être postérieur ou égal à ${ constraint.formatted }`],
183
+ ["time.match", () => "Format horaire incorrect"],
184
+
185
+ ["number.min", ({ constraint }) => `Doit être supérieur ou égal à ${ constraint.formatted }`],
186
+ ["number.max", ({ constraint }) => `Doit être inférieur ou égal à ${ constraint.formatted }`],
187
+
188
+ ["date.min", ({ constraint }) => `La date doit être postérieure ou égale au ${ constraint.formatted }`],
189
+ ["date.max", ({ constraint }) => `La date doit être antérieure ou égale au ${ constraint.formatted }`],
190
+
191
+ ["file.min", ({ constraint }) => `Le fichier doit avoir une taille d'au moins ${ constraint.formatted } octet${ s(constraint.plural) }`],
192
+ ["file.max", ({ constraint }) => `Le fichier doit avoir une taille d'au plus ${ constraint.formatted } octet${ s(constraint.plural) }`],
193
+
194
+ ["array.min", ({ constraint }) => `Au moins ${ constraint.formatted } élément${ s(constraint.plural) }`],
195
+ ["array.max", ({ constraint }) => `Au plus ${ constraint.formatted } élément${ s(constraint.plural) }`],
196
+
197
+ ["type", ({ constraint }) => `Valeur du mauvais type (${ constraint.raw } attendu)`],
198
+ ["test", ({ level }) => level === "pending" ? "En cours..." : level === "error" ? "Valeur incorrecte" : ""],
199
+ ["oneOf", ({ constraint }) => `Doit être parmi : ${ constraint.formatted }`],
200
+ ["exists", () => "Champ obligatoire"],
201
+ ["defined", () => "Champ obligatoire"],
202
+ ["notnull", () => "Champ obligatoire"],
203
+ ["required", () => "Champ obligatoire"]
204
+ ])