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:
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
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!
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
| 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-validator | 🛠 | 🛠 | set of utilities for validating inputs |
|
||||
| [@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](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 |
|
||||
|
||||
# HACKTOBERFEST 2022
|
||||
|
|
|
@ -3,11 +3,7 @@
|
|||
"description": "Demo App for Astro Reactive Library",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"author": {
|
||||
"name": "Ayo Ayco",
|
||||
"email": "ayo@ayco.io",
|
||||
"url": "https://ayco.io"
|
||||
},
|
||||
"author": "Ayo Ayco <ayo@ayco.io> (https://ayco.io)",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
|
@ -17,8 +13,9 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.4.4",
|
||||
"astro-reactive-form": "file:packages/astro-reactive-form"
|
||||
"@astro-reactive/form": "file:packages/form",
|
||||
"@astro-reactive/validator": "file:packages/validator",
|
||||
"astro": "^1.5.0"
|
||||
},
|
||||
"main": "index.js",
|
||||
"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,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
} from "astro-reactive-form/core";
|
||||
import Form from "astro-reactive-form";
|
||||
FormControl,
|
||||
} from "@astro-reactive/form";
|
||||
import { Validators } from "@astro-reactive/validator";
|
||||
|
||||
const form = new FormGroup([
|
||||
{
|
||||
name: "username",
|
||||
label: "Username",
|
||||
placeholder: "astroIscool",
|
||||
validators: [Validators.required],
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
label: "Email",
|
||||
validators: [Validators.email, Validators.required],
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "Password",
|
||||
type: "password",
|
||||
validators: [Validators.required, Validators.minLength(8)],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -49,28 +55,6 @@ form.get("is-awesome")?.setValue("checked");
|
|||
</head>
|
||||
<body>
|
||||
<h1>Astro Reactive Form</h1>
|
||||
<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>
|
||||
<Form showValidationHints={true} formGroups={form} />
|
||||
</body>
|
||||
</html>
|
0
docs/.gitignore → apps/docs/.gitignore
vendored
0
docs/.gitignore → apps/docs/.gitignore
vendored
|
@ -36,5 +36,6 @@
|
|||
"bugs": {
|
||||
"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 |
|
||||
| --- | --- | --- | --- |
|
||||
| [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-validator | 🛠 | 🛠 | set of utilities for validating inputs |
|
||||
| [@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](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 |
|
||||
|
||||
# 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 🔥
|
||||
|
||||
|
@ -23,7 +23,7 @@ _[All contributions are welcome.](https://github.com/ayoayco/astro-reactive-libr
|
|||
In your Astro project:
|
||||
|
||||
```
|
||||
npm i astro-reactive-form
|
||||
npm i @astro-reactive/form
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
@ -31,8 +31,8 @@ Use in an Astro page:
|
|||
|
||||
```astro
|
||||
---
|
||||
import { FormControl, FormGroup } from "astro-reactive-form/core";
|
||||
import Form from "astro-reactive-form";
|
||||
import { FormControl, FormGroup } from "@astro-reactive/form/core";
|
||||
import Form from "@astro-reactive/form";
|
||||
|
||||
// create a form group
|
||||
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"
|
||||
},
|
||||
"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",
|
||||
"lint": "npm run lint --workspaces --if-present",
|
||||
"lint:fix": "npm run lint:fix --workspaces --if-present",
|
||||
"build": "npm run build --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:minor": "npm version minor -w",
|
||||
"bump:major": "npm version major -w",
|
||||
"publish": "npm publish --public -w"
|
||||
"publish": "npm publish --access public -w"
|
||||
},
|
||||
"license": "ISC",
|
||||
"workspaces": [
|
||||
"demo",
|
||||
"packages/astro-reactive-form",
|
||||
"packages/astro-reactive-validator",
|
||||
"docs"
|
||||
"packages/form",
|
||||
"packages/validator",
|
||||
"apps/demo",
|
||||
"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[];
|
||||
submitControl?: Submit;
|
||||
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 formName = Array.isArray(form) ? null : form?.name || null;
|
||||
---
|
||||
|
||||
<form class={formTheme}>
|
||||
<form class={formTheme} name={formName} id={formName}>
|
||||
{
|
||||
Array.isArray(form)
|
||||
? form?.map((group) => <FieldSet group={group} />)
|
||||
: form?.controls.map((control) => <Field control={control} />)
|
||||
? form?.map((group) => <FieldSet showValidationHints={showValidationHints} group={group} />)
|
||||
: form?.controls.map((control) => (
|
||||
<Field showValidationHints={showValidationHints} control={control} />
|
||||
))
|
||||
}
|
||||
{
|
||||
submitControl && (
|
||||
<Field showValidationHints={showValidationHints} control={new FormControl(submitControl)} />
|
||||
)
|
||||
}
|
||||
{submitControl && <Field control={new FormControl(submitControl)} />}
|
||||
</form>
|
||||
|
||||
<style>
|
|
@ -5,10 +5,10 @@
|
|||
Generate a dynamic form based on your data, and modify programatically.
|
||||
<br />
|
||||
<br />
|
||||
<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/dt/astro-reactive-form" />
|
||||
<img src="https://img.shields.io/librariesio/release/npm/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/dt/@astro-reactive/form" />
|
||||
<img src="https://img.shields.io/librariesio/release/npm/@astro-reactive/form" />
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
|
@ -17,7 +17,7 @@
|
|||
In your [Astro](https://astro.build) project:
|
||||
|
||||
```
|
||||
npm i astro-reactive-form
|
||||
npm i @astro-reactive/form
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
@ -25,8 +25,8 @@ Use in an Astro page:
|
|||
|
||||
```astro
|
||||
---
|
||||
import { FormControl, FormGroup } from "astro-reactive-form/core";
|
||||
import Form from "astro-reactive-form";
|
||||
import { FormControl, FormGroup } from "@astro-reactive/form/core";
|
||||
import Form from "@astro-reactive/form";
|
||||
|
||||
// create a form group
|
||||
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
|
||||
|
||||
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. FormGroup class
|
||||
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
|
||||
- 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 {
|
||||
group: FormGroup;
|
||||
showValidationHints: boolean;
|
||||
}
|
||||
|
||||
const { group } = Astro.props;
|
||||
const { group, showValidationHints } = Astro.props;
|
||||
---
|
||||
|
||||
<fieldset id={group.name} name={group.name}>
|
||||
{group.name && <legend>{group.name}</legend>}
|
||||
{group?.controls?.map((control) => <Field control={control} />)}
|
||||
{
|
||||
group?.controls?.map((control) => (
|
||||
<Field showValidationHints={showValidationHints} control={control} />
|
||||
))
|
||||
}
|
||||
</fieldset>
|
|
@ -34,6 +34,7 @@ export interface ControlBase {
|
|||
label?: string;
|
||||
labelPosition?: 'right' | 'left';
|
||||
placeholder?: string;
|
||||
validators?: string[]; // TODO: implement validator type
|
||||
}
|
||||
|
||||
export interface Checkbox extends ControlBase {
|
|
@ -8,16 +8,18 @@ export class FormControl {
|
|||
private _labelPosition?: 'right' | 'left' = 'left';
|
||||
private _isValid = true;
|
||||
private _isPristine = true;
|
||||
private _placeholder?;
|
||||
private _placeholder?: string;
|
||||
private _validators?: string[];
|
||||
|
||||
constructor(config: ControlConfig) {
|
||||
const { name, type, value, label, labelPosition, placeholder } = config;
|
||||
const { name, type, value, label, labelPosition, placeholder, validators } = config;
|
||||
this._name = name;
|
||||
this._type = type || 'text';
|
||||
this._value = value || null;
|
||||
this._label = label || '';
|
||||
this._labelPosition = labelPosition || 'left';
|
||||
this._placeholder = placeholder || '';
|
||||
this._type = type ?? 'text';
|
||||
this._value = value ?? null;
|
||||
this._label = label ?? '';
|
||||
this._labelPosition = labelPosition ?? 'left';
|
||||
this._placeholder = placeholder ?? '';
|
||||
this._validators = validators ?? [];
|
||||
}
|
||||
|
||||
get name() {
|
||||
|
@ -56,8 +58,13 @@ export class FormControl {
|
|||
return this._isValid;
|
||||
}
|
||||
|
||||
get validators() {
|
||||
return this._validators;
|
||||
}
|
||||
|
||||
setValue(value: string) {
|
||||
this._value = value;
|
||||
this._isPristine = false;
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export class FormGroup {
|
||||
controls: FormControl[];
|
||||
name?: string;
|
||||
|
||||
constructor(controls: ControlBase[], name = '') {
|
||||
constructor(controls: ControlConfig[], name = '') {
|
||||
this.name = name;
|
||||
this.controls = controls
|
||||
.filter((control) => control.type !== 'submit')
|
|
@ -1,3 +1,3 @@
|
|||
import Form from './Form.astro';
|
||||
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 🔥",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.1",
|
||||
"repository": "https://github.com/ayoayco/astro-reactive-library",
|
||||
"homepage": "https://github.com/ayoayco/astro-reactive-library#readme",
|
||||
"author": {
|
||||
|
@ -11,14 +11,11 @@
|
|||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./core": "./core/index.ts",
|
||||
"./client": "./client/index.ts"
|
||||
".": "./index.ts"
|
||||
},
|
||||
"files": [
|
||||
"core/",
|
||||
"components/",
|
||||
"client/",
|
||||
"Form.astro",
|
||||
"index.ts"
|
||||
],
|
||||
|
@ -43,7 +40,7 @@
|
|||
"@types/prettier": "^2.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||
"@typescript-eslint/parser": "^5.37.0",
|
||||
"astro": "^1.4.4",
|
||||
"astro": "^1.5.0",
|
||||
"astro-component-tester": "^0.6.0",
|
||||
"chai": "^4.3.6",
|
||||
"eslint": "^8.23.1",
|
||||
|
@ -55,7 +52,7 @@
|
|||
"typescript": "^4.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^1.0.0"
|
||||
"astro": "^1.5.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"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