@lifi/widget
Version:
LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.
235 lines (215 loc) • 6.66 kB
text/typescript
import { createWithEqualityFn } from 'zustand/traditional'
import type {
DefaultValues,
FormFieldArray,
FormFieldNames,
FormValueControl,
FormValues,
FormValuesState,
} from './types.js'
export const formDefaultValues: DefaultValues = {
fromAmount: '',
toAmount: '',
tokenSearchFilter: '',
}
const defaultValueToFormValue = <T>(value: T): FormValueControl<T> => ({
isTouched: false,
isDirty: false,
value,
})
const valuesToFormValues = (defaultValues: DefaultValues): FormValues => {
return (Object.keys(defaultValues) as FormFieldNames[]).reduce(
(accum, key) => {
accum[key] = defaultValueToFormValue(defaultValues[key]) as never
return accum
},
{} as FormValues
)
}
const isString = (str: any) => typeof str === 'string' || str instanceof String
const getUpdatedTouchedFields = (userValues: FormValues) => {
return (Object.keys(userValues) as FormFieldNames[]).reduce(
(accum, key) => {
if (userValues[key]?.isTouched) {
accum[key] = true
}
return accum
},
{} as Record<FormFieldNames, boolean>
)
}
const mergeDefaultFormValues = (
userValues: FormValues,
defaultValues: FormValues
) =>
(Object.keys(defaultValues) as FormFieldNames[]).reduce<FormValues>(
(accum, key) => {
const formValue = {
isTouched: !!(
userValues[key]?.isTouched || defaultValues[key]?.isTouched
),
isDirty: !!(userValues[key]?.isDirty || defaultValues[key]?.isTouched),
value:
userValues[key]?.value || Number.isFinite(userValues[key]?.value)
? userValues[key]?.value
: defaultValues[key]?.value,
}
accum[key] = formValue as never
return accum
},
{ ...valuesToFormValues(formDefaultValues) }
)
export const createFormStore = (defaultValues?: DefaultValues) =>
createWithEqualityFn<FormValuesState>((set, get) => {
const _defaultValues = valuesToFormValues({
...formDefaultValues,
...defaultValues,
})
return {
defaultValues: _defaultValues,
userValues: _defaultValues,
touchedFields: {},
isValid: true,
isValidating: false,
errors: {},
validation: {},
setDefaultValues: (defaultValue) => {
const defaultFormValues = valuesToFormValues(defaultValue)
set((state) => ({
defaultValues: defaultFormValues,
userValues: mergeDefaultFormValues(
state.userValues,
defaultFormValues
),
}))
},
setUserAndDefaultValues: (formValues) => {
const currentUserValues = get().userValues
;(Object.keys(formValues) as FormFieldNames[]).forEach((key) => {
if (formValues[key] !== currentUserValues[key]?.value) {
get().resetField(key, { defaultValue: formValues[key] })
get().setFieldValue(key, formValues[key], { isTouched: true })
}
})
},
isTouched: (fieldName: FormFieldNames) =>
!!get().userValues[fieldName]?.isTouched,
isDirty: (fieldName: FormFieldNames) =>
!!get().userValues[fieldName]?.isDirty,
setAsTouched: (fieldName: FormFieldNames) => {
const userValues = {
...get().userValues,
[fieldName]: {
...get().userValues[fieldName],
isTouched: true,
},
}
const touchedFields = getUpdatedTouchedFields(userValues)
set(() => ({
userValues,
touchedFields,
}))
},
resetField: (fieldName, { defaultValue } = {}) => {
if (defaultValue) {
const fieldValues = {
...get().defaultValues[fieldName],
value: defaultValue,
}
const defaultValues = {
...get().defaultValues,
[fieldName]: { ...fieldValues },
}
const userValues = {
...get().userValues,
[fieldName]: { ...fieldValues },
}
const touchedFields = getUpdatedTouchedFields(userValues)
set(() => {
return {
defaultValues,
userValues,
touchedFields,
}
})
} else {
const userValues = {
...get().userValues,
[fieldName]: { ...get().defaultValues[fieldName] },
}
const touchedFields = getUpdatedTouchedFields(userValues)
set(() => ({
userValues,
touchedFields,
}))
}
},
setFieldValue: (fieldName, value, { isDirty, isTouched } = {}) => {
const userValues = {
...get().userValues,
[fieldName]: {
value,
isDirty:
isDirty === undefined
? get().userValues[fieldName]?.isDirty
: isDirty,
isTouched:
isTouched === undefined
? get().userValues[fieldName]?.isTouched
: isTouched,
},
}
const touchedFields = getUpdatedTouchedFields(userValues)
set(() => ({
userValues,
touchedFields,
}))
},
getFieldValues: <T extends FormFieldNames[]>(...names: T) =>
names.map((name) => get().userValues[name]?.value) as FormFieldArray<T>,
addFieldValidation: (name, validationFn) => {
set((state) => ({
validation: {
...state.validation,
[name]: validationFn,
},
}))
},
triggerFieldValidation: async (name) => {
try {
let valid = true
set(() => ({ isValid: false, isValidating: true }))
const validationFn = get().validation[name]
if (validationFn) {
const result = await validationFn(get().userValues?.[name]?.value)
if (isString(result)) {
valid = false
set((state) => ({
errors: {
...state.errors,
[name]: result,
},
}))
} else {
valid = result as boolean
if (valid) {
get().clearErrors(name)
}
}
}
set(() => ({ isValid: valid, isValidating: false }))
return valid
} catch (err) {
set(() => ({ isValidating: false }))
throw err
}
},
clearErrors: (name) => {
const newErrors = { ...get().errors }
delete newErrors[name]
set(() => ({
errors: newErrors,
}))
},
}
}, Object.is)