@codeleap/form 4.3.9 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts DELETED
@@ -1,268 +0,0 @@
1
- import { ReactNode } from 'react'
2
- import { Join, Paths, Prev } from '@codeleap/types'
3
- import * as yup from 'yup'
4
- import { WebInputFile, MobileInputFile, DeepPartial, RNMaskedTextTypes } from '../../types'
5
- import { AnyObject } from 'yup/lib/object'
6
-
7
- type ValidationReturn = { message?: Label; valid?: boolean }
8
-
9
- export type ValidatorFunction<T = any, F = any> = (
10
- value: T,
11
- formValues: F
12
- ) => ValidationReturn
13
- export type ValidatorFunctionWithoutForm<T = any> = (
14
- value: T
15
- ) => ValidationReturn
16
-
17
- export type WithTransformer<T> = {
18
- transformer: (value: T) => any
19
- }
20
- export type Validator<T> = T extends boolean
21
- ?
22
- | ValidatorFunction<true>
23
- | ValidatorFunction<false>
24
- | yup.BooleanSchema<boolean, AnyObject, true>
25
- | yup.BooleanSchema<boolean, AnyObject, false>
26
- : ValidatorFunction<T> | yup.SchemaOf<T>
27
-
28
- export type ValidatorWithoutForm<T> = T extends boolean
29
- ?
30
- | ValidatorFunction<true>
31
- | ValidatorFunction<false>
32
- | yup.BooleanSchema<boolean, AnyObject, true>
33
- | yup.BooleanSchema<boolean, AnyObject, false>
34
- : ValidatorFunctionWithoutForm<T> | yup.SchemaOf<T>
35
-
36
- export type Options<T> = { label: Label; value: T }[]
37
- export type Option<T> = Options<T>[number]
38
- type FormValidateOn = 'change'
39
-
40
- export type FormOutput = 'json'
41
-
42
- export type CommonSliderTypes = {
43
- min?: number
44
- max?: number
45
- trackMarks?: number[] | Record<string|number, string>
46
- }
47
-
48
- type Mask = Partial<RNMaskedTextTypes.TextInputMaskProps> &{
49
- saveFormatted?: boolean
50
- }
51
-
52
- export type InputValueTypes = {
53
- checkbox: boolean
54
- switch: boolean
55
- text: string
56
- select: any
57
- radio: any
58
- file: AnyFile
59
- multipleFile: AnyFile[]
60
- 'range-slider': number[]
61
- slider: number[]
62
- list: any[]
63
- date: Date
64
- number: number
65
- }
66
- export type Label = string | ReactNode
67
-
68
- export type CheckboxField = {
69
- type: 'checkbox'
70
- defaultValue: boolean
71
- validate?: Validator<boolean>
72
- required?: boolean
73
- } & WithTransformer<boolean>
74
-
75
- export type SwitchField = {
76
- type: 'switch'
77
- defaultValue: boolean
78
- validate?: Validator<boolean>
79
- required?: boolean
80
- } & WithTransformer<boolean>
81
-
82
- export type ListField<T = any> = {
83
- type: 'list'
84
- defaultValue: T[]
85
- validate?: Validator<T[]>
86
- required?: boolean
87
- options?: Options<T>
88
- placeholder?: string
89
- } & WithTransformer<T[]>
90
-
91
- export type SliderField = {
92
- type: 'slider'
93
- defaultValue: number
94
- validate?: Validator<number>
95
- required?: boolean
96
-
97
- } & CommonSliderTypes & WithTransformer<number>
98
-
99
- export type RangeSliderField = {
100
- type: 'range-slider'
101
- defaultValue: number[]
102
- validate?: Validator<number[]>
103
- required?: boolean
104
- } & CommonSliderTypes & WithTransformer<number[]>
105
-
106
- export type TextField = {
107
- type: 'text'
108
- password?: boolean
109
- defaultValue: string
110
- placeholder?: string
111
- validate?: Validator<string>
112
- required?: boolean
113
- masking?: Mask
114
- multiline?: boolean
115
- } & WithTransformer<string>
116
- export type NumberField = {
117
- type: 'number'
118
-
119
- defaultValue: number
120
- placeholder?: string
121
- validate?: Validator<number>
122
- required?: boolean
123
- precision?: number
124
- masking?: Mask
125
- min?: number
126
- max?: number
127
-
128
- } & WithTransformer<number>
129
- export type SelectField<T = any> = {
130
- type: 'select'
131
- options: Options<T>
132
- defaultValue: T
133
- native?: boolean
134
- placeholder?: string
135
- validate?: Validator<T>
136
- required?: boolean
137
- } & WithTransformer<T>
138
-
139
- export type RadioField<T = any> = {
140
- type: 'radio'
141
- options: Options<T>
142
- defaultValue: T
143
- validate?: Validator<T>
144
- required?: boolean
145
- } & WithTransformer<T>
146
-
147
- export type AnyFile = WebInputFile | MobileInputFile|string|number
148
- export type FileField = {
149
- type: 'file'
150
- allow?: string[]
151
- defaultValue: AnyFile
152
- imageToBase64?: boolean
153
- multiple?: boolean
154
- validate?: Validator<AnyFile>
155
- required?: boolean
156
- } & WithTransformer<AnyFile>
157
-
158
- export type MultipleFileField = {
159
- type: 'multipleFile'
160
- allow?: string[]
161
- defaultValue: AnyFile[]
162
- imageToBase64?: boolean
163
- multiple?: boolean
164
- validate?: Validator<AnyFile[]>
165
- required?: boolean
166
- } & WithTransformer<AnyFile[]>
167
-
168
- export type DateField = {
169
- type: 'date'
170
- defaultValue: Date
171
- validate?: Validator<Date>
172
- required?: boolean
173
- minimumDate?: Date
174
- maximumDate?: Date
175
- } & WithTransformer<Date>
176
-
177
- export type AllFields =
178
- | CheckboxField
179
- | SwitchField
180
- | TextField
181
- | SelectField
182
- | RadioField
183
- | FileField
184
- | SliderField
185
- | RangeSliderField
186
- | ListField
187
- | NumberField
188
- | MultipleFileField
189
- | DateField
190
-
191
- export type FormField = {
192
- disabled?: boolean
193
- label?: Label
194
- autoCapitalize?: boolean | string
195
- keyboardType?: string
196
- returnKeyType?: string
197
- textContentType?: string
198
- autoComplete?: string
199
- description?: Label
200
- debugName?: string
201
- required?: boolean
202
- debounce?: number
203
- componentProps?: any
204
-
205
- } & AllFields
206
-
207
- export type FieldsMap = Record<string, Partial<FormField>>
208
-
209
- export type ResolveOptionsFieldValidation<
210
- T extends RadioField<any> | SelectField<any>
211
- > = T extends RadioField<any>
212
- ? RadioField<T['options'][number]['value']>
213
- : SelectField<T['options'][number]['value']>
214
-
215
- export type ValidateFieldsMap<T extends FieldsMap> = {
216
- [Property in keyof T]: T[Property] extends RadioField<any> | SelectField<any>
217
- ? ResolveOptionsFieldValidation<T[Property]>
218
- : T[Property];
219
- }
220
-
221
- export type FormConfig<T extends FieldsMap> = ValidateFieldsMap<T>
222
-
223
- export type FlattenFields<T extends FieldsMap> = {
224
- [Property in keyof T]: T[Property] extends RadioField<any> | SelectField
225
- ? T[Property]['options'][number]['value']
226
- : InputValueTypes[T[Property]['type']];
227
- }
228
-
229
- export type MapValues<T extends FieldsMap> = {
230
- [Property in keyof T]: T[Property] extends RadioField<any> | SelectField
231
- ? T[Property]['options'][number]['value']
232
- : InputValueTypes[T[Property]['type']];
233
- }
234
-
235
- export type CreateFormReturn<T extends FieldsMap> = {
236
- config: T
237
- defaultValue: MapValues<ValidateFieldsMap<T>>
238
- staticFieldProps: Record<string, any>
239
- name: string
240
- numberOfTextFields: number
241
- }
242
-
243
- export type FormStep<Keys extends string = string> = 'setValue' | 'validate' | `validate.${Keys}` | `setValue.${Keys}`
244
- export type UseFormConfig<T extends Record<string, any>> = {
245
- log?: FormStep<Exclude<keyof T, symbol|number>>[]
246
- initialState?: DeepPartial<T>
247
- validateOn?: 'change' | 'none'
248
- }
249
-
250
- export type PathsWithValues<T, D extends number = 2> = [D] extends [never]
251
- ? never
252
- : T extends Record<string, any>
253
- ? {
254
- [K in keyof T]-?: K extends string | number
255
- ?
256
- | [`${K}`, T[K]]
257
- | [
258
- Join<K, Paths<T[K], Prev[D]>>,
259
- T[K] extends FlattenFields<any> ? T[K][keyof T[K]] : T[K]
260
- ]
261
- : never;
262
- }[keyof T]
263
- : ''
264
-
265
- export type FormShape<Form extends CreateFormReturn<any>> = MapValues<Form['config']>
266
- export type FormSetters<Values> = {
267
- [Property in keyof Values]: (value: Values[Property]) => void
268
- }
package/src/useForm.ts DELETED
@@ -1,269 +0,0 @@
1
- import * as FormTypes from './types'
2
- import { TypeGuards } from '@codeleap/types'
3
- import { deepGet, deepSet, deepMerge } from '@codeleap/utils'
4
- import { usePartialState, useMemo } from '@codeleap/hooks'
5
- import { FunctionType } from '../../types'
6
- import { createRef, useCallback, useRef } from 'react'
7
- import { useI18N } from '@codeleap/i18n'
8
-
9
- export * as FormTypes from './types'
10
-
11
- function testBrowser() {
12
- try {
13
- // @ts-ignore
14
- return typeof localStorage !== 'undefined'
15
- } catch {
16
- return false
17
- }
18
- }
19
-
20
- const isBrowser = testBrowser()
21
-
22
- const SCOPE = 'useForm'
23
-
24
- const shouldLog = (
25
- x: FormTypes.FormStep,
26
- config: FormTypes.UseFormConfig<any>,
27
- ) => {
28
- return (config.log || []).includes(x)
29
- }
30
-
31
- export function useForm<
32
- Form extends FormTypes.CreateFormReturn<any>,
33
- FieldPaths extends FormTypes.PathsWithValues<
34
- FormTypes.FlattenFields<Form['config']>
35
- >,
36
- Values extends FormTypes.MapValues<Form['config']> = FormTypes.MapValues<
37
- Form['config']
38
- >
39
- >(formParam: (() => Form) | Form, formConfig: FormTypes.UseFormConfig<Values> = {}) {
40
-
41
- const i18n = useI18N()
42
-
43
- const form = useMemo(() => {
44
- return TypeGuards.isFunction(formParam) ? formParam() : formParam
45
- }, [formParam, i18n?.locale])
46
-
47
- const config:FormTypes.UseFormConfig<Values> = {
48
- validateOn: 'change',
49
- ...formConfig,
50
- }
51
-
52
- const getInitialState = useCallback(() => {
53
- return deepMerge(form.defaultValue, config.initialState || {}) as Values
54
- }, [form.defaultValue, config.initialState])
55
-
56
- const getInitialErrors = useCallback(() => {
57
- const errors = Object.keys(form.staticFieldProps).map((key) => [key, ''])
58
-
59
- return Object.fromEntries(errors)
60
- }, [form.staticFieldProps])
61
-
62
- const [formValues, setFormValues] = usePartialState<Values>(getInitialState)
63
- const [fieldErrors, setFieldErrors] = usePartialState(getInitialErrors)
64
- // @ts-ignore
65
- function setFieldValue(...args: FieldPaths) {
66
- // @ts-ignore
67
- const transform = form?.staticFieldProps[args[0]]?.transformer || (x => x)
68
- // @ts-ignore
69
- const val = deepSet([args[0], transform(args[1])])
70
-
71
- if (shouldLog('setValue', config) || shouldLog(`setValue.${args[0]}`, config)) {
72
- // @ts-ignore
73
- logger.log(
74
- // @ts-ignore
75
- `Set ${form.name}/${args[0]} to ${String(args[1])}`,
76
- '',
77
- SCOPE,
78
- )
79
- }
80
- setFormValues(val)
81
- }
82
-
83
- function validateField(field: FieldPaths[0], set = false, val?: any) {
84
- // @ts-ignore
85
- const { validate } = form.staticFieldProps[field as string]
86
-
87
- if (validate) {
88
- const value = val !== undefined ? val : deepGet(field, formValues)
89
- // @ts-ignore
90
- const result = validate(
91
- value,
92
- formValues,
93
- )
94
- if (set) {
95
- setFieldErrors(() => ({
96
- [field]: result.valid ? '' : result.message,
97
- }))
98
- }
99
-
100
- return result
101
- }
102
-
103
- return {
104
- valid: true,
105
- message: '',
106
- }
107
- }
108
-
109
- function validateMultiple<T extends readonly FieldPaths[0][]>(fields: T, set = false) {
110
- // @ts-ignore
111
- const results = fields.map((field) => [field, validateField(field, set)])
112
-
113
- const overallValid = results.every(([, result]) => result.valid)
114
-
115
- const fieldResults = Object.fromEntries(results) as Record<T[number], ReturnType<FormTypes.ValidatorFunction>>
116
-
117
- return {
118
- valid: overallValid,
119
- results: fieldResults,
120
- }
121
- }
122
-
123
- function validateAll(set = false) {
124
- const errors = { ...fieldErrors }
125
- for (const [path] of Object.entries(form.staticFieldProps)) {
126
- const result = validateField(path)
127
- errors[path] = result.valid ? '' : result?.message
128
- }
129
-
130
- if (set) {
131
- setFieldErrors(errors)
132
- }
133
- return Object.values(errors).join('').length === 0
134
- }
135
-
136
- const inputRefs = useRef([])
137
-
138
- function focus(idx:number) {
139
- // if (!(idx < inputRefs.current.length)) return
140
- const nextRef = inputRefs?.current?.[idx]?.current
141
- if (nextRef?.focus && nextRef?.isTextInput) {
142
- nextRef.focus?.()
143
- }
144
- }
145
-
146
- const registeredFields = useRef([])
147
- let registeredTextRefsOnThisRender = 0
148
- function register(field: FieldPaths[0], transformProps = (p) => p) {
149
- const nFields = registeredFields.current.length
150
-
151
- const { changeEventName, validate, type, componentProps, ...staticProps } =
152
- // @ts-ignore
153
- form.staticFieldProps[field as string]
154
-
155
- const dynamicProps: any = {
156
- value: deepGet(field, formValues),
157
- }
158
-
159
- if (type === 'number') {
160
- dynamicProps.value = Number.isNaN(dynamicProps.value) ? '' : String(dynamicProps.value)
161
- if (isBrowser) {
162
- dynamicProps.type = 'number'
163
- } else {
164
- dynamicProps.keyboardType = 'numeric'
165
- }
166
- }
167
-
168
- if (changeEventName) {
169
- dynamicProps[changeEventName] = (value) => {
170
- if (type === 'number') {
171
- value = Number(value)
172
- if (typeof staticProps.precision !== 'undefined') {
173
- value = Number(value.toFixed(staticProps.precision))
174
- }
175
- } else if (type === 'file') {
176
- value = TypeGuards.isArray(value) && !!value.length ? value[0] : null
177
- }
178
-
179
- // @ts-ignore
180
- setFieldValue(field, value)
181
- }
182
- }
183
-
184
- if (type === 'text' || type === 'number') {
185
- if (!isBrowser && !staticProps.multiline) {
186
- dynamicProps.returnKeyType = 'next'
187
- }
188
-
189
- const thisRefIdx = registeredTextRefsOnThisRender
190
-
191
- if (thisRefIdx >= inputRefs.current.length) {
192
- inputRefs.current.push(createRef())
193
- }
194
- dynamicProps.ref = inputRefs.current[thisRefIdx]
195
- registeredTextRefsOnThisRender++
196
- if (!isBrowser && !staticProps.multiline) {
197
- dynamicProps.onSubmitEditing = () => {
198
- const nextRef = thisRefIdx + 1
199
- if (inputRefs.current.length <= nextRef) return
200
- focus(nextRef)
201
- }
202
- }
203
-
204
- }
205
-
206
- registeredFields.current.push(type)
207
-
208
- const resolvedProps = {
209
- ...staticProps,
210
- ...dynamicProps,
211
- validate: (v) => {
212
- return validateField(field, true, v)
213
- },
214
- }
215
-
216
- const componentPropsConfig = TypeGuards.isFunction(componentProps) ? componentProps(resolvedProps) : (componentProps || {})
217
-
218
- const t = transformProps({
219
- ...resolvedProps,
220
- ...componentProps,
221
- ...componentPropsConfig,
222
- })
223
-
224
- return t
225
- }
226
-
227
- function getTransformedValue(): Values {
228
- return formValues as Values
229
- }
230
-
231
- async function onSubmit(cb: FunctionType<[Values], any>, e?: any) {
232
- if (e?.preventDefault) e.preventDefault()
233
-
234
- await cb(getTransformedValue())
235
- }
236
-
237
- function reset(states?: ('values'|'errors')[]) {
238
- const resetStates = states || ['values', 'errors']
239
- if (resetStates.includes('values')) {
240
- setFormValues(getInitialState())
241
- }
242
- if (resetStates.includes('errors')) {
243
- setFieldErrors(getInitialErrors())
244
- }
245
- }
246
-
247
- const setters = {} as FormTypes.FormSetters<Values>
248
-
249
- for (const fieldName in formValues) {
250
- // @ts-ignore
251
- setters[fieldName] = (value) => setFieldValue(fieldName, value)
252
- }
253
-
254
- return {
255
- setFieldValue,
256
- values: formValues as Values,
257
- register,
258
- validateAll,
259
- validateField,
260
- onSubmit,
261
- fieldErrors,
262
- getTransformedValue,
263
- setFormValues,
264
- reset,
265
- validateMultiple,
266
- isValid: validateAll(),
267
- setters,
268
- }
269
- }
package/src/utils.ts DELETED
@@ -1,55 +0,0 @@
1
- import { ReactNode, useMemo, useRef, useState } from 'react'
2
- import { onUpdate } from '@codeleap/hooks'
3
- import { ValidatorFunctionWithoutForm } from './types'
4
- import { getValidator } from './createForm'
5
-
6
- import * as yup from 'yup'
7
-
8
- const emptyValues = ['', null, undefined]
9
-
10
- export function useValidate(value: any, validator: yup.SchemaOf<any> | ValidatorFunctionWithoutForm) {
11
-
12
- const isEmpty = emptyValues.includes(value)
13
-
14
- const [_message, setMessage] = useState<ReactNode>('')
15
- const [isValid, setIsValid] = useState<boolean>(true)
16
- const updateErrorOnChange = useRef(false)
17
-
18
- const _validator = useMemo(() => getValidator(validator), [validator])
19
-
20
- onUpdate(() => {
21
- if (!updateErrorOnChange.current || !_validator) return
22
-
23
- const { valid, message } = _validator(value, {})
24
-
25
- setIsValid(valid)
26
- setMessage(message)
27
- }, [value])
28
-
29
- return {
30
- onInputBlurred: () => {
31
-
32
- if (!_validator) return
33
-
34
- updateErrorOnChange.current = false
35
- const { valid, message } = _validator(value, {})
36
-
37
- setIsValid(valid)
38
- setMessage(message)
39
-
40
- },
41
- onInputFocused: () => {
42
-
43
- if (isValid) return
44
- updateErrorOnChange.current = true
45
- },
46
- message: _message,
47
- isValid,
48
- showError: !isValid && !isEmpty,
49
- error: {
50
- message: _message,
51
- },
52
- }
53
-
54
- }
55
-