Skip to content

Feature/rewrite for 3 #270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: 3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
"node": true,
"es6": true
},
"ignorePatterns": ["dist", "setupTests.ts", "babel.config.js"],
"ignorePatterns": ["dist", "setupTests.ts", "vite.config.ts"],
"rules": {
"comma-dangle": "off",
"comma-dangle": ["error", "only-multiline"],
"class-methods-use-this": "off",
"import/prefer-default-export": "off",
"import/no-dynamic-require": "off",
"global-require": "off",
"quotes": ["error", "single", { "allowTemplateLiterals": true }],
"@typescript-eslint/indent": ["error", 4],
"@typescript-eslint/no-non-null-assertion": ["off"]
"@typescript-eslint/no-non-null-assertion": ["off"],
"@typescript-eslint/no-explicit-any": ["off"],
"react/react-in-jsx-scope": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/ban-ts-comment": "off"
}
}
50 changes: 30 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,42 @@ name: CI

on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 20, latest ]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: pnpm install
- name: Run tests
run: pnpm test
test-coverage:
name: Test on Node.js Latest
name: Test on latest NodeJS
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use Node.js latest
uses: actions/setup-node@v2.4.1
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
node-version: "15"
version: 8
- name: Use NodeJS
uses: actions/setup-node@v4
with:
node-version: latest
- name: Install dependencies
run: npm install
run: pnpm install
- name: Generate coverage report
run: npm run test-coverage
run: pnpm run test-coverage
- name: Upload coverage report
uses: codecov/codecov-action@v2.1.0
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV }}
test-node-12:
name: Test on Node.js v12
runs-on: ubuntu-latest
steps:
- name: Checkout codd
uses: actions/checkout@v2
- name: Use Node.js v12
uses: actions/setup-node@v2.4.1
with:
node-version: "12"


136 changes: 45 additions & 91 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,187 +4,141 @@ The smart React element validator
[![example workflow name](https://github.com/coderan-io/validator/workflows/CI/badge.svg)](https://github.com/coderan-io/validator/actions?query=workflow%3ACI)
[![codecov](https://codecov.io/gh/coderan-io/validator/branch/develop/graph/badge.svg?token=OX5CACK0K0)](https://codecov.io/gh/coderan-io/validator)

> [!WARNING]
> Full V3 docs coming soon! The following docs are updated for V3, but not complete yet.

### Introduction
The goal of this package, is to simplify the struggle of validating elements in React, with a simple system which allows
users to add their own rules.
The system communicates directly with the elements in the DOM, and is therefore widely compatible with other libraries,
like [Bootstrap](https://react-bootstrap.github.io/).

### The concept
Validator consists of two main elements, an `Area` and a `Provider`. Areas are a sort of wrappers having elements that
need validation as their children. An area scans the underlying components and elements and indexes validatable elements.
Validator consists of two main elements, an `Area` and a `Field`. Fields are some sort of containers having elements that
need validation as their children. A field scans the underlying components and elements and indexes validatable elements.

Providers on the other hand are wrappers around areas, and allow them to communicate between each other. This communication
is needed in order to match with values in other areas. It can also be used to validate all areas at once, and preventing
actions to happen while not all areas are valid.
Areas on the other hand are containers around fields, and allow them to communicate between each other. This communication
is needed in order to match with values in other fields. It can also be used to validate all areas at once, and preventing
actions to happen while not all areas are valid. There should always be an area defined around the fields in your form.

### How to use
First, start with adding rules to the validator in order to use them. There are some rules pre-made, but more specific
rules you have to create yourself.

```javascript
import { Validator } from '@coderan/validator';
import { min } from '@coderan/rules/min';

Validator.extend('min', min);
```

#### Area
#### Field
Basic usage:
```jsx
import { ValidatorArea } from '@coderan/validator';
import { ValidationField } from '@coderan/validator';
import { required } from '@coderan/validator';

<ValidatorArea rules="required">
<ValidationField rules={[required]}>
<input name="username" />
</ValidatorArea>
</ValidationField>
```
When the input is focused and blurred, the `required` rule is called.
When the input is blurred, the `required` rule is called.

Every area needs a name. This name is used to index areas in the provider, and make meaningful error messages. When using
multiple inputs within an area, i.e. when validating a multi-input date of birth, `name` prop is required when defining
the `ValidatorArea` component. Like so:
Every field needs a name. This name is used to index fields in the area, and make meaningful error messages. When using
multiple inputs within an field, i.e. when validating a multi-input date of birth, `name` prop is required when defining
the `ValidationField` component. Like so:

```jsx
import { ValidatorArea } from '@coderan/validator';
import { ValidationField, min } from '@coderan/validator';

<ValidatorArea rules="min" name="dob">
<ValidationField rules={[min(5)]} name="dob">
<input name="day" />
<input name="month" />
<input name="year" />
</ValidatorArea>
</ValidationField>
```

Showing errors:
```jsx
import { ValidatorArea } from '@coderan/validator';
import { ValidationField, min } from '@coderan/validator';

<ValidatorArea rules="min" name="dob">
<ValidationField rules={[min(1)]} name="dob">
{({ errors }) => (
<>
<input name="username" />
{ errors.length && <span>{errors[0]}</span> }
</>
)}
</ValidatorArea>
</ValidationField>
```

#### Provider
Basic usage:
```jsx
import { ValidatorProvider, ValidatorArea } from '@coderan/validator';
import { ValidationArea, ValidationField, min } from '@coderan/validator';

<ValidatorProvider>
<ValidationArea>
{({ validate }) => (
<>
<ValidatorArea rules="min" name="dob">
<ValidationField rules={[min(1)]} name="dob">
<input name="day" />
<input name="month" />
<input name="year" />
</ValidatorArea>
<ValidatorArea rules="min" name="dob">
</ValidationField>
<ValidationField rules={min(1)} name="dob">
<input name="day" />
<input name="month" />
<input name="year" />
</ValidatorArea>
</ValidationField>
<button
onClick={() => validate(() => alert('valid'))}>Check</button>
</>
)}
</ValidatorProvider>
</ValidationArea>
```

It is possible to give the validator a `rules` prop as well, whose rules apply to all underlying areas:

```jsx
import { ValidatorProvider, ValidatorArea } from '@coderan/validator';
import { ValidationArea, ValidationField, required, min } from '@coderan/validator';

<ValidatorProvider rules="required">
<ValidatorArea rules="min:5">
<ValidationArea rules={[required]}>
<ValidationField rules={[min(5)]}>
{/* on blur, both required and min rules are applied */}
<input name="username" />
</ValidatorArea>
</ValidatorProvider>
```

#### Adding rules

With access to validator
```javascript
import { Validator } from '@coderan/validator'
Validator.extend('test_types', (validator: Validator) => ({
passed(): boolean {
return validator.refs(undefined, HTMLInputElement).length === 1
&& validator.refs('test1', HTMLTextAreaElement).length === 1
&& validator.refs('test1').length === 1
&& validator.refs('test1', HTMLProgressElement).length === 0;
},
message(): string {
return 'test';
}
}));
```

or without
```javascript
import { getValue, isInputElement, isSelectElement } from '@/utils/dom';

export default {
passed(elements: HTMLElement[]): boolean {
return elements.every((element: HTMLElement) => {
if (isInputElement(element) || isSelectElement(element)) {
const value = getValue(element);

return value && value.length;
}

return true;
})
},

message(): string {
return `{name} is required`;
}
};
</ValidationField>
</ValidationArea>
```

You can create your own rules, as long as it follows this interface:
```typescript
import { Validator } from '@coderan/validator';

import { FieldManager } from '@coderan/validator';
/**
* Function to access validator using the rule
*/
declare type RuleFunction = (validator: Validator) => RuleObject;
export type RuleFunction = (fieldManager: FieldManager) => RuleObject;

/**
* Object structure rules must implement
*/
declare type RuleObject = {
export type RuleObject = {
name: string;
/**
* Returns whether the rule passed with the given element(s)
*/
passed(elements: HTMLElement[], ...args: string[]): boolean;
passed(elements: HTMLElement[], ...args: string[]): boolean | Promise<boolean>;
/**
* Message shown when the rule doesn't pass
* Message shown when the rule doesn't pass. This returns a tuple with the translation key and the parameters
*/
message(): string;
message(): [string, Record<string, number | string>?];
}

export type Rule = RuleObject | RuleFunction;
```

Perhaps you would like to use a different name for the message than the `name`-attribute. That's perfectly fine!
```tsx
import { ValidatorArea } from '@coderan/validator';
import { ValidationField, required } from '@coderan/validator';

<ValidatorArea rules="required" validationName="Surname">
<ValidationField rules={[required]} validationName="Surname">
{({ errors }) => (
<>
<input name="username" />
{ errors.length && <span>{errors[0]}</span> }
</>
)}
</ValidatorArea>
</ValidationField>
```
and when no value is present in the input, a message like "Surname is required" will appear.

Expand Down
Loading