@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
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@4riders/reform",
3
+ "version": "3.0.24",
4
+ "description": "Reform is a powerful, type-safe, and extensible validation and form management library for TypeScript and React",
5
+ "type": "module",
6
+ "module": "./dist/index.es.js",
7
+ "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "license": "MIT",
10
+ "publishConfig": {
11
+ "access": "public",
12
+ "registry": "https://registry.npmjs.org/"
13
+ },
14
+ "homepage": "https://github.com/4riders/reform#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/4riders/reform.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/4riders/reform/issues"
21
+ },
22
+ "keywords": [
23
+ "react",
24
+ "form",
25
+ "typescript"
26
+ ],
27
+ "author": "Franck Wolff <franck.wolff@4riders.net>",
28
+ "files": [
29
+ "dist",
30
+ "src"
31
+ ],
32
+ "exports": {
33
+ ".": {
34
+ "import": "./dist/index.es.js",
35
+ "types": "./dist/index.d.ts"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "build": "tsc && vite build",
40
+ "test": "vitest --run",
41
+ "prepublishOnly": "NODE_ENV=release tsc && vite build",
42
+ "typedoc": "typedoc --options typedoc.json"
43
+ },
44
+ "peerDependencies": {
45
+ "react": ">=18",
46
+ "react-dom": ">=18"
47
+ },
48
+ "devDependencies": {
49
+ "@babel/core": "^7.29.0",
50
+ "@babel/plugin-proposal-decorators": "^7.29.0",
51
+ "@rolldown/plugin-babel": "^0.2.2",
52
+ "@testing-library/dom": "^10.4.1",
53
+ "@testing-library/react": "^16.3.2",
54
+ "@types/babel__core": "^7.20.5",
55
+ "@types/node": "^25.5.0",
56
+ "@types/react": "^18.3.28",
57
+ "@types/react-dom": "^18.3.7",
58
+ "@vitejs/plugin-react": "^6.0.1",
59
+ "babel-plugin-react-compiler": "^1.0.0",
60
+ "jsdom": "^29.0.1",
61
+ "react": "^18.3.1",
62
+ "react-dom": "^18.3.1",
63
+ "typedoc": "^0.28.18",
64
+ "typescript": "^6.0.2",
65
+ "vite": "^8.0.3",
66
+ "vite-plugin-dts": "^4.5.4",
67
+ "vitest": "^4.1.2"
68
+ },
69
+ "packageManager": "yarn@4.13.0"
70
+ }
package/src/index.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * @categoryDescription Property Decorators
5
+ * Property decorators are a powerful feature in Javascript that allow you to annotate and modify the behavior of class properties.
6
+ * In the context of form management, property decorators can be used to define metadata for form fields, such as validation rules,
7
+ * input types, or custom behavior. By applying decorators to your form model classes, you can easily specify how each field should
8
+ * be handled without having to write additional boilerplate code. This makes it easier to maintain and extend your form models as your
9
+ * application grows.
10
+ *
11
+ * @categoryDescription Class Decorators
12
+ * Class decorators are a type of decorator in JavaScript that can be applied to classes to modify their behavior or add metadata.
13
+ * In the context of form management, class decorators can be used to define metadata for form models, such as validation rules.
14
+ *
15
+ * @categoryDescription Shared Constraints
16
+ * Shared constraints are common validation rules that can be applied to form fields or entire form models. They define the conditions
17
+ * that the field values must satisfy in order to be considered valid. Constraints can be simple, such as checking if a value is required
18
+ * or if it falls within a certain range, or they can be complex, involving custom logic that depends on multiple fields.
19
+ *
20
+ * @categoryDescription Observers
21
+ * Observers allow you to react to changes in other fields by specifying a path to observe and a callback function that
22
+ * receives context about the change. The observer paths support a flexible syntax for targeting specific fields, including
23
+ * wildcards, absolute and relative paths.
24
+ *
25
+ * @categoryDescription Form Management
26
+ * Form management involves handling the state and behavior of forms in a web application. This includes managing form data, validation,
27
+ * and user interactions. A form management library can provide tools and utilities to simplify these tasks, allowing developers to
28
+ * focus on building the user interface and business logic of their applications.
29
+ *
30
+ * @categoryDescription Base Inputs Components
31
+ * Base input components are reusable building blocks for creating form fields in a web application. They provide a consistent interface and
32
+ * behavior for common input types, such as text fields, checkboxes, radio buttons, and select dropdowns.
33
+ *
34
+ * @categoryDescription Validation Management
35
+ * Validation management involves defining and enforcing rules for validating form data. This can include simple checks, such as ensuring
36
+ * that a field is not empty, as well as more complex rules that involve multiple fields or custom logic.
37
+ *
38
+ * @categoryDescription Localization
39
+ * Localization is the process of adapting a product or content to a specific locale or market. In the context of form management, localization
40
+ * involves providing translations for error messages.
41
+ *
42
+ * @categoryDescription Utilities
43
+ * Utilities are helper functions or classes that provide common functionality that can be reused across different parts of an application. In the
44
+ * context of form management, utilities include functions for manipulating and comparing form data.
45
+ */
46
+
47
+ export * from "./reform/ArrayHelper"
48
+ export * from "./reform/Form"
49
+ export * from "./reform/FormManager"
50
+ export * from "./reform/observers/observer"
51
+ export * from "./reform/observers/useObservers"
52
+ export * from "./reform/Reform"
53
+ export * from "./reform/useForm"
54
+ export * from "./reform/useFormContext"
55
+ export * from "./reform/useFormField"
56
+ export * from "./reform/useRender"
57
+
58
+ export * from "./reform/components/BaseCheckboxField"
59
+ export * from "./reform/components/BaseDateField"
60
+ export * from "./reform/components/BaseRadioField"
61
+ export * from "./reform/components/BaseSelectField"
62
+ export * from "./reform/components/BaseTextAreaField"
63
+ export * from "./reform/components/BaseTextField"
64
+ export * from "./reform/components/InputHTMLProps"
65
+
66
+ export * from "./yop/constraints/CommonConstraints"
67
+ export * from "./yop/constraints/Constraint"
68
+ export * from "./yop/constraints/MinMaxConstraints"
69
+ export * from "./yop/constraints/OneOfConstraint"
70
+ export * from "./yop/constraints/TestConstraint"
71
+
72
+ export * from "./yop/decorators/array"
73
+ export * from "./yop/decorators/boolean"
74
+ export * from "./yop/decorators/date"
75
+ export * from "./yop/decorators/email"
76
+ export * from "./yop/decorators/file"
77
+ export * from "./yop/decorators/id"
78
+ export * from "./yop/decorators/ignored"
79
+ export * from "./yop/decorators/instance"
80
+ export * from "./yop/decorators/number"
81
+ export * from "./yop/decorators/string"
82
+ export * from "./yop/decorators/test"
83
+ export * from "./yop/decorators/time"
84
+
85
+ export * from "./yop/MessageProvider"
86
+ export * from "./yop/Metadata"
87
+ export * from "./yop/ObjectsUtil"
88
+ export * from "./yop/TypesUtil"
89
+ export * from "./yop/ValidationContext"
90
+ export * from "./yop/Yop"
@@ -0,0 +1,164 @@
1
+ import { Path } from "../yop/ObjectsUtil"
2
+ import { FormManager, InternalFormManager } from "./FormManager"
3
+
4
+ /**
5
+ * Utility class for manipulating array fields in a form model, with automatic touch, validation, and rendering. See {@link FormManager.array} for details.
6
+ * @template T - The type of array elements.
7
+ * @category Form Management
8
+ */
9
+ export class ArrayHelper<T = any> {
10
+
11
+ private array: T[] | undefined
12
+
13
+ /**
14
+ * Creates an ArrayHelper for a given form and path.
15
+ * @param form - The form manager instance.
16
+ * @param path - The path to the array field in the form.
17
+ */
18
+ constructor(readonly form: InternalFormManager<any>, readonly path: string | Path) {
19
+ this.array = form.getValue<T[]>(path)
20
+ if (!Array.isArray(this.array))
21
+ this.array = undefined
22
+ }
23
+
24
+ /**
25
+ * Tells if the field at the given path was an array.
26
+ * @returns True if the field was an array, false otherwise.
27
+ */
28
+ isArray() {
29
+ return this.array != null
30
+ }
31
+
32
+ /**
33
+ * Appends an element to the array.
34
+ * @param element - The element to append.
35
+ * @param commit - Whether to validate and render after the operation (default: true).
36
+ */
37
+ append(element: T, commit = true) {
38
+ this.array!.push(element)
39
+ this.form.touch(this.path)
40
+ this.commit(commit)
41
+ }
42
+
43
+ /**
44
+ * Replaces the element at the given index.
45
+ * @param index - The index to replace.
46
+ * @param element - The new element.
47
+ * @param commit - Whether to validate and render after the operation (default: true).
48
+ */
49
+ replace(index: number, element: T, commit = true) {
50
+ this.array![index] = element
51
+ const touched = this.form.getTouchedValue<any[]>(this.path)
52
+ if (touched == null)
53
+ this.form.touch(this.path)
54
+ else if (Array.isArray(touched))
55
+ touched[index] = undefined
56
+ this.commit(commit)
57
+ }
58
+
59
+ /**
60
+ * Inserts an element at the given index.
61
+ * @param index - The index to insert at.
62
+ * @param element - The element to insert.
63
+ * @param commit - Whether to validate and render after the operation (default: true).
64
+ */
65
+ insert(index: number, element: T, commit = true) {
66
+ this.array!.splice(index, 0, element)
67
+ const touched = this.form.getTouchedValue<any[]>(this.path)
68
+ if (touched == null)
69
+ this.form.touch(this.path)
70
+ else if (Array.isArray(touched))
71
+ touched.splice(index, 0, undefined)
72
+ this.commit(commit)
73
+ }
74
+
75
+ /**
76
+ * Removes the element at the given index.
77
+ * @param index - The index to remove.
78
+ * @param commit - Whether to validate and render after the operation (default: true).
79
+ */
80
+ remove(index: number, commit = true) {
81
+ this.array!.splice(index, 1)
82
+ const touched = this.form.getTouchedValue<any[]>(this.path)
83
+ if (touched == null)
84
+ this.form.touch(this.path)
85
+ else if (Array.isArray(touched))
86
+ touched.splice(index, 1)
87
+ this.commit(commit)
88
+ }
89
+
90
+ /**
91
+ * Swaps two elements in the array.
92
+ * @param index1 - The first index.
93
+ * @param index2 - The second index.
94
+ * @param commit - Whether to validate and render after the operation (default: true).
95
+ */
96
+ swap(index1: number, index2: number, commit = true) {
97
+ const action = <T>(array: T[]) => {
98
+ const value1 = array[index1]
99
+ array[index1] = array[index2]
100
+ array[index2] = value1
101
+ }
102
+
103
+ action(this.array!)
104
+ const touched = this.form.getTouchedValue<any[]>(this.path)
105
+ if (touched == null)
106
+ this.form.touch(this.path)
107
+ else if (Array.isArray(touched))
108
+ action(touched)
109
+ this.commit(commit)
110
+ }
111
+
112
+ /**
113
+ * Moves an element from one index to another.
114
+ * @param from - The source index.
115
+ * @param to - The destination index.
116
+ * @param commit - Whether to validate and render after the operation (default: true).
117
+ */
118
+ move(from: number, to: number, commit = true) {
119
+ if (from !== to) {
120
+ const action = from < to ?
121
+ <T>(array: T[]) => {
122
+ const fromElement = array[from]
123
+ for (let i = from; i < to; i++)
124
+ array[i] = array[i + 1]
125
+ array[to] = fromElement
126
+ } :
127
+ <T>(array: T[]) => {
128
+ const toElement = array[to]
129
+ for (let i = to; i > from; i--)
130
+ array[i + 1] = array[i]
131
+ array[from] = toElement
132
+ }
133
+
134
+ action(this.array!)
135
+ const touched = this.form.getTouchedValue<any[]>(this.path)
136
+ if (touched == null)
137
+ this.form.touch(this.path)
138
+ else if (Array.isArray(touched))
139
+ action(touched)
140
+ this.commit(commit)
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Removes all elements from the array.
146
+ * @param commit - Whether to validate and render after the operation (default: true).
147
+ */
148
+ clear(commit = true) {
149
+ this.array!.splice(0, this.array!.length)
150
+ this.form.setTouchedValue(this.path, true)
151
+ this.commit(commit)
152
+ }
153
+
154
+ /**
155
+ * Validates and renders the form if value is true.
156
+ * @param value - Whether to commit (validate and render).
157
+ */
158
+ private commit(value: boolean) {
159
+ if (value) {
160
+ this.form.validate()
161
+ this.form.render()
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,81 @@
1
+ import { FormHTMLAttributes, useCallback } from "react"
2
+ import { FormContext } from "./useFormContext"
3
+ import React from "react"
4
+ import { renderToStaticMarkup } from "react-dom/server"
5
+ import { FormManager, InternalFormManager } from "./FormManager"
6
+ import { ValidationStatus } from "../yop/ValidationContext"
7
+ import { Reform } from "./Reform"
8
+
9
+ /**
10
+ * Props for the Form component.
11
+ * @property form - The form manager instance.
12
+ * @property disabled - Whether the form is disabled.
13
+ * @category Form Management
14
+ */
15
+ export interface FormProps extends Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
16
+
17
+ /** The form manager instance. */
18
+ form: FormManager<unknown>
19
+ /** Whether the form is disabled. */
20
+ disabled?: boolean
21
+ }
22
+
23
+ /**
24
+ * React component for rendering an HTML form with context, error display, and automatic form disabling. All children of
25
+ * this component will have access to the form manager via context, and they will be enclosed in an HTML `fieldset` that is disabled according to the
26
+ * `disabled` prop. If there are any validation errors, and if the debug option `displayFormErrors` is enabled (see {@link Reform.displayFormErrors}),
27
+ * they will be displayed in a formatted block below the form.
28
+ *
29
+ * Example usage:
30
+ * ```tsx
31
+ * const form = useForm(MyFormModel, onSubmit)
32
+ * return (
33
+ * <Form form={form} autoComplete="off" noValidate disabled={form.submitting}>
34
+ * // form fields here, using form context
35
+ * <button type="submit">Submit</button>
36
+ * </Form>
37
+ * )
38
+ * ```
39
+ *
40
+ * @param props - The Form props.
41
+ * @returns The rendered Form component.
42
+ * @category Form Management
43
+ */
44
+ export function Form(props: FormProps) {
45
+ const { form, children, disabled, ...formAttrs } = props
46
+
47
+ const formRef = useCallback((htmlForm: HTMLFormElement) => {
48
+ (form as InternalFormManager<any>).htmlForm = htmlForm
49
+ }, [form])
50
+
51
+ const errors = new Map<string, ValidationStatus>([...form.statuses].filter(([_, status]) => status.level === "error"))
52
+
53
+ return (
54
+ <FormContext.Provider value={ form }>
55
+ <form ref={ formRef } onSubmit={ (e) => form.submit(e) } { ...formAttrs }>
56
+ <fieldset disabled={ disabled }>{ children }</fieldset>
57
+
58
+ { errors.size > 0 && Reform.displayFormErrors &&
59
+ <div style={{
60
+ all: "initial",
61
+ display: "block",
62
+ marginTop: "1em",
63
+ padding: "1em",
64
+ fontFamily: "monospace",
65
+ border: "2px solid firebrick",
66
+ borderInline: "2px solid firebrick",
67
+ color: "firebrick",
68
+ background: "white",
69
+ whiteSpace: "pre-wrap"
70
+ }}>
71
+ { JSON.stringify(
72
+ Object.fromEntries(errors.entries()),
73
+ (key, value) => key === "message" && React.isValidElement(value) ? renderToStaticMarkup(value) : value,
74
+ 4
75
+ )}
76
+ </div>
77
+ }
78
+ </form>
79
+ </FormContext.Provider>
80
+ )
81
+ }