Skip to content

Commit

Permalink
Improve type clarity and dx
Browse files Browse the repository at this point in the history
  • Loading branch information
dbssman committed Sep 2, 2023
1 parent 2af05a7 commit 973c6fe
Show file tree
Hide file tree
Showing 15 changed files with 211 additions and 182 deletions.
7 changes: 5 additions & 2 deletions docs/api/use-form-handler/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ Notice how the template looks much cleaner with this approach, and this helps us
## Type Declarations

```ts
export type Build<T> = <TBuild extends Record<keyof T, RegisterOptions>>(
export type Build<T extends Record<string, any> = Record<string, any>> = <
TBuild extends Partial<Record<keyof T, RegisterOptions>>,
>(
configuration: TBuild | Ref<TBuild> | ComputedRef<TBuild>
) => ComputedRef<Record<keyof TBuild, Readonly<RegisterReturn>>>
) => ComputedRef<Record<keyof TBuild, Readonly<RegisterReturn<TBuild>>>>

```
7 changes: 4 additions & 3 deletions docs/api/use-form-handler/modified-values.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# modifiedValues

Returns the current modified fields of the form.
Computed property that holds the current modified values of the form e.g values that are different from initial value if any initial value was indicated or different than the default value for the field if no initial values were given.

## Demo

Expand All @@ -15,7 +15,7 @@ Coming soon...
<input type="text" v-bind="register('email')" />
<input type="text" v-bind="register('summary')" />
<pre>
{{ modifiedValues() }} //should be initially
{{ modifiedValues }} //should initially be an empty object
</pre
>
<pre>
Expand All @@ -41,5 +41,6 @@ Let's say your form is initialized as above, because you're editing an existing
## Type Declarations

```ts
export type ModifiedValues = <TModified extends T>() => TModified
type ModifiedValues = ComputedRef<Partial<T>>// T being the form interface

```
25 changes: 25 additions & 0 deletions docs/api/use-form-handler/register.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,31 @@ Custom validations are kept very simple, can be synchronous or asynchronous. We
## Type Declarations

