diff --git a/index.html b/index.html
new file mode 100644
index 0000000..81d5cb6
--- /dev/null
+++ b/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Vite + Vue + TS
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/constants.ts b/src/constants.ts
index df20226..6143919 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,14 +1,16 @@
import { BaseControlEmits } from "./types"
+export const DEFAULT_FIELD_VALUE = null;
+
export const BaseInputProps = {
name: { type: String, required: true },
- isDirty: {type:Boolean, default:()=>false},
- isTouched: {type:Boolean, default:()=>false},
- errors: { type: Object, default:()=> {} },
+ isDirty: { type: Boolean, default: () => false },
+ isTouched: { type: Boolean, default: () => false },
+ errors: { type: Object, default: () => { } },
onBlur: { type: Function, required: true },
- onClear: { type: Function, default:()=> null },
- modelValue: { type: [String, Object, Array, Number,Boolean, null], required:true},
+ onClear: { type: Function, default: () => null },
+ modelValue: { type: [String, Object, Array, Number, Boolean, null], required: true },
'onUpdate:modelValue': { type: Function, required: true },
}
-export const BaseInputEmits:BaseControlEmits = ['update:modelValue', 'blur', 'clear']
\ No newline at end of file
+export const BaseInputEmits: BaseControlEmits = ['update:modelValue', 'blur', 'clear']
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index cf2ebd4..cfd473d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,5 @@
-import FormHandler from './FormHandler'
-import useFormHandler from './useFormHandler'
+export { default as FormHandler } from './FormHandler'
+export { default as useFormHandler } from './useFormHandler'
export * from './constants'
-export * from './types'
-export { useFormHandler, FormHandler }
\ No newline at end of file
+export * from './types'
\ No newline at end of file
diff --git a/src/logic/getDefaultFieldValue.ts b/src/logic/getDefaultFieldValue.ts
new file mode 100644
index 0000000..e5aea9f
--- /dev/null
+++ b/src/logic/getDefaultFieldValue.ts
@@ -0,0 +1,12 @@
+import { isCheckboxInput } from "../utils"
+import { DEFAULT_FIELD_VALUE } from "../constants"
+
+export default (el: any) => {
+ if (!el) {
+ return DEFAULT_FIELD_VALUE
+ }
+ if (isCheckboxInput(el)) {
+ return false
+ }
+ return DEFAULT_FIELD_VALUE
+}
\ No newline at end of file
diff --git a/src/logic/getNativeFieldValue.ts b/src/logic/getNativeFieldValue.ts
new file mode 100644
index 0000000..c5a1ded
--- /dev/null
+++ b/src/logic/getNativeFieldValue.ts
@@ -0,0 +1,22 @@
+import { DEFAULT_FIELD_VALUE } from './../constants';
+import { isCheckboxInput, isMultipleSelect, isRadioInput } from "../utils"
+
+export default (el: any) => {
+ if (!el) {
+ return DEFAULT_FIELD_VALUE
+ }
+ if (Array.isArray(el)) {
+ if (isRadioInput(el[0])) {
+ return el.find(({ checked, disabled }) => checked && !disabled)?.value || el[0].value
+ }
+ return el[0].value
+ }
+ if (isCheckboxInput(el)) {
+ return el.checked
+ }
+ if (isMultipleSelect(el)) {
+ const values = [...el.options]?.filter(({ selected }: any) => selected).map(({ value }: any) => value)
+ return !values.length ? DEFAULT_FIELD_VALUE : values
+ }
+ return el.value || DEFAULT_FIELD_VALUE
+}
\ No newline at end of file
diff --git a/src/logic/index.ts b/src/logic/index.ts
new file mode 100644
index 0000000..d1f1a03
--- /dev/null
+++ b/src/logic/index.ts
@@ -0,0 +1,2 @@
+export { default as getNativeFieldValue } from './getNativeFieldValue'
+export { default as getDefaultFieldValue } from './getNativeFieldValue'
\ No newline at end of file
diff --git a/src/playground/App.vue b/src/playground/App.vue
new file mode 100644
index 0000000..18d2afa
--- /dev/null
+++ b/src/playground/App.vue
@@ -0,0 +1,63 @@
+
+ VSSTS
+
+
+ Female
+ Other
+
+ Male
+ Female
+ Other
+
+ Other
+
+
+
+
+
+
+ {{ values }}
+ {{ formState }}
+ {{ modifiedValues() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/playground/main.ts b/src/playground/main.ts
new file mode 100644
index 0000000..43d27f6
--- /dev/null
+++ b/src/playground/main.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+const app = createApp(App)
+app.mount('#app')
diff --git a/src/playground/vite-env.d.ts b/src/playground/vite-env.d.ts
new file mode 100644
index 0000000..c71587c
--- /dev/null
+++ b/src/playground/vite-env.d.ts
@@ -0,0 +1,7 @@
+///
+
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/src/types.ts b/src/types.ts
index be14214..677045e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,34 +1,34 @@
/** Props for a base control */
-export interface BaseControlProps {
+export interface BaseControlProps {
/** Name of the control */
name: string,
/** Current errors of the control */
- errors?:string[]
+ errors?: string[]
/** Current dirty state of the control */
- isDirty?:boolean
+ isDirty?: boolean
/** Current touched state of the control */
- isTouched?:boolean
-
+ isTouched?: boolean
+
/** Value binding for native inputs */
- value?:any,
+ ref: any,
/** Handler binding for native inputs */
- onInput?:(el:any)=>void,
+ onChange?: (el: any) => void,
- /** Value binding for custom inputs */
- modelValue?: any,
+ /** Value binding for custom inputs */
+ modelValue: any,
/** Handler binding for custom inputs */
- 'onUpdate:modelValue'?:(value:any)=>void,
+ 'onUpdate:modelValue': (value: any) => void,
/** Blur handler */
- onBlur?:()=>void,
+ onBlur?: () => void,
/** Clear handler */
- onClear?:()=>void,
+ onClear?: () => void,
}
/** Each emit the handler could be expecting */
@@ -47,34 +47,34 @@ export interface FormState {
isValid: boolean
/** Object holding the dirty fields of the form */
- dirty: Record
+ dirty: Record
/** Object holding the touched fields of the form */
- touched: Record
+ touched: Record
/** Object holding the fields with errors (one or multiple, check:validationErrors)*/
- errors: Record
+ errors: Record
}
/** Function returning true for correct validation or a string with an error if it's invalid */
-export type ValidationFn = (_:any)=>Promise|boolean|string
+export type ValidationFn = (_: any) => Promise | boolean | string
/** Validations collection as an object */
-export type Validations = Record
+export type Validations = Record
export interface RegisterOptions {
/** Indicates wether the input is native or not, set to false if the extra listeners are not desired */
native?: boolean
/** Indicates wether the input is clearable or not */
- clearable?:boolean
+ clearable?: boolean
/** Default value for the field */
defaultValue?: any
/** Required indicator for the control */
- required?:boolean
-
+ required?: boolean
+
/** Validations for the field */
validations?: Validations
@@ -83,46 +83,46 @@ export interface RegisterOptions {
}
/** Gets the initial/default/fallback value for a control */
-export type GetInitValueForControl = (name:string) => any
+export type GetInitValueForControl = (name: string) => any
/** Field initializer */
-export type InitControl = (name:string, options:RegisterOptions) => void
+export type InitControl = (name: string, options: RegisterOptions) => void
/** Function that allows you to register a control to interact with the form */
-export type Register = (name:string, options?: RegisterOptions) => BaseControlProps
+export type Register = (name: string, options?: RegisterOptions) => BaseControlProps
/** Sets dirty state of a control */
-export type SetDirty = (name:string, dirty:boolean) => void
+export type SetDirty = (name: string, dirty: boolean) => void
/** Sets touched state of a control */
-export type SetTouched = (name:string, touched:boolean) => void
+export type SetTouched = (name: string, touched: boolean) => void
/** Function to set a value programmatically */
-export type SetValue = (name:string, value:any) => Promise
+export type SetValue = (name: string, value: any) => Promise
/** Function to trigger validations programmatically */
-export type TriggerValidation = (name?:string) => Promise
+export type TriggerValidation = (name?: string) => Promise
/** Control blur handler */
-export type HandleBlur = (name:string) => void
+export type HandleBlur = (name: string) => void
/** Control change handler */
-export type HandleChange = (name:string, value?:any) => Promise
+export type HandleChange = (name: string, value?: any) => Promise
/** Function to set an error programmatically */
-export type ClearField = (name:string) => Promise
+export type ClearField = (name: string) => Promise
/** Function to reset a control programmatically*/
-export type ResetField = (name:string) => void
+export type ResetField = (name: string) => void
/** Function to reset the whole form programmatically*/
export type ResetForm = () => void
/** Function to set an error programmatically */
-export type SetError = (name:string, error:any, replace?:boolean) => void
+export type SetError = (name: string, error: any, replace?: boolean) => void
/** Function to clear an error programmatically */
-export type ClearErrors = (name?:string, errors?:string|string[]) => void
+export type ClearErrors = (name?: string, errors?: string | string[]) => void
/** Function to get the modified values of the form */
export type ModifiedValues = () => Object
@@ -131,85 +131,82 @@ export type ModifiedValues = () => Object
export type HandleSubmitSuccessFn = (values: Object) => void
/** Optional function to be called after a form failed to submit */
-export type HandleSubmitErrorFn = (errors:Object) => void
+export type HandleSubmitErrorFn = (errors: Object) => void
/** Submit handler */
-export type HandleSubmit = (successFn:HandleSubmitSuccessFn, errorFn?:HandleSubmitErrorFn)=>void
+export type HandleSubmit = (successFn: HandleSubmitSuccessFn, errorFn?: HandleSubmitErrorFn) => void
export interface InterceptorParams {
/** Name of the field that is currently about to be set*/
- name:string,
+ name: string,
/** Value of the field that is currently about to be set */
- value:any,
+ value: any,
/** Current form values */
- values: Record
+ values: Record
/** Current form state */
formState: FormState
/** Triggers the validation of a field */
- triggerValidation:TriggerValidation
+ triggerValidation: TriggerValidation
/** Function to reset a field */
resetField: ResetField
/** Function to reset the whole form */
resetForm: ResetForm
-
+
/** Function to set an error on a field programmatically */
setError: SetError
/** Function to clear one or more errors on a desired field or the whole form*/
- clearErrors:ClearErrors
+ clearErrors: ClearErrors
/** Function that returns the modified values of the form */
modifiedValues: ModifiedValues
}
-export interface FormHandlerOptions{
+export interface FormHandlerOptions {
/** Set to submit if validations are desired before sending the form */
validationBehaviour?: 'always' | 'submit'
-
- /** Set to all if we want to track all the validation errors a field could have */
- validationErrors?: 'first' | 'all'
}
export interface FormHandlerParams {
/** Values to initialize the form */
- initialValues?: Record
+ initialValues?: Record
/** Field change interceptor */
- interceptor?:(_:InterceptorParams)=>Promise
+ interceptor?: (_: InterceptorParams) => Promise
/** Validation function to execute before submitting (when using this individual validations are invalidated) */
- validate?:()=>Promise|boolean
+ validate?: () => Promise | boolean
/** Options for the form handler */
- options?:FormHandlerOptions
+ options?: FormHandlerOptions
}
-export interface FormHandlerReturn {
+export interface FormHandlerReturn {
/** Current form values */
- values: Record
+ values: Record
/** Current form state */
formState: FormState
/** Triggers the validation of a field */
- triggerValidation:TriggerValidation
+ triggerValidation: TriggerValidation
/** Function to reset a field */
resetField: ResetField
/** Function to reset the whole form */
resetForm: ResetForm
-
+
/** Function to set an error on a field programmatically */
setError: SetError
/** Function to clear one or more errors on a desired field or the whole form*/
- clearErrors:ClearErrors
+ clearErrors: ClearErrors
/** Function that returns the modified values of the form */
modifiedValues: ModifiedValues
@@ -218,7 +215,7 @@ export interface FormHandlerReturn {
setValue: SetValue
/** Function to clear a desired field*/
- clearField:ClearField
+ clearField: ClearField
/** Method to register a field and make it interact with the current form */
register: Register
@@ -228,4 +225,4 @@ export interface FormHandlerReturn {
}
/** Form handler solution as a composable function */
-export type FormHandler = (_?:FormHandlerParams)=>FormHandlerReturn
+export type FormHandler = (_?: FormHandlerParams) => FormHandlerReturn
diff --git a/src/useFormHandler.ts b/src/useFormHandler.ts
index 2541f0a..b785b7c 100644
--- a/src/useFormHandler.ts
+++ b/src/useFormHandler.ts
@@ -1,7 +1,11 @@
+import { DEFAULT_FIELD_VALUE } from './constants';
import { ModifiedValues, TriggerValidation, Validations, FormHandler, ResetField, ResetForm, InitControl, SetError, ClearErrors, SetValue, ClearField, SetDirty, SetTouched, HandleBlur, HandleChange, GetInitValueForControl } from './types';
import { FormState, HandleSubmit, Register } from './types';
import { reactive, readonly, watch } from 'vue'
import { isEqual } from 'lodash-es'
+import { getNativeFieldValue } from './logic';
+import { isCheckboxInput, isRadioInput, isMultipleSelect, isNativeControl, isDefined } from './utils';
+import getDefaultFieldValue from './logic/getDefaultFieldValue';
export const initialState = () => ({
touched: {},
@@ -13,21 +17,24 @@ export const initialState = () => ({
})
//TODO: separate synchronous from asynchronous validations
-//TODO: extend validation behaviours
-const useFormHandler:FormHandler = ({
- initialValues = {},
- interceptor,
+//TODO: extend validation behaviors
+const useFormHandler: FormHandler = ({
+ initialValues = {},
+ interceptor,
validate,
- options = { validationBehaviour: 'always', validationErrors: 'first' }
+ // TODO: if no further options, validation behavior should be considered along with the base params
+ options = { validationBehaviour: 'always' }
} = {}) => {
- const values:Record = reactive({ ...initialValues })
- const formState = reactive({...initialState()})
+ const values: Record = reactive({ ...initialValues })
+ const formState = reactive({ ...initialState() })
- let validations:Record = {}
- let defaultValues:Record = {}
+ let validations: Record = {}
+ let defaultValues: Record = {}
+ let refs: Record = {}
- const getInitValueForControl:GetInitValueForControl = (name) => initialValues[name] ?? (defaultValues[name] ?? null)
- const initControl:InitControl = (name, options) => {
+ const getDefaultValueForControl = (name: string) => defaultValues[name] ?? getDefaultFieldValue(refs[name])
+ const getInitValueForControl: GetInitValueForControl = (name) => initialValues[name] ?? getDefaultValueForControl(name)
+ const initControl: InitControl = (name, options) => {
validations = {
...validations,
[name]: options.validations || {}
@@ -36,8 +43,8 @@ const useFormHandler:FormHandler = ({
...defaultValues,
[name]: options.defaultValue
}
- if(initialValues[name] === undefined && values[name] === undefined){
- values[name] = options.defaultValue ?? null
+ if (initialValues[name] === undefined && values[name] === undefined) {
+ values[name] = getDefaultValueForControl(name)
}
}
@@ -45,112 +52,126 @@ const useFormHandler:FormHandler = ({
formState.isValid = !Object.keys(formState.errors).some(field => Object.keys(formState.errors[field]).length)
}
- const triggerValidation:TriggerValidation = async (name) => {
- if(!Object.keys(validations).length){
+ const clearErrors: ClearErrors = (name, errors) => {
+ if (!name) {
+ formState.errors = {}
+ return
+ }
+ if (!errors) {
+ delete formState.errors[name]
return
}
- if(!name){
- for(const field of Object.keys(values)){
+ formState.errors[name] = Object.fromEntries(Object
+ .entries(formState.errors[name])
+ .filter(([errorName]) => Array.isArray(errors)
+ ? !errors.includes(errorName)
+ : errors !== errorName))
+
+ if (Object.entries(formState.errors[name]).length < 1) {
+ delete formState.errors[name]
+ }
+ }
+
+ const triggerValidation: TriggerValidation = async (name) => {
+ if (!Object.keys(validations).length) {
+ return
+ }
+ if (!name) {
+ for (const field of Object.keys(values)) {
await triggerValidation(field)
}
return
}
- if(!Object.keys(validations[name]).length){
+ if (!Object.keys(validations[name]).length) {
return
}
- formState.errors[name] = {}
- for(const [validationName, validation] of Object.entries(validations[name])){
+ for (const [validationName, validation] of Object.entries(validations[name])) {
const result = await validation(values[name])
formState.errors[name] = {
- ...formState.errors[name],
- ...(result !== true && {[validationName]: result})
+ ...(result !== true && { [validationName]: result })
}
- if(result !== true && options.validationErrors !== 'all'){
+ if (result !== true) {
break;
}
+ //TODO: improve this
+ clearErrors(name)
}
}
- const setDirty:SetDirty = (name, dirty) => {
- if(formState.dirty[name] !== dirty){
- formState.dirty[name] = dirty
- formState.isDirty = dirty || Object.values(formState.dirty).some(Boolean)
+ const setDirty: SetDirty = (name, dirty) => {
+ if (formState.dirty[name] !== dirty) {
+ if (dirty) {
+ formState.dirty[name] = true
+ formState.isDirty = true
+ return
+ }
+ delete formState.dirty[name]
+ formState.isDirty = Object.values(formState.touched).some(Boolean)
}
}
- const setTouched:SetTouched = (name, touched) => {
- if(formState.touched[name] !== touched){
- formState.touched[name] = touched
- formState.isTouched = touched || Object.values(formState.touched).some(Boolean)
+ const setTouched: SetTouched = (name, touched) => {
+ if (formState.touched[name] !== touched) {
+ if (touched) {
+ formState.touched[name] = true
+ formState.isTouched = true
+ return
+ }
+ delete formState.touched[name]
+ formState.isTouched = Object.values(formState.touched).some(Boolean)
}
}
- const resetField:ResetField = (name) => {
+ const resetField: ResetField = (name) => {
values[name] = getInitValueForControl(name)
setTouched(name, false)
setDirty(name, false)
}
- const resetForm:ResetForm = () => {
- Object.keys(values).forEach((key)=>{
+ const resetForm: ResetForm = () => {
+ Object.keys(values).forEach((key) => {
resetField(key)
})
}
- const setError:SetError = (name, error, replace = false) => {
+ const setError: SetError = (name, error, replace = false) => {
formState.errors[name] = {
...(!replace && formState.errors[name]),
...error
}
}
- const clearErrors:ClearErrors = (name, errors) => {
- if(!name){
- formState.errors = {}
- return;
- }
- if(!errors){
- formState.errors[name] = {}
- return;
- }
- formState.errors[name] = Object.fromEntries(Object
- .entries(formState.errors[name])
- .filter(([errorName])=> Array.isArray(errors)
- ? !errors.includes(errorName)
- : errors !== errorName))
- }
-
- const modifiedValues:ModifiedValues = () => {
+ const modifiedValues: ModifiedValues = () => {
return Object.fromEntries(Object
.entries(values)
.filter(([name]) => formState.dirty[name]))
}
- const setValue:SetValue = async (name, value = null) => {
- if(!interceptor || await interceptor({name, value, values, formState, clearErrors, modifiedValues, resetField, resetForm, setError, triggerValidation})) {
+ const setValue: SetValue = async (name, value = DEFAULT_FIELD_VALUE) => {
+ if (!interceptor || await interceptor({ name, value, values, formState, clearErrors, modifiedValues, resetField, resetForm, setError, triggerValidation })) {
values[name] = value
setDirty(name, !isEqual(value, getInitValueForControl(name)))
}
}
- const handleBlur:HandleBlur = (name) => {
- setTouched(name,true)
+ const handleBlur: HandleBlur = (name) => {
+ setTouched(name, true)
triggerValidation(name)
}
- const handleChange:HandleChange = async (name, value = null) => {
+ const handleChange: HandleChange = async (name, value = DEFAULT_FIELD_VALUE) => {
await setValue(name, value)
- setTouched(name,true)
+ setTouched(name, true)
triggerValidation(name)
}
- const clearField:ClearField = async (name) => {
- await setValue(name, defaultValues[name] ?? null)
+ const clearField: ClearField = async (name) => {
+ await setValue(name, getDefaultValueForControl(name))
}
- const register:Register = (name, options = {}) => {
- initControl(name,options);
- return({
+ const register: Register = (name, options = {}) => {
+ initControl(name, options);
+ return ({
name,
errors: Object.values(formState.errors[name] || {}) || [],
onBlur: () => handleBlur(name),
@@ -158,32 +179,70 @@ const useFormHandler:FormHandler = ({
isDirty: !!formState.dirty[name],
isTouched: !!formState.touched[name],
}),
- ...(options.native !== true && {
- modelValue: values[name],
- 'onUpdate:modelValue': (value:any) => handleChange(name,value),
- }),
+ modelValue: values[name],
+ 'onUpdate:modelValue': (value: any) => handleChange(name, value),
+ ref: (fieldRef: any) => {
+ if (!fieldRef || !fieldRef.nodeName || !isNativeControl(fieldRef)) {
+ refs = {
+ ...refs,
+ [name]: {
+ type: 'custom'
+ }
+ }
+ return
+ }
+ if (!refs[name] || (isRadioInput(fieldRef) && !refs[name].some((option: any) => option.value === fieldRef.value))) {
+ refs = {
+ ...refs,
+ [name]: isRadioInput(fieldRef) ? [...(refs[name] || []), fieldRef] : fieldRef
+ }
+ }
+ if (isRadioInput(fieldRef)) {
+ if (fieldRef.checked) {
+ values[name] = fieldRef.value
+ return
+ }
+ fieldRef.checked = (values[name] === fieldRef.value)
+ return
+ }
+ if (isCheckboxInput(fieldRef)) {
+ if (isDefined(fieldRef.checked)) {
+ values[name] = !!fieldRef.checked
+ return
+ }
+ fieldRef.checked = !!values[name]
+ return
+ }
+ if (isMultipleSelect(fieldRef)) {
+ [...fieldRef.options].forEach((option: any, index) => {
+ fieldRef[index].selected = !!values[name]?.includes(option.value)
+ })
+ return
+ }
+ fieldRef.value = values[name]
+ },
...(options.native !== false && {
- value: values[name],
- onInput: (el:any) => handleChange(name,el && (el.target && el.target.value)),
- ...(options.required && {required: true})
+ onChange: () => handleChange(name, getNativeFieldValue(refs[name] as HTMLInputElement)),
+ ...(options.required && { required: true })
}),
- ...(options.clearable && {onClear: () => resetField(name)})
- })}
+ ...(options.clearable && { onClear: () => resetField(name) })
+ })
+ }
- const handleSubmit:HandleSubmit = async (successFn, errorFn) => {
+ const handleSubmit: HandleSubmit = async (successFn, errorFn) => {
let valid = false;
- if(validate){
+ if (validate) {
valid = await validate()
}
- else{
+ else {
await triggerValidation()
valid = formState.isValid
}
- if(valid){
+ if (valid) {
successFn(values)
return
}
- if(errorFn){
+ if (errorFn) {
errorFn(formState.errors)
return
}
@@ -191,10 +250,10 @@ const useFormHandler:FormHandler = ({
}
watch(
- ()=> formState.errors,
- ()=> {
- isValid();
- }, {deep:true, immediate:true})
+ () => formState.errors,
+ () => {
+ isValid();
+ }, { deep: true, immediate: true })
return {
values: readonly(values),
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 0000000..ace2176
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1,5 @@
+export { default as isCheckboxInput } from './isCheckboxInput'
+export { default as isNativeControl } from './isNativeControl'
+export { default as isRadioInput } from './isRadioInput'
+export { default as isMultipleSelect } from './isMultipleSelect'
+export { default as isDefined } from './isDefined'
\ No newline at end of file
diff --git a/src/utils/isCheckboxInput.ts b/src/utils/isCheckboxInput.ts
new file mode 100644
index 0000000..33e3134
--- /dev/null
+++ b/src/utils/isCheckboxInput.ts
@@ -0,0 +1,2 @@
+export default (element: HTMLInputElement) =>
+ element.type === 'checkbox';
diff --git a/src/utils/isDefined.ts b/src/utils/isDefined.ts
new file mode 100644
index 0000000..249f489
--- /dev/null
+++ b/src/utils/isDefined.ts
@@ -0,0 +1 @@
+export default (element: unknown) => element !== undefined
\ No newline at end of file
diff --git a/src/utils/isMultipleSelect.ts b/src/utils/isMultipleSelect.ts
new file mode 100644
index 0000000..250027c
--- /dev/null
+++ b/src/utils/isMultipleSelect.ts
@@ -0,0 +1,2 @@
+export default (element: HTMLInputElement) =>
+ element.type === `select-multiple`;
diff --git a/src/utils/isNativeControl.ts b/src/utils/isNativeControl.ts
new file mode 100644
index 0000000..87ba065
--- /dev/null
+++ b/src/utils/isNativeControl.ts
@@ -0,0 +1,2 @@
+export default (element: HTMLElement) =>
+ ['input', 'select', 'textarea'].includes(element.nodeName.toLowerCase())
diff --git a/src/utils/isRadioInput.ts b/src/utils/isRadioInput.ts
new file mode 100644
index 0000000..eec4495
--- /dev/null
+++ b/src/utils/isRadioInput.ts
@@ -0,0 +1,2 @@
+export default (element: HTMLInputElement) =>
+ element.type === 'radio';
\ No newline at end of file
diff --git a/test/useFormHandler.test.ts b/test/useFormHandler.test.ts
index 19f0715..6c38384 100644
--- a/test/useFormHandler.test.ts
+++ b/test/useFormHandler.test.ts
@@ -1,29 +1,29 @@
import useFormHandler, { initialState } from '../src/useFormHandler';
import { expect, it, describe } from "vitest"
-const sleep = () => new Promise((resolve) => setTimeout(()=> resolve(true),50))
+const sleep = () => new Promise((resolve) => setTimeout(() => resolve(true), 50))
describe('Form handler testing', () => {
it('Initial form state and values', () => {
- const {values, formState} = useFormHandler();
+ const { values, formState } = useFormHandler();
expect(values).toStrictEqual({})
- expect(formState).toStrictEqual({...initialState()})
+ expect(formState).toStrictEqual({ ...initialState() })
})
it('Initial values should be applied without', () => {
const initialValues = {
field: 'test'
}
- const {values} = useFormHandler({initialValues});
+ const { values } = useFormHandler({ initialValues });
expect(values).toStrictEqual(initialValues)
})
it('Setting a value programmatically', async () => {
- const {values, setValue, formState} = useFormHandler();
+ const { values, setValue, formState } = useFormHandler();
await setValue('field', 'oneTwoThree');
expect(values.field).toBe('oneTwoThree')
expect(formState.isDirty).toBeTruthy()
})
it('Clearing a field programmatically', async () => {
- const {values, setValue, formState, clearField} = useFormHandler();
+ const { values, setValue, formState, clearField } = useFormHandler();
await setValue('field', 'oneTwoThree');
expect(values.field).toBe('oneTwoThree')
expect(formState.isDirty).toBeTruthy()
@@ -32,8 +32,8 @@ describe('Form handler testing', () => {
expect(formState.isDirty).toBeFalsy()
})
it('Clearing an initialized field leaves it dirty', async () => {
- const {values, formState, clearField} = useFormHandler({
- initialValues:{ field: 'value' }
+ const { values, formState, clearField } = useFormHandler({
+ initialValues: { field: 'value' }
});
expect(values.field).toBe('value')
expect(formState.isDirty).toBeFalsy()
@@ -42,38 +42,38 @@ describe('Form handler testing', () => {
expect(formState.isDirty).toBeTruthy()
})
it('Setting an error programmatically', async () => {
- const {formState,setError} = useFormHandler();
- setError('field', {error: 'some error'})
- expect(formState.errors).toStrictEqual({field:{error: 'some error'}})
+ const { formState, setError } = useFormHandler();
+ setError('field', { error: 'some error' })
+ expect(formState.errors).toStrictEqual({ field: { error: 'some error' } })
await sleep()
expect(formState.isValid).toBeFalsy()
})
it('Clearing one error of a control programmatically', async () => {
- const {formState,setError,clearErrors} = useFormHandler();
- const errors = {error: 'some error' , error2: 'some other error'}
+ const { formState, setError, clearErrors } = useFormHandler();
+ const errors = { error: 'some error', error2: 'some other error' }
setError('field', errors)
await sleep()
expect(formState.isValid).toBeFalsy()
clearErrors('field', 'error')
- expect(formState.errors.field).toStrictEqual({error2: 'some other error'})
+ expect(formState.errors.field).toStrictEqual({ error2: 'some other error' })
await sleep()
expect(formState.isValid).toBeFalsy()
})
it('Clearing all errors of a control programmatically', async () => {
- const {formState,setError,clearErrors} = useFormHandler();
- const errors = {error: 'some error' , error2: 'some other error'}
+ const { formState, setError, clearErrors } = useFormHandler();
+ const errors = { error: 'some error', error2: 'some other error' }
setError('field', errors)
await sleep()
expect(formState.isValid).toBeFalsy()
clearErrors('field')
- expect(formState.errors.field).toStrictEqual({})
+ expect(formState.errors.field).toBeUndefined()
await sleep()
expect(formState.isValid).toBeTruthy()
})
it('Clearing all errors of the form programmatically', async () => {
- const {formState,setError,clearErrors} = useFormHandler();
- const errorField1 = {error1: 'some error'}
- const errorField2 = {error2: 'some error'}
+ const { formState, setError, clearErrors } = useFormHandler();
+ const errorField1 = { error1: 'some error' }
+ const errorField2 = { error2: 'some error' }
setError('field1', errorField1)
setError('field2', errorField2)
expect(formState.errors.field1).toStrictEqual(errorField1)
@@ -87,8 +87,8 @@ describe('Form handler testing', () => {
expect(formState.isValid).toBeTruthy()
})
it('Resetting a field it back to its initial values and state', async () => {
- const {values, formState, resetField, setValue} = useFormHandler({
- initialValues:{ field: 'value' }
+ const { values, formState, resetField, setValue } = useFormHandler({
+ initialValues: { field: 'value' }
});
expect(values.field).toBe('value')
expect(formState.isDirty).toBeFalsy()
@@ -99,20 +99,20 @@ describe('Form handler testing', () => {
expect(values.field).toBe('value')
expect(formState.isDirty).toBeFalsy()
})
- it('Expecting modifiedValues to work', async() => {
- const {modifiedValues, setValue} = useFormHandler({
- initialValues:{
- field:'something'
+ it('Expecting modifiedValues to work', async () => {
+ const { modifiedValues, setValue } = useFormHandler({
+ initialValues: {
+ field: 'something'
}
});
await setValue('field2', 'another thing')
- expect(modifiedValues()).toStrictEqual({field2:'another thing'})
+ expect(modifiedValues()).toStrictEqual({ field2: 'another thing' })
})
})
describe('Register function testing', () => {
it('Registering a field', () => {
- const {values, register} = useFormHandler();
+ const { values, register } = useFormHandler();
const field = register('field')
expect(field.name).toBe('field')
expect(field.errors).toStrictEqual([])
@@ -120,66 +120,62 @@ describe('Register function testing', () => {
expect(field.isDirty).toBeUndefined()
expect(field.isTouched).toBeUndefined()
expect(field.onClear).toBeUndefined()
- expect(field.value).toBe(null)
- expect(field.onInput).toBeDefined()
+ expect(field.onChange).toBeDefined()
expect(field.modelValue).toBe(null)
expect(field['onUpdate:modelValue']).toBeDefined()
expect(values.field).toBe(null)
})
- it('Specified native field shouldn\'t have custom handlers', () => {
- const {register} = useFormHandler();
- const field = register('field', {native:true})
- expect(field.value).toBeDefined()
- expect(field.onInput).toBeDefined()
- expect(field.modelValue).toBeUndefined()
- expect(field['onUpdate:modelValue']).toBeUndefined()
+ it('Specified native field should have native handlers', () => {
+ const { register } = useFormHandler();
+ const field = register('field', { native: true })
+ expect(field.ref).toBeDefined()
+ expect(field.onChange).toBeDefined()
})
it('Specified custom field shouldn\'t have native handlers', () => {
- const {register} = useFormHandler();
- const field = register('field', {native:false})
- expect(field.value).toBeUndefined()
- expect(field.onInput).toBeUndefined()
+ const { register } = useFormHandler();
+ const field = register('field', { native: false })
+ expect(field.onChange).toBeUndefined()
expect(field.modelValue).toBeDefined()
expect(field['onUpdate:modelValue']).toBeDefined()
})
it('Input registered with details receives dirty and touched states', () => {
- const {register} = useFormHandler();
- const field = register('field', {withDetails:true})
+ const { register } = useFormHandler();
+ const field = register('field', { withDetails: true })
expect(field.isDirty).toBeDefined()
expect(field.isTouched).toBeDefined()
})
it('Clearable inputs should have a bound clear handler', () => {
- const {register} = useFormHandler();
- const field = register('field', {clearable:true})
+ const { register } = useFormHandler();
+ const field = register('field', { clearable: true })
expect(field.onClear).toBeDefined()
})
it('Registering a field with default value', () => {
- const {values, register} = useFormHandler();
- register('field', {defaultValue: 'something'})
+ const { values, register } = useFormHandler();
+ register('field', { defaultValue: 'something' })
expect(values.field).toBe('something')
})
- it('Registered validations work on update via handler', async() => {
- const {values, register, formState} = useFormHandler();
+ it('Registered validations work on update via handler', async () => {
+ const { values, register, formState } = useFormHandler();
const field = register('field', {
validations: {
- error: (val) => val !== 'error' || 'Error with your field'
+ error: (val) => val !== 'error' || 'Error with your field'
}
})
- if(field['onUpdate:modelValue']){
+ if (field['onUpdate:modelValue']) {
field['onUpdate:modelValue']('error')
await sleep()
expect(values.field).toBe('error')
expect(formState.isValid).toBeFalsy()
}
})
- it('Registered validations work on update via setter', async() => {
- const {values, register, formState ,setValue, triggerValidation} = useFormHandler();
+ it('Registered validations work on update via setter', async () => {
+ const { values, register, formState, setValue, triggerValidation } = useFormHandler();
register('field', {
validations: {
- error: (val) => val !== 'error' || 'Error with your field'
+ error: (val) => val !== 'error' || 'Error with your field'
}
})
- await setValue('field','error')
+ await setValue('field', 'error')
await triggerValidation('field')
expect(values.field).toBe('error')
expect(formState.isValid).toBeFalsy()
diff --git a/vite.config.ts b/vite.config.ts
index 986947d..85680ca 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -2,7 +2,7 @@
///
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
-import {resolve} from 'path'
+import { resolve } from 'path'
import dts from 'vite-plugin-dts'
// https://vitejs.dev/config/
@@ -12,7 +12,8 @@ export default defineConfig({
},
test: {
includeSource: ['test/*'],
- environment: 'happy-dom'
+ environment: 'happy-dom',
+ reporters: 'verbose',
},
build: {
lib: {