mirror of
https://github.com/cupcakearmy/formhero.git
synced 2024-12-22 16:16:24 +00:00
initial commit
This commit is contained in:
parent
00c6efd73a
commit
0c19233b4d
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Node
|
||||||
|
node_modules
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Parcel
|
||||||
|
.cache
|
||||||
|
public
|
||||||
|
|
||||||
|
# Generated
|
||||||
|
dist
|
2
.npmignore
Normal file
2
.npmignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!lib/
|
84
lib/index.ts
Normal file
84
lib/index.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export type useFormExtractor = (from: any) => any
|
||||||
|
|
||||||
|
export type useFormAutoOptions = {
|
||||||
|
getter?: string,
|
||||||
|
setter?: string,
|
||||||
|
extractor?: useFormExtractor
|
||||||
|
}
|
||||||
|
|
||||||
|
export type useFormOptions = {
|
||||||
|
extractor?: useFormExtractor,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type useFormValidatorFunction = ((s: any) => boolean | Promise<boolean>)
|
||||||
|
export type useFormValidatorMethod = useFormValidatorFunction | RegExp
|
||||||
|
|
||||||
|
export type useFormValidatorObject = {
|
||||||
|
validator: useFormValidatorMethod,
|
||||||
|
message?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type useFormValidator = useFormValidatorMethod | useFormValidatorObject
|
||||||
|
|
||||||
|
export const HTMLInputExtractor: useFormExtractor = (e: React.FormEvent<HTMLInputElement>) => e.currentTarget.value
|
||||||
|
export const HTMLCheckboxExtractor: useFormExtractor = (e: React.FormEvent<HTMLInputElement>) => e.currentTarget.checked
|
||||||
|
|
||||||
|
export const useForm = <T, U extends { [key in keyof T]: useFormValidator }, E extends { [key in keyof U]?: string }>(init: T, validators: Partial<U> = {}, options: useFormOptions = {}) => {
|
||||||
|
const [form, setForm] = useState<T>(init)
|
||||||
|
|
||||||
|
const [errors, setErrors] = useState<Partial<E>>({})
|
||||||
|
|
||||||
|
const _set = (key: keyof T, value: any) => {
|
||||||
|
setForm({
|
||||||
|
...form,
|
||||||
|
[key]: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const _validateAll = async (value: any, validator: useFormValidatorMethod): Promise<boolean> => {
|
||||||
|
if (validator.constructor.name === 'Function' || validator.constructor.name === 'AsyncFunction')
|
||||||
|
return (validator as useFormValidatorFunction)(value)
|
||||||
|
else if (validator.constructor.name === 'RegExp')
|
||||||
|
return (validator as RegExp).test(value)
|
||||||
|
else return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const _getValidatorMessage = (key: keyof T): string => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (validators[key] && validators[key].message) return validators[key].message
|
||||||
|
else return `Error in: ${key}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const _validate = (key: keyof T, value: any) => {
|
||||||
|
const validator: useFormValidator | undefined = validators[key]
|
||||||
|
if (!validator) return
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
_validateAll(value, validator.constructor.name === 'Object' ? (validator as useFormValidatorObject).validator : validator)
|
||||||
|
.then((valid: boolean) => {
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
[key]: valid
|
||||||
|
? undefined
|
||||||
|
: _getValidatorMessage(key),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = (key: keyof T, extractor = options.extractor) => (value: any) => {
|
||||||
|
const extracted = extractor ? extractor(value) : HTMLInputExtractor(value)
|
||||||
|
_set(key, extracted)
|
||||||
|
_validate(key, extracted)
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto = (key: keyof T, opts: useFormAutoOptions = {}) => ({
|
||||||
|
[opts.getter || 'onChange']: update(key, opts.extractor),
|
||||||
|
[opts.setter || 'value']: form[key] as any,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { form, update, auto, errors }
|
||||||
|
}
|
22
package.json
Normal file
22
package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "formhero",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"typings": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "parcel -d public ./test/index.html",
|
||||||
|
"build": "tsc -w",
|
||||||
|
"dev": "pnpm run build & pnpm run test"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"last 1 year"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^16.9.3",
|
||||||
|
"@types/react-dom": "^16.9.1",
|
||||||
|
"parcel-bundler": "^1.12.3",
|
||||||
|
"react": "^16.9.0",
|
||||||
|
"react-dom": "^16.9.0",
|
||||||
|
"typescript": "^3.6.2"
|
||||||
|
}
|
||||||
|
}
|
27
test/index.html
Normal file
27
test/index.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Form Hero</title>
|
||||||
|
<link rel="stylesheet" type="text/css"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 1em
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="./test.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
68
test/test.tsx
Normal file
68
test/test.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
|
||||||
|
import { useForm, HTMLInputExtractor } from '../'
|
||||||
|
|
||||||
|
|
||||||
|
const TextError: React.FC<{ error?: string }> = ({ error }) => !error
|
||||||
|
? null
|
||||||
|
: <div className="has-text-danger">{error}</div>
|
||||||
|
|
||||||
|
const Index: React.FC = () => {
|
||||||
|
|
||||||
|
const { auto, form, update, errors } = useForm({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
type: 'formhero',
|
||||||
|
awesome: true,
|
||||||
|
}, {
|
||||||
|
username: /^test/,
|
||||||
|
password: {
|
||||||
|
validator: /^.{3,}$/,
|
||||||
|
message: 'To short',
|
||||||
|
},
|
||||||
|
awesome: (value) => !!value
|
||||||
|
}, { extractor: HTMLInputExtractor })
|
||||||
|
|
||||||
|
const _submit = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
console.log(form, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form>
|
||||||
|
<div>Username</div>
|
||||||
|
<input {...auto('username')} />
|
||||||
|
<TextError error={errors.username} />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div>Password</div>
|
||||||
|
<input {...auto('password')} />
|
||||||
|
<TextError error={errors.password} />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div>Which one to choose?</div>
|
||||||
|
<select {...auto('type')}>
|
||||||
|
<option value="redux-form">Redux-Form</option>
|
||||||
|
<option value="react-hook-forms">React-Hook-Forms</option>
|
||||||
|
<option value="formik">Formik</option>
|
||||||
|
<option value="formhero">FormHero</option>
|
||||||
|
</select>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div>Is it awesome?</div>
|
||||||
|
<input type="checkbox" name="vehicle" {...auto('awesome', { setter: 'checked', extractor: (e) => e.target.checked })} />
|
||||||
|
<TextError error={errors.awesome} />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<button onClick={_submit}>Test</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(<Index />, document.getElementById('root'))
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// if (module.hot) module.hot.accept()
|
25
tsconfig.json
Normal file
25
tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "commonjs",
|
||||||
|
"jsx": "react",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"declaration": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
|
"strictPropertyInitialization": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./lib"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user