@coxy/react-validator 4.0.0 → 5.0.1

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.
@@ -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' | 'errors'>] {
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', () => {
@@ -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 { customErrors, registerField, unregisterField } = useContext(Context)
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.password} value="" />
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.password} value="successpasswword" />
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.password} value="successpasswword" />
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, useState } from 'react'
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, setCustomError, clearCustomErrors],
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
  })
@@ -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.password,
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 { rules, value, required, id } = this
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 { isValid, message, id }
29
+ return {
30
+ isValid,
31
+ message,
32
+ id,
33
+ result: { success: true, data: value },
34
+ }
27
35
  }
28
- for (const instance of rules) {
29
- if (isValid) {
30
- isValid = instance.rule(value)
31
- if (!isValid) {
32
- if (typeof instance.message === 'function') {
33
- message = instance.message(value)
34
- } else {
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 errors = statuses.filter((inst) => inst && inst.isValid === false)
88
+ const results = statuses.filter((inst) => inst && inst.isValid === false)
83
89
 
84
- if (errors.length) {
85
- const { isValid, message } = errors[0]
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
@@ -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
- })