@coxy/react-validator 4.0.0 → 5.0.0
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/dist/index.js +206 -2
- package/example/example.tsx +2 -30
- package/package.json +10 -9
- package/src/context.ts +0 -1
- package/src/index.ts +1 -1
- package/src/rules.test.ts +16 -111
- package/src/rules.ts +5 -66
- package/src/types.ts +2 -8
- package/src/use-validator.test.tsx +3 -3
- package/src/use-validator.ts +1 -1
- package/src/validator-field.test.tsx +0 -26
- package/src/validator-field.tsx +1 -8
- package/src/validator-wrapper.test.tsx +6 -6
- package/src/validator-wrapper.tsx +3 -19
- package/src/validator.test.tsx +1 -1
- package/src/validator.ts +22 -17
- package/dist/index.d.mts +0 -106
- package/dist/index.d.ts +0 -106
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
- package/src/custom-errors.test.tsx +0 -73
- package/tsup.config.ts +0 -14
package/dist/index.js
CHANGED
|
@@ -1,2 +1,206 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
Validator: () => Validator,
|
|
23
|
+
ValidatorField: () => ValidatorField,
|
|
24
|
+
ValidatorWrapper: () => ValidatorWrapper,
|
|
25
|
+
rules: () => rules,
|
|
26
|
+
useValidator: () => useValidator
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/rules.ts
|
|
31
|
+
var import_zod = require("zod");
|
|
32
|
+
var rules = {
|
|
33
|
+
notEmpty: [import_zod.z.string().min(1, { error: "Field is required" })],
|
|
34
|
+
isTrue: [import_zod.z.boolean({ error: "Value is required" }).and(import_zod.z.literal(true))],
|
|
35
|
+
email: [import_zod.z.string().min(1, { error: "Email is required" }), import_zod.z.email({ message: "Email is invalid" })]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/validator.ts
|
|
39
|
+
var Field = class {
|
|
40
|
+
rules;
|
|
41
|
+
required;
|
|
42
|
+
value;
|
|
43
|
+
id;
|
|
44
|
+
constructor({ rules: rules2, required, value, id }) {
|
|
45
|
+
this.rules = rules2;
|
|
46
|
+
this.required = required;
|
|
47
|
+
this.value = value;
|
|
48
|
+
this.id = id;
|
|
49
|
+
}
|
|
50
|
+
validate() {
|
|
51
|
+
let isValid = true;
|
|
52
|
+
let message = "";
|
|
53
|
+
const { value, required, id } = this;
|
|
54
|
+
let result = { success: true, data: value };
|
|
55
|
+
const isEmptyValue = !value && Number.parseFloat(value) !== 0;
|
|
56
|
+
const rules2 = Array.isArray(this.rules) ? this.rules : [this.rules];
|
|
57
|
+
if (!rules2.length || isEmptyValue && required === false) {
|
|
58
|
+
return {
|
|
59
|
+
isValid,
|
|
60
|
+
message,
|
|
61
|
+
id,
|
|
62
|
+
result: { success: true, data: value }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
for (const ruleItem of rules2) {
|
|
66
|
+
if (isValid && ruleItem && "safeParse" in ruleItem) {
|
|
67
|
+
result = ruleItem.safeParse(value);
|
|
68
|
+
isValid = result.success;
|
|
69
|
+
if (!isValid && result.error) {
|
|
70
|
+
message = result.error.issues[0]?.message || "Validation error";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { isValid, message, id, result };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var Validator = class {
|
|
78
|
+
fields;
|
|
79
|
+
params;
|
|
80
|
+
constructor(params) {
|
|
81
|
+
this.params = params || null;
|
|
82
|
+
this.fields = [];
|
|
83
|
+
}
|
|
84
|
+
addField(params) {
|
|
85
|
+
const field = new Field(params);
|
|
86
|
+
this.fields.push(field);
|
|
87
|
+
return field;
|
|
88
|
+
}
|
|
89
|
+
removeField(field) {
|
|
90
|
+
const index = this.fields.indexOf(field);
|
|
91
|
+
if (index > -1) this.fields.splice(index, 1);
|
|
92
|
+
}
|
|
93
|
+
getField(id) {
|
|
94
|
+
return this.fields.find((field) => field.id === id) || null;
|
|
95
|
+
}
|
|
96
|
+
validate() {
|
|
97
|
+
let prevResult;
|
|
98
|
+
const statuses = this.fields.map((field) => {
|
|
99
|
+
if (this.params?.stopAtFirstError && prevResult && prevResult.isValid === false) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
prevResult = field.validate();
|
|
103
|
+
return prevResult;
|
|
104
|
+
});
|
|
105
|
+
const results = statuses.filter((inst) => inst && inst.isValid === false);
|
|
106
|
+
if (results.length) {
|
|
107
|
+
return results[0];
|
|
108
|
+
}
|
|
109
|
+
return { isValid: true, message: "", result: results[0]?.result };
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/use-validator.ts
|
|
114
|
+
function useValidator(value, rules2) {
|
|
115
|
+
const validator = new Validator();
|
|
116
|
+
validator.addField({ value, rules: rules2 });
|
|
117
|
+
const { isValid, ...validateObject } = validator.validate();
|
|
118
|
+
return [isValid, validateObject];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/validator-field.tsx
|
|
122
|
+
var import_react2 = require("react");
|
|
123
|
+
|
|
124
|
+
// src/context.ts
|
|
125
|
+
var import_react = require("react");
|
|
126
|
+
var Context = (0, import_react.createContext)(null);
|
|
127
|
+
|
|
128
|
+
// src/validator-field.tsx
|
|
129
|
+
var ValidatorField = (0, import_react2.forwardRef)(function ValidatorField2(props, _ref) {
|
|
130
|
+
const { children, value } = props;
|
|
131
|
+
const { registerField, unregisterField } = (0, import_react2.useContext)(Context);
|
|
132
|
+
const propsRef = (0, import_react2.useRef)(props);
|
|
133
|
+
propsRef.current = props;
|
|
134
|
+
const handleRef = (0, import_react2.useRef)(null);
|
|
135
|
+
if (!handleRef.current) {
|
|
136
|
+
handleRef.current = {
|
|
137
|
+
get props() {
|
|
138
|
+
return propsRef.current;
|
|
139
|
+
},
|
|
140
|
+
validate: () => {
|
|
141
|
+
const curr = propsRef.current;
|
|
142
|
+
const field = new Field({
|
|
143
|
+
rules: curr.rules,
|
|
144
|
+
required: curr.required,
|
|
145
|
+
value: curr.value,
|
|
146
|
+
id: curr.id
|
|
147
|
+
});
|
|
148
|
+
return field.validate();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
(0, import_react2.useEffect)(() => {
|
|
153
|
+
registerField(handleRef.current);
|
|
154
|
+
return () => {
|
|
155
|
+
unregisterField(handleRef.current);
|
|
156
|
+
};
|
|
157
|
+
}, [registerField, unregisterField]);
|
|
158
|
+
const validity = handleRef.current.validate();
|
|
159
|
+
return typeof children === "function" ? children(validity, value) : children;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// src/validator-wrapper.tsx
|
|
163
|
+
var import_react3 = require("react");
|
|
164
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
165
|
+
var ValidatorWrapper = (0, import_react3.forwardRef)(function ValidatorWrapper2({ children, stopAtFirstError }, ref) {
|
|
166
|
+
const fieldsRef = (0, import_react3.useRef)([]);
|
|
167
|
+
const registerField = (0, import_react3.useCallback)((field) => {
|
|
168
|
+
if (field && !fieldsRef.current.includes(field)) {
|
|
169
|
+
fieldsRef.current.push(field);
|
|
170
|
+
}
|
|
171
|
+
}, []);
|
|
172
|
+
const unregisterField = (0, import_react3.useCallback)((field) => {
|
|
173
|
+
const index = fieldsRef.current.indexOf(field);
|
|
174
|
+
if (index > -1) fieldsRef.current.splice(index, 1);
|
|
175
|
+
}, []);
|
|
176
|
+
const getField = (0, import_react3.useCallback)((id) => {
|
|
177
|
+
return fieldsRef.current.find((field) => field?.props?.id === id) || null;
|
|
178
|
+
}, []);
|
|
179
|
+
const validate = (0, import_react3.useCallback)(() => {
|
|
180
|
+
const validator = new Validator({ stopAtFirstError });
|
|
181
|
+
for (const comp of fieldsRef.current) {
|
|
182
|
+
validator.addField(comp.props);
|
|
183
|
+
}
|
|
184
|
+
return validator.validate();
|
|
185
|
+
}, [stopAtFirstError]);
|
|
186
|
+
(0, import_react3.useImperativeHandle)(
|
|
187
|
+
ref,
|
|
188
|
+
() => ({
|
|
189
|
+
validate,
|
|
190
|
+
getField,
|
|
191
|
+
registerField,
|
|
192
|
+
unregisterField
|
|
193
|
+
}),
|
|
194
|
+
[validate, getField, registerField, unregisterField]
|
|
195
|
+
);
|
|
196
|
+
const contextValue = (0, import_react3.useMemo)(() => ({ registerField, unregisterField }), [registerField, unregisterField]);
|
|
197
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Context.Provider, { value: contextValue, children });
|
|
198
|
+
});
|
|
199
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
200
|
+
0 && (module.exports = {
|
|
201
|
+
Validator,
|
|
202
|
+
ValidatorField,
|
|
203
|
+
ValidatorWrapper,
|
|
204
|
+
rules,
|
|
205
|
+
useValidator
|
|
206
|
+
});
|
package/example/example.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// biome-ignore lint/correctness/noUnusedImports: <need>
|
|
1
2
|
import React, { createRef, useState } from 'react'
|
|
2
3
|
import ReactDOM from 'react-dom/client'
|
|
3
|
-
import { rules,
|
|
4
|
+
import { rules, ValidatorField, ValidatorWrapper } from '../dist/index'
|
|
4
5
|
|
|
5
6
|
function App() {
|
|
6
7
|
const [email, setEmail] = useState('')
|
|
@@ -15,32 +16,6 @@ function App() {
|
|
|
15
16
|
console.log('success')
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
function handleValidateCustomFields() {
|
|
19
|
-
const validator = new Validator({ stopAtFirstError: true })
|
|
20
|
-
const fieldPassword = validator.addField({
|
|
21
|
-
rules: rules.password,
|
|
22
|
-
value: '',
|
|
23
|
-
id: 'for-remove',
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const fieldSearchPassword = validator.getField('for-remove')
|
|
27
|
-
console.log(fieldPassword === fieldSearchPassword)
|
|
28
|
-
|
|
29
|
-
validator.removeField(fieldPassword)
|
|
30
|
-
|
|
31
|
-
validator.addField({
|
|
32
|
-
rules: rules.password,
|
|
33
|
-
value: 'testpassword',
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const { isValid, message, errors } = validator.validate()
|
|
37
|
-
if (!isValid) {
|
|
38
|
-
console.log(isValid, message, errors)
|
|
39
|
-
return
|
|
40
|
-
}
|
|
41
|
-
console.log('success')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
19
|
return (
|
|
45
20
|
<ValidatorWrapper ref={jsxValidator}>
|
|
46
21
|
<ValidatorField rules={rules.email} value={email}>
|
|
@@ -55,9 +30,6 @@ function App() {
|
|
|
55
30
|
</>
|
|
56
31
|
)}
|
|
57
32
|
</ValidatorField>
|
|
58
|
-
<button onClick={handleValidateCustomFields} type="button">
|
|
59
|
-
Validate custom fields
|
|
60
|
-
</button>
|
|
61
33
|
</ValidatorWrapper>
|
|
62
34
|
)
|
|
63
35
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coxy/react-validator",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "🚀 Simple validation form for React or NodeJS apps. useValidator are included ;)",
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "🚀 Simple validation form for React or NodeJS apps with zod. useValidator are included ;)",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"author": "dsshard",
|
|
23
23
|
"scripts": {
|
|
24
24
|
"start": "tsup ./src/index.ts --watch",
|
|
25
|
-
"build": "tsup",
|
|
26
|
-
"lint": "biome lint --fix",
|
|
25
|
+
"build": "tsup ./src/index.ts",
|
|
26
|
+
"lint": "npx biome lint --fix",
|
|
27
27
|
"example": "NODE_ENV=production npx esbuild example/example.tsx --bundle --outfile=example/dist/example.js --minify --format=esm --target=es2020",
|
|
28
28
|
"coverage": "npx codecov",
|
|
29
29
|
"coveralls": "npx coveralls .",
|
|
@@ -33,11 +33,10 @@
|
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": ">=18.x.x"
|
|
35
35
|
},
|
|
36
|
-
"dependencies": {
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"zod": "^3.25.0 || ^4.0.0"
|
|
38
|
+
},
|
|
37
39
|
"devDependencies": {
|
|
38
|
-
"react": ">=18.x.x",
|
|
39
|
-
"react-dom": ">=18.x.x",
|
|
40
|
-
"@biomejs/biome": "2.2.2",
|
|
41
40
|
"@testing-library/dom": "10.4.1",
|
|
42
41
|
"@testing-library/react": "16.3.0",
|
|
43
42
|
"@types/jest": "30.0.0",
|
|
@@ -46,10 +45,12 @@
|
|
|
46
45
|
"@types/react-dom": "19.1.7",
|
|
47
46
|
"jest": "30.0.5",
|
|
48
47
|
"jest-environment-jsdom": "30.0.5",
|
|
49
|
-
"
|
|
48
|
+
"react": ">=18.x.x",
|
|
49
|
+
"react-dom": ">=18.x.x",
|
|
50
50
|
"react-test-renderer": "19.1.1",
|
|
51
51
|
"ts-jest": "29.4.0",
|
|
52
52
|
"ts-node": "10.9.2",
|
|
53
|
+
"tsup": "8.5.0",
|
|
53
54
|
"typescript": "5.8.3"
|
|
54
55
|
}
|
|
55
56
|
}
|
package/src/context.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { rules } from './rules'
|
|
2
2
|
|
|
3
|
-
export type { ErrorMessage, FieldParams,
|
|
3
|
+
export type { ErrorMessage, FieldParams, Validity } from './types'
|
|
4
4
|
export { useValidator } from './use-validator'
|
|
5
5
|
export { Validator } from './validator'
|
|
6
6
|
export { ValidatorField } from './validator-field'
|
package/src/rules.test.ts
CHANGED
|
@@ -1,132 +1,37 @@
|
|
|
1
|
+
import type { ZodSafeParseResult } from 'zod'
|
|
1
2
|
import { rules } from './rules'
|
|
2
3
|
|
|
3
4
|
it('check rule email', () => {
|
|
4
5
|
expect(rules.email.length).toBe(2)
|
|
5
6
|
|
|
6
|
-
let result:
|
|
7
|
+
let result: ZodSafeParseResult<string>
|
|
7
8
|
// email.length > 0
|
|
8
|
-
result = rules.email[0].
|
|
9
|
-
expect(result).toBe(true)
|
|
9
|
+
result = rules.email[0].safeParse('test')
|
|
10
|
+
expect(result.success).toBe(true)
|
|
10
11
|
|
|
11
12
|
// email check regexp
|
|
12
|
-
result = rules.email[1].
|
|
13
|
-
expect(result).toBe(false)
|
|
13
|
+
result = rules.email[1].safeParse('test')
|
|
14
|
+
expect(result.success).toBe(false)
|
|
14
15
|
|
|
15
16
|
// email check regexp
|
|
16
|
-
result = rules.email[1].
|
|
17
|
-
expect(result).toBe(true)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('check rule password', () => {
|
|
21
|
-
expect(rules.password.length).toBe(2)
|
|
22
|
-
|
|
23
|
-
let result: boolean
|
|
24
|
-
// password.length > 0
|
|
25
|
-
result = rules.password[0].rule('test')
|
|
26
|
-
expect(result).toBe(true)
|
|
27
|
-
|
|
28
|
-
// password.length > 5
|
|
29
|
-
result = rules.password[1].rule('test')
|
|
30
|
-
expect(result).toBe(false)
|
|
17
|
+
result = rules.email[1].safeParse('test@gmail.com')
|
|
18
|
+
expect(result.success).toBe(true)
|
|
31
19
|
})
|
|
32
20
|
|
|
33
21
|
it('check rule bool', () => {
|
|
34
|
-
expect(rules.
|
|
22
|
+
expect(rules.isTrue.length).toBe(1)
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expect(result).toBe(true)
|
|
24
|
+
const result = rules.isTrue[0].safeParse(true)
|
|
25
|
+
expect(result.success).toBe(true)
|
|
39
26
|
})
|
|
40
27
|
|
|
41
28
|
it('check rule notEmpty', () => {
|
|
42
29
|
expect(rules.notEmpty.length).toBe(1)
|
|
43
30
|
|
|
44
|
-
let result:
|
|
45
|
-
result = rules.notEmpty[0].
|
|
46
|
-
expect(result).toBe(false)
|
|
47
|
-
|
|
48
|
-
result = rules.notEmpty[0].rule('test')
|
|
49
|
-
expect(result).toBe(true)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('check rule min', () => {
|
|
53
|
-
expect(rules.min(1).length).toBe(1)
|
|
54
|
-
expect(typeof rules.min).toBe('function')
|
|
55
|
-
|
|
56
|
-
let result: boolean
|
|
57
|
-
result = rules.min(10)[0].rule('')
|
|
58
|
-
expect(result).toBe(false)
|
|
59
|
-
|
|
60
|
-
result = rules.min(9)[0].rule('test-test-test')
|
|
61
|
-
expect(result).toBe(false)
|
|
62
|
-
|
|
63
|
-
result = rules.min(9)[0].rule('11')
|
|
64
|
-
expect(result).toBe(true)
|
|
65
|
-
|
|
66
|
-
// @ts-expect-error
|
|
67
|
-
result = rules.min(9)[0].rule(10)
|
|
68
|
-
expect(result).toBe(true)
|
|
69
|
-
|
|
70
|
-
result = rules.min(9)[0].rule('8')
|
|
71
|
-
expect(result).toBe(false)
|
|
72
|
-
|
|
73
|
-
// @ts-expect-error
|
|
74
|
-
result = rules.min(9)[0].rule(7)
|
|
75
|
-
expect(result).toBe(false)
|
|
76
|
-
|
|
77
|
-
result = rules.min(-1)[0].rule('')
|
|
78
|
-
expect(result).toBe(false)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('check rule max', () => {
|
|
82
|
-
let result: boolean
|
|
83
|
-
result = rules.max(10)[0].rule('')
|
|
84
|
-
expect(result).toBe(false)
|
|
85
|
-
|
|
86
|
-
result = rules.max(9)[0].rule('test-test-test')
|
|
87
|
-
expect(result).toBe(false)
|
|
88
|
-
|
|
89
|
-
result = rules.max(9)[0].rule('11')
|
|
90
|
-
expect(result).toBe(false)
|
|
91
|
-
|
|
92
|
-
// @ts-expect-error
|
|
93
|
-
result = rules.max(9)[0].rule(10)
|
|
94
|
-
expect(result).toBe(false)
|
|
95
|
-
|
|
96
|
-
result = rules.max(9)[0].rule('5')
|
|
97
|
-
expect(result).toBe(true)
|
|
98
|
-
|
|
99
|
-
// @ts-expect-error
|
|
100
|
-
result = rules.max(9)[0].rule(5)
|
|
101
|
-
expect(result).toBe(true)
|
|
102
|
-
|
|
103
|
-
result = rules.max(-1)[0].rule('')
|
|
104
|
-
expect(result).toBe(false)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('check rule length', () => {
|
|
108
|
-
let result: boolean
|
|
109
|
-
result = rules.length(1)[0].rule('')
|
|
110
|
-
expect(result).toBe(false)
|
|
111
|
-
|
|
112
|
-
result = rules.length(1)[0].rule('1')
|
|
113
|
-
expect(result).toBe(true)
|
|
114
|
-
|
|
115
|
-
result = rules.length(1, 10)[0].rule('test-test-test')
|
|
116
|
-
expect(result).toBe(true)
|
|
117
|
-
|
|
118
|
-
result = rules.length(1, 10)[1].rule('test-test-test')
|
|
119
|
-
expect(result).toBe(false)
|
|
120
|
-
|
|
121
|
-
result = rules.length(1, 10)[0].rule('lol')
|
|
122
|
-
expect(result).toBe(true)
|
|
123
|
-
|
|
124
|
-
result = rules.length(1, 10)[1].rule('lol')
|
|
125
|
-
expect(result).toBe(true)
|
|
126
|
-
|
|
127
|
-
result = rules.length(1)[1].rule('test undefined 2 param')
|
|
128
|
-
expect(result).toBe(true)
|
|
31
|
+
let result: ZodSafeParseResult<string>
|
|
32
|
+
result = rules.notEmpty[0].safeParse('')
|
|
33
|
+
expect(result.success).toBe(false)
|
|
129
34
|
|
|
130
|
-
result = rules.
|
|
131
|
-
expect(result).toBe(
|
|
35
|
+
result = rules.notEmpty[0].safeParse('test')
|
|
36
|
+
expect(result.success).toBe(true)
|
|
132
37
|
})
|
package/src/rules.ts
CHANGED
|
@@ -1,70 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from 'zod'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const emailReg =
|
|
5
|
-
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
|
6
|
-
|
|
7
|
-
export type ValidatorRules = ValidatorRule[]
|
|
3
|
+
export type ValidatorRules = z.ZodType[] | z.ZodType
|
|
8
4
|
|
|
9
5
|
export const rules = {
|
|
10
|
-
notEmpty: [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
message: 'Value is required',
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
|
|
17
|
-
bool: [
|
|
18
|
-
{
|
|
19
|
-
rule: (value: string) => !!value,
|
|
20
|
-
message: 'Value is required',
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
|
|
24
|
-
password: [
|
|
25
|
-
{
|
|
26
|
-
rule: (value: string) => value.length > 0,
|
|
27
|
-
message: 'Password field cannot be empty',
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
rule: (value: string) => value.length > 5,
|
|
31
|
-
message: 'Password field can not be less than 6 characters',
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
|
|
35
|
-
email: [
|
|
36
|
-
{
|
|
37
|
-
rule: (value: string) => !!value && value !== '' && value.length !== 0,
|
|
38
|
-
message: 'Email is required',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
rule: (value: string) => emailReg.test(String(value).toLowerCase()),
|
|
42
|
-
message: 'Email is invalid',
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
|
|
46
|
-
min: (min: number) => [
|
|
47
|
-
{
|
|
48
|
-
rule: (value: string) => Number.parseFloat(value) > min,
|
|
49
|
-
message: `The value must be greater than ${min}`,
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
|
|
53
|
-
max: (max: number) => [
|
|
54
|
-
{
|
|
55
|
-
rule: (value: string) => Number.parseFloat(value) < max,
|
|
56
|
-
message: `The value must be smaller ${max}`,
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
|
|
60
|
-
length: (min: number, max?: number) => [
|
|
61
|
-
{
|
|
62
|
-
rule: (value: string) => String(value).length >= min,
|
|
63
|
-
message: `No less than ${min} symbols`,
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
rule: (value: string) => (max !== undefined ? String(value).length <= max : true),
|
|
67
|
-
message: `No more than ${max} symbols`,
|
|
68
|
-
},
|
|
69
|
-
],
|
|
6
|
+
notEmpty: [z.string().min(1, { error: 'Field is required' })],
|
|
7
|
+
isTrue: [z.boolean({ error: 'Value is required' }).and(z.literal(true))],
|
|
8
|
+
email: [z.string().min(1, { error: 'Email is required' }), z.email({ message: 'Email is invalid' })],
|
|
70
9
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
+
import type { ZodSafeParseResult } from 'zod/index'
|
|
1
2
|
import type { ValidatorRules } from './rules'
|
|
2
3
|
import type { Value } from './validator-field'
|
|
3
4
|
|
|
4
|
-
type Fn = (value: Value) => string
|
|
5
|
-
|
|
6
|
-
export interface ValidatorRule {
|
|
7
|
-
rule: (value: Value) => boolean
|
|
8
|
-
message: string | Fn
|
|
9
|
-
}
|
|
10
|
-
|
|
11
5
|
export interface ErrorMessage {
|
|
12
6
|
message: string
|
|
13
7
|
isValid: boolean
|
|
@@ -16,7 +10,7 @@ export interface ErrorMessage {
|
|
|
16
10
|
export interface Validity {
|
|
17
11
|
message: string
|
|
18
12
|
isValid: boolean
|
|
19
|
-
|
|
13
|
+
result: ZodSafeParseResult<unknown>
|
|
20
14
|
id?: string | number
|
|
21
15
|
}
|
|
22
16
|
|
|
@@ -24,14 +24,14 @@ jest.useFakeTimers()
|
|
|
24
24
|
it('check state change and hide field', () => {
|
|
25
25
|
function Comp() {
|
|
26
26
|
const [value, setValue] = useState(false)
|
|
27
|
-
const [isValid, validateObject] = useValidator(value, rules.
|
|
27
|
+
const [isValid, validateObject] = useValidator(value, rules.isTrue)
|
|
28
28
|
|
|
29
29
|
useEffect(() => {
|
|
30
30
|
setTimeout(() => {
|
|
31
31
|
act(() => {
|
|
32
32
|
setValue(true)
|
|
33
33
|
})
|
|
34
|
-
},
|
|
34
|
+
}, 200)
|
|
35
35
|
}, [])
|
|
36
36
|
|
|
37
37
|
return (
|
|
@@ -46,7 +46,7 @@ it('check state change and hide field', () => {
|
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
expect(screen.getByTestId('test1').textContent).toContain('false')
|
|
49
|
-
expect(screen.getByTestId('test2').textContent).toContain('
|
|
49
|
+
expect(screen.getByTestId('test2').textContent).toContain('Invalid input: expected true')
|
|
50
50
|
|
|
51
51
|
jest.runAllTimers()
|
|
52
52
|
|
package/src/use-validator.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Validity } from './types'
|
|
|
3
3
|
import { Validator } from './validator'
|
|
4
4
|
import type { Value } from './validator-field'
|
|
5
5
|
|
|
6
|
-
export function useValidator(value: Value, rules: ValidatorRules): [boolean, Pick<Validity, 'message'
|
|
6
|
+
export function useValidator(value: Value, rules: ValidatorRules): [boolean, Pick<Validity, 'message'>] {
|
|
7
7
|
const validator = new Validator()
|
|
8
8
|
validator.addField({ value, rules })
|
|
9
9
|
const { isValid, ...validateObject } = validator.validate()
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import { act, render } from '@testing-library/react'
|
|
6
6
|
import { createRef, useEffect, useState } from 'react'
|
|
7
|
-
|
|
8
7
|
import { rules } from './rules'
|
|
9
8
|
import { ValidatorField } from './validator-field'
|
|
10
9
|
import { ValidatorWrapper } from './validator-wrapper'
|
|
@@ -51,13 +50,11 @@ it('check failed validation', () => {
|
|
|
51
50
|
|
|
52
51
|
expect(validateResult1.isValid).toBe(false)
|
|
53
52
|
expect(validateResult1.message).toBe('Email is invalid')
|
|
54
|
-
expect(validateResult1.errors.length).toBe(1)
|
|
55
53
|
|
|
56
54
|
const validateResult2 = validator2.current.validate()
|
|
57
55
|
|
|
58
56
|
expect(validateResult2.isValid).toBe(false)
|
|
59
57
|
expect(validateResult2.message).toBe('Email is required')
|
|
60
|
-
expect(validateResult2.errors.length).toBe(1)
|
|
61
58
|
})
|
|
62
59
|
})
|
|
63
60
|
|
|
@@ -93,7 +90,6 @@ it('check state change and hide field', () => {
|
|
|
93
90
|
|
|
94
91
|
expect(validateResult1.isValid).toBe(false)
|
|
95
92
|
expect(validateResult1.message).toBe('Email is invalid')
|
|
96
|
-
expect(validateResult1.errors.length).toBe(1)
|
|
97
93
|
})
|
|
98
94
|
|
|
99
95
|
it('check success validation', () => {
|
|
@@ -126,28 +122,6 @@ it('check success validation fot child function', () => {
|
|
|
126
122
|
expect(validateResult.message).toBe('')
|
|
127
123
|
})
|
|
128
124
|
|
|
129
|
-
it('check custom rule message function', () => {
|
|
130
|
-
const validator = createRef<ValidatorWrapper>()
|
|
131
|
-
const rule = [
|
|
132
|
-
{
|
|
133
|
-
rule: (value: string) => value !== 'test',
|
|
134
|
-
message: (value: string) => `test message ${value}`,
|
|
135
|
-
},
|
|
136
|
-
]
|
|
137
|
-
render(
|
|
138
|
-
<ValidatorWrapper ref={validator}>
|
|
139
|
-
<ValidatorField rules={rule} value="test">
|
|
140
|
-
{({ isValid, message }) => <>{!isValid && <div>{message}</div>}</>}
|
|
141
|
-
</ValidatorField>
|
|
142
|
-
</ValidatorWrapper>,
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
const validateResult = validator.current.validate()
|
|
146
|
-
|
|
147
|
-
expect(validateResult.isValid).toBe(false)
|
|
148
|
-
expect(validateResult.message).toBe('test message test')
|
|
149
|
-
})
|
|
150
|
-
|
|
151
125
|
jest.useFakeTimers()
|
|
152
126
|
|
|
153
127
|
it('re-renders the same field to cover handleRef initialization false branch and else-validate path', () => {
|
package/src/validator-field.tsx
CHANGED
|
@@ -15,14 +15,11 @@ type Props = FieldParams & {
|
|
|
15
15
|
|
|
16
16
|
export const ValidatorField = forwardRef<unknown, Props>(function ValidatorField(props: Props, _ref) {
|
|
17
17
|
const { children, value } = props
|
|
18
|
-
const {
|
|
18
|
+
const { registerField, unregisterField } = useContext(Context)
|
|
19
19
|
|
|
20
20
|
const propsRef = useRef(props)
|
|
21
21
|
propsRef.current = props
|
|
22
22
|
|
|
23
|
-
const customErrorsRef = useRef(customErrors)
|
|
24
|
-
customErrorsRef.current = customErrors
|
|
25
|
-
|
|
26
23
|
const handleRef = useRef<RegisteredFieldHandle | null>(null)
|
|
27
24
|
if (!handleRef.current) {
|
|
28
25
|
handleRef.current = {
|
|
@@ -31,10 +28,6 @@ export const ValidatorField = forwardRef<unknown, Props>(function ValidatorField
|
|
|
31
28
|
},
|
|
32
29
|
validate: () => {
|
|
33
30
|
const curr = propsRef.current
|
|
34
|
-
const customError = customErrorsRef.current.find((item) => item.id === curr.id)
|
|
35
|
-
if (customError) {
|
|
36
|
-
return customError
|
|
37
|
-
}
|
|
38
31
|
const field = new Field({
|
|
39
32
|
rules: curr.rules,
|
|
40
33
|
required: curr.required,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { render } from '@testing-library/react'
|
|
6
6
|
import { createRef } from 'react'
|
|
7
|
-
|
|
7
|
+
import type { Validity } from 'types'
|
|
8
8
|
import { rules } from './rules'
|
|
9
9
|
import { ValidatorField } from './validator-field'
|
|
10
10
|
import { ValidatorWrapper } from './validator-wrapper'
|
|
@@ -62,13 +62,13 @@ it('check stopAtFirstError validator', () => {
|
|
|
62
62
|
<ValidatorWrapper ref={validator} stopAtFirstError>
|
|
63
63
|
<ValidatorField rules={[]} value="test" />
|
|
64
64
|
<ValidatorField rules={rules.email} value="test" />
|
|
65
|
-
<ValidatorField rules={rules.
|
|
65
|
+
<ValidatorField rules={rules.isTrue} value={false} />
|
|
66
66
|
</ValidatorWrapper>,
|
|
67
67
|
)
|
|
68
|
+
|
|
68
69
|
const fieldValidate = validator.current.validate()
|
|
69
70
|
expect(fieldValidate.isValid).toBe(false)
|
|
70
71
|
expect(fieldValidate.message).toBe('Email is invalid')
|
|
71
|
-
expect(fieldValidate.errors.length).toBe(1)
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
it('check unregisterField, registerField', () => {
|
|
@@ -107,7 +107,7 @@ it('check wrapper in wrapper', () => {
|
|
|
107
107
|
<ValidatorWrapper ref={validatorOut}>
|
|
108
108
|
<ValidatorField rules={rules.email} value="" />
|
|
109
109
|
<ValidatorWrapper ref={validatorIn}>
|
|
110
|
-
<ValidatorField rules={rules.
|
|
110
|
+
<ValidatorField rules={rules.isTrue} value={true} />
|
|
111
111
|
</ValidatorWrapper>
|
|
112
112
|
</ValidatorWrapper>,
|
|
113
113
|
)
|
|
@@ -121,7 +121,7 @@ it('check two validators', () => {
|
|
|
121
121
|
render(
|
|
122
122
|
<>
|
|
123
123
|
<ValidatorWrapper ref={validatorSuccess}>
|
|
124
|
-
<ValidatorField rules={rules.
|
|
124
|
+
<ValidatorField rules={rules.notEmpty} value="successpasswword" />
|
|
125
125
|
</ValidatorWrapper>
|
|
126
126
|
<ValidatorWrapper ref={validatorFailed}>
|
|
127
127
|
<ValidatorField rules={rules.email} value="" />
|
|
@@ -146,7 +146,7 @@ it('covers registerField duplicate and unregisterField non-existing branches', (
|
|
|
146
146
|
validator.current.unregisterField(handle)
|
|
147
147
|
const dummy = {
|
|
148
148
|
props: { value: '', rules: [], id: 'dummy' },
|
|
149
|
-
validate: () => ({ isValid: true, message: '' }),
|
|
149
|
+
validate: (): Validity => ({ isValid: true, message: '', result: { success: true, data: null } }),
|
|
150
150
|
}
|
|
151
151
|
validator.current.unregisterField(dummy)
|
|
152
152
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef, type ReactNode, useCallback, useImperativeHandle, useMemo, useRef
|
|
1
|
+
import { forwardRef, type ReactNode, useCallback, useImperativeHandle, useMemo, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
import { Context, type RegisteredFieldHandle } from './context'
|
|
4
4
|
import type { Validity } from './types'
|
|
@@ -14,8 +14,6 @@ export interface ValidatorWrapper {
|
|
|
14
14
|
getField: (id: string | number) => RegisteredFieldHandle | null
|
|
15
15
|
registerField: (field: RegisteredFieldHandle) => void
|
|
16
16
|
unregisterField: (field: RegisteredFieldHandle) => void
|
|
17
|
-
setCustomError: (customError: Validity) => void
|
|
18
|
-
clearCustomErrors: () => void
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
export const ValidatorWrapper = forwardRef<ValidatorWrapper, ComponentProps>(function ValidatorWrapper(
|
|
@@ -23,7 +21,6 @@ export const ValidatorWrapper = forwardRef<ValidatorWrapper, ComponentProps>(fun
|
|
|
23
21
|
ref,
|
|
24
22
|
) {
|
|
25
23
|
const fieldsRef = useRef<RegisteredFieldHandle[]>([])
|
|
26
|
-
const [customErrors, setCustomErrors] = useState<Validity[]>([])
|
|
27
24
|
|
|
28
25
|
const registerField = useCallback((field: RegisteredFieldHandle) => {
|
|
29
26
|
if (field && !fieldsRef.current.includes(field)) {
|
|
@@ -40,14 +37,6 @@ export const ValidatorWrapper = forwardRef<ValidatorWrapper, ComponentProps>(fun
|
|
|
40
37
|
return fieldsRef.current.find((field) => field?.props?.id === id) || null
|
|
41
38
|
}, [])
|
|
42
39
|
|
|
43
|
-
const setCustomError = useCallback<ValidatorWrapper['setCustomError']>((customError) => {
|
|
44
|
-
setCustomErrors((prev) => [...prev, customError])
|
|
45
|
-
}, [])
|
|
46
|
-
|
|
47
|
-
const clearCustomErrors = useCallback<ValidatorWrapper['clearCustomErrors']>(() => {
|
|
48
|
-
setCustomErrors([])
|
|
49
|
-
}, [])
|
|
50
|
-
|
|
51
40
|
const validate = useCallback<ValidatorWrapper['validate']>(() => {
|
|
52
41
|
const validator = new Validator({ stopAtFirstError })
|
|
53
42
|
for (const comp of fieldsRef.current) {
|
|
@@ -63,16 +52,11 @@ export const ValidatorWrapper = forwardRef<ValidatorWrapper, ComponentProps>(fun
|
|
|
63
52
|
getField,
|
|
64
53
|
registerField,
|
|
65
54
|
unregisterField,
|
|
66
|
-
setCustomError,
|
|
67
|
-
clearCustomErrors,
|
|
68
55
|
}),
|
|
69
|
-
[validate, getField, registerField, unregisterField
|
|
56
|
+
[validate, getField, registerField, unregisterField],
|
|
70
57
|
)
|
|
71
58
|
|
|
72
|
-
const contextValue = useMemo(
|
|
73
|
-
() => ({ customErrors, registerField, unregisterField }),
|
|
74
|
-
[customErrors, registerField, unregisterField],
|
|
75
|
-
)
|
|
59
|
+
const contextValue = useMemo(() => ({ registerField, unregisterField }), [registerField, unregisterField])
|
|
76
60
|
|
|
77
61
|
return <Context.Provider value={contextValue}>{children}</Context.Provider>
|
|
78
62
|
})
|
package/src/validator.test.tsx
CHANGED
|
@@ -10,7 +10,7 @@ it('check normal create validator', () => {
|
|
|
10
10
|
it('check normal add and remove fields', () => {
|
|
11
11
|
const validator = new Validator({ stopAtFirstError: true })
|
|
12
12
|
const fieldPassword = validator.addField({
|
|
13
|
-
rules: rules.
|
|
13
|
+
rules: rules.email,
|
|
14
14
|
value: '',
|
|
15
15
|
id: 'for-remove',
|
|
16
16
|
})
|
package/src/validator.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ZodSafeParseResult } from 'zod'
|
|
1
2
|
import type { ValidatorRules } from './rules'
|
|
2
3
|
import type { FieldParams, Validity } from './types'
|
|
3
4
|
import type { Value } from './validator-field'
|
|
@@ -18,26 +19,31 @@ export class Field {
|
|
|
18
19
|
validate(): Validity {
|
|
19
20
|
let isValid = true
|
|
20
21
|
let message = ''
|
|
21
|
-
const {
|
|
22
|
+
const { value, required, id } = this
|
|
23
|
+
let result: ZodSafeParseResult<unknown> = { success: true, data: value }
|
|
22
24
|
|
|
23
25
|
const isEmptyValue = !value && Number.parseFloat(value) !== 0
|
|
26
|
+
const rules = Array.isArray(this.rules) ? this.rules : [this.rules]
|
|
24
27
|
|
|
25
28
|
if (!rules.length || (isEmptyValue && required === false)) {
|
|
26
|
-
return {
|
|
29
|
+
return {
|
|
30
|
+
isValid,
|
|
31
|
+
message,
|
|
32
|
+
id,
|
|
33
|
+
result: { success: true, data: value },
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
|
-
for (const
|
|
29
|
-
if (isValid) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
message = instance.message
|
|
36
|
-
}
|
|
36
|
+
for (const ruleItem of rules) {
|
|
37
|
+
if (isValid && ruleItem && 'safeParse' in ruleItem) {
|
|
38
|
+
// Handle Zod schemas
|
|
39
|
+
result = ruleItem.safeParse(value)
|
|
40
|
+
isValid = result.success
|
|
41
|
+
if (!isValid && result.error) {
|
|
42
|
+
message = result.error.issues[0]?.message || 'Validation error'
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
|
-
return { isValid, message, id }
|
|
46
|
+
return { isValid, message, id, result }
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
|
|
@@ -79,12 +85,11 @@ export class Validator {
|
|
|
79
85
|
return prevResult
|
|
80
86
|
})
|
|
81
87
|
|
|
82
|
-
const
|
|
88
|
+
const results = statuses.filter((inst) => inst && inst.isValid === false)
|
|
83
89
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
return { isValid, message, errors }
|
|
90
|
+
if (results.length) {
|
|
91
|
+
return results[0]
|
|
87
92
|
}
|
|
88
|
-
return { isValid: true, message: '' }
|
|
93
|
+
return { isValid: true, message: '', result: results[0]?.result }
|
|
89
94
|
}
|
|
90
95
|
}
|
package/dist/index.d.mts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
type Value = any;
|
|
5
|
-
type Fn$1 = (validity: Validity, value: Value) => ReactNode;
|
|
6
|
-
declare const ValidatorField: react.ForwardRefExoticComponent<FieldParams & {
|
|
7
|
-
children?: ReactNode | Fn$1;
|
|
8
|
-
} & react.RefAttributes<unknown>>;
|
|
9
|
-
|
|
10
|
-
type Fn = (value: Value) => string;
|
|
11
|
-
interface ValidatorRule {
|
|
12
|
-
rule: (value: Value) => boolean;
|
|
13
|
-
message: string | Fn;
|
|
14
|
-
}
|
|
15
|
-
interface ErrorMessage {
|
|
16
|
-
message: string;
|
|
17
|
-
isValid: boolean;
|
|
18
|
-
}
|
|
19
|
-
interface Validity {
|
|
20
|
-
message: string;
|
|
21
|
-
isValid: boolean;
|
|
22
|
-
errors?: ErrorMessage[];
|
|
23
|
-
id?: string | number;
|
|
24
|
-
}
|
|
25
|
-
interface FieldParams {
|
|
26
|
-
value: Value;
|
|
27
|
-
rules: ValidatorRules;
|
|
28
|
-
required?: boolean;
|
|
29
|
-
id?: string | number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type ValidatorRules = ValidatorRule[];
|
|
33
|
-
declare const rules: {
|
|
34
|
-
notEmpty: {
|
|
35
|
-
rule: (value: string) => boolean;
|
|
36
|
-
message: string;
|
|
37
|
-
}[];
|
|
38
|
-
bool: {
|
|
39
|
-
rule: (value: string) => boolean;
|
|
40
|
-
message: string;
|
|
41
|
-
}[];
|
|
42
|
-
password: {
|
|
43
|
-
rule: (value: string) => boolean;
|
|
44
|
-
message: string;
|
|
45
|
-
}[];
|
|
46
|
-
email: {
|
|
47
|
-
rule: (value: string) => boolean;
|
|
48
|
-
message: string;
|
|
49
|
-
}[];
|
|
50
|
-
min: (min: number) => {
|
|
51
|
-
rule: (value: string) => boolean;
|
|
52
|
-
message: string;
|
|
53
|
-
}[];
|
|
54
|
-
max: (max: number) => {
|
|
55
|
-
rule: (value: string) => boolean;
|
|
56
|
-
message: string;
|
|
57
|
-
}[];
|
|
58
|
-
length: (min: number, max?: number) => {
|
|
59
|
-
rule: (value: string) => boolean;
|
|
60
|
-
message: string;
|
|
61
|
-
}[];
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
declare function useValidator(value: Value, rules: ValidatorRules): [boolean, Pick<Validity, 'message' | 'errors'>];
|
|
65
|
-
|
|
66
|
-
declare class Field {
|
|
67
|
-
protected rules: ValidatorRules;
|
|
68
|
-
protected required: boolean;
|
|
69
|
-
protected value: Value;
|
|
70
|
-
id: string | number;
|
|
71
|
-
constructor({ rules, required, value, id }: FieldParams);
|
|
72
|
-
validate(): Validity;
|
|
73
|
-
}
|
|
74
|
-
interface ValidatorParams {
|
|
75
|
-
stopAtFirstError: boolean;
|
|
76
|
-
}
|
|
77
|
-
declare class Validator {
|
|
78
|
-
private fields;
|
|
79
|
-
private params;
|
|
80
|
-
constructor(params?: ValidatorParams);
|
|
81
|
-
addField(params: FieldParams): Field;
|
|
82
|
-
removeField(field: Field): void;
|
|
83
|
-
getField(id: Field['id']): Field;
|
|
84
|
-
validate(): Validity;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface RegisteredFieldHandle {
|
|
88
|
-
props: FieldParams;
|
|
89
|
-
validate: () => Validity;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface ComponentProps {
|
|
93
|
-
children?: ReactNode;
|
|
94
|
-
stopAtFirstError?: boolean;
|
|
95
|
-
}
|
|
96
|
-
interface ValidatorWrapper {
|
|
97
|
-
validate: () => Validity;
|
|
98
|
-
getField: (id: string | number) => RegisteredFieldHandle | null;
|
|
99
|
-
registerField: (field: RegisteredFieldHandle) => void;
|
|
100
|
-
unregisterField: (field: RegisteredFieldHandle) => void;
|
|
101
|
-
setCustomError: (customError: Validity) => void;
|
|
102
|
-
clearCustomErrors: () => void;
|
|
103
|
-
}
|
|
104
|
-
declare const ValidatorWrapper: react.ForwardRefExoticComponent<ComponentProps & react.RefAttributes<ValidatorWrapper>>;
|
|
105
|
-
|
|
106
|
-
export { type ErrorMessage, type FieldParams, Validator, ValidatorField, type ValidatorRule, ValidatorWrapper, type Validity, rules, useValidator };
|
package/dist/index.d.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
type Value = any;
|
|
5
|
-
type Fn$1 = (validity: Validity, value: Value) => ReactNode;
|
|
6
|
-
declare const ValidatorField: react.ForwardRefExoticComponent<FieldParams & {
|
|
7
|
-
children?: ReactNode | Fn$1;
|
|
8
|
-
} & react.RefAttributes<unknown>>;
|
|
9
|
-
|
|
10
|
-
type Fn = (value: Value) => string;
|
|
11
|
-
interface ValidatorRule {
|
|
12
|
-
rule: (value: Value) => boolean;
|
|
13
|
-
message: string | Fn;
|
|
14
|
-
}
|
|
15
|
-
interface ErrorMessage {
|
|
16
|
-
message: string;
|
|
17
|
-
isValid: boolean;
|
|
18
|
-
}
|
|
19
|
-
interface Validity {
|
|
20
|
-
message: string;
|
|
21
|
-
isValid: boolean;
|
|
22
|
-
errors?: ErrorMessage[];
|
|
23
|
-
id?: string | number;
|
|
24
|
-
}
|
|
25
|
-
interface FieldParams {
|
|
26
|
-
value: Value;
|
|
27
|
-
rules: ValidatorRules;
|
|
28
|
-
required?: boolean;
|
|
29
|
-
id?: string | number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type ValidatorRules = ValidatorRule[];
|
|
33
|
-
declare const rules: {
|
|
34
|
-
notEmpty: {
|
|
35
|
-
rule: (value: string) => boolean;
|
|
36
|
-
message: string;
|
|
37
|
-
}[];
|
|
38
|
-
bool: {
|
|
39
|
-
rule: (value: string) => boolean;
|
|
40
|
-
message: string;
|
|
41
|
-
}[];
|
|
42
|
-
password: {
|
|
43
|
-
rule: (value: string) => boolean;
|
|
44
|
-
message: string;
|
|
45
|
-
}[];
|
|
46
|
-
email: {
|
|
47
|
-
rule: (value: string) => boolean;
|
|
48
|
-
message: string;
|
|
49
|
-
}[];
|
|
50
|
-
min: (min: number) => {
|
|
51
|
-
rule: (value: string) => boolean;
|
|
52
|
-
message: string;
|
|
53
|
-
}[];
|
|
54
|
-
max: (max: number) => {
|
|
55
|
-
rule: (value: string) => boolean;
|
|
56
|
-
message: string;
|
|
57
|
-
}[];
|
|
58
|
-
length: (min: number, max?: number) => {
|
|
59
|
-
rule: (value: string) => boolean;
|
|
60
|
-
message: string;
|
|
61
|
-
}[];
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
declare function useValidator(value: Value, rules: ValidatorRules): [boolean, Pick<Validity, 'message' | 'errors'>];
|
|
65
|
-
|
|
66
|
-
declare class Field {
|
|
67
|
-
protected rules: ValidatorRules;
|
|
68
|
-
protected required: boolean;
|
|
69
|
-
protected value: Value;
|
|
70
|
-
id: string | number;
|
|
71
|
-
constructor({ rules, required, value, id }: FieldParams);
|
|
72
|
-
validate(): Validity;
|
|
73
|
-
}
|
|
74
|
-
interface ValidatorParams {
|
|
75
|
-
stopAtFirstError: boolean;
|
|
76
|
-
}
|
|
77
|
-
declare class Validator {
|
|
78
|
-
private fields;
|
|
79
|
-
private params;
|
|
80
|
-
constructor(params?: ValidatorParams);
|
|
81
|
-
addField(params: FieldParams): Field;
|
|
82
|
-
removeField(field: Field): void;
|
|
83
|
-
getField(id: Field['id']): Field;
|
|
84
|
-
validate(): Validity;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface RegisteredFieldHandle {
|
|
88
|
-
props: FieldParams;
|
|
89
|
-
validate: () => Validity;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface ComponentProps {
|
|
93
|
-
children?: ReactNode;
|
|
94
|
-
stopAtFirstError?: boolean;
|
|
95
|
-
}
|
|
96
|
-
interface ValidatorWrapper {
|
|
97
|
-
validate: () => Validity;
|
|
98
|
-
getField: (id: string | number) => RegisteredFieldHandle | null;
|
|
99
|
-
registerField: (field: RegisteredFieldHandle) => void;
|
|
100
|
-
unregisterField: (field: RegisteredFieldHandle) => void;
|
|
101
|
-
setCustomError: (customError: Validity) => void;
|
|
102
|
-
clearCustomErrors: () => void;
|
|
103
|
-
}
|
|
104
|
-
declare const ValidatorWrapper: react.ForwardRefExoticComponent<ComponentProps & react.RefAttributes<ValidatorWrapper>>;
|
|
105
|
-
|
|
106
|
-
export { type ErrorMessage, type FieldParams, Validator, ValidatorField, type ValidatorRule, ValidatorWrapper, type Validity, rules, useValidator };
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rules.ts","../src/validator.ts","../src/use-validator.ts","../src/context.ts","../src/validator-field.tsx","../src/validator-wrapper.tsx"],"names":["emailReg","rules","value","min","max","Field","required","id","isValid","message","isEmptyValue","instance","Validator","params","field","index","prevResult","errors","inst","useValidator","validator","validateObject","Context","createContext","ValidatorField","forwardRef","props","_ref","children","customErrors","registerField","unregisterField","useContext","propsRef","useRef","customErrorsRef","handleRef","curr","customError","item","useEffect","validity","ValidatorWrapper","stopAtFirstError","ref","fieldsRef","setCustomErrors","useState","useCallback","getField","setCustomError","prev","clearCustomErrors","validate","comp","useImperativeHandle","contextValue","useMemo","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { ValidatorRule } from './types'\n\n// eslint-disable-next-line\nconst emailReg =\n /^(([^<>()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/\n\nexport type ValidatorRules = ValidatorRule[]\n\nexport const rules = {\n notEmpty: [\n {\n rule: (value: string) => value !== '' && value.length > 0,\n message: 'Value is required',\n },\n ],\n\n bool: [\n {\n rule: (value: string) => !!value,\n message: 'Value is required',\n },\n ],\n\n password: [\n {\n rule: (value: string) => value.length > 0,\n message: 'Password field cannot be empty',\n },\n {\n rule: (value: string) => value.length > 5,\n message: 'Password field can not be less than 6 characters',\n },\n ],\n\n email: [\n {\n rule: (value: string) => !!value && value !== '' && value.length !== 0,\n message: 'Email is required',\n },\n {\n rule: (value: string) => emailReg.test(String(value).toLowerCase()),\n message: 'Email is invalid',\n },\n ],\n\n min: (min: number) => [\n {\n rule: (value: string) => Number.parseFloat(value) > min,\n message: `The value must be greater than ${min}`,\n },\n ],\n\n max: (max: number) => [\n {\n rule: (value: string) => Number.parseFloat(value) < max,\n message: `The value must be smaller ${max}`,\n },\n ],\n\n length: (min: number, max?: number) => [\n {\n rule: (value: string) => String(value).length >= min,\n message: `No less than ${min} symbols`,\n },\n {\n rule: (value: string) => (max !== undefined ? String(value).length <= max : true),\n message: `No more than ${max} symbols`,\n },\n ],\n}\n","import type { ValidatorRules } from './rules'\nimport type { FieldParams, Validity } from './types'\nimport type { Value } from './validator-field'\n\nexport class Field {\n protected rules: ValidatorRules\n protected required: boolean\n protected value: Value\n public id: string | number\n\n constructor({ rules, required, value, id }: FieldParams) {\n this.rules = rules\n this.required = required\n this.value = value\n this.id = id\n }\n\n validate(): Validity {\n let isValid = true\n let message = ''\n const { rules, value, required, id } = this\n\n const isEmptyValue = !value && Number.parseFloat(value) !== 0\n\n if (!rules.length || (isEmptyValue && required === false)) {\n return { isValid, message, id }\n }\n for (const instance of rules) {\n if (isValid) {\n isValid = instance.rule(value)\n if (!isValid) {\n if (typeof instance.message === 'function') {\n message = instance.message(value)\n } else {\n message = instance.message\n }\n }\n }\n }\n return { isValid, message, id }\n }\n}\n\nexport interface ValidatorParams {\n stopAtFirstError: boolean\n}\n\nexport class Validator {\n private fields: Field[]\n private params: ValidatorParams\n\n constructor(params?: ValidatorParams) {\n this.params = params || null\n this.fields = []\n }\n\n addField(params: FieldParams): Field {\n const field = new Field(params)\n this.fields.push(field)\n return field\n }\n\n removeField(field: Field): void {\n const index = this.fields.indexOf(field)\n if (index > -1) this.fields.splice(index, 1)\n }\n\n getField(id: Field['id']): Field {\n return this.fields.find((field) => field.id === id) || null\n }\n\n validate(): Validity {\n let prevResult: Validity | null\n const statuses = this.fields.map((field) => {\n if (this.params?.stopAtFirstError && prevResult && prevResult.isValid === false) {\n return null\n }\n prevResult = field.validate()\n return prevResult\n })\n\n const errors = statuses.filter((inst) => inst && inst.isValid === false)\n\n if (errors.length) {\n const { isValid, message } = errors[0]\n return { isValid, message, errors }\n }\n return { isValid: true, message: '' }\n }\n}\n","import type { ValidatorRules } from './rules'\nimport type { Validity } from './types'\nimport { Validator } from './validator'\nimport type { Value } from './validator-field'\n\nexport function useValidator(value: Value, rules: ValidatorRules): [boolean, Pick<Validity, 'message' | 'errors'>] {\n const validator = new Validator()\n validator.addField({ value, rules })\n const { isValid, ...validateObject } = validator.validate()\n return [isValid, validateObject]\n}\n","import { createContext } from 'react'\n\nimport type { FieldParams, Validity } from './types'\n\nexport interface RegisteredFieldHandle {\n props: FieldParams\n validate: () => Validity\n}\n\nexport const Context = createContext<{\n registerField: (field: RegisteredFieldHandle) => void\n unregisterField: (field: RegisteredFieldHandle) => void\n customErrors: Array<Validity>\n}>(null)\n","import { forwardRef, type ReactNode, useContext, useEffect, useRef } from 'react'\nimport type { FieldParams, Validity } from 'types'\n\nimport { Context, type RegisteredFieldHandle } from './context'\nimport { Field } from './validator'\n\n// biome-ignore lint/suspicious/noExplicitAny: <need>\nexport type Value = any\n\ntype Fn = (validity: Validity, value: Value) => ReactNode\n\ntype Props = FieldParams & {\n children?: ReactNode | Fn\n}\n\nexport const ValidatorField = forwardRef<unknown, Props>(function ValidatorField(props: Props, _ref) {\n const { children, value } = props\n const { customErrors, registerField, unregisterField } = useContext(Context)\n\n const propsRef = useRef(props)\n propsRef.current = props\n\n const customErrorsRef = useRef(customErrors)\n customErrorsRef.current = customErrors\n\n const handleRef = useRef<RegisteredFieldHandle | null>(null)\n if (!handleRef.current) {\n handleRef.current = {\n get props() {\n return propsRef.current\n },\n validate: () => {\n const curr = propsRef.current\n const customError = customErrorsRef.current.find((item) => item.id === curr.id)\n if (customError) {\n return customError\n }\n const field = new Field({\n rules: curr.rules,\n required: curr.required,\n value: curr.value,\n id: curr.id,\n })\n return field.validate()\n },\n }\n }\n\n useEffect(() => {\n registerField(handleRef.current as RegisteredFieldHandle)\n return () => {\n unregisterField(handleRef.current as RegisteredFieldHandle)\n }\n }, [registerField, unregisterField])\n\n const validity = handleRef.current.validate()\n\n return typeof children === 'function' ? (children as Fn)(validity, value) : (children as ReactNode)\n})\n","import { forwardRef, type ReactNode, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'\n\nimport { Context, type RegisteredFieldHandle } from './context'\nimport type { Validity } from './types'\nimport { Validator } from './validator'\n\ninterface ComponentProps {\n children?: ReactNode\n stopAtFirstError?: boolean\n}\n\nexport interface ValidatorWrapper {\n validate: () => Validity\n getField: (id: string | number) => RegisteredFieldHandle | null\n registerField: (field: RegisteredFieldHandle) => void\n unregisterField: (field: RegisteredFieldHandle) => void\n setCustomError: (customError: Validity) => void\n clearCustomErrors: () => void\n}\n\nexport const ValidatorWrapper = forwardRef<ValidatorWrapper, ComponentProps>(function ValidatorWrapper(\n { children, stopAtFirstError },\n ref,\n) {\n const fieldsRef = useRef<RegisteredFieldHandle[]>([])\n const [customErrors, setCustomErrors] = useState<Validity[]>([])\n\n const registerField = useCallback((field: RegisteredFieldHandle) => {\n if (field && !fieldsRef.current.includes(field)) {\n fieldsRef.current.push(field)\n }\n }, [])\n\n const unregisterField = useCallback((field: RegisteredFieldHandle) => {\n const index = fieldsRef.current.indexOf(field)\n if (index > -1) fieldsRef.current.splice(index, 1)\n }, [])\n\n const getField = useCallback<ValidatorWrapper['getField']>((id) => {\n return fieldsRef.current.find((field) => field?.props?.id === id) || null\n }, [])\n\n const setCustomError = useCallback<ValidatorWrapper['setCustomError']>((customError) => {\n setCustomErrors((prev) => [...prev, customError])\n }, [])\n\n const clearCustomErrors = useCallback<ValidatorWrapper['clearCustomErrors']>(() => {\n setCustomErrors([])\n }, [])\n\n const validate = useCallback<ValidatorWrapper['validate']>(() => {\n const validator = new Validator({ stopAtFirstError })\n for (const comp of fieldsRef.current) {\n validator.addField(comp.props)\n }\n return validator.validate()\n }, [stopAtFirstError])\n\n useImperativeHandle(\n ref,\n () => ({\n validate,\n getField,\n registerField,\n unregisterField,\n setCustomError,\n clearCustomErrors,\n }),\n [validate, getField, registerField, unregisterField, setCustomError, clearCustomErrors],\n )\n\n const contextValue = useMemo(\n () => ({ customErrors, registerField, unregisterField }),\n [customErrors, registerField, unregisterField],\n )\n\n return <Context.Provider value={contextValue}>{children}</Context.Provider>\n})\n"]}
|
package/dist/index.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import {createContext,forwardRef,useContext,useRef,useEffect,useState,useCallback,useImperativeHandle,useMemo}from'react';import {jsx}from'react/jsx-runtime';var h=/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,x={notEmpty:[{rule:r=>r!==""&&r.length>0,message:"Value is required"}],bool:[{rule:r=>!!r,message:"Value is required"}],password:[{rule:r=>r.length>0,message:"Password field cannot be empty"},{rule:r=>r.length>5,message:"Password field can not be less than 6 characters"}],email:[{rule:r=>!!r&&r!==""&&r.length!==0,message:"Email is required"},{rule:r=>h.test(String(r).toLowerCase()),message:"Email is invalid"}],min:r=>[{rule:e=>Number.parseFloat(e)>r,message:`The value must be greater than ${r}`}],max:r=>[{rule:e=>Number.parseFloat(e)<r,message:`The value must be smaller ${r}`}],length:(r,e)=>[{rule:t=>String(t).length>=r,message:`No less than ${r} symbols`},{rule:t=>e!==void 0?String(t).length<=e:true,message:`No more than ${e} symbols`}]};var V=class{rules;required;value;id;constructor({rules:e,required:t,value:s,id:i}){this.rules=e,this.required=t,this.value=s,this.id=i;}validate(){let e=true,t="",{rules:s,value:i,required:d,id:u}=this,n=!i&&Number.parseFloat(i)!==0;if(!s.length||n&&d===false)return {isValid:e,message:t,id:u};for(let a of s)e&&(e=a.rule(i),e||(typeof a.message=="function"?t=a.message(i):t=a.message));return {isValid:e,message:t,id:u}}},c=class{fields;params;constructor(e){this.params=e||null,this.fields=[];}addField(e){let t=new V(e);return this.fields.push(t),t}removeField(e){let t=this.fields.indexOf(e);t>-1&&this.fields.splice(t,1);}getField(e){return this.fields.find(t=>t.id===e)||null}validate(){let e,s=this.fields.map(i=>this.params?.stopAtFirstError&&e&&e.isValid===false?null:(e=i.validate(),e)).filter(i=>i&&i.isValid===false);if(s.length){let{isValid:i,message:d}=s[0];return {isValid:i,message:d,errors:s}}return {isValid:true,message:""}}};function b(r,e){let t=new c;t.addField({value:r,rules:e});let{isValid:s,...i}=t.validate();return [s,i]}var v=createContext(null);var w=forwardRef(function(e,t){let{children:s,value:i}=e,{customErrors:d,registerField:u,unregisterField:n}=useContext(v),a=useRef(e);a.current=e;let g=useRef(d);g.current=d;let p=useRef(null);p.current||(p.current={get props(){return a.current},validate:()=>{let m=a.current,y=g.current.find(o=>o.id===m.id);return y||new V({rules:m.rules,required:m.required,value:m.value,id:m.id}).validate()}}),useEffect(()=>(u(p.current),()=>{n(p.current);}),[u,n]);let F=p.current.validate();return typeof s=="function"?s(F,i):s});var S=forwardRef(function({children:e,stopAtFirstError:t},s){let i=useRef([]),[d,u]=useState([]),n=useCallback(l=>{l&&!i.current.includes(l)&&i.current.push(l);},[]),a=useCallback(l=>{let o=i.current.indexOf(l);o>-1&&i.current.splice(o,1);},[]),g=useCallback(l=>i.current.find(o=>o?.props?.id===l)||null,[]),p=useCallback(l=>{u(o=>[...o,l]);},[]),F=useCallback(()=>{u([]);},[]),m=useCallback(()=>{let l=new c({stopAtFirstError:t});for(let o of i.current)l.addField(o.props);return l.validate()},[t]);useImperativeHandle(s,()=>({validate:m,getField:g,registerField:n,unregisterField:a,setCustomError:p,clearCustomErrors:F}),[m,g,n,a,p,F]);let y=useMemo(()=>({customErrors:d,registerField:n,unregisterField:a}),[d,n,a]);return jsx(v.Provider,{value:y,children:e})});export{c as Validator,w as ValidatorField,S as ValidatorWrapper,x as rules,b as useValidator};//# sourceMappingURL=index.mjs.map
|
|
2
|
-
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rules.ts","../src/validator.ts","../src/use-validator.ts","../src/context.ts","../src/validator-field.tsx","../src/validator-wrapper.tsx"],"names":["emailReg","rules","value","min","max","Field","required","id","isValid","message","isEmptyValue","instance","Validator","params","field","index","prevResult","errors","inst","useValidator","validator","validateObject","Context","createContext","ValidatorField","forwardRef","props","_ref","children","customErrors","registerField","unregisterField","useContext","propsRef","useRef","customErrorsRef","handleRef","curr","customError","item","useEffect","validity","ValidatorWrapper","stopAtFirstError","ref","fieldsRef","setCustomErrors","useState","useCallback","getField","setCustomError","prev","clearCustomErrors","validate","comp","useImperativeHandle","contextValue","useMemo","jsx"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ValidatorRule } from './types'\n\n// eslint-disable-next-line\nconst emailReg =\n /^(([^<>()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/\n\nexport type ValidatorRules = ValidatorRule[]\n\nexport const rules = {\n notEmpty: [\n {\n rule: (value: string) => value !== '' && value.length > 0,\n message: 'Value is required',\n },\n ],\n\n bool: [\n {\n rule: (value: string) => !!value,\n message: 'Value is required',\n },\n ],\n\n password: [\n {\n rule: (value: string) => value.length > 0,\n message: 'Password field cannot be empty',\n },\n {\n rule: (value: string) => value.length > 5,\n message: 'Password field can not be less than 6 characters',\n },\n ],\n\n email: [\n {\n rule: (value: string) => !!value && value !== '' && value.length !== 0,\n message: 'Email is required',\n },\n {\n rule: (value: string) => emailReg.test(String(value).toLowerCase()),\n message: 'Email is invalid',\n },\n ],\n\n min: (min: number) => [\n {\n rule: (value: string) => Number.parseFloat(value) > min,\n message: `The value must be greater than ${min}`,\n },\n ],\n\n max: (max: number) => [\n {\n rule: (value: string) => Number.parseFloat(value) < max,\n message: `The value must be smaller ${max}`,\n },\n ],\n\n length: (min: number, max?: number) => [\n {\n rule: (value: string) => String(value).length >= min,\n message: `No less than ${min} symbols`,\n },\n {\n rule: (value: string) => (max !== undefined ? String(value).length <= max : true),\n message: `No more than ${max} symbols`,\n },\n ],\n}\n","import type { ValidatorRules } from './rules'\nimport type { FieldParams, Validity } from './types'\nimport type { Value } from './validator-field'\n\nexport class Field {\n protected rules: ValidatorRules\n protected required: boolean\n protected value: Value\n public id: string | number\n\n constructor({ rules, required, value, id }: FieldParams) {\n this.rules = rules\n this.required = required\n this.value = value\n this.id = id\n }\n\n validate(): Validity {\n let isValid = true\n let message = ''\n const { rules, value, required, id } = this\n\n const isEmptyValue = !value && Number.parseFloat(value) !== 0\n\n if (!rules.length || (isEmptyValue && required === false)) {\n return { isValid, message, id }\n }\n for (const instance of rules) {\n if (isValid) {\n isValid = instance.rule(value)\n if (!isValid) {\n if (typeof instance.message === 'function') {\n message = instance.message(value)\n } else {\n message = instance.message\n }\n }\n }\n }\n return { isValid, message, id }\n }\n}\n\nexport interface ValidatorParams {\n stopAtFirstError: boolean\n}\n\nexport class Validator {\n private fields: Field[]\n private params: ValidatorParams\n\n constructor(params?: ValidatorParams) {\n this.params = params || null\n this.fields = []\n }\n\n addField(params: FieldParams): Field {\n const field = new Field(params)\n this.fields.push(field)\n return field\n }\n\n removeField(field: Field): void {\n const index = this.fields.indexOf(field)\n if (index > -1) this.fields.splice(index, 1)\n }\n\n getField(id: Field['id']): Field {\n return this.fields.find((field) => field.id === id) || null\n }\n\n validate(): Validity {\n let prevResult: Validity | null\n const statuses = this.fields.map((field) => {\n if (this.params?.stopAtFirstError && prevResult && prevResult.isValid === false) {\n return null\n }\n prevResult = field.validate()\n return prevResult\n })\n\n const errors = statuses.filter((inst) => inst && inst.isValid === false)\n\n if (errors.length) {\n const { isValid, message } = errors[0]\n return { isValid, message, errors }\n }\n return { isValid: true, message: '' }\n }\n}\n","import type { ValidatorRules } from './rules'\nimport type { Validity } from './types'\nimport { Validator } from './validator'\nimport type { Value } from './validator-field'\n\nexport function useValidator(value: Value, rules: ValidatorRules): [boolean, Pick<Validity, 'message' | 'errors'>] {\n const validator = new Validator()\n validator.addField({ value, rules })\n const { isValid, ...validateObject } = validator.validate()\n return [isValid, validateObject]\n}\n","import { createContext } from 'react'\n\nimport type { FieldParams, Validity } from './types'\n\nexport interface RegisteredFieldHandle {\n props: FieldParams\n validate: () => Validity\n}\n\nexport const Context = createContext<{\n registerField: (field: RegisteredFieldHandle) => void\n unregisterField: (field: RegisteredFieldHandle) => void\n customErrors: Array<Validity>\n}>(null)\n","import { forwardRef, type ReactNode, useContext, useEffect, useRef } from 'react'\nimport type { FieldParams, Validity } from 'types'\n\nimport { Context, type RegisteredFieldHandle } from './context'\nimport { Field } from './validator'\n\n// biome-ignore lint/suspicious/noExplicitAny: <need>\nexport type Value = any\n\ntype Fn = (validity: Validity, value: Value) => ReactNode\n\ntype Props = FieldParams & {\n children?: ReactNode | Fn\n}\n\nexport const ValidatorField = forwardRef<unknown, Props>(function ValidatorField(props: Props, _ref) {\n const { children, value } = props\n const { customErrors, registerField, unregisterField } = useContext(Context)\n\n const propsRef = useRef(props)\n propsRef.current = props\n\n const customErrorsRef = useRef(customErrors)\n customErrorsRef.current = customErrors\n\n const handleRef = useRef<RegisteredFieldHandle | null>(null)\n if (!handleRef.current) {\n handleRef.current = {\n get props() {\n return propsRef.current\n },\n validate: () => {\n const curr = propsRef.current\n const customError = customErrorsRef.current.find((item) => item.id === curr.id)\n if (customError) {\n return customError\n }\n const field = new Field({\n rules: curr.rules,\n required: curr.required,\n value: curr.value,\n id: curr.id,\n })\n return field.validate()\n },\n }\n }\n\n useEffect(() => {\n registerField(handleRef.current as RegisteredFieldHandle)\n return () => {\n unregisterField(handleRef.current as RegisteredFieldHandle)\n }\n }, [registerField, unregisterField])\n\n const validity = handleRef.current.validate()\n\n return typeof children === 'function' ? (children as Fn)(validity, value) : (children as ReactNode)\n})\n","import { forwardRef, type ReactNode, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'\n\nimport { Context, type RegisteredFieldHandle } from './context'\nimport type { Validity } from './types'\nimport { Validator } from './validator'\n\ninterface ComponentProps {\n children?: ReactNode\n stopAtFirstError?: boolean\n}\n\nexport interface ValidatorWrapper {\n validate: () => Validity\n getField: (id: string | number) => RegisteredFieldHandle | null\n registerField: (field: RegisteredFieldHandle) => void\n unregisterField: (field: RegisteredFieldHandle) => void\n setCustomError: (customError: Validity) => void\n clearCustomErrors: () => void\n}\n\nexport const ValidatorWrapper = forwardRef<ValidatorWrapper, ComponentProps>(function ValidatorWrapper(\n { children, stopAtFirstError },\n ref,\n) {\n const fieldsRef = useRef<RegisteredFieldHandle[]>([])\n const [customErrors, setCustomErrors] = useState<Validity[]>([])\n\n const registerField = useCallback((field: RegisteredFieldHandle) => {\n if (field && !fieldsRef.current.includes(field)) {\n fieldsRef.current.push(field)\n }\n }, [])\n\n const unregisterField = useCallback((field: RegisteredFieldHandle) => {\n const index = fieldsRef.current.indexOf(field)\n if (index > -1) fieldsRef.current.splice(index, 1)\n }, [])\n\n const getField = useCallback<ValidatorWrapper['getField']>((id) => {\n return fieldsRef.current.find((field) => field?.props?.id === id) || null\n }, [])\n\n const setCustomError = useCallback<ValidatorWrapper['setCustomError']>((customError) => {\n setCustomErrors((prev) => [...prev, customError])\n }, [])\n\n const clearCustomErrors = useCallback<ValidatorWrapper['clearCustomErrors']>(() => {\n setCustomErrors([])\n }, [])\n\n const validate = useCallback<ValidatorWrapper['validate']>(() => {\n const validator = new Validator({ stopAtFirstError })\n for (const comp of fieldsRef.current) {\n validator.addField(comp.props)\n }\n return validator.validate()\n }, [stopAtFirstError])\n\n useImperativeHandle(\n ref,\n () => ({\n validate,\n getField,\n registerField,\n unregisterField,\n setCustomError,\n clearCustomErrors,\n }),\n [validate, getField, registerField, unregisterField, setCustomError, clearCustomErrors],\n )\n\n const contextValue = useMemo(\n () => ({ customErrors, registerField, unregisterField }),\n [customErrors, registerField, unregisterField],\n )\n\n return <Context.Provider value={contextValue}>{children}</Context.Provider>\n})\n"]}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment jsdom
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { act, render } from '@testing-library/react'
|
|
6
|
-
import { createRef } from 'react'
|
|
7
|
-
|
|
8
|
-
import { rules } from './rules'
|
|
9
|
-
import { ValidatorField } from './validator-field'
|
|
10
|
-
import { ValidatorWrapper, type ValidatorWrapper as ValidatorWrapperHandle } from './validator-wrapper'
|
|
11
|
-
|
|
12
|
-
it('setCustomError overrides field validation result and clearCustomErrors restores it', () => {
|
|
13
|
-
const validator = createRef<ValidatorWrapperHandle>()
|
|
14
|
-
|
|
15
|
-
render(
|
|
16
|
-
<ValidatorWrapper ref={validator}>
|
|
17
|
-
<ValidatorField id="email-field" rules={rules.email} value="user@example.com" />
|
|
18
|
-
</ValidatorWrapper>,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
// Initially valid
|
|
22
|
-
const fieldBefore = validator.current.getField('email-field')
|
|
23
|
-
const validityBefore = fieldBefore.validate()
|
|
24
|
-
expect(validityBefore.isValid).toBe(true)
|
|
25
|
-
expect(validityBefore.message).toBe('')
|
|
26
|
-
|
|
27
|
-
// Set a custom error
|
|
28
|
-
act(() => {
|
|
29
|
-
validator.current.setCustomError({ id: 'email-field', isValid: false, message: 'Custom error' })
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
const fieldWithCustom = validator.current.getField('email-field')
|
|
33
|
-
const validityWithCustom = fieldWithCustom.validate()
|
|
34
|
-
expect(validityWithCustom.isValid).toBe(false)
|
|
35
|
-
expect(validityWithCustom.message).toBe('Custom error')
|
|
36
|
-
|
|
37
|
-
// Clear custom errors
|
|
38
|
-
act(() => {
|
|
39
|
-
validator.current.clearCustomErrors()
|
|
40
|
-
})
|
|
41
|
-
const fieldAfter = validator.current.getField('email-field')
|
|
42
|
-
const validityAfter = fieldAfter.validate()
|
|
43
|
-
expect(validityAfter.isValid).toBe(true)
|
|
44
|
-
expect(validityAfter.message).toBe('')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('custom error is used inside ValidatorField render-prop child', () => {
|
|
48
|
-
const validator = createRef<ValidatorWrapperHandle>()
|
|
49
|
-
const messages: string[] = []
|
|
50
|
-
|
|
51
|
-
render(
|
|
52
|
-
<ValidatorWrapper ref={validator}>
|
|
53
|
-
<ValidatorField id="field-x" rules={rules.password} value="strongpassword">
|
|
54
|
-
{({ message }) => {
|
|
55
|
-
messages.push(message)
|
|
56
|
-
return null
|
|
57
|
-
}}
|
|
58
|
-
</ValidatorField>
|
|
59
|
-
</ValidatorWrapper>,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
// Initially valid → message pushed should be ''
|
|
63
|
-
expect(messages[messages.length - 1]).toBe('')
|
|
64
|
-
|
|
65
|
-
// After setting custom error, the render-prop should see the custom message.
|
|
66
|
-
act(() => {
|
|
67
|
-
validator.current.setCustomError({ id: 'field-x', isValid: false, message: 'Injected' })
|
|
68
|
-
})
|
|
69
|
-
const field = validator.current.getField('field-x')
|
|
70
|
-
const res = field.validate()
|
|
71
|
-
expect(res.isValid).toBe(false)
|
|
72
|
-
expect(res.message).toBe('Injected')
|
|
73
|
-
})
|
package/tsup.config.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'tsup'
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
entry: ['src/index.ts'],
|
|
5
|
-
format: ['cjs', 'esm'],
|
|
6
|
-
target: 'esnext',
|
|
7
|
-
clean: true,
|
|
8
|
-
dts: true,
|
|
9
|
-
treeshake: true,
|
|
10
|
-
splitting: false,
|
|
11
|
-
minify: true,
|
|
12
|
-
sourcemap: true,
|
|
13
|
-
external: ['react'],
|
|
14
|
-
})
|