```ts

interface ValidationWithMessage {
value: number | string | RegExp
message: string
}

interface ValidationsConfiguration {
required?: boolean | string
min?: number | ValidationWithMessage<number>
max?: number | ValidationWithMessage<number>
minLength?: number | ValidationWithMessage<number>
maxLength?: number | ValidationWithMessage<number>
pattern?: string | RegExp | ValidationWithMessage<string | RegExp>
}

export interface RegisterOptions extends ValidationsConfiguration {
native?: boolean
defaultValue?: any
validate?: Validations
withDetails?: boolean
disabled?: boolean
useNativeValidation?: boolean
dependentFields?: string[]
}

export type Register = (
name: keyof T,
options?: RegisterOptions
Expand Down
2 changes: 1 addition & 1 deletion docs/api/use-form-handler/values.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ const { register, values } = useFormHandler()
## Type Declarations

```ts
export type Values = T
export type Values = Partial<T>
```
2 changes: 0 additions & 2 deletions src/logic/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
export { default as getNativeFieldValue } from './getNativeFieldValue'
export { default as getDefaultFieldValue } from './getDefaultFieldValue'
export { default as validateField } from './validateField'
export { default as validateForm } from './validateForm'
export { default as refFn } from './refFn'
export { default as transformValidations } from './transformValidations'
89 changes: 46 additions & 43 deletions src/logic/refFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,55 @@ import {
isRadioInput,
} from '../utils'

export default <T>(name: keyof T, _refs: Refs<T>, values: T) => (fieldRef: any) => {
if (!fieldRef) {
delete _refs[name]
return
}
if (fieldRef.$el || !fieldRef.nodeName || !isNativeControl(fieldRef)) {
//TODO: Correctly type this in order to expect a fixed data structure
_refs[name].ref = {
type: 'custom',
export default <T>(name: keyof T, _refs: Refs<T>, values: Partial<T>) =>
(fieldRef: any) => {
if (!fieldRef) {
delete _refs[name]
return
}
return
}
const isFirstRegister =
_refs[name] &&
(!_refs[name].ref ||
(Array.isArray(_refs[name].ref) &&
isRadioInput(fieldRef) &&
!(_refs[name].ref as FieldReference[]).some(
(option: any) => option.value === fieldRef.value
)))
if (isFirstRegister) {
_refs[name].ref = isRadioInput(fieldRef)
? [...((_refs[name].ref as FieldReference[]) || []), fieldRef]
: fieldRef
}
if (isRadioInput(fieldRef)) {
if (isFirstRegister && !!fieldRef.checked) {
values[name] = fieldRef.value
if (fieldRef.$el || !fieldRef.nodeName || !isNativeControl(fieldRef)) {
//TODO: Correctly type this in order to expect a fixed data structure
_refs[name].ref = {
type: 'custom',
}
return
}
fieldRef.checked = values[name] === fieldRef.value
return
}
if (isCheckboxInput(fieldRef)) {
if (isFirstRegister && !!fieldRef.checked) {
values[name] = true as any
const isFirstRegister =
_refs[name] &&
(!_refs[name].ref ||
(Array.isArray(_refs[name].ref) &&
isRadioInput(fieldRef) &&
!(_refs[name].ref as FieldReference[]).some(
(option: any) => option.value === fieldRef.value
)))
if (isFirstRegister) {
_refs[name].ref = isRadioInput(fieldRef)
? [...((_refs[name].ref as FieldReference[]) || []), fieldRef]
: fieldRef
}
if (isRadioInput(fieldRef)) {
if (isFirstRegister && !!fieldRef.checked) {
values[name] = fieldRef.value
return
}
fieldRef.checked = values[name] === fieldRef.value
return
}
fieldRef.checked = !!values[name]
return
}
if (isMultipleSelect(fieldRef)) {
;[...fieldRef.options].forEach((option: any, index) => {
fieldRef[index].selected = !!((values[name] as any)?.includes(option.value))
})
return
if (isCheckboxInput(fieldRef)) {
if (isFirstRegister && !!fieldRef.checked) {
values[name] = true as any
return
}
fieldRef.checked = !!values[name]
return
}
if (isMultipleSelect(fieldRef)) {
;[...fieldRef.options].forEach((option: any, index) => {
fieldRef[index].selected = !!(values[name] as any)?.includes(
option.value
)
})
return
}
fieldRef.value = values[name]
}
fieldRef.value = values[name]
}
59 changes: 27 additions & 32 deletions src/logic/transformValidations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ import {
} from '../types'
import { max, maxLength, min, minLength, pattern, required } from '../utils'

const isValidationWithMessage = (
validation: any
): validation is ValidationWithMessage => {
return (
typeof validation === 'object' &&
validation !== null &&
'value' in validation &&
'message' in validation
)
}

const getValidationForConfiguration = (validation: any, fn: Function) =>
isValidationWithMessage(validation)
? fn(validation.value, validation.message)
: fn(validation)

export default (validations: ValidationsConfiguration = {}): Validations => {
return {
...(validations.required && {
Expand All @@ -14,46 +30,25 @@ export default (validations: ValidationsConfiguration = {}): Validations => {
: required(),
}),
...(validations.min && {
min:
typeof validations.min === 'number'
? min(validations.min)
: min(validations.min.value as number, validations.min.message),
min: getValidationForConfiguration(validations.min, min),
}),
...(validations.max && {
max:
typeof validations.max === 'number'
? max(validations.max)
: max(validations.max.value as number, validations.max.message),
max: getValidationForConfiguration(validations.max, max),
}),
...(validations.minLength && {
minLength:
typeof validations.minLength === 'number'
? minLength(validations.minLength)
: minLength(
validations.minLength.value as number,
validations.minLength.message
),
minLength: getValidationForConfiguration(
validations.minLength,
minLength
),
}),
...(validations.maxLength && {
maxLength:
typeof validations.maxLength === 'number'
? maxLength(validations.maxLength)
: maxLength(
validations.maxLength.value as number,
validations.maxLength.message
),
maxLength: getValidationForConfiguration(
validations.maxLength,
maxLength
),
}),
...(validations.pattern && {
pattern: !(validations.pattern as ValidationWithMessage)?.value
? pattern(
(typeof validations.pattern === 'string'
? new RegExp(validations.pattern)
: validations.pattern) as RegExp
)
: pattern(
(validations.pattern as ValidationWithMessage).value as RegExp,
(validations.pattern as ValidationWithMessage).message as string
),
pattern: getValidationForConfiguration(validations.pattern, pattern),
}),
}
}
31 changes: 0 additions & 31 deletions src/logic/validateField.ts

This file was deleted.

8 changes: 0 additions & 8 deletions src/logic/validateForm.ts

This file was deleted.

15 changes: 6 additions & 9 deletions src/types/formHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ export type HandleSubmitErrorFn<T> = (errors: FormState<T>['errors']) => void
export type HandleSubmitSuccessFn<T> = (values: Record<keyof T, any>) => void

export type Build<T extends Record<string, any> = Record<string, any>> = <
TBuild extends Record<keyof T, RegisterOptions>,
TBuild extends Partial<Record<keyof T, RegisterOptions>>,
>(
configuration: TBuild | Ref<TBuild> | ComputedRef<TBuild>
) => ComputedRef<Record<keyof TBuild, Readonly<RegisterReturn<TBuild>>>>

export interface PartialReturn<T> {
/** Current form values */
values: T
values: Partial<T>

/** Current form state */
formState: FormState<T>
Expand All @@ -51,7 +51,7 @@ export interface PartialReturn<T> {
clearField: (name: keyof T) => void

/** Function that returns the modified values of the form */
modifiedValues: <TModified extends T>() => TModified
modifiedValues: ComputedRef<Partial<T>>
}

export interface InterceptorParams<T> extends PartialReturn<T> {
Expand Down Expand Up @@ -87,18 +87,15 @@ export type SubmitValidation = (

export type InjectionKey = string | Symbol

export interface FormHandlerParams<
TValues extends Record<string, any>,
TInitial extends TValues = TValues,
> {
export interface FormHandlerParams<TForm, TInitial> {
/** Values to initialize the form */
initialValues?: TInitial | Ref<TInitial> | ComputedRef<TInitial>

/** Field change interceptor */
interceptor?: Interceptor<TValues>
interceptor?: Interceptor<TForm>

/** Validation function to execute before submitting (when using this individual validations are invalidated) */
validate?: (values: TValues) => Promise<boolean> | boolean
validate?: (values: Partial<TForm>) => Promise<boolean> | boolean

/** Validation behavior options */
validationMode?: 'onChange' | 'onBlur' | 'onSubmit' | 'always'
Expand Down
14 changes: 7 additions & 7 deletions src/types/validations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface ValidationWithMessage {
export interface ValidationWithMessage<T = any> {
/** Validation value */
value: number | string | RegExp
value: T

/** Validation message */
message: string
Expand Down Expand Up @@ -35,25 +35,25 @@ export interface ValidationsConfiguration {
/** Min validation */
/** If a number, the field must be greater than or equal to the number */
/** If an object, the field must be greater than or equal to the object.value and the object.message is the validation message */
min?: number | ValidationWithMessage
min?: number | ValidationWithMessage<number>

/** Max validation */
/** If a number, the field must be less than or equal to the number */
/** If an object, the field must be less than or equal to the object.value and the object.message is the validation message */
max?: number | ValidationWithMessage
max?: number | ValidationWithMessage<number>

/** MinLength validation */
/** If a number, the field must be greater than or equal to the number */
/** If an object, the field must be greater than or equal to the object.value and the object.message is the validation message */
minLength?: number | ValidationWithMessage
minLength?: number | ValidationWithMessage<number>

/** MaxLength validation */
/** If a number, the field must be less than or equal to the number */
/** If an object, the field must be less than or equal to the object.value and the object.message is the validation message */
maxLength?: number | ValidationWithMessage
maxLength?: number | ValidationWithMessage<number>

/** Pattern validation */
/** If a RegExp, the field must match the RegExp */
/** If an object, the field must match the object.value and the object.message is the validation message */
pattern?: string | RegExp | ValidationWithMessage
pattern?: string | RegExp | ValidationWithMessage<string | RegExp>
}
Loading

0 comments on commit 973c6fe

Please sign in to comment.