feat: create astro reactive validator (#90)
* feat: initial validator component * chore: fix eslint for validator * chore: update package info for validator * chore: remove vscode settings for docs * chore: put docs and demo into apps * chore: move package scope @astro-reactive * test: update tests for validator * feat: validator functions, hooks * feat: validator sets haserrors attribute * feat: use data-validator attributes * feat: showValidationHints * feature: add logic for all validators * refactor: remove Validator component usage * docs(validator): initial readme * chore: comment out unsupported validator * docs(validator): update installation * chore: package docs and publish * chore: update deps * docs: update npm info on docs * docs(validator): update docs for validator * fix(form): handle undefined form * test(validator): update tests * chore: organize files; update deps * chore: fix build scripts
This commit is contained in:
parent
e5d4e90805
commit
4dc020027f
92 changed files with 1615 additions and 1090 deletions
|
@ -28,7 +28,7 @@ Currently, it is made up of [NPM workspaces](https://docs.npmjs.com/cli/v7/using
|
||||||
Packages:
|
Packages:
|
||||||
1. [demo](https://github.com/ayoayco/astro-reactive-library/tree/main/demo#readme) - found in the directory `demo`
|
1. [demo](https://github.com/ayoayco/astro-reactive-library/tree/main/demo#readme) - found in the directory `demo`
|
||||||
- the demo Astro app that we use to test and demonstrate the library features
|
- the demo Astro app that we use to test and demonstrate the library features
|
||||||
2. [astro-reactive-form](https://github.com/ayoayco/astro-reactive-library/tree/main/packages/astro-reactive-form#readme) - found in the directory `packages/astro-reactive-form`
|
2. [form](https://github.com/ayoayco/astro-reactive-library/tree/main/packages/form#readme) - found in the directory `packages/form`
|
||||||
- allows developers to programatically build a Form for Astro
|
- allows developers to programatically build a Form for Astro
|
||||||
3. [astro-reactive-validator](https://github.com/ayoayco/astro-reactive-library/tree/main/packages/astro-reactive-validator) - found in the directory `packages/astro-reactive-validator`
|
3. [astro-reactive-validator](https://github.com/ayoayco/astro-reactive-library/tree/main/packages/astro-reactive-validator) - found in the directory `packages/astro-reactive-validator`
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ npm run lint:fix
|
||||||
|
|
||||||
As mentioned above, this project involves packages that are intened to be used in Astro applications. The demo app is our way to test and play with the packages.
|
As mentioned above, this project involves packages that are intened to be used in Astro applications. The demo app is our way to test and play with the packages.
|
||||||
|
|
||||||
If you plan to add features or fix bugs that are found in the packages, such as `astro-reactive-form`, you can find the source code for this inside the `packages` directory.
|
If you plan to add features or fix bugs that are found in the packages, such as `@astro-reactive/form`, you can find the source code for this inside the `packages` directory.
|
||||||
|
|
||||||
Thank you again for your interest in contributing!
|
Thank you again for your interest in contributing!
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
| Packages | Version | Docs | Description |
|
| Packages | Version | Docs | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| [astro-reactive-form](https://github.com/ayoayco/astro-reactive-library/blob/main/packages/astro-reactive-form/README.md) | [](https://github.com/ayoayco/astro-reactive-library/blob/main/packages/astro-reactive-form/RELEASE.md) | 🛠 | generate a dynamic form which can be modified programatically |
|
| [@astro-reactive/form](https://github.com/ayoayco/astro-reactive-library/blob/main/packages/form/README.md) | [](https://www.npmjs.com/package/@astro-reactive/form) | 🛠 | generate a dynamic form which can be modified programatically |
|
||||||
| astro-reactive-validator | 🛠 | 🛠 | set of utilities for validating inputs |
|
| [@astro-reactive/validator](https://github.com/ayoayco/astro-reactive-library/blob/main/packages/validator/README.md)|  | 🛠 | set up validators for your form easily |
|
||||||
| astro-reactive-datagrid | 🛠 | 🛠 | generate a dynamic datagrid or table of values |
|
| astro-reactive-datagrid | 🛠 | 🛠 | generate a dynamic datagrid or table of values |
|
||||||
|
|
||||||
# HACKTOBERFEST 2022
|
# HACKTOBERFEST 2022
|
||||||
|
|
|
@ -3,11 +3,7 @@
|
||||||
"description": "Demo App for Astro Reactive Library",
|
"description": "Demo App for Astro Reactive Library",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"author": {
|
"author": "Ayo Ayco <ayo@ayco.io> (https://ayco.io)",
|
||||||
"name": "Ayo Ayco",
|
|
||||||
"email": "ayo@ayco.io",
|
|
||||||
"url": "https://ayco.io"
|
|
||||||
},
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
|
@ -17,8 +13,9 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.4.4",
|
"@astro-reactive/form": "file:packages/form",
|
||||||
"astro-reactive-form": "file:packages/astro-reactive-form"
|
"@astro-reactive/validator": "file:packages/validator",
|
||||||
|
"astro": "^1.5.0"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": {
|
"repository": {
|
Before Width: | Height: | Size: 873 B After Width: | Height: | Size: 873 B |
10
apps/demo/public/validator.js
Normal file
10
apps/demo/public/validator.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
|
const form = document.querySelector('form');
|
||||||
|
form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('build script');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
0
demo/src/env.d.ts → apps/demo/src/env.d.ts
vendored
0
demo/src/env.d.ts → apps/demo/src/env.d.ts
vendored
|
@ -1,21 +1,27 @@
|
||||||
---
|
---
|
||||||
import {
|
import Form, {
|
||||||
ControlConfig,
|
ControlConfig,
|
||||||
FormControl,
|
|
||||||
FormGroup,
|
FormGroup,
|
||||||
} from "astro-reactive-form/core";
|
FormControl,
|
||||||
import Form from "astro-reactive-form";
|
} from "@astro-reactive/form";
|
||||||
|
import { Validators } from "@astro-reactive/validator";
|
||||||
|
|
||||||
const form = new FormGroup([
|
const form = new FormGroup([
|
||||||
{
|
{
|
||||||
name: "username",
|
name: "username",
|
||||||
label: "Username",
|
label: "Username",
|
||||||
placeholder: "astroIscool",
|
validators: [Validators.required],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
label: "Email",
|
||||||
|
validators: [Validators.email, Validators.required],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "password",
|
name: "password",
|
||||||
label: "Password",
|
label: "Password",
|
||||||
type: "password",
|
type: "password",
|
||||||
|
validators: [Validators.required, Validators.minLength(8)],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -49,28 +55,6 @@ form.get("is-awesome")?.setValue("checked");
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Astro Reactive Form</h1>
|
<h1>Astro Reactive Form</h1>
|
||||||
<Form
|
<Form showValidationHints={true} formGroups={form} />
|
||||||
submitControl={{
|
|
||||||
type: "submit",
|
|
||||||
name: "what",
|
|
||||||
}}
|
|
||||||
formGroups={form}
|
|
||||||
/>
|
|
||||||
<script>
|
|
||||||
import { getFormGroup } from "astro-reactive-form/client";
|
|
||||||
|
|
||||||
const form = document.querySelector("form") as HTMLFormElement;
|
|
||||||
const simpleForm = getFormGroup("Simple Form");
|
|
||||||
|
|
||||||
form.addEventListener("submit", () => {
|
|
||||||
const username = simpleForm?.get("username")?.value;
|
|
||||||
const isAwesome = simpleForm?.get("is-awesome")?.value;
|
|
||||||
alert(
|
|
||||||
`Hi, My username is ${username}. This Library is ${
|
|
||||||
isAwesome === "checked" ? "awesome" : "not awesome"
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
0
docs/.gitignore → apps/docs/.gitignore
vendored
0
docs/.gitignore → apps/docs/.gitignore
vendored
|
@ -36,5 +36,6 @@
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Rishav-12/astro-reactive-library/issues"
|
"url": "https://github.com/Rishav-12/astro-reactive-library/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Rishav-12/astro-reactive-library#readme"
|
"homepage": "https://github.com/Rishav-12/astro-reactive-library#readme",
|
||||||
|
"devDependencies": {}
|
||||||
}
|
}
|
Before Width: | Height: | Size: 731 KiB After Width: | Height: | Size: 731 KiB |
Before Width: | Height: | Size: 873 B After Width: | Height: | Size: 873 B |
0
docs/src/env.d.ts → apps/docs/src/env.d.ts
vendored
0
docs/src/env.d.ts → apps/docs/src/env.d.ts
vendored
|
@ -15,8 +15,8 @@ Let your data build your UI. Blazing-fast, reactive user interfaces with native
|
||||||
|
|
||||||
| Packages | Version | Docs | Description |
|
| Packages | Version | Docs | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| [astro-reactive-form](https://github.com/ayoayco/astro-reactive-library/blob/main/packages/astro-reactive-form/README.md) | [](https://www.npmjs.com/package/astro-reactive-form) | 🛠 | generate a dynamic form which can be modified programatically |
|
| [@astro-reactive/form](https://github.com/ayoayco/astro-reactive-library/blob/main/packages/form/README.md) | [](https://www.npmjs.com/package/@astro-reactive/form) | 🛠 | generate a dynamic form which can be modified programatically |
|
||||||
| astro-reactive-validator | 🛠 | 🛠 | set of utilities for validating inputs |
|
| [@astro-reactive/validator](https://github.com/ayoayco/astro-reactive-library/blob/main/packages/validator/README.md)|  | 🛠 | set up validators for your form easily |
|
||||||
| astro-reactive-datagrid | 🛠 | 🛠 | generate a dynamic datagrid or table of values |
|
| astro-reactive-datagrid | 🛠 | 🛠 | generate a dynamic datagrid or table of values |
|
||||||
|
|
||||||
# Running locally
|
# Running locally
|
|
@ -6,10 +6,10 @@ layout: ../../layouts/MainLayout.astro
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[](https://www.npmjs.com/package/astro-reactive-form)
|
[](https://www.npmjs.com/package/@astro-reactive/form)
|
||||||
[](https://www.npmjs.com/package/astro-reactive-form)
|
[](https://www.npmjs.com/package/@astro-reactive/form)
|
||||||
[](https://www.npmjs.com/package/astro-reactive-form)
|
[](https://www.npmjs.com/package/@astro-reactive/form)
|
||||||
[](https://www.npmjs.com/package/astro-reactive-form)
|
[](https://www.npmjs.com/package/@astro-reactive/form)
|
||||||
|
|
||||||
# Astro Reactive Form 🔥
|
# Astro Reactive Form 🔥
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ _[All contributions are welcome.](https://github.com/ayoayco/astro-reactive-libr
|
||||||
In your Astro project:
|
In your Astro project:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm i astro-reactive-form
|
npm i @astro-reactive/form
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -31,8 +31,8 @@ Use in an Astro page:
|
||||||
|
|
||||||
```astro
|
```astro
|
||||||
---
|
---
|
||||||
import { FormControl, FormGroup } from "astro-reactive-form/core";
|
import { FormControl, FormGroup } from "@astro-reactive/form/core";
|
||||||
import Form from "astro-reactive-form";
|
import Form from "@astro-reactive/form";
|
||||||
|
|
||||||
// create a form group
|
// create a form group
|
||||||
const form = new FormGroup([
|
const form = new FormGroup([
|
4
demo/.vscode/extensions.json
vendored
4
demo/.vscode/extensions.json
vendored
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": ["astro-build.astro-vscode"],
|
|
||||||
"unwantedRecommendations": []
|
|
||||||
}
|
|
11
demo/.vscode/launch.json
vendored
11
demo/.vscode/launch.json
vendored
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"command": "./node_modules/.bin/astro dev",
|
|
||||||
"name": "Development server",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
4
docs/.vscode/extensions.json
vendored
4
docs/.vscode/extensions.json
vendored
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": ["astro-build.astro-vscode"],
|
|
||||||
"unwantedRecommendations": []
|
|
||||||
}
|
|
11
docs/.vscode/launch.json
vendored
11
docs/.vscode/launch.json
vendored
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"command": "./node_modules/.bin/astro dev",
|
|
||||||
"name": "Development server",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
1795
package-lock.json
generated
1795
package-lock.json
generated
File diff suppressed because it is too large
Load diff
16
package.json
16
package.json
|
@ -14,24 +14,24 @@
|
||||||
".": "./index.ts"
|
".": "./index.ts"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run dev -w demo",
|
"start": "npm run dev -w apps/demo",
|
||||||
|
"dev": "npm run dev -w apps/demo",
|
||||||
|
"docs": "npm run dev -w apps/docs",
|
||||||
"test": "npm run test --workspaces --if-present",
|
"test": "npm run test --workspaces --if-present",
|
||||||
"lint": "npm run lint --workspaces --if-present",
|
"lint": "npm run lint --workspaces --if-present",
|
||||||
"lint:fix": "npm run lint:fix --workspaces --if-present",
|
"lint:fix": "npm run lint:fix --workspaces --if-present",
|
||||||
"build": "npm run build --workspaces --if-present",
|
"build": "npm run build --workspaces --if-present",
|
||||||
"test:watch": "npm run test:watch --workspaces --if-present",
|
"test:watch": "npm run test:watch --workspaces --if-present",
|
||||||
"dev": "npm run dev -w demo",
|
|
||||||
"docs": "npm run dev -w docs",
|
|
||||||
"bump:patch": "npm version patch -w",
|
"bump:patch": "npm version patch -w",
|
||||||
"bump:minor": "npm version minor -w",
|
"bump:minor": "npm version minor -w",
|
||||||
"bump:major": "npm version major -w",
|
"bump:major": "npm version major -w",
|
||||||
"publish": "npm publish --public -w"
|
"publish": "npm publish --access public -w"
|
||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"demo",
|
"packages/form",
|
||||||
"packages/astro-reactive-form",
|
"packages/validator",
|
||||||
"packages/astro-reactive-validator",
|
"apps/demo",
|
||||||
"docs"
|
"apps/docs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
import { FormControl, FormGroup } from '../core';
|
|
||||||
import type { ControlType } from '../core/form-control-types';
|
|
||||||
|
|
||||||
export const getFormGroup = (formName: string) => {
|
|
||||||
const fieldSetEl = (document.getElementById(formName) as HTMLFieldSetElement) || null;
|
|
||||||
if (fieldSetEl === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formGroup = new FormGroup([], formName);
|
|
||||||
|
|
||||||
fieldSetEl.querySelectorAll('input').forEach((field) => {
|
|
||||||
const formControl = getFormControl(field.name);
|
|
||||||
if (!formControl) return;
|
|
||||||
formGroup.controls.push(formControl);
|
|
||||||
});
|
|
||||||
|
|
||||||
return formGroup;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFormControl = (name: string) => {
|
|
||||||
const inputEl = document.getElementById(name) as HTMLInputElement | null;
|
|
||||||
if (inputEl === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formControl = new FormControl({
|
|
||||||
name: inputEl.name,
|
|
||||||
value: inputEl.value,
|
|
||||||
type: inputEl.type as ControlType,
|
|
||||||
label: inputEl.dataset.label as string,
|
|
||||||
labelPosition: inputEl.dataset.labelPosition as 'right' | 'left',
|
|
||||||
});
|
|
||||||
|
|
||||||
inputEl.addEventListener('change', (e) => {
|
|
||||||
if (!(e.target instanceof HTMLInputElement)) return;
|
|
||||||
let value = e.target.value;
|
|
||||||
if (formControl.type === 'checkbox') {
|
|
||||||
value = formControl.value === 'checked' ? '' : 'checked';
|
|
||||||
}
|
|
||||||
formControl.setValue(value);
|
|
||||||
formControl.isPristine = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const controlProxy = new Proxy(formControl, {
|
|
||||||
set() {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return controlProxy;
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './controls';
|
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
import type { FormControl } from '../core/form-control';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
control: FormControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { control } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{
|
|
||||||
control.label && control.labelPosition === 'left' && (
|
|
||||||
<label for={control.name}>{control.label}</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
<input
|
|
||||||
name={control.name}
|
|
||||||
id={control.name}
|
|
||||||
type={control.type}
|
|
||||||
value={control.value}
|
|
||||||
checked={control.value === 'checked'}
|
|
||||||
placeholder={control.placeholder}
|
|
||||||
data-label={control.label}
|
|
||||||
data-label-position={control.labelPosition}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
control.label && control.labelPosition === 'right' && (
|
|
||||||
<label for={control.name}>{control.label}</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"name": "astro-reactive-validator",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "Validation Library for Astro Reactive Form 🔥",
|
|
||||||
"author": {
|
|
||||||
"name": "Ayo Ayco",
|
|
||||||
"email": "ayo@ayco.io",
|
|
||||||
"url": "https://ayco.io"
|
|
||||||
},
|
|
||||||
"main": "index.js",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/ayoayco/astro-reactive-library.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"astro-components",
|
|
||||||
"ui",
|
|
||||||
"form",
|
|
||||||
"validation"
|
|
||||||
],
|
|
||||||
"license": "ISC",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/ayoayco/astro-reactive-library/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/ayoayco/astro-reactive-library#readme",
|
|
||||||
"dependencies": {
|
|
||||||
"astro": "^1.4.4"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,20 +7,28 @@ export interface Props {
|
||||||
formGroups: FormGroup | FormGroup[];
|
formGroups: FormGroup | FormGroup[];
|
||||||
submitControl?: Submit;
|
submitControl?: Submit;
|
||||||
theme?: 'light' | 'dark';
|
theme?: 'light' | 'dark';
|
||||||
|
showValidationHints?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { submitControl, formGroups: form, theme } = Astro.props;
|
const { submitControl, formGroups: form, theme, showValidationHints = false } = Astro.props;
|
||||||
|
|
||||||
const formTheme = theme ?? 'light';
|
const formTheme = theme ?? 'light';
|
||||||
|
const formName = Array.isArray(form) ? null : form?.name || null;
|
||||||
---
|
---
|
||||||
|
|
||||||
<form class={formTheme}>
|
<form class={formTheme} name={formName} id={formName}>
|
||||||
{
|
{
|
||||||
Array.isArray(form)
|
Array.isArray(form)
|
||||||
? form?.map((group) => <FieldSet group={group} />)
|
? form?.map((group) => <FieldSet showValidationHints={showValidationHints} group={group} />)
|
||||||
: form?.controls.map((control) => <Field control={control} />)
|
: form?.controls.map((control) => (
|
||||||
|
<Field showValidationHints={showValidationHints} control={control} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
submitControl && (
|
||||||
|
<Field showValidationHints={showValidationHints} control={new FormControl(submitControl)} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
{submitControl && <Field control={new FormControl(submitControl)} />}
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style>
|
<style>
|
|
@ -5,10 +5,10 @@
|
||||||
Generate a dynamic form based on your data, and modify programatically.
|
Generate a dynamic form based on your data, and modify programatically.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<img src="https://img.shields.io/npm/v/astro-reactive-form" />
|
<img src="https://img.shields.io/npm/v/@astro-reactive/form" />
|
||||||
<img src="https://img.shields.io/npm/l/astro-reactive-form" />
|
<img src="https://img.shields.io/npm/l/@astro-reactive/form" />
|
||||||
<img src="https://img.shields.io/npm/dt/astro-reactive-form" />
|
<img src="https://img.shields.io/npm/dt/@astro-reactive/form" />
|
||||||
<img src="https://img.shields.io/librariesio/release/npm/astro-reactive-form" />
|
<img src="https://img.shields.io/librariesio/release/npm/@astro-reactive/form" />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
In your [Astro](https://astro.build) project:
|
In your [Astro](https://astro.build) project:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm i astro-reactive-form
|
npm i @astro-reactive/form
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -25,8 +25,8 @@ Use in an Astro page:
|
||||||
|
|
||||||
```astro
|
```astro
|
||||||
---
|
---
|
||||||
import { FormControl, FormGroup } from "astro-reactive-form/core";
|
import { FormControl, FormGroup } from "@astro-reactive/form/core";
|
||||||
import Form from "astro-reactive-form";
|
import Form from "@astro-reactive/form";
|
||||||
|
|
||||||
// create a form group
|
// create a form group
|
||||||
const form = new FormGroup([
|
const form = new FormGroup([
|
||||||
|
@ -85,14 +85,14 @@ Example of multiple form groups:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
# Validation
|
||||||
|
|
||||||
|
See our [package for setting up validators](https://www.npmjs.com/package/@astro-reactive/validator).
|
||||||
|
|
||||||
# Future Plans
|
# Future Plans
|
||||||
|
|
||||||
Currently this only supports very basic form creation, but the goal of the project is ambitious:
|
Currently this only supports very basic form creation, but the goal of the project is ambitious:
|
||||||
|
|
||||||
1. Validator library for common validation scenarios
|
|
||||||
1. Client-side validation
|
|
||||||
1. Server-side validation
|
|
||||||
1. validation hooks - onFocus, onBlur, onSubmit
|
|
||||||
1. Themes - unstyled, light mode, dark mode, compact, large
|
1. Themes - unstyled, light mode, dark mode, compact, large
|
||||||
1. FormGroup class
|
1. FormGroup class
|
||||||
1. `statusChanges` - observable that emits the form status when it changes
|
1. `statusChanges` - observable that emits the form status when it changes
|
|
@ -1,3 +1,6 @@
|
||||||
|
# v0.4.1
|
||||||
|
- set `showValidationHints` to true to show validation hints
|
||||||
|
|
||||||
# v0.3.0
|
# v0.3.0
|
||||||
- new control configuration type `ControlConfig`
|
- new control configuration type `ControlConfig`
|
||||||
|
|
69
packages/form/components/Field.astro
Normal file
69
packages/form/components/Field.astro
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
---
|
||||||
|
import type { FormControl } from '../core/form-control';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
control: FormControl;
|
||||||
|
showValidationHints: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { control, showValidationHints } = Astro.props;
|
||||||
|
|
||||||
|
const { validators = [] } = control;
|
||||||
|
|
||||||
|
const isRequired = showValidationHints && validators.includes('validator-required');
|
||||||
|
|
||||||
|
const validatorAttirbutes: Record<string, string> = validators?.reduce(
|
||||||
|
(prev, validator) => {
|
||||||
|
const split: string[] = validator.split(':');
|
||||||
|
const label: string = `data-${split[0]}` || 'invalid';
|
||||||
|
const value: string | null = split.length > 1 ? split[1] ?? null : 'true';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[label]: value,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="field" data-validator-hints={showValidationHints.toString()}>
|
||||||
|
{
|
||||||
|
control.label && control.labelPosition === 'left' && (
|
||||||
|
<label for={control.name}>
|
||||||
|
{isRequired && <span>*</span>}
|
||||||
|
{control.label}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<input
|
||||||
|
name={control.name}
|
||||||
|
id={control.name}
|
||||||
|
type={control.type}
|
||||||
|
value={control.value}
|
||||||
|
checked={control.value === 'checked'}
|
||||||
|
placeholder={control.placeholder}
|
||||||
|
data-label={control.label}
|
||||||
|
data-label-position={control.labelPosition}
|
||||||
|
{...validatorAttirbutes}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
control.label && control.labelPosition === 'right' && (
|
||||||
|
<label for={control.name}>
|
||||||
|
{isRequired && <span>*</span>}
|
||||||
|
{control.label}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
[data-validator-hints='true'][data-validator-haserrors='true'],
|
||||||
|
[data-validator-hints='true'] [data-validator-haserrors='true'] {
|
||||||
|
color: red;
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,12 +4,17 @@ import Field from './Field.astro';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
group: FormGroup;
|
group: FormGroup;
|
||||||
|
showValidationHints: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { group } = Astro.props;
|
const { group, showValidationHints } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<fieldset id={group.name} name={group.name}>
|
<fieldset id={group.name} name={group.name}>
|
||||||
{group.name && <legend>{group.name}</legend>}
|
{group.name && <legend>{group.name}</legend>}
|
||||||
{group?.controls?.map((control) => <Field control={control} />)}
|
{
|
||||||
|
group?.controls?.map((control) => (
|
||||||
|
<Field showValidationHints={showValidationHints} control={control} />
|
||||||
|
))
|
||||||
|
}
|
||||||
</fieldset>
|
</fieldset>
|
|
@ -34,6 +34,7 @@ export interface ControlBase {
|
||||||
label?: string;
|
label?: string;
|
||||||
labelPosition?: 'right' | 'left';
|
labelPosition?: 'right' | 'left';
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
validators?: string[]; // TODO: implement validator type
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Checkbox extends ControlBase {
|
export interface Checkbox extends ControlBase {
|
|
@ -8,16 +8,18 @@ export class FormControl {
|
||||||
private _labelPosition?: 'right' | 'left' = 'left';
|
private _labelPosition?: 'right' | 'left' = 'left';
|
||||||
private _isValid = true;
|
private _isValid = true;
|
||||||
private _isPristine = true;
|
private _isPristine = true;
|
||||||
private _placeholder?;
|
private _placeholder?: string;
|
||||||
|
private _validators?: string[];
|
||||||
|
|
||||||
constructor(config: ControlConfig) {
|
constructor(config: ControlConfig) {
|
||||||
const { name, type, value, label, labelPosition, placeholder } = config;
|
const { name, type, value, label, labelPosition, placeholder, validators } = config;
|
||||||
this._name = name;
|
this._name = name;
|
||||||
this._type = type || 'text';
|
this._type = type ?? 'text';
|
||||||
this._value = value || null;
|
this._value = value ?? null;
|
||||||
this._label = label || '';
|
this._label = label ?? '';
|
||||||
this._labelPosition = labelPosition || 'left';
|
this._labelPosition = labelPosition ?? 'left';
|
||||||
this._placeholder = placeholder || '';
|
this._placeholder = placeholder ?? '';
|
||||||
|
this._validators = validators ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
|
@ -56,8 +58,13 @@ export class FormControl {
|
||||||
return this._isValid;
|
return this._isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get validators() {
|
||||||
|
return this._validators;
|
||||||
|
}
|
||||||
|
|
||||||
setValue(value: string) {
|
setValue(value: string) {
|
||||||
this._value = value;
|
this._value = value;
|
||||||
|
this._isPristine = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsPristine(value: boolean) {
|
setIsPristine(value: boolean) {
|
|
@ -1,11 +1,11 @@
|
||||||
import type { ControlBase } from './form-control-types';
|
import type { ControlConfig } from './form-control-types';
|
||||||
import { FormControl } from './form-control';
|
import { FormControl } from './form-control';
|
||||||
|
|
||||||
export class FormGroup {
|
export class FormGroup {
|
||||||
controls: FormControl[];
|
controls: FormControl[];
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
constructor(controls: ControlBase[], name = '') {
|
constructor(controls: ControlConfig[], name = '') {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.controls = controls
|
this.controls = controls
|
||||||
.filter((control) => control.type !== 'submit')
|
.filter((control) => control.type !== 'submit')
|
|
@ -1,3 +1,3 @@
|
||||||
import Form from './Form.astro';
|
import Form from './Form.astro';
|
||||||
export default Form;
|
export default Form;
|
||||||
export * from './Form.astro';
|
export * from './core';
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "astro-reactive-form",
|
"name": "@astro-reactive/form",
|
||||||
"description": "The Reactive Form component for Astro 🔥",
|
"description": "The Reactive Form component for Astro 🔥",
|
||||||
"version": "0.3.0",
|
"version": "0.4.1",
|
||||||
"repository": "https://github.com/ayoayco/astro-reactive-library",
|
"repository": "https://github.com/ayoayco/astro-reactive-library",
|
||||||
"homepage": "https://github.com/ayoayco/astro-reactive-library#readme",
|
"homepage": "https://github.com/ayoayco/astro-reactive-library#readme",
|
||||||
"author": {
|
"author": {
|
||||||
|
@ -11,14 +11,11 @@
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts"
|
||||||
"./core": "./core/index.ts",
|
|
||||||
"./client": "./client/index.ts"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"core/",
|
"core/",
|
||||||
"components/",
|
"components/",
|
||||||
"client/",
|
|
||||||
"Form.astro",
|
"Form.astro",
|
||||||
"index.ts"
|
"index.ts"
|
||||||
],
|
],
|
||||||
|
@ -43,7 +40,7 @@
|
||||||
"@types/prettier": "^2.7.0",
|
"@types/prettier": "^2.7.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||||
"@typescript-eslint/parser": "^5.37.0",
|
"@typescript-eslint/parser": "^5.37.0",
|
||||||
"astro": "^1.4.4",
|
"astro": "^1.5.0",
|
||||||
"astro-component-tester": "^0.6.0",
|
"astro-component-tester": "^0.6.0",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
"eslint": "^8.23.1",
|
"eslint": "^8.23.1",
|
||||||
|
@ -55,7 +52,7 @@
|
||||||
"typescript": "^4.8.3"
|
"typescript": "^4.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"astro": "^1.0.0"
|
"astro": "^1.5.0"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strictest",
|
"extends": "astro/tsconfigs/strictest",
|
||||||
"exclude": ["./index.ts"]
|
"exclude": ["index.ts"]
|
||||||
}
|
}
|
12
packages/validator/.editorconfig
Normal file
12
packages/validator/.editorconfig
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = tab
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[{.*,*.md,*.json,*.toml,*.yml,}]
|
||||||
|
indent_style = space
|
1
packages/validator/.eslintignore
Normal file
1
packages/validator/.eslintignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
test/**/*.js
|
17
packages/validator/.eslintrc.cjs
Normal file
17
packages/validator/.eslintrc.cjs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/** @type {import("@types/eslint").Linter.Config} */
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint', 'prettier'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
// We don't want to leak logging into our user's console unless it's an error
|
||||||
|
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||||
|
},
|
||||||
|
};
|
8
packages/validator/.gitignore
vendored
Normal file
8
packages/validator/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
.DS_Store
|
24
packages/validator/.prettierrc.cjs
Normal file
24
packages/validator/.prettierrc.cjs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/** @type {import("@types/prettier").Options} */
|
||||||
|
module.exports = {
|
||||||
|
printWidth: 100,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
tabWidth: 2,
|
||||||
|
trailingComma: 'es5',
|
||||||
|
useTabs: true,
|
||||||
|
plugins: ['../../node_modules/prettier-plugin-astro'],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: '*.astro',
|
||||||
|
options: {
|
||||||
|
parser: 'astro',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['.*', '*.json', '*.md', '*.toml', '*.yml'],
|
||||||
|
options: {
|
||||||
|
useTabs: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
92
packages/validator/README.md
Normal file
92
packages/validator/README.md
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/ayoayco/astro-reactive-library/main/.github/assets/logo/min-banner.png" alt="Astro Reactive Library Logo">
|
||||||
|
<strong>Astro Reactive Validator</strong>
|
||||||
|
<br />
|
||||||
|
Set up validators for your forms easily.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<img src="https://img.shields.io/npm/v/@astro-reactive/validator" />
|
||||||
|
<img src="https://img.shields.io/npm/l/@astro-reactive/validator" />
|
||||||
|
<img src="https://img.shields.io/npm/dt/@astro-reactive/validator" />
|
||||||
|
<img src="https://img.shields.io/librariesio/release/npm/@astro-reactive/validator" />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
In your [Astro](https://astro.build) project:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i @astro-reactive/validator @astro-reactive/form
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Use in an Astro page:
|
||||||
|
|
||||||
|
```astro
|
||||||
|
---
|
||||||
|
import { FormControl, FormGroup } from "@astro-reactive/form/core";
|
||||||
|
import Form from "@astro-reactive/form";
|
||||||
|
import { Validators } from "@astro-reactive/validator";
|
||||||
|
|
||||||
|
const form = new FormGroup([
|
||||||
|
{
|
||||||
|
name: "username",
|
||||||
|
label: "Username",
|
||||||
|
validators: [Validators.required],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
label: "Email",
|
||||||
|
validators: [Validators.email, Validators.required],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password",
|
||||||
|
label: "Password",
|
||||||
|
type: "password",
|
||||||
|
validators: [Validators.required, Validators.minLength(8)],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// set the name optionally
|
||||||
|
form.name = "Simple Form";
|
||||||
|
|
||||||
|
// you can insert a control at any point
|
||||||
|
form.controls.push(
|
||||||
|
new FormControl({
|
||||||
|
type: "checkbox",
|
||||||
|
name: "is-awesome",
|
||||||
|
label: "is Awesome?",
|
||||||
|
labelPosition: "right",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// you can get a FormControl object
|
||||||
|
const userNameControl = form.get("username");
|
||||||
|
|
||||||
|
// you can set values dynamically
|
||||||
|
userNameControl?.setValue("RAMOOOON");
|
||||||
|
form.get('is-awesome')?.setValue("checked");
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
the `formGroups` attribute can take a single FormGroup
|
||||||
|
or an array of FormGroup for multiple fieldsets;
|
||||||
|
we do a single for now in this example
|
||||||
|
-->
|
||||||
|
<Form showValidationHints={true} formGroups={form} />
|
||||||
|
```
|
||||||
|
|
||||||
|
# Screenshots
|
||||||
|
Result of example above:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Validators available
|
||||||
|
1. `Validators.min(limit)` - checks if number value is greater than or equal to limit
|
||||||
|
1. `Validators.max(limit)` - checks if number value is less than or equal to limit
|
||||||
|
1. `Validators.required` - checks if value is empty
|
||||||
|
1. `Validators.requiredChecked` - checks if value is "checked"
|
||||||
|
1. `Validators.email` - checks if value is a valid email
|
||||||
|
1. `Validators.minLength(limit)` - checks if value length is greater than or equal to limit
|
||||||
|
1. `Validators.maxLength(limit)` - checks if value length is less than or equal to limit
|
3
packages/validator/RELEASE.md
Normal file
3
packages/validator/RELEASE.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 0.0.1
|
||||||
|
- Validators
|
||||||
|
- validator functions
|
35
packages/validator/Validator.astro
Normal file
35
packages/validator/Validator.astro
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
import type { HookType } from './core';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
hook?: HookType;
|
||||||
|
displayErrorMessages?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hook = 'all', displayErrorMessages = false } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<input hidden name="hook" id="hook" value={hook} />
|
||||||
|
<input
|
||||||
|
hidden
|
||||||
|
name="displayErrorMessages"
|
||||||
|
id="displayErrorMessages"
|
||||||
|
value={displayErrorMessages.toString()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// TODO: selectors should by unique IDs generated by our library
|
||||||
|
// TODO: implement type guards pls 😱
|
||||||
|
// TODO: create deserializer util
|
||||||
|
// TODO: handle hooks / when to attach event listeners
|
||||||
|
// const form = document.querySelector('form');
|
||||||
|
|
||||||
|
import { clearErrors, validate } from './core';
|
||||||
|
|
||||||
|
// const hook: HookType = (document.getElementById('hook') as HTMLInputElement).value as HookType;
|
||||||
|
const inputs = [...document.querySelectorAll('form input')] as HTMLInputElement[];
|
||||||
|
inputs?.forEach((input) => {
|
||||||
|
input.addEventListener('blur', validate);
|
||||||
|
input.addEventListener('input', clearErrors);
|
||||||
|
});
|
||||||
|
</script>
|
3
packages/validator/core/index.ts
Normal file
3
packages/validator/core/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './validator.functions';
|
||||||
|
export * from './validator.types';
|
||||||
|
export * from './validators';
|
152
packages/validator/core/validator.functions.ts
Normal file
152
packages/validator/core/validator.functions.ts
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import type { ValidationResult } from './validator.types';
|
||||||
|
import { Validators } from './validators';
|
||||||
|
|
||||||
|
export function validate(event: FocusEvent) {
|
||||||
|
// NOTE: event target attribute names are converted to lowercase
|
||||||
|
const element = event.target as HTMLInputElement;
|
||||||
|
const attributeNames = element?.getAttributeNames() || [];
|
||||||
|
const validatorAttirbutes = attributeNames.filter((attribute) =>
|
||||||
|
attribute.includes('data-validator-')
|
||||||
|
);
|
||||||
|
const value = element.value;
|
||||||
|
|
||||||
|
const validityArray: ValidationResult[] = validatorAttirbutes
|
||||||
|
.map((validator) => validator.replace('data-', ''))
|
||||||
|
.map((validator): ValidationResult => {
|
||||||
|
// insert logic for each validator
|
||||||
|
// TODO: implement a map of functions,validator
|
||||||
|
|
||||||
|
if (validator === Validators.min()) {
|
||||||
|
const limit = parseInt(element.getAttribute('data-validator-min') || '0', 10);
|
||||||
|
return validateMin(value, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validator === Validators.max()) {
|
||||||
|
const limit = parseInt(element.getAttribute('data-validator-min') || '0', 10);
|
||||||
|
return validateMax(value, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validator === Validators.required) {
|
||||||
|
return validateRequired(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validator === Validators.requiredChecked) {
|
||||||
|
return validateRequiredChecked(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validator === Validators.email) {
|
||||||
|
return validateEmail(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validator === Validators.minLength()) {
|
||||||
|
const limit = parseInt(element.getAttribute('data-validator-minlength') || '0', 10);
|
||||||
|
return validateMinLength(value, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validator === Validators.maxLength()) {
|
||||||
|
const limit = parseInt(element.getAttribute('data-validator-maxlength') || '0', 10);
|
||||||
|
return validateMaxLength(value, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const errors = validityArray.filter((result) => result !== true);
|
||||||
|
|
||||||
|
// set element hasErrors
|
||||||
|
if (errors.length) {
|
||||||
|
element.parentElement?.setAttribute('data-validator-haserrors', 'true');
|
||||||
|
element.setAttribute('data-validator-haserrors', 'true');
|
||||||
|
// TODO: display error messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearErrors(event: Event) {
|
||||||
|
const element = event.target as HTMLInputElement;
|
||||||
|
element.parentElement?.setAttribute('data-validator-haserrors', 'false');
|
||||||
|
element.setAttribute('data-validator-haserrors', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateMin(value: string, limit: number): ValidationResult {
|
||||||
|
const isValid = parseInt(value, 10) >= limit;
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return {
|
||||||
|
error: 'min',
|
||||||
|
limit: limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateMax(value: string, limit: number): ValidationResult {
|
||||||
|
const isValid = parseInt(value, 10) <= limit;
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return {
|
||||||
|
error: 'max',
|
||||||
|
limit: limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateRequired(value: string): ValidationResult {
|
||||||
|
const isValid = !!value;
|
||||||
|
if (!isValid) {
|
||||||
|
return {
|
||||||
|
error: 'required',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateRequiredChecked(value: string): ValidationResult {
|
||||||
|
const isValid = value === 'checked';
|
||||||
|
if (!isValid) {
|
||||||
|
return {
|
||||||
|
error: 'requiredChecked',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: review regexp vulnerability
|
||||||
|
function validateEmail(value: string): ValidationResult {
|
||||||
|
const isValid = String(value)
|
||||||
|
.toLowerCase()
|
||||||
|
.match(
|
||||||
|
/^(([^<>()[\]\\.,;:\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,}))$/
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return {
|
||||||
|
error: 'email',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateMinLength(value: string, limit: number): ValidationResult {
|
||||||
|
const isValid = value.length >= limit;
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return {
|
||||||
|
error: 'minLength',
|
||||||
|
limit: limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateMaxLength(value: string, limit: number): ValidationResult {
|
||||||
|
const isValid = value.length <= limit;
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return {
|
||||||
|
error: 'minLength',
|
||||||
|
limit: limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
8
packages/validator/core/validator.types.ts
Normal file
8
packages/validator/core/validator.types.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export type HookType = 'onSubmit' | 'onControlBlur' | 'all';
|
||||||
|
|
||||||
|
export type ValidationResult = true | ValidationError;
|
||||||
|
|
||||||
|
export type ValidationError = {
|
||||||
|
error: string;
|
||||||
|
limit?: number;
|
||||||
|
};
|
50
packages/validator/core/validators.ts
Normal file
50
packages/validator/core/validators.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// improvement: implement min&max inclusive/exclusive
|
||||||
|
export class Validators {
|
||||||
|
static min(min?: number): string {
|
||||||
|
const label = 'validator-min';
|
||||||
|
if (min !== undefined) {
|
||||||
|
return `${label}:${min}`;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
static max(max?: number): string {
|
||||||
|
const label = 'validator-max';
|
||||||
|
if (max !== undefined) {
|
||||||
|
return `${label}:${max}`;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get required(): string {
|
||||||
|
return `validator-required`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get requiredChecked(): string {
|
||||||
|
return `validator-required:checked`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get email(): string {
|
||||||
|
return `validator-email`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static minLength(minLength?: number): string {
|
||||||
|
const label = 'validator-minlength';
|
||||||
|
if (minLength !== undefined) {
|
||||||
|
return `${label}:${minLength}`;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
static maxLength(maxLength?: number): string {
|
||||||
|
const label = 'validator-maxlength';
|
||||||
|
if (maxLength !== undefined) {
|
||||||
|
return `${label}:${maxLength}`;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static pattern(pattern: string | RegExp): string {
|
||||||
|
// return `validator-pattern:${pattern}`;
|
||||||
|
// }
|
||||||
|
}
|
3
packages/validator/index.ts
Normal file
3
packages/validator/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import Validator from './Validator.astro';
|
||||||
|
export default Validator;
|
||||||
|
export * from './core';
|
62
packages/validator/package.json
Normal file
62
packages/validator/package.json
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"name": "@astro-reactive/validator",
|
||||||
|
"description": "Form validation library for Astro 🔥",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"repository": "https://github.com/ayoayco/astro-reactive-library",
|
||||||
|
"homepage": "https://github.com/ayoayco/astro-reactive-library#readme",
|
||||||
|
"author": {
|
||||||
|
"name": "Ayo Ayco",
|
||||||
|
"email": "ayo@ayco.io",
|
||||||
|
"url": "https://ayco.io"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./index.ts"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"core/",
|
||||||
|
"Validator.astro",
|
||||||
|
"index.ts"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"astro-component"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha --parallel --timeout 15000",
|
||||||
|
"test:watch": "mocha --watch --parallel --timeout 15000",
|
||||||
|
"format": "prettier -w .",
|
||||||
|
"lint": "eslint . --ext .ts,.js",
|
||||||
|
"lint:fix": "eslint --fix . --ext .ts,.js",
|
||||||
|
"build": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/chai": "^4.3.3",
|
||||||
|
"@types/eslint": "^8.4.6",
|
||||||
|
"@types/mocha": "^10.0.0",
|
||||||
|
"@types/node": "^18.7.18",
|
||||||
|
"@types/prettier": "^2.7.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||||
|
"@typescript-eslint/parser": "^5.37.0",
|
||||||
|
"astro": "^1.5.0",
|
||||||
|
"astro-component-tester": "^0.6.0",
|
||||||
|
"chai": "^4.3.6",
|
||||||
|
"eslint": "^8.23.1",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"mocha": "^10.0.0",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"prettier-plugin-astro": "^0.5.4",
|
||||||
|
"typescript": "^4.8.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"astro": "^1.5.0"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ayoayco/astro-reactive-library/issues"
|
||||||
|
}
|
||||||
|
}
|
18
packages/validator/test/Validator.astro.test.js
Normal file
18
packages/validator/test/Validator.astro.test.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { describe, beforeEach, it } from 'mocha';
|
||||||
|
import { getComponentOutput } from 'astro-component-tester';
|
||||||
|
|
||||||
|
describe('Example Tests', () => {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Component test', async () => {
|
||||||
|
it('example component should not be empty', async () => {
|
||||||
|
component = await getComponentOutput('./Validator.astro');
|
||||||
|
expect(component.html).not.to.equal('\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
4
packages/validator/tsconfig.json
Normal file
4
packages/validator/tsconfig.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strictest",
|
||||||
|
"exclude": ["index.ts"]
|
||||||
|
}
|
Loading…
Reference in a new issue