Skip to content

Commit

Permalink
fix: corrected bug with $resetAll
Browse files Browse the repository at this point in the history
  • Loading branch information
victorgarciaesgi committed Dec 11, 2024
1 parent 502817b commit 0e775fe
Show file tree
Hide file tree
Showing 25 changed files with 436 additions and 261 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,14 @@ TODO

- [x] Unit tests
- [x] E2E tests
- [ ] Type tests
- [ ] Simplify/reduce core code
- [ ] `tip` on createRule
- [ ] Example repo, Reproduction repl
- [ ] Type tests

### Next iterations

- [ ] `Valibot` support
- [ ] `Yup` support
- [ ] TS docs
- [ ] `withErrorType`

### 🤔 Maybe in roadmap

Expand Down
28 changes: 19 additions & 9 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ const CoreConcepts: (DefaultTheme.NavItemWithLink | DefaultTheme.NavItemChildren
{
text: 'Rules',
items: [
{ text: 'Inline rules', link: '/core-concepts/rules/' },
{ text: 'Writing a rule', link: '/core-concepts/rules/' },
{
text: 'Advanced rules',
link: '/core-concepts/rules/advanced-rules',
text: 'Reusable rules',
link: '/core-concepts/rules/reusable-rules',
},
{
text: 'Rule wrappers',
link: '/core-concepts/rules/rule-wrappers',
},
{
text: 'Built-in rules',
Expand Down Expand Up @@ -56,7 +60,11 @@ const Typescript: (DefaultTheme.NavItemWithLink | DefaultTheme.NavItemChildren)[
},
];

const Examples: DefaultTheme.NavItemWithLink[] = [{ text: 'Base example', link: '/examples/base' }];
const Examples: DefaultTheme.NavItemWithLink[] = [{ text: 'Simple form example', link: '/examples/simple' }];

const Troubleshooting: DefaultTheme.NavItemWithLink[] = [
{ text: 'Reactivity caveats', link: '/troubleshooting/reactivity' },
];

export default defineConfig({
title: 'Regle',
Expand Down Expand Up @@ -112,6 +120,11 @@ export default defineConfig({
items: Examples,
collapsed: false,
},
{
text: 'Troubleshooting',
items: Troubleshooting,
collapsed: false,
},
],
socialLinks: [{ icon: 'github', link: 'https://github.com/victorgarciaesgi/regle' }],
footer: {
Expand Down Expand Up @@ -155,10 +168,7 @@ export default defineConfig({
],
['meta', { name: 'twitter:site', content: '@regle' }],
['meta', { name: 'twitter:card', content: 'summary' }],
[
'meta',
{ name: 'google-site-verification', content: 'mYJKnciAjHTdI7nsB2xame8QO61IeKoXCZeGyWGjs-4' },
],
['meta', { name: 'google-site-verification', content: 'mYJKnciAjHTdI7nsB2xame8QO61IeKoXCZeGyWGjs-4' }],
],
markdown: {
codeTransformers: [transformerTwoslash({})],
Expand All @@ -177,7 +187,7 @@ export default defineConfig({
'<svg width="14px" height="14px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 319 477"><linearGradient id="a"><stop offset="0" stop-color="#52ce63"/><stop offset="1" stop-color="#51a256"/></linearGradient><linearGradient id="b" x1="55.342075%" x2="42.816933%" xlink:href="#a" y1="0%" y2="42.862855%"/><linearGradient id="c" x1="55.348642%" x2="42.808103%" xlink:href="#a" y1="0%" y2="42.862855%"/><linearGradient id="d" x1="50%" x2="50%" y1="0%" y2="58.811243%"><stop offset="0" stop-color="#8ae99c"/><stop offset="1" stop-color="#52ce63"/></linearGradient><linearGradient id="e" x1="51.37763%" x2="44.584719%" y1="17.472551%" y2="100%"><stop offset="0" stop-color="#ffe56c"/><stop offset="1" stop-color="#ffc63a"/></linearGradient><g fill="none" fill-rule="evenodd" transform="translate(-34 -24)"><g transform="matrix(.99254615 .12186934 -.12186934 .99254615 33.922073 .976691)"><path d="m103.950535 258.274149c44.361599-4.360825 60.014503-40.391282 65.353094-94.699444s-30.93219-103.451001-46.020347-101.9678079c-15.088156 1.4831932-63.0385313 58.9051239-68.3771222 113.2132869-5.3385908 54.308162 4.6827754 87.814791 49.0443752 83.453965z" fill="url(#b)" transform="matrix(.70710678 -.70710678 .70710678 .70710678 -80.496332 125.892944)"/><path d="m275.876752 258.273992c44.3616 4.360826 53.167133-29.265322 47.828542-83.573485-5.338591-54.308162-52.073133-111.6105744-67.16129-113.0937675-15.088156-1.4831931-52.57477 47.5401275-47.236179 101.8482895s22.207328 90.458137 66.568927 94.818963z" fill="url(#c)" transform="matrix(.70710678 .70710678 -.70710678 .70710678 191.403399 -141.861963)"/><path d="m188.370027 216.876305c39.941834 0 50.95265-38.251987 50.95265-97.89874 0-59.6467532-37.367733-118.10125956-50.95265-118.10125956s-52.04735 58.45450636-52.04735 118.10125956c0 59.646753 12.105516 97.89874 52.04735 97.89874z" fill="url(#d)"/></g><path d="m184.473473 501c83.118854 0 150.526527-24.145148 150.526527-133.645148s-67.407673-199.354852-150.526527-199.354852c-83.118855 0-150.473473 89.854852-150.473473 199.354852s67.354618 133.645148 150.473473 133.645148z" fill="url(#e)"/><ellipse cx="260.5" cy="335" fill="#eaadcc" rx="21.5" ry="10"/><ellipse cx="102.5" cy="329" fill="#eaadcc" rx="21.5" ry="10" transform="matrix(.99254615 .12186934 -.12186934 .99254615 40.859033 -10.039292)"/><g transform="matrix(-.99939083 .0348995 .0348995 .99939083 269.284825 271.027667)"><path d="m73.1046985 58.2728794c6.7372416 4.9130333 14.3132632 6.6640587 22.7280649 5.2530761 8.4148016-1.4109825 14.5054466-5.2535769 18.2719346-11.527783" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="6" transform="matrix(.9998477 .01745241 -.01745241 .9998477 1.026464 -1.624794)"/><path d="m154.501124 3c-5.937545 0-11.312782 2.40629805-15.203644 6.29680621-3.89094 3.89058529-6.29748 9.26545449-6.29748 15.20263179 0 5.9376888 2.406488 11.3127422 6.297291 15.2034272 3.890886 3.8907673 9.266197 6.2971348 15.203833 6.2971348 5.937109 0 11.311896-2.4063889 15.202387-6.2972348 3.890299-3.8906535 6.296489-9.2656636 6.296489-15.2033272 0-5.9371521-2.406242-11.3119781-6.296677-15.20253181-3.890469-3.89058674-9.265181-6.29690619-15.202199-6.29690619z" fill="#000"/><path d="m154 21c0-3.865549 3.135362-7 6.999413-7 3.866399 0 7.000587 3.134451 7.000587 7s-3.134188 7-7.000587 7c-3.864051-.0011735-6.999413-3.134451-6.999413-7z" fill="#fff"/><path d="m24.5 13c-5.9375292 0-11.312426 2.406268-15.20299427 6.2967181-3.89069464 3.8905765-6.29700573 9.2654765-6.29700573 15.2027199 0 5.9377549 2.40625962 11.3128391 6.29681766 15.2035153 3.89059104 3.8907092 9.26556184 6.2970467 15.20318234 6.2970467 5.9371249 0 11.3122514-2.406419 15.2030371-6.2973229 3.8905441-3.8906623 6.2969629-9.2656416 6.2969629-15.2032391 0-5.937086-2.4064703-11.3118811-6.297151-15.2024437-3.890763-3.8906448-9.2658154-6.2969943-15.202849-6.2969943z" fill="#000"/><g fill="#fff"><path d="m136 24.499438c0 10.2185232 8.282911 18.500562 18.501124 18.500562 10.217089 0 18.498876-8.2820388 18.498876-18.500562 0-10.2173992-8.281787-18.499438-18.498876-18.499438-10.218213 0-18.501124 8.2820388-18.501124 18.499438zm-6 0c0-13.5311954 10.96929-24.499438 24.501124-24.499438 13.530838 0 24.498876 10.9683711 24.498876 24.499438 0 13.5319607-10.967808 24.500562-24.498876 24.500562-13.532064 0-24.501124-10.9684728-24.501124-24.500562z" fill-rule="nonzero" stroke="#fff" stroke-width="3"/><path d="m6 34.499438c0 10.2185232 8.2817873 18.500562 18.5 18.500562 10.2170889 0 18.5-8.2820388 18.5-18.500562 0-10.2173992-8.2829111-18.499438-18.5-18.499438-10.2182127 0-18.5 8.2820388-18.5 18.499438zm-6 0c0-13.531297 10.9682681-24.499438 24.5-24.499438 13.5309398 0 24.5 10.9684728 24.5 24.499438 0 13.5318591-10.96883 24.500562-24.5 24.500562-13.531962 0-24.5-10.9683711-24.5-24.500562z" fill-rule="nonzero" stroke="#fff" stroke-width="3"/><path d="m24 31c0-3.865549 3.134451-7 7-7s7 3.134451 7 7-3.134451 7-7 7-7-3.134451-7-7z"/></g></g><g stroke-linecap="round" stroke-width="11"><g stroke="#ecb732"><path d="m70.5 377.5 74 77"/><path d="m134.5 386.5-47 50"/></g><g stroke="#ecb732" transform="matrix(-1 0 0 1 298 377)"><path d="m.5.5 74 77"/><path d="m64.5 9.5-47 50"/></g><g stroke="#ffc73b" transform="matrix(0 1 -1 0 215 207)"><path d="m.5.5 49 49"/><path d="m.5 10.5 49 49" transform="matrix(-1 0 0 1 50 0)"/></g></g></g></svg>',
zod: `<svg xmlns='http://www.w3.org/2000/svg" width="1.27em" height="1em" viewBox="0 0 256 203"><defs><filter id="logosZod0" width="105.2%" height="106.5%" x="-2.2%" y="-2.8%" filterUnits="objectBoundingBox"><feOffset dx="1" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="2"/><feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.36 0"/></filter><path id="logosZod1" fill="#000" d="M200.42 0H53.63L0 53.355l121.76 146.624l9.714-10.9L252 53.857zm-5.362 12.562l39.84 41.6l-112.8 126.558L17 54.162l41.815-41.6z"/></defs><g transform="translate(2 1.51)"><path fill="#18253f" d="M58.816 12.522h136.278l39.933 41.691l-112.989 126.553L16.957 54.213z"/><path fill="#274d82" d="M149.427 150.875H96.013l-24.124-29.534l68.364-.002l.002-4.19h39.078z"/><path fill="#274d82" d="M223.56 42.323L76.178 127.414l-19.226-24.052l114.099-65.877l-2.096-3.631l30.391-17.546zm-78.964-29.759L33.93 76.457L16.719 54.972l74.095-42.779z"/><use filter="url(#logosZod0)" href="#logosZod1"/><use fill="#3068b7" href="#logosZod1"/></g></svg>`,
},
}),
}) as any,
],
},
srcDir: './src',
Expand Down
83 changes: 51 additions & 32 deletions docs/src/core-concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,25 @@ title: useRegle

# `useRegle`

`useRegle` is the core of all your forms. This composable takes a state and a set of rules as inputs and provides everything you need as output.
`useRegle` is the core of the validation logic.

Let’s look at a simple example to understand how it works:
It takes as input:

``` ts twoslash [useRegle.ts]
import { useRegle } from '@regle/core';

const { r$ } = useRegle({ name: '' }, {
name: {
// rules
}
})
```
- Your **state** (either plain object, `ref`, `reactive`, or nested `refs`)
- Your **rules** that will match de structure of your state
- (Optional) Your modifiers

## `r$`

If you’ve used Vuelidate before, useRegle functions similarly to `v$`.

Regle is a reactive object containing various computed properties and methods that you can freely use based on your requirements.

You can find all the [available properties here](/core-concepts/validation-properties)
<br/>

``` ts twoslash
// @noErrors
```vue [App.vue]
<script setup lang='ts'>
import { useRegle } from '@regle/core';
import { required } from '@regle/rules';
const { r$ } = useRegle({ name: '' }, {
name: { required }
})

r$.$fields.
// ^|
const { r$ } = useRegle(/* state */, /* rules */, /* modifiers */);
</script>
```


## State

The first parameter of `useRegle` is the state. It will be targeted by your rules.
Expand All @@ -54,24 +38,59 @@ If you pass a reactive state, you have the flexibility to bind your model either

## Rules

The second parameter of `useRegle` is the rules declaration, you can declare a tree matching your input state. Each property can then declare a record of validation rules to define its `$invalid` state.
The second parameter of `useRegle` is the rules declaration, you can declare a tree matching your input state.

Each property can then declare a record of validation rules to define its `$invalid` state.

Regle provide a list of default rules that you can use from `@regle/rules`.

You can find the [list of built-in rules here](/core-concepts/rules/built-in-rules)

<br/>

``` ts twoslash
import {ref} from 'vue';
// @noErrors
// ---cut---
import { useRegle } from '@regle/core';
import { required, num } from '@regle/rules';
// ^|
import { required } from '@regle/rules';

const state = ref({
user: {
firstName: '',
lastName: ''
}
title: '',
})

const { r$ } = useRegle({ title: '', user: { firstName: '', lastName: '' }}, {
const { r$ } = useRegle(state, {
user: {
firstName: { required },
lastName: { required },
},
title: { "" }
title: { m }
// ^|
})
```

## `r$`

If you’ve used Vuelidate before, useRegle behave similarly to `v$`.

Regle is a reactive object containing various computed properties and methods that you can freely use based on your requirements.

You can find all the [available properties here](/core-concepts/validation-properties)

``` ts twoslash
// @noErrors
import { useRegle } from '@regle/core';
import { required } from '@regle/rules';

const { r$ } = useRegle({ email: '' }, {
email: { required }
})

r$.$fields.
// ^|
```

145 changes: 15 additions & 130 deletions docs/src/core-concepts/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ title: Rules

Rules are the core concept of Regle (and also it's name 🙄).

A rule is a function (inline or created with the `createRule` helper) that receive the matching value and return either a boolean or an object with a $valid property. If the validation passes, return true or an object containing at least `{ $valid: boolean }`, false otherwise.
A rule is created using either:

- An inline function
- `createRule`

The rule take the value (and parameters if there is) as input, and will return the result as output.

The **result** can either be:

- `boolean`
- An object containing at least the `{$valid: boolean}` property.

The boolean represent the result of the validation.

:::tip
You can jump directly into the [createRule section](/core-concepts/rules/advanced-rules) to see more advanced features
You can jump directly into the [createRule section](/core-concepts/rules/reusable-rules) to see more advanced features
:::

## Inline rules
Expand All @@ -22,7 +34,7 @@ const someAsyncCall = async () => await Promise.resolve(true);
// ---cut---
import type { Maybe, InlineRuleDeclaration } from '@regle/core';

const customRuleInline = (value: Maybe<string>) => value === 'regle'
const customRuleInline = (value: Maybe<string>) => value === 'regle';

/** Async rule that will activate the $pending state of your field */
const customRuleInlineAsync = async (value: Maybe<string>) => {
Expand All @@ -36,130 +48,3 @@ const customRuleInlineWithMetaData = ((value: Maybe<string>) => ({
})) satisfies InlineRuleDeclaration;
```

Inline rules are then usable with a set of tools from `@regle/rules`

### `withMessage`

This tool take your rule as a first argument and your error message as a second. It will define what error to set

``` ts twoslash {3-11}
// @noErrors
import {useRegle, type InlineRuleDeclaration, type Maybe} from '@regle/core';
import {withMessage} from '@regle/rules';

const customRuleInlineWithMetaData = ((value: Maybe<string>) => ({
$valid: value === 'regle',
foo: 'bar' as const
})) satisfies InlineRuleDeclaration;


// ---cut---
const {r$} = useRegle({name: ''}, {
name: {
// Inline functions can be also written... inline
customRule1: withMessage((value) => !!value, "Custom Error"),
customRule2: withMessage(customRuleInlineWithMetaData, "Custom Error"),

// You can also access current value and metadata with a getter function
customRule3: withMessage(
customRuleInlineWithMetaData,
({ $value, foo }) => `Custom Error: ${$value} ${foo}`
// ^?
),
}
})
```

### `withParams`

You rule result can sometimes depends on an other part of your component or store.
For this, `useRegle` can already observe the changes by changing the rule object to a getter function or a computed.


```ts
useRegle({}, { /* rules */})
```

⬇️

```ts
useRegle({}, () => ({ /* rules */ }))
// or
const rules = computed(() => ({/* rules */ }))
useRegle({}, rules)
```

But sometimes, values cannot be tracked properly, so you can use this tool to force dependencies on a rule

``` ts twoslash {7-9}
// @noErrors
import {useRegle} from '@regle/core';
import {ref} from 'vue';
// ---cut---
import {withParams} from '@regle/rules';

const base = ref('foo');

const {r$} = useRegle({name: ''}, {
name: {
customRule: withParams((value, param) => value === param, [base]),
// or
customRule: withParams((value, param) => value === param, [() => base.value]),
}
})
```


### `withAsync`

withAsync works the same as `withParams`, but for async rules depending on external values

``` ts twoslash {7}
// @noErrors
import {useRegle} from '@regle/core';
import {ref} from 'vue';
const someAsyncCall = async (param: string) => await Promise.resolve(true);

// ---cut---
import {withAsync} from '@regle/rules';

const base = ref('foo');

const {r$} = useRegle({name: ''}, {
name: {
customRule: withAsync(async (value, param) => await someAsyncCall(param), [base]),
}
})
```


### Chaining helpers

Rule tools can work with each other and still keeps thing typed

``` ts twoslash {9-14}
// @noErrors
import {useRegle} from '@regle/core';
import {ref} from 'vue';
const someAsyncCall = async (param: string) => await Promise.resolve(true);
// ---cut---
import {withAsync, withMessage} from '@regle/rules';

const base = ref(1);

const { r$ } = useRegle(
{ name: 0 },
{
name: {
customRule: withMessage(
withAsync(
async (value, param) => await someAsyncCall(param),
[base]
),
({ $params: [param] }) => `Custom error: ${value} != ${param}`
// ^?
),
},
}
);
```
Loading

0 comments on commit 0e775fe

Please sign in to comment.