@codeleap/form 4.3.8 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeleap/form",
3
- "version": "4.3.8",
3
+ "version": "5.0.0",
4
4
  "main": "src/index.ts",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -9,23 +9,22 @@
9
9
  "directory": "packages/form"
10
10
  },
11
11
  "devDependencies": {
12
- "@codeleap/config": "4.3.8",
13
- "@codeleap/types": "4.3.8",
14
- "@codeleap/utils": "4.3.8",
15
- "@codeleap/hooks": "4.3.8",
16
- "@codeleap/i18n": "4.3.8",
12
+ "@codeleap/config": "5.0.0",
13
+ "@codeleap/types": "5.0.0",
14
+ "@codeleap/store": "5.0.0",
15
+ "zod": "3.23.8",
17
16
  "ts-node-dev": "1.1.8"
18
17
  },
19
18
  "scripts": {
20
- "build": "echo 'No build needed'"
19
+ "build": "echo 'No build needed'",
20
+ "playground": "bun src/test.ts"
21
21
  },
22
22
  "peerDependencies": {
23
- "@codeleap/types": "4.3.8",
24
- "@codeleap/utils": "4.3.8",
25
- "@codeleap/hooks": "4.3.8",
26
- "@codeleap/i18n": "4.3.8",
27
- "react": "18.1.0",
28
- "typescript": "5.0.4",
29
- "yup": "^0.32.11"
23
+ "@codeleap/types": "5.0.0",
24
+ "@codeleap/store": "5.0.0",
25
+ "zod": "*",
26
+ "react": "18.2.0",
27
+ "typescript": "5.5.2",
28
+ "yup": "1.6.1"
30
29
  }
31
30
  }
package/package.json.bak CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeleap/form",
3
- "version": "4.3.8",
3
+ "version": "5.0.0",
4
4
  "main": "src/index.ts",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -11,21 +11,20 @@
11
11
  "devDependencies": {
12
12
  "@codeleap/config": "workspace:*",
13
13
  "@codeleap/types": "workspace:*",
14
- "@codeleap/utils": "workspace:*",
15
- "@codeleap/hooks": "workspace:*",
16
- "@codeleap/i18n": "workspace:*",
14
+ "@codeleap/store": "workspace:*",
15
+ "zod": "3.23.8",
17
16
  "ts-node-dev": "1.1.8"
18
17
  },
19
18
  "scripts": {
20
- "build": "echo 'No build needed'"
19
+ "build": "echo 'No build needed'",
20
+ "playground": "bun src/test.ts"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "@codeleap/types": "workspace:*",
24
- "@codeleap/utils": "workspace:*",
25
- "@codeleap/hooks": "workspace:*",
26
- "@codeleap/i18n": "workspace:*",
27
- "react": "18.1.0",
28
- "typescript": "5.0.4",
29
- "yup": "^0.32.11"
24
+ "@codeleap/store": "workspace:*",
25
+ "zod": "*",
26
+ "react": "18.2.0",
27
+ "typescript": "5.5.2",
28
+ "yup": "1.6.1"
30
29
  }
31
30
  }
@@ -0,0 +1,21 @@
1
+ import { Field } from "../lib/Field"
2
+ import { z } from 'zod'
3
+ import { FieldOptions, Validator } from "../types"
4
+ import { zodValidator } from "../validators"
5
+
6
+ type VALUE = boolean
7
+
8
+ export type BooleanValidator<R = any, Err = any> = Validator<VALUE, R, Err>
9
+
10
+ type BooleanFieldOptions<Validate extends BooleanValidator> = FieldOptions<VALUE, Validate>
11
+
12
+ export class BooleanField<Validate extends BooleanValidator> extends Field<boolean, Validate> {
13
+ _type = "BOOLEAN"
14
+
15
+ constructor(options: BooleanFieldOptions<Validate>) {
16
+ super({
17
+ validate: zodValidator(z.boolean()) as unknown as Validate,
18
+ ...options
19
+ })
20
+ }
21
+ }
@@ -0,0 +1,22 @@
1
+ import { Field } from "../lib/Field"
2
+ import { z } from 'zod'
3
+ import { FieldOptions, Validator } from "../types"
4
+ import { zodValidator } from "../validators"
5
+
6
+ export type DateValidator<R = any, Err = any> = Validator<Date, R, Err>
7
+
8
+ type DateFieldOptions<Validate extends DateValidator> = FieldOptions<Date, Validate> & {
9
+ minimumDate?: Date
10
+ maximumDate?: Date
11
+ }
12
+
13
+ export class DateField<Validate extends DateValidator> extends Field<Date, Validate> {
14
+ _type = "DATE"
15
+
16
+ constructor(options: DateFieldOptions<Validate>) {
17
+ super({
18
+ validate: zodValidator(z.date()) as unknown as Validate,
19
+ ...options
20
+ })
21
+ }
22
+ }
@@ -0,0 +1,19 @@
1
+ import { Field } from "../lib/Field"
2
+ import { z } from 'zod'
3
+ import { FieldOptions, Validator } from "../types"
4
+ import { zodValidator } from "../validators"
5
+
6
+ export type FileValidator<R = any, Err = any> = Validator<any, R, Err>
7
+
8
+ type FileFieldOptions<Validate extends FileValidator> = FieldOptions<any, Validate>
9
+
10
+ export class FileField<Validate extends FileValidator> extends Field<any, Validate> {
11
+ _type = "FILE"
12
+
13
+ constructor(options: FileFieldOptions<Validate>) {
14
+ super({
15
+ validate: zodValidator(z.any()) as unknown as Validate,
16
+ ...options
17
+ })
18
+ }
19
+ }
@@ -0,0 +1,21 @@
1
+ import { Field } from "../lib/Field"
2
+ import { z } from 'zod'
3
+ import { FieldOptions, Validator } from "../types"
4
+ import { zodValidator } from "../validators"
5
+
6
+ type VALUE = string
7
+
8
+ type GroupValidator<R = any, Err = any> = Validator<VALUE, R, Err>
9
+
10
+ type GroupFieldOptions<Validate extends GroupValidator> = FieldOptions<VALUE, Validate>
11
+
12
+ export class GroupField<Validate extends GroupValidator> extends Field<string, Validate> {
13
+ _type = "group"
14
+
15
+ constructor(options: GroupFieldOptions<Validate>) {
16
+ super({
17
+ validate: zodValidator(z.string()) as unknown as Validate,
18
+ ...options
19
+ })
20
+ }
21
+ }
@@ -0,0 +1,28 @@
1
+ import { fieldFactory } from "../lib/factories"
2
+ import { TextField } from "./text"
3
+ import { ListField } from "./list"
4
+ import { NumberField } from "./number"
5
+ import { BooleanField } from "./bool"
6
+ import { SelectableField } from "./selectable"
7
+ import { DateField } from "./date"
8
+ import { FileField } from "./file"
9
+
10
+ export const fields = {
11
+ text: fieldFactory(TextField),
12
+ list: fieldFactory(ListField),
13
+ number: fieldFactory(NumberField),
14
+ boolean: fieldFactory(BooleanField),
15
+ selectable: fieldFactory(SelectableField),
16
+ date: fieldFactory(DateField),
17
+ file: fieldFactory(FileField),
18
+ }
19
+
20
+ export {
21
+ TextField,
22
+ ListField,
23
+ NumberField,
24
+ BooleanField,
25
+ SelectableField,
26
+ DateField,
27
+ FileField,
28
+ }
@@ -0,0 +1,36 @@
1
+ import { Field } from "../lib/Field"
2
+ import { FieldOptions, Validator } from "../types"
3
+
4
+ export type ListValidator<Val, R = any, Err = any> = Validator<Val[] | Val, R, Err>
5
+
6
+ type ListFieldOptions<T, Validate extends Validator<T | T[], any, any>> = FieldOptions<T, Validate> & {
7
+ item: Field<T, Validate>
8
+ }
9
+
10
+ export class ListField<T, Validate extends ListValidator<T>> extends Field<T | T[], Validate> {
11
+ _type = "LIST"
12
+
13
+ constructor(options: ListFieldOptions<T[] | T, Validate>) {
14
+ super({
15
+ // @ts-ignore fix this
16
+ validate: (v) => {
17
+ if (!options.validate) return {
18
+ isValid: true
19
+ }
20
+
21
+ for (const value of v as T[]) {
22
+ const validation = options.validate?.(value, null)
23
+
24
+ if (!validation.isValid) {
25
+ return validation
26
+ }
27
+ }
28
+
29
+ return {
30
+ isValid: true
31
+ }
32
+ },
33
+ ...options
34
+ })
35
+ }
36
+ }
@@ -0,0 +1,33 @@
1
+ import { Field } from "../lib/Field"
2
+ import { z } from 'zod'
3
+ import { FieldOptions, Validator } from "../types"
4
+ import { zodValidator } from "../validators"
5
+
6
+ export type NumberValidator<R = any, Err = any> = Validator<number | number[], R, Err>
7
+
8
+ type Props = {
9
+ min?: number
10
+ max?: number
11
+ }
12
+
13
+ type NumberFieldOptions<Validate extends NumberValidator> = FieldOptions<number | number[], Validate> & Props
14
+
15
+ export class NumberField<Validate extends NumberValidator> extends Field<number | number[], Validate> {
16
+ _type = "NUMBER"
17
+
18
+ constructor(options: NumberFieldOptions<Validate>) {
19
+ const { min = 0, max = 100000, defaultValue, ...others } = options
20
+
21
+ const isMultiple = Array.isArray(defaultValue)
22
+
23
+ const zScheme = z.number().min(min).max(max)
24
+
25
+ super({
26
+ validate: zodValidator(isMultiple ? z.array(zScheme) : zScheme) as unknown as Validate,
27
+ min,
28
+ max,
29
+ defaultValue,
30
+ ...others
31
+ } as NumberFieldOptions<Validate>)
32
+ }
33
+ }
@@ -0,0 +1,32 @@
1
+ import { Field } from "../lib/Field"
2
+ import { z } from 'zod'
3
+ import { FieldOptions, Validator } from "../types"
4
+ import { zodValidator } from "../validators"
5
+ import { Options } from '@codeleap/types'
6
+
7
+ type VALUE = string | number
8
+
9
+ export type SelectableValidator<V extends VALUE, R = any, Err = any> = Validator<V, R, Err>
10
+
11
+ type SelectableFieldOptions<V extends VALUE, Validate extends SelectableValidator<V>> = FieldOptions<V, Validate> & {
12
+ options: Options<V>
13
+ minItems?: number
14
+ maxItems?: number
15
+ }
16
+
17
+ export class SelectableField<V extends VALUE, Validate extends SelectableValidator<V>> extends Field<V, Validate> {
18
+ _type = "SELECTABLE"
19
+
20
+ constructor(options: SelectableFieldOptions<V, Validate>) {
21
+ const { minItems = 1, maxItems = options?.options?.length } = options
22
+
23
+ const zScheme = z.string().or(z.number())
24
+
25
+ super({
26
+ validate: zodValidator(z.array(zScheme).min(minItems).max(maxItems).or(zScheme)) as unknown as Validate,
27
+ minItems,
28
+ maxItems,
29
+ ...options,
30
+ })
31
+ }
32
+ }
@@ -0,0 +1,24 @@
1
+ import { Field } from "../lib/Field"
2
+ import { z } from 'zod'
3
+ import { FieldOptions, Validator } from "../types"
4
+ import { zodValidator } from "../validators"
5
+
6
+ type VALUE = string
7
+
8
+ export type TextValidator<R = any, Err = any> = Validator<VALUE, R, Err>
9
+
10
+ type TextFieldOptions<Validate extends TextValidator> = FieldOptions<VALUE, Validate> & {
11
+ secure?: boolean
12
+ multiline?: boolean
13
+ }
14
+
15
+ export class TextField<Validate extends TextValidator> extends Field<string, Validate> {
16
+ _type = "TEXT"
17
+
18
+ constructor(options: TextFieldOptions<Validate>) {
19
+ super({
20
+ validate: zodValidator(z.string()) as unknown as Validate,
21
+ ...options
22
+ })
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ export { useField } from './useField'
@@ -0,0 +1,14 @@
1
+ import { useMemo } from "react"
2
+ import type { Field } from "../lib"
3
+
4
+ export function useField<V, T extends Field<V, any, any>>(field: T, params: Parameters<T['use']>, defaultField: () => T): ReturnType<T['use']> {
5
+ if (field) {
6
+ return field.use(params?.[0], params?.[1]) as ReturnType<T['use']>
7
+ }
8
+
9
+ const tempField = useMemo(() => {
10
+ return defaultField()
11
+ }, [])
12
+
13
+ return tempField.use(params[0], params?.[1]) as ReturnType<T['use']>
14
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
- export * from './useForm'
2
- export * from './constants'
3
- export * from './createForm'
4
- export * from './utils'
5
- export * as Form from './types'
6
- export * as yup from 'yup'
1
+ export * from './fields'
2
+ export * from './types'
3
+ export * from './lib'
4
+ export * from './validators'
5
+ export * from './hooks'
@@ -0,0 +1,310 @@
1
+ import { FieldState, FieldOptions, FieldMeasureResult } from '../types/field';
2
+ import { ValidationResult, Validator } from '../types/validation';
3
+ import { IFieldRef, IFieldProps } from '../types/globals';
4
+ import { atom } from 'nanostores'
5
+ import { AnyRecord, SecondToLastArguments, TypeGuards } from '@codeleap/types';
6
+
7
+ import { useStore } from '@nanostores/react'
8
+ import { useFieldBinding } from './useFieldBinding';
9
+ import { createRef, useRef, useState } from 'react';
10
+
11
+ class ValidationError extends Error {
12
+ data: any
13
+
14
+ constructor(data: any) {
15
+ super()
16
+ this.data = data
17
+ }
18
+ }
19
+
20
+
21
+
22
+ export class Field<
23
+ T,
24
+ Validate extends Validator<T,any,any>,
25
+ Result = Validate extends Validator<T, infer R, any> ? R : never,
26
+ Err = Validate extends Validator<T, any, infer E> ? E : never
27
+ > {
28
+ _type: string
29
+
30
+ deep: boolean
31
+
32
+ state: FieldState<T>
33
+
34
+ properties: AnyRecord = {}
35
+
36
+ options: FieldOptions<T, Validate>
37
+
38
+ ref?: React.RefObject<IFieldRef<T>>
39
+
40
+ __validationRes: ValidationResult<Result, Err>
41
+
42
+ private initialValue: T
43
+
44
+ static enableLogs = false
45
+
46
+ static getProps = (field: Field<any,any>) => {
47
+ throw new Error('Field.getProps not implemented')
48
+ }
49
+
50
+ static methodMeasurePosition = (field: Field<any,any>, wrapperRef: any): Promise<FieldMeasureResult> => {
51
+ throw new Error('Field.measurePosition not implemented')
52
+ }
53
+
54
+ static methodScrollTo = (field: Field<any,any>, scrollRef: any, measure: FieldMeasureResult): Promise<void> => {
55
+ throw new Error('Field.scrollTo not implemented')
56
+ }
57
+
58
+ static methodGetPadding = (field: Field<any,any>): number => {
59
+ throw new Error('Field.getPadding not implemented')
60
+ }
61
+
62
+ constructor(options: FieldOptions<T, Validate>) {
63
+ this.options = options
64
+ this.ref = createRef()
65
+ this.loadState()
66
+
67
+ this.setValue = this.setValue.bind(this)
68
+ this.use = this.use.bind(this)
69
+ this.useBinding = this.useBinding.bind(this)
70
+
71
+ this.properties = this.toInternalProperties(options)
72
+ }
73
+
74
+ get name(){
75
+ return this.options.name
76
+ }
77
+
78
+ get value(){
79
+ return this.state.get()
80
+ }
81
+
82
+ get isValid(){
83
+ const res = this.validate()
84
+
85
+ return res.isValid
86
+ }
87
+
88
+ private toInternalProperties(options: FieldOptions<T, Validate>) {
89
+ const internalKeys = new Set(['name', 'defaultValue', 'state', 'validate', 'loader', 'onValueChange'])
90
+
91
+ return Object.assign(
92
+ { field: this },
93
+ Object.fromEntries(Object.entries(options).filter(([key]) => !internalKeys.has(key)))
94
+ )
95
+ }
96
+
97
+ attach(to: FieldState<T>) {
98
+ const val = this.value
99
+
100
+ this.state = to
101
+
102
+ this.setValue(val)
103
+ }
104
+
105
+ setValue(to: T) {
106
+ if (this.options.onValueChange) this.options.onValueChange(to)
107
+ return this.state.set(to)
108
+ }
109
+
110
+ useValue(){
111
+ const value = useStore(this.state)
112
+
113
+ return value
114
+ }
115
+
116
+ use(impl?: Partial<IFieldRef<T>>, deps?: any[]){
117
+ const value = this.useValue()
118
+
119
+ const validation = this.useValidation()
120
+
121
+ // Yes, this is dangerous and doesn't follow the rules, but not passing an implementation to imperative handle after passing it once would break the app anyway
122
+ if(impl) {
123
+ this.useBinding(impl, deps)
124
+ }
125
+
126
+ const changed = value != this.initialValue
127
+
128
+ return {
129
+ validation,
130
+ value,
131
+ setValue: this.setValue,
132
+ changed,
133
+ representation: this.toRepresentation(value),
134
+ options: this.options,
135
+ }
136
+ }
137
+
138
+ useBinding(...args: SecondToLastArguments<typeof useFieldBinding<T>>){
139
+ useFieldBinding(this.ref, ...args)
140
+
141
+ }
142
+
143
+ changed() {
144
+ return this.state.get() != this.initialValue
145
+ }
146
+
147
+ // If we make this async, the js engine will not delay further execution while the funcion is not done. This way we wait until we know wheter there's a promise or not
148
+ loadState(){
149
+ let defaultValuePromise: Promise<T> = undefined
150
+ let defaultValue:T = undefined
151
+
152
+ if(TypeGuards.isFunction(this.options.defaultValue)) {
153
+ const v = this.options.defaultValue()
154
+
155
+ if(TypeGuards.isPromise(v)) {
156
+ defaultValuePromise = v
157
+ } else {
158
+ defaultValue = v
159
+ }
160
+ } else {
161
+ const v = this.options?.defaultValue
162
+
163
+ if(TypeGuards.isPromise(v)) {
164
+ defaultValuePromise = v
165
+ } else {
166
+ defaultValue = v
167
+ }
168
+ }
169
+
170
+ this.state = this?.options?.state ?? atom(defaultValue)
171
+
172
+ if(!defaultValuePromise) {
173
+ this.initialValue = defaultValue
174
+ }
175
+
176
+ if(!!defaultValuePromise) {
177
+ this.log('debug', 'Waiting for initial value')
178
+
179
+ return defaultValuePromise
180
+ .then((v) => {
181
+ this.state.set(v)
182
+ this.initialValue = v
183
+ this.log('debug', `Got initial value`, v)
184
+ })
185
+ .catch(e => {
186
+ this.log('error', `Failed to resolve default value`, e)
187
+ })
188
+ } else {
189
+ return Promise.resolve()
190
+ }
191
+ }
192
+
193
+ formatLog(...args: any[]) {
194
+ const [firstArgument, ...otherArgs] = args
195
+
196
+ let PREFIX = `[FIELD: ${this.name}]`
197
+
198
+ if(TypeGuards.isString(firstArgument)) {
199
+ PREFIX += ` ${firstArgument}`
200
+ } else {
201
+ otherArgs.unshift(firstArgument)
202
+ }
203
+
204
+ const logArgs = [
205
+ PREFIX,
206
+ ...otherArgs
207
+ ]
208
+
209
+ return logArgs
210
+ }
211
+
212
+ validate(value?: any): ValidationResult<Result, Err> {
213
+
214
+ const validate = this.options.validate
215
+
216
+ const valueToCheck = TypeGuards.isUndefined(value) ? this.state.get() : this.toInternalValue(value)
217
+
218
+ try {
219
+ const result = validate(valueToCheck, {})
220
+
221
+ return result
222
+
223
+ } catch(e) {
224
+
225
+ if(e instanceof ValidationError) {
226
+ return {
227
+ isValid: false,
228
+ error: e.data
229
+ }
230
+ }
231
+
232
+ throw e
233
+ }
234
+
235
+ }
236
+
237
+ useValidation() {
238
+ const value = this.useValue()
239
+
240
+ const isUnset = typeof value === 'undefined'
241
+
242
+ const startedUnset = useRef(isUnset).current
243
+
244
+ const isSet = !isUnset
245
+
246
+ const validation = this.validate(value)
247
+
248
+ const isValid = validation.isValid
249
+
250
+ const isInvalid = !isValid
251
+
252
+ const hasChanged = this.initialValue != value
253
+
254
+ const message = validation.readableError ?? validation.error?.[0]?.message
255
+
256
+ const errorDisplayRequiresBlur = startedUnset
257
+
258
+
259
+ const [hasBlurred, setHasBlurred] = useState(false)
260
+
261
+ const showError = isInvalid && (errorDisplayRequiresBlur ? hasBlurred : true)
262
+
263
+
264
+
265
+ return {
266
+ onInputBlurred(){
267
+ setHasBlurred(true)
268
+ },
269
+ hasBlurred,
270
+ hasChanged,
271
+ startedUnset,
272
+ isSet,
273
+ isInvalid,
274
+ isValid,
275
+ message,
276
+ showError,
277
+ isUnset,
278
+ validation,
279
+ value
280
+ }
281
+ }
282
+
283
+ log(level = 'log', ...args: any[]){
284
+ if (Field.enableLogs) console[level](...this.formatLog(...args))
285
+ }
286
+
287
+ toInternalValue(v: any) {
288
+ return v as T
289
+ }
290
+
291
+ toRepresentation(v: T) {
292
+ return v as any
293
+ }
294
+
295
+ props(): IFieldProps {
296
+ return this.properties
297
+ }
298
+
299
+ measurePosition<T>(wrapperRef: T): Promise<FieldMeasureResult> {
300
+ return Field.methodMeasurePosition(this, wrapperRef)
301
+ }
302
+
303
+ scrollTo<T>(scrollRef: T, measure: FieldMeasureResult): Promise<void>{
304
+ return Field.methodScrollTo(this, scrollRef, measure)
305
+ }
306
+
307
+ getPadding(): number {
308
+ return Field.methodGetPadding(this)
309
+ }
310
+ }