@lucas-barake/effect-form 0.19.0 → 0.21.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/dist/{dts/Field.d.ts → Field.d.ts} +1 -0
- package/dist/Field.d.ts.map +1 -0
- package/dist/{esm/Field.js → Field.js} +29 -0
- package/dist/Field.js.map +1 -0
- package/dist/FieldState.d.ts +20 -0
- package/dist/FieldState.d.ts.map +1 -0
- package/dist/FieldState.js +2 -0
- package/dist/FieldState.js.map +1 -0
- package/dist/{dts/FormAtoms.d.ts → FormAtoms.d.ts} +26 -10
- package/dist/FormAtoms.d.ts.map +1 -0
- package/dist/{esm/FormAtoms.js → FormAtoms.js} +224 -17
- package/dist/FormAtoms.js.map +1 -0
- package/dist/{dts/FormBuilder.d.ts → FormBuilder.d.ts} +1 -1
- package/dist/FormBuilder.d.ts.map +1 -0
- package/dist/FormBuilder.js.map +1 -0
- package/dist/Mode.d.ts +34 -0
- package/dist/Mode.d.ts.map +1 -0
- package/dist/Mode.js +26 -0
- package/dist/Mode.js.map +1 -0
- package/dist/Path.d.ts.map +1 -0
- package/dist/Path.js.map +1 -0
- package/dist/Validation.d.ts.map +1 -0
- package/dist/{esm/Validation.js → Validation.js} +69 -36
- package/dist/Validation.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/dirty.d.ts.map +1 -0
- package/dist/internal/dirty.js.map +1 -0
- package/dist/internal/weak-registry.d.ts.map +1 -0
- package/dist/internal/weak-registry.js.map +1 -0
- package/package.json +33 -66
- package/src/Field.ts +82 -50
- package/src/FieldState.ts +22 -0
- package/src/FormAtoms.ts +554 -271
- package/src/FormBuilder.ts +102 -102
- package/src/Mode.ts +23 -23
- package/src/Path.ts +38 -38
- package/src/Validation.ts +108 -72
- package/src/index.ts +7 -28
- package/src/internal/dirty.ts +43 -43
- package/src/internal/weak-registry.ts +21 -21
- package/Field/package.json +0 -6
- package/FormAtoms/package.json +0 -6
- package/FormBuilder/package.json +0 -6
- package/Mode/package.json +0 -6
- package/Path/package.json +0 -6
- package/Validation/package.json +0 -6
- package/dist/cjs/Field.js +0 -94
- package/dist/cjs/Field.js.map +0 -1
- package/dist/cjs/FormAtoms.js +0 -362
- package/dist/cjs/FormAtoms.js.map +0 -1
- package/dist/cjs/FormBuilder.js +0 -107
- package/dist/cjs/FormBuilder.js.map +0 -1
- package/dist/cjs/Mode.js +0 -52
- package/dist/cjs/Mode.js.map +0 -1
- package/dist/cjs/Path.js +0 -71
- package/dist/cjs/Path.js.map +0 -1
- package/dist/cjs/Validation.js +0 -140
- package/dist/cjs/Validation.js.map +0 -1
- package/dist/cjs/index.js +0 -39
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/internal/dirty.js +0 -108
- package/dist/cjs/internal/dirty.js.map +0 -1
- package/dist/cjs/internal/weak-registry.js +0 -41
- package/dist/cjs/internal/weak-registry.js.map +0 -1
- package/dist/dts/Field.d.ts.map +0 -1
- package/dist/dts/FormAtoms.d.ts.map +0 -1
- package/dist/dts/FormBuilder.d.ts.map +0 -1
- package/dist/dts/Mode.d.ts +0 -29
- package/dist/dts/Mode.d.ts.map +0 -1
- package/dist/dts/Path.d.ts.map +0 -1
- package/dist/dts/Validation.d.ts.map +0 -1
- package/dist/dts/index.d.ts +0 -25
- package/dist/dts/index.d.ts.map +0 -1
- package/dist/dts/internal/dirty.d.ts.map +0 -1
- package/dist/dts/internal/weak-registry.d.ts.map +0 -1
- package/dist/esm/Field.js.map +0 -1
- package/dist/esm/FormAtoms.js.map +0 -1
- package/dist/esm/FormBuilder.js.map +0 -1
- package/dist/esm/Mode.js +0 -25
- package/dist/esm/Mode.js.map +0 -1
- package/dist/esm/Path.js.map +0 -1
- package/dist/esm/Validation.js.map +0 -1
- package/dist/esm/index.js +0 -25
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/internal/dirty.js.map +0 -1
- package/dist/esm/internal/weak-registry.js.map +0 -1
- package/dist/esm/package.json +0 -4
- /package/dist/{esm/FormBuilder.js → FormBuilder.js} +0 -0
- /package/dist/{dts/Path.d.ts → Path.d.ts} +0 -0
- /package/dist/{esm/Path.js → Path.js} +0 -0
- /package/dist/{dts/Validation.d.ts → Validation.d.ts} +0 -0
- /package/dist/{dts/internal → internal}/dirty.d.ts +0 -0
- /package/dist/{esm/internal → internal}/dirty.js +0 -0
- /package/dist/{dts/internal → internal}/weak-registry.d.ts +0 -0
- /package/dist/{esm/internal → internal}/weak-registry.js +0 -0
package/src/FormAtoms.ts
CHANGED
|
@@ -1,88 +1,107 @@
|
|
|
1
|
-
import * as Atom from "@effect-atom/atom/Atom"
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import * as
|
|
7
|
-
import * as
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import * as
|
|
1
|
+
import * as Atom from "@effect-atom/atom/Atom"
|
|
2
|
+
import * as Cause from "effect/Cause"
|
|
3
|
+
import * as Effect from "effect/Effect"
|
|
4
|
+
import { pipe } from "effect/Function"
|
|
5
|
+
import * as Option from "effect/Option"
|
|
6
|
+
import * as ParseResult from "effect/ParseResult"
|
|
7
|
+
import * as Schema from "effect/Schema"
|
|
8
|
+
import * as Field from "./Field.ts"
|
|
9
|
+
import * as FormBuilder from "./FormBuilder.ts"
|
|
10
|
+
import { recalculateDirtyFieldsForArray, recalculateDirtySubtree } from "./internal/dirty.ts"
|
|
11
|
+
import { createWeakRegistry, type WeakRegistry } from "./internal/weak-registry.ts"
|
|
12
|
+
import * as Mode from "./Mode.ts"
|
|
13
|
+
import { getNestedValue, isPathOrParentDirty, setNestedValue } from "./Path.ts"
|
|
14
|
+
import * as Validation from "./Validation.ts"
|
|
13
15
|
|
|
14
16
|
export interface FieldAtoms {
|
|
15
|
-
readonly valueAtom: Atom.Writable<unknown, unknown
|
|
16
|
-
readonly initialValueAtom: Atom.Atom<unknown
|
|
17
|
-
readonly touchedAtom: Atom.Writable<boolean, boolean
|
|
18
|
-
readonly errorAtom: Atom.Atom<Option.Option<Validation.ErrorEntry
|
|
19
|
-
readonly isDirtyAtom: Atom.Atom<boolean
|
|
17
|
+
readonly valueAtom: Atom.Writable<unknown, unknown>
|
|
18
|
+
readonly initialValueAtom: Atom.Atom<unknown>
|
|
19
|
+
readonly touchedAtom: Atom.Writable<boolean, boolean>
|
|
20
|
+
readonly errorAtom: Atom.Atom<Option.Option<Validation.ErrorEntry>>
|
|
21
|
+
readonly isDirtyAtom: Atom.Atom<boolean>
|
|
22
|
+
readonly validationAtom: Atom.AtomResultFn<unknown, void, ParseResult.ParseError>
|
|
23
|
+
readonly displayErrorAtom: Atom.Atom<Option.Option<string>>
|
|
24
|
+
readonly shouldValidateAtom: Atom.Atom<boolean>
|
|
25
|
+
readonly triggerValidationAtom: Atom.Atom<void>
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
export interface
|
|
23
|
-
readonly
|
|
24
|
-
readonly
|
|
25
|
-
readonly
|
|
28
|
+
export interface PublicFieldAtoms<E,> {
|
|
29
|
+
readonly value: Atom.Atom<Option.Option<E>>
|
|
30
|
+
readonly error: Atom.Atom<Option.Option<string>>
|
|
31
|
+
readonly isDirty: Atom.Atom<boolean>
|
|
32
|
+
readonly isTouched: Atom.Atom<boolean>
|
|
33
|
+
readonly isValidating: Atom.Atom<boolean>
|
|
34
|
+
readonly setValue: Atom.Writable<void, E | ((prev: E) => E)>
|
|
35
|
+
readonly setTouched: Atom.Writable<void, boolean>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type SetValuesArg<TFields extends Field.FieldsRecord,> =
|
|
39
|
+
| Field.EncodedFromFields<TFields>
|
|
40
|
+
| ((prev: Field.EncodedFromFields<TFields>) => Field.EncodedFromFields<TFields>)
|
|
41
|
+
|
|
42
|
+
export interface FormAtomsConfig<TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = void,> {
|
|
43
|
+
readonly runtime: Atom.AtomRuntime<R, any>
|
|
44
|
+
readonly formBuilder: FormBuilder.FormBuilder<TFields, R>
|
|
45
|
+
readonly mode?: Mode.FormMode
|
|
46
|
+
readonly reactivityKeys?: ReadonlyArray<unknown> | Readonly<Record<string, ReadonlyArray<unknown>>> | undefined
|
|
26
47
|
readonly onSubmit: (
|
|
27
48
|
args: SubmitArgs,
|
|
28
49
|
ctx: {
|
|
29
|
-
readonly decoded: Field.DecodedFromFields<TFields
|
|
30
|
-
readonly encoded: Field.EncodedFromFields<TFields
|
|
31
|
-
readonly get: Atom.FnContext
|
|
32
|
-
}
|
|
33
|
-
) => A | Effect.Effect<A, E, R
|
|
50
|
+
readonly decoded: Field.DecodedFromFields<TFields>
|
|
51
|
+
readonly encoded: Field.EncodedFromFields<TFields>
|
|
52
|
+
readonly get: Atom.FnContext
|
|
53
|
+
}
|
|
54
|
+
) => A | Effect.Effect<A, E, R>
|
|
34
55
|
}
|
|
35
56
|
|
|
36
|
-
export type FieldRefs<TFields extends Field.FieldsRecord
|
|
57
|
+
export type FieldRefs<TFields extends Field.FieldsRecord,> = {
|
|
37
58
|
readonly [K in keyof TFields]: TFields[K] extends Field.FieldDef<any, infer S>
|
|
38
59
|
? FormBuilder.FieldRef<Schema.Schema.Encoded<S>>
|
|
39
60
|
: TFields[K] extends Field.ArrayFieldDef<any, infer S>
|
|
40
61
|
? FormBuilder.FieldRef<ReadonlyArray<Schema.Schema.Encoded<S>>>
|
|
41
|
-
: never
|
|
42
|
-
}
|
|
62
|
+
: never
|
|
63
|
+
}
|
|
43
64
|
|
|
44
|
-
export interface FormAtoms<TFields extends Field.FieldsRecord, R, A = void, E = never, SubmitArgs = void
|
|
65
|
+
export interface FormAtoms<TFields extends Field.FieldsRecord, R, A = void, E = never, SubmitArgs = void,> {
|
|
45
66
|
readonly stateAtom: Atom.Writable<
|
|
46
67
|
Option.Option<FormBuilder.FormState<TFields>>,
|
|
47
68
|
Option.Option<FormBuilder.FormState<TFields>>
|
|
48
|
-
|
|
49
|
-
readonly errorsAtom: Atom.Writable<Map<string, Validation.ErrorEntry>, Map<string, Validation.ErrorEntry
|
|
50
|
-
readonly rootErrorAtom: Atom.Atom<Option.Option<string
|
|
51
|
-
readonly valuesAtom: Atom.Atom<Option.Option<Field.EncodedFromFields<TFields
|
|
52
|
-
readonly dirtyFieldsAtom: Atom.Atom<ReadonlySet<string
|
|
53
|
-
readonly isDirtyAtom: Atom.Atom<boolean
|
|
54
|
-
readonly submitCountAtom: Atom.Atom<number
|
|
55
|
-
readonly lastSubmittedValuesAtom: Atom.Atom<Option.Option<FormBuilder.SubmittedValues<TFields
|
|
56
|
-
readonly changedSinceSubmitFieldsAtom: Atom.Atom<ReadonlySet<string
|
|
57
|
-
readonly hasChangedSinceSubmitAtom: Atom.Atom<boolean
|
|
69
|
+
>
|
|
70
|
+
readonly errorsAtom: Atom.Writable<Map<string, Validation.ErrorEntry>, Map<string, Validation.ErrorEntry>>
|
|
71
|
+
readonly rootErrorAtom: Atom.Atom<Option.Option<string>>
|
|
72
|
+
readonly valuesAtom: Atom.Atom<Option.Option<Field.EncodedFromFields<TFields>>>
|
|
73
|
+
readonly dirtyFieldsAtom: Atom.Atom<ReadonlySet<string>>
|
|
74
|
+
readonly isDirtyAtom: Atom.Atom<boolean>
|
|
75
|
+
readonly submitCountAtom: Atom.Atom<number>
|
|
76
|
+
readonly lastSubmittedValuesAtom: Atom.Atom<Option.Option<FormBuilder.SubmittedValues<TFields>>>
|
|
77
|
+
readonly changedSinceSubmitFieldsAtom: Atom.Atom<ReadonlySet<string>>
|
|
78
|
+
readonly hasChangedSinceSubmitAtom: Atom.Atom<boolean>
|
|
58
79
|
|
|
59
|
-
readonly submitAtom: Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError
|
|
80
|
+
readonly submitAtom: Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError>
|
|
60
81
|
|
|
61
|
-
readonly combinedSchema: Schema.Schema<Field.DecodedFromFields<TFields>, Field.EncodedFromFields<TFields>, R
|
|
82
|
+
readonly combinedSchema: Schema.Schema<Field.DecodedFromFields<TFields>, Field.EncodedFromFields<TFields>, R>
|
|
62
83
|
|
|
63
|
-
readonly fieldRefs: FieldRefs<TFields
|
|
84
|
+
readonly fieldRefs: FieldRefs<TFields>
|
|
64
85
|
|
|
65
|
-
readonly validationAtomsRegistry: WeakRegistry<Atom.AtomResultFn<unknown, void, ParseResult.ParseError
|
|
66
|
-
readonly fieldAtomsRegistry: WeakRegistry<FieldAtoms
|
|
86
|
+
readonly validationAtomsRegistry: WeakRegistry<Atom.AtomResultFn<unknown, void, ParseResult.ParseError>>
|
|
87
|
+
readonly fieldAtomsRegistry: WeakRegistry<FieldAtoms>
|
|
67
88
|
|
|
68
89
|
readonly getOrCreateValidationAtom: (
|
|
69
90
|
fieldPath: string,
|
|
70
|
-
schema: Schema.Schema.Any
|
|
71
|
-
) => Atom.AtomResultFn<unknown, void, ParseResult.ParseError
|
|
91
|
+
schema: Schema.Schema.Any
|
|
92
|
+
) => Atom.AtomResultFn<unknown, void, ParseResult.ParseError>
|
|
72
93
|
|
|
73
|
-
readonly getOrCreateFieldAtoms: (fieldPath: string) => FieldAtoms
|
|
94
|
+
readonly getOrCreateFieldAtoms: (fieldPath: string, schema: Schema.Schema.Any) => FieldAtoms
|
|
74
95
|
|
|
75
|
-
readonly resetValidationAtoms: (ctx: { set: <R, W
|
|
96
|
+
readonly resetValidationAtoms: (ctx: { set: <R, W,>(atom: Atom.Writable<R, W>, value: W) => void }) => void
|
|
76
97
|
|
|
77
|
-
readonly operations: FormOperations<TFields
|
|
98
|
+
readonly operations: FormOperations<TFields>
|
|
78
99
|
|
|
79
|
-
readonly resetAtom: Atom.Writable<void, void
|
|
80
|
-
readonly revertToLastSubmitAtom: Atom.Writable<void, void
|
|
81
|
-
readonly setValuesAtom: Atom.Writable<void,
|
|
82
|
-
readonly setValue: <S>(field: FormBuilder.FieldRef<S>) => Atom.Writable<void, S | ((prev: S) => S)>;
|
|
100
|
+
readonly resetAtom: Atom.Writable<void, void>
|
|
101
|
+
readonly revertToLastSubmitAtom: Atom.Writable<void, void>
|
|
102
|
+
readonly setValuesAtom: Atom.Writable<void, SetValuesArg<TFields>>
|
|
83
103
|
|
|
84
|
-
readonly
|
|
85
|
-
readonly getFieldIsDirty: (field: FormBuilder.FieldRef<any>) => Atom.Atom<boolean>;
|
|
104
|
+
readonly getFieldAtoms: <S,>(field: FormBuilder.FieldRef<S>) => PublicFieldAtoms<S>
|
|
86
105
|
|
|
87
106
|
/**
|
|
88
107
|
* Root anchor atom for the form's dependency graph.
|
|
@@ -102,110 +121,114 @@ export interface FormAtoms<TFields extends Field.FieldsRecord, R, A = void, E =
|
|
|
102
121
|
* }
|
|
103
122
|
* ```
|
|
104
123
|
*/
|
|
105
|
-
readonly
|
|
124
|
+
readonly autoSubmitAtom: Atom.Atom<void>
|
|
125
|
+
readonly onBlurSubmitAtom: Atom.Writable<void, void>
|
|
106
126
|
|
|
107
|
-
readonly
|
|
127
|
+
readonly mountAtom: Atom.Atom<void>
|
|
128
|
+
|
|
129
|
+
readonly keepAliveActiveAtom: Atom.Writable<boolean, boolean>
|
|
108
130
|
}
|
|
109
131
|
|
|
110
|
-
export interface FormOperations<TFields extends Field.FieldsRecord
|
|
111
|
-
readonly createInitialState: (defaultValues: Field.EncodedFromFields<TFields>) => FormBuilder.FormState<TFields
|
|
132
|
+
export interface FormOperations<TFields extends Field.FieldsRecord,> {
|
|
133
|
+
readonly createInitialState: (defaultValues: Field.EncodedFromFields<TFields>) => FormBuilder.FormState<TFields>
|
|
112
134
|
|
|
113
|
-
readonly createResetState: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields
|
|
135
|
+
readonly createResetState: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields>
|
|
114
136
|
|
|
115
|
-
readonly createSubmitState: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields
|
|
137
|
+
readonly createSubmitState: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields>
|
|
116
138
|
|
|
117
139
|
readonly setFieldValue: (
|
|
118
140
|
state: FormBuilder.FormState<TFields>,
|
|
119
141
|
fieldPath: string,
|
|
120
|
-
value: unknown
|
|
121
|
-
) => FormBuilder.FormState<TFields
|
|
142
|
+
value: unknown
|
|
143
|
+
) => FormBuilder.FormState<TFields>
|
|
122
144
|
|
|
123
145
|
readonly setFormValues: (
|
|
124
146
|
state: FormBuilder.FormState<TFields>,
|
|
125
|
-
values: Field.EncodedFromFields<TFields
|
|
126
|
-
) => FormBuilder.FormState<TFields
|
|
147
|
+
values: Field.EncodedFromFields<TFields>
|
|
148
|
+
) => FormBuilder.FormState<TFields>
|
|
127
149
|
|
|
128
150
|
readonly setFieldTouched: (
|
|
129
151
|
state: FormBuilder.FormState<TFields>,
|
|
130
152
|
fieldPath: string,
|
|
131
|
-
touched: boolean
|
|
132
|
-
) => FormBuilder.FormState<TFields
|
|
153
|
+
touched: boolean
|
|
154
|
+
) => FormBuilder.FormState<TFields>
|
|
133
155
|
|
|
134
156
|
readonly appendArrayItem: (
|
|
135
157
|
state: FormBuilder.FormState<TFields>,
|
|
136
158
|
arrayPath: string,
|
|
137
159
|
itemSchema: Schema.Schema.Any,
|
|
138
|
-
value?: unknown
|
|
139
|
-
) => FormBuilder.FormState<TFields
|
|
160
|
+
value?: unknown
|
|
161
|
+
) => FormBuilder.FormState<TFields>
|
|
140
162
|
|
|
141
163
|
readonly removeArrayItem: (
|
|
142
164
|
state: FormBuilder.FormState<TFields>,
|
|
143
165
|
arrayPath: string,
|
|
144
|
-
index: number
|
|
145
|
-
) => FormBuilder.FormState<TFields
|
|
166
|
+
index: number
|
|
167
|
+
) => FormBuilder.FormState<TFields>
|
|
146
168
|
|
|
147
169
|
readonly swapArrayItems: (
|
|
148
170
|
state: FormBuilder.FormState<TFields>,
|
|
149
171
|
arrayPath: string,
|
|
150
172
|
indexA: number,
|
|
151
|
-
indexB: number
|
|
152
|
-
) => FormBuilder.FormState<TFields
|
|
173
|
+
indexB: number
|
|
174
|
+
) => FormBuilder.FormState<TFields>
|
|
153
175
|
|
|
154
176
|
readonly moveArrayItem: (
|
|
155
177
|
state: FormBuilder.FormState<TFields>,
|
|
156
178
|
arrayPath: string,
|
|
157
179
|
fromIndex: number,
|
|
158
|
-
toIndex: number
|
|
159
|
-
) => FormBuilder.FormState<TFields
|
|
180
|
+
toIndex: number
|
|
181
|
+
) => FormBuilder.FormState<TFields>
|
|
160
182
|
|
|
161
|
-
readonly revertToLastSubmit: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields
|
|
183
|
+
readonly revertToLastSubmit: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields>
|
|
162
184
|
}
|
|
163
185
|
|
|
164
|
-
export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = void
|
|
165
|
-
config: FormAtomsConfig<TFields, R, A, E, SubmitArgs
|
|
186
|
+
export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = void,>(
|
|
187
|
+
config: FormAtomsConfig<TFields, R, A, E, SubmitArgs>
|
|
166
188
|
): FormAtoms<TFields, R, A, E, SubmitArgs> => {
|
|
167
|
-
const { formBuilder, runtime } = config
|
|
168
|
-
const { fields } = formBuilder
|
|
189
|
+
const { formBuilder, runtime } = config
|
|
190
|
+
const { fields } = formBuilder
|
|
191
|
+
const parsedMode = Mode.parse(config.mode)
|
|
169
192
|
|
|
170
|
-
const combinedSchema = FormBuilder.buildSchema(formBuilder)
|
|
193
|
+
const combinedSchema = FormBuilder.buildSchema(formBuilder)
|
|
171
194
|
|
|
172
|
-
const stateAtom = Atom.make(Option.none<FormBuilder.FormState<TFields>>()).pipe(Atom.setIdleTTL(0))
|
|
173
|
-
const errorsAtom = Atom.make<Map<string, Validation.ErrorEntry>>(new Map()).pipe(Atom.setIdleTTL(0))
|
|
195
|
+
const stateAtom = Atom.make(Option.none<FormBuilder.FormState<TFields>>()).pipe(Atom.setIdleTTL(0))
|
|
196
|
+
const errorsAtom = Atom.make<Map<string, Validation.ErrorEntry>>(new Map()).pipe(Atom.setIdleTTL(0))
|
|
174
197
|
|
|
175
198
|
const rootErrorAtom = Atom.readable((get) => {
|
|
176
|
-
const errors = get(errorsAtom)
|
|
177
|
-
const entry = errors.get("")
|
|
178
|
-
return entry ? Option.some(entry.message) : Option.none<string>()
|
|
179
|
-
}).pipe(Atom.setIdleTTL(0))
|
|
199
|
+
const errors = get(errorsAtom)
|
|
200
|
+
const entry = errors.get("")
|
|
201
|
+
return entry ? Option.some(entry.message) : Option.none<string>()
|
|
202
|
+
}).pipe(Atom.setIdleTTL(0))
|
|
180
203
|
|
|
181
204
|
const valuesAtom = Atom.readable((get) => Option.map(get(stateAtom), (state) => state.values)).pipe(
|
|
182
|
-
Atom.setIdleTTL(0)
|
|
183
|
-
)
|
|
205
|
+
Atom.setIdleTTL(0)
|
|
206
|
+
)
|
|
184
207
|
|
|
185
208
|
const dirtyFieldsAtom = Atom.readable((get) =>
|
|
186
209
|
Option.match(get(stateAtom), {
|
|
187
210
|
onNone: () => new Set<string>(),
|
|
188
|
-
onSome: (state) => state.dirtyFields
|
|
211
|
+
onSome: (state) => state.dirtyFields
|
|
189
212
|
})
|
|
190
|
-
).pipe(Atom.setIdleTTL(0))
|
|
213
|
+
).pipe(Atom.setIdleTTL(0))
|
|
191
214
|
|
|
192
215
|
const isDirtyAtom = Atom.readable((get) =>
|
|
193
216
|
Option.match(get(stateAtom), {
|
|
194
217
|
onNone: () => false,
|
|
195
|
-
onSome: (state) => state.dirtyFields.size > 0
|
|
218
|
+
onSome: (state) => state.dirtyFields.size > 0
|
|
196
219
|
})
|
|
197
|
-
).pipe(Atom.setIdleTTL(0))
|
|
220
|
+
).pipe(Atom.setIdleTTL(0))
|
|
198
221
|
|
|
199
222
|
const submitCountAtom = Atom.readable((get) =>
|
|
200
223
|
Option.match(get(stateAtom), {
|
|
201
224
|
onNone: () => 0,
|
|
202
|
-
onSome: (state) => state.submitCount
|
|
225
|
+
onSome: (state) => state.submitCount
|
|
203
226
|
})
|
|
204
|
-
).pipe(Atom.setIdleTTL(0))
|
|
227
|
+
).pipe(Atom.setIdleTTL(0))
|
|
205
228
|
|
|
206
229
|
const lastSubmittedValuesAtom = Atom.readable((get) =>
|
|
207
230
|
Option.flatMap(get(stateAtom), (state) => state.lastSubmittedValues)
|
|
208
|
-
).pipe(Atom.setIdleTTL(0))
|
|
231
|
+
).pipe(Atom.setIdleTTL(0))
|
|
209
232
|
|
|
210
233
|
const changedSinceSubmitFieldsAtom = Atom.readable((get) =>
|
|
211
234
|
Option.match(get(stateAtom), {
|
|
@@ -213,110 +236,229 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
|
|
|
213
236
|
onSome: (state) =>
|
|
214
237
|
Option.match(state.lastSubmittedValues, {
|
|
215
238
|
onNone: () => new Set<string>(),
|
|
216
|
-
onSome: (lastSubmitted) => recalculateDirtySubtree(new Set(), lastSubmitted.encoded, state.values, "")
|
|
217
|
-
})
|
|
239
|
+
onSome: (lastSubmitted) => recalculateDirtySubtree(new Set(), lastSubmitted.encoded, state.values, "")
|
|
240
|
+
})
|
|
218
241
|
})
|
|
219
|
-
).pipe(Atom.setIdleTTL(0))
|
|
242
|
+
).pipe(Atom.setIdleTTL(0))
|
|
220
243
|
|
|
221
244
|
const hasChangedSinceSubmitAtom = Atom.readable((get) =>
|
|
222
245
|
Option.match(get(stateAtom), {
|
|
223
246
|
onNone: () => false,
|
|
224
247
|
onSome: (state) => {
|
|
225
|
-
if (Option.isNone(state.lastSubmittedValues)) return false
|
|
226
|
-
if (state.values === state.lastSubmittedValues.value.encoded) return false
|
|
227
|
-
return get(changedSinceSubmitFieldsAtom).size > 0
|
|
228
|
-
}
|
|
248
|
+
if (Option.isNone(state.lastSubmittedValues)) return false
|
|
249
|
+
if (state.values === state.lastSubmittedValues.value.encoded) return false
|
|
250
|
+
return get(changedSinceSubmitFieldsAtom).size > 0
|
|
251
|
+
}
|
|
229
252
|
})
|
|
230
|
-
).pipe(Atom.setIdleTTL(0))
|
|
231
|
-
|
|
232
|
-
const validationAtomsRegistry = createWeakRegistry<Atom.AtomResultFn<unknown, void, ParseResult.ParseError>>()
|
|
233
|
-
const fieldAtomsRegistry = createWeakRegistry<FieldAtoms>()
|
|
234
|
-
const
|
|
253
|
+
).pipe(Atom.setIdleTTL(0))
|
|
254
|
+
|
|
255
|
+
const validationAtomsRegistry = createWeakRegistry<Atom.AtomResultFn<unknown, void, ParseResult.ParseError>>()
|
|
256
|
+
const fieldAtomsRegistry = createWeakRegistry<FieldAtoms>()
|
|
257
|
+
const publicFieldAtomsRegistry = createWeakRegistry<PublicFieldAtoms<unknown>>()
|
|
258
|
+
const validationSchemaRegistry = new Map<string, Schema.Schema.Any>()
|
|
259
|
+
const fieldSchemaRegistry = new Map<string, Schema.Schema.Any>()
|
|
260
|
+
const isDirtyAtomsRegistry = createWeakRegistry<Atom.Atom<boolean>>()
|
|
261
|
+
|
|
262
|
+
const fieldSchemasByKey = new Map<string, Schema.Schema.Any>()
|
|
263
|
+
for (const [key, def] of Object.entries(fields)) {
|
|
264
|
+
if (Field.isArrayFieldDef(def)) {
|
|
265
|
+
fieldSchemasByKey.set(key, Schema.Array(def.itemSchema))
|
|
266
|
+
} else if (Field.isFieldDef(def)) {
|
|
267
|
+
fieldSchemasByKey.set(key, def.schema)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
235
270
|
|
|
236
271
|
const getOrCreateValidationAtom = (
|
|
237
272
|
fieldPath: string,
|
|
238
|
-
schema: Schema.Schema.Any
|
|
273
|
+
schema: Schema.Schema.Any
|
|
239
274
|
): Atom.AtomResultFn<unknown, void, ParseResult.ParseError> => {
|
|
240
|
-
const existing = validationAtomsRegistry.get(fieldPath)
|
|
241
|
-
|
|
275
|
+
const existing = validationAtomsRegistry.get(fieldPath)
|
|
276
|
+
const existingSchema = validationSchemaRegistry.get(fieldPath)
|
|
277
|
+
if (existing && existingSchema === schema) return existing
|
|
242
278
|
|
|
243
279
|
const validationAtom = runtime
|
|
244
280
|
.fn<unknown>()((value: unknown) =>
|
|
245
281
|
pipe(Schema.decodeUnknown(schema)(value) as Effect.Effect<unknown, ParseResult.ParseError, R>, Effect.asVoid)
|
|
246
282
|
)
|
|
247
|
-
.pipe(Atom.setIdleTTL(0)) as Atom.AtomResultFn<unknown, void, ParseResult.ParseError
|
|
283
|
+
.pipe(Atom.setIdleTTL(0)) as Atom.AtomResultFn<unknown, void, ParseResult.ParseError>
|
|
248
284
|
|
|
249
|
-
validationAtomsRegistry.set(fieldPath, validationAtom)
|
|
250
|
-
|
|
251
|
-
|
|
285
|
+
validationAtomsRegistry.set(fieldPath, validationAtom)
|
|
286
|
+
validationSchemaRegistry.set(fieldPath, schema)
|
|
287
|
+
return validationAtom
|
|
288
|
+
}
|
|
252
289
|
|
|
253
|
-
const getOrCreateFieldAtoms = (fieldPath: string): FieldAtoms => {
|
|
254
|
-
const existing = fieldAtomsRegistry.get(fieldPath)
|
|
255
|
-
|
|
290
|
+
const getOrCreateFieldAtoms = (fieldPath: string, schema: Schema.Schema.Any): FieldAtoms => {
|
|
291
|
+
const existing = fieldAtomsRegistry.get(fieldPath)
|
|
292
|
+
const existingSchema = fieldSchemaRegistry.get(fieldPath)
|
|
293
|
+
if (existing && existingSchema === schema) return existing
|
|
256
294
|
|
|
257
295
|
const valueAtom = Atom.writable(
|
|
258
296
|
(get) => getNestedValue(Option.getOrThrow(get(stateAtom)).values, fieldPath),
|
|
259
297
|
(ctx, value) => {
|
|
260
|
-
const currentState = Option.getOrThrow(ctx.get(stateAtom))
|
|
261
|
-
ctx.set(stateAtom, Option.some(operations.setFieldValue(currentState, fieldPath, value)))
|
|
262
|
-
}
|
|
263
|
-
).pipe(Atom.setIdleTTL(0))
|
|
298
|
+
const currentState = Option.getOrThrow(ctx.get(stateAtom))
|
|
299
|
+
ctx.set(stateAtom, Option.some(operations.setFieldValue(currentState, fieldPath, value)))
|
|
300
|
+
}
|
|
301
|
+
).pipe(Atom.setIdleTTL(0))
|
|
264
302
|
|
|
265
303
|
const initialValueAtom = Atom.readable((get) =>
|
|
266
304
|
getNestedValue(Option.getOrThrow(get(stateAtom)).initialValues, fieldPath)
|
|
267
|
-
).pipe(Atom.setIdleTTL(0))
|
|
305
|
+
).pipe(Atom.setIdleTTL(0))
|
|
268
306
|
|
|
269
307
|
const touchedAtom = Atom.writable(
|
|
270
308
|
(get) => (getNestedValue(Option.getOrThrow(get(stateAtom)).touched, fieldPath) ?? false) as boolean,
|
|
271
309
|
(ctx, value) => {
|
|
272
|
-
const currentState = Option.getOrThrow(ctx.get(stateAtom))
|
|
310
|
+
const currentState = Option.getOrThrow(ctx.get(stateAtom))
|
|
273
311
|
ctx.set(
|
|
274
312
|
stateAtom,
|
|
275
313
|
Option.some({
|
|
276
314
|
...currentState,
|
|
277
|
-
touched: setNestedValue(currentState.touched, fieldPath, value)
|
|
278
|
-
})
|
|
279
|
-
)
|
|
280
|
-
}
|
|
281
|
-
).pipe(Atom.setIdleTTL(0))
|
|
315
|
+
touched: setNestedValue(currentState.touched, fieldPath, value)
|
|
316
|
+
})
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
).pipe(Atom.setIdleTTL(0))
|
|
282
320
|
|
|
283
321
|
const errorAtom = Atom.readable((get) => {
|
|
284
|
-
const errors = get(errorsAtom)
|
|
285
|
-
const entry = errors.get(fieldPath)
|
|
286
|
-
return entry ? Option.some(entry) : Option.none<Validation.ErrorEntry>()
|
|
287
|
-
}).pipe(Atom.setIdleTTL(0))
|
|
322
|
+
const errors = get(errorsAtom)
|
|
323
|
+
const entry = errors.get(fieldPath)
|
|
324
|
+
return entry ? Option.some(entry) : Option.none<Validation.ErrorEntry>()
|
|
325
|
+
}).pipe(Atom.setIdleTTL(0))
|
|
288
326
|
|
|
289
|
-
const
|
|
327
|
+
const existingIsDirtyAtom = isDirtyAtomsRegistry.get(fieldPath)
|
|
328
|
+
const isDirtyAtom = existingIsDirtyAtom ?? Atom.readable((get) =>
|
|
290
329
|
isPathOrParentDirty(
|
|
291
330
|
Option.match(get(stateAtom), {
|
|
292
331
|
onNone: () => new Set<string>(),
|
|
293
|
-
onSome: (state) => state.dirtyFields
|
|
332
|
+
onSome: (state) => state.dirtyFields
|
|
294
333
|
}),
|
|
295
|
-
fieldPath
|
|
334
|
+
fieldPath
|
|
296
335
|
)
|
|
297
|
-
).pipe(Atom.setIdleTTL(0))
|
|
336
|
+
).pipe(Atom.setIdleTTL(0))
|
|
337
|
+
if (!existingIsDirtyAtom) {
|
|
338
|
+
isDirtyAtomsRegistry.set(fieldPath, isDirtyAtom)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const validationAtom = getOrCreateValidationAtom(fieldPath, schema)
|
|
342
|
+
|
|
343
|
+
const shouldValidateAtom = Atom.readable((get) => {
|
|
344
|
+
if (parsedMode.validation === "onChange") return true
|
|
345
|
+
if (parsedMode.validation === "onBlur") return get(touchedAtom)
|
|
346
|
+
return get(submitCountAtom) > 0
|
|
347
|
+
}).pipe(Atom.setIdleTTL(0))
|
|
348
|
+
|
|
349
|
+
const displayErrorAtom = Atom.readable((get) => {
|
|
350
|
+
const validationResult = get(validationAtom)
|
|
351
|
+
const storedError = get(errorAtom)
|
|
352
|
+
const isDirty = get(isDirtyAtom)
|
|
353
|
+
const isTouched = get(touchedAtom)
|
|
354
|
+
const submitCount = get(submitCountAtom)
|
|
355
|
+
|
|
356
|
+
let livePerFieldError: Option.Option<string> = Option.none()
|
|
357
|
+
if (validationResult._tag === "Failure") {
|
|
358
|
+
const parseError = Cause.failureOption(validationResult.cause)
|
|
359
|
+
if (Option.isSome(parseError) && ParseResult.isParseError(parseError.value)) {
|
|
360
|
+
livePerFieldError = Validation.extractFirstError(parseError.value)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let validationError: Option.Option<string> = Option.none()
|
|
365
|
+
if (Option.isSome(livePerFieldError)) {
|
|
366
|
+
validationError = livePerFieldError
|
|
367
|
+
} else if (Option.isSome(storedError)) {
|
|
368
|
+
const isValidating = validationResult.waiting
|
|
369
|
+
const shouldHideStoredError = storedError.value.source === "field" &&
|
|
370
|
+
(validationResult._tag === "Success" || isValidating)
|
|
371
|
+
if (!shouldHideStoredError) {
|
|
372
|
+
validationError = Option.some(storedError.value.message)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const shouldShowError = parsedMode.validation === "onChange"
|
|
377
|
+
? isDirty || submitCount > 0
|
|
378
|
+
: parsedMode.validation === "onBlur"
|
|
379
|
+
? isTouched || submitCount > 0
|
|
380
|
+
: submitCount > 0
|
|
381
|
+
|
|
382
|
+
return shouldShowError ? validationError : Option.none()
|
|
383
|
+
}).pipe(Atom.setIdleTTL(0))
|
|
384
|
+
|
|
385
|
+
const triggerValidationAtom = Atom.readable((get) => {
|
|
386
|
+
let lastValue = get.once(valueAtom)
|
|
387
|
+
let timeout: ReturnType<typeof setTimeout> | undefined
|
|
388
|
+
|
|
389
|
+
const shouldDebounce = parsedMode.validation === "onChange" &&
|
|
390
|
+
parsedMode.debounce !== null && !parsedMode.autoSubmit
|
|
391
|
+
const debounceMs = shouldDebounce ? parsedMode.debounce : null
|
|
392
|
+
|
|
393
|
+
const trigger = (value: unknown) => {
|
|
394
|
+
if (!get.once(shouldValidateAtom)) return
|
|
395
|
+
if (debounceMs !== null && debounceMs > 0) {
|
|
396
|
+
if (timeout !== undefined) clearTimeout(timeout)
|
|
397
|
+
timeout = setTimeout(() => {
|
|
398
|
+
timeout = undefined
|
|
399
|
+
get.set(validationAtom, value)
|
|
400
|
+
}, debounceMs)
|
|
401
|
+
} else {
|
|
402
|
+
get.set(validationAtom, value)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
298
405
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
406
|
+
get.addFinalizer(() => {
|
|
407
|
+
if (timeout !== undefined) clearTimeout(timeout)
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
get.subscribe(valueAtom, (newValue) => {
|
|
411
|
+
if (newValue === lastValue) return
|
|
412
|
+
lastValue = newValue
|
|
413
|
+
trigger(newValue)
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
if (parsedMode.validation === "onBlur") {
|
|
417
|
+
get.subscribe(touchedAtom, (isTouched) => {
|
|
418
|
+
if (isTouched) {
|
|
419
|
+
const currentValue = get.once(valueAtom)
|
|
420
|
+
get.set(validationAtom, currentValue)
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
}).pipe(Atom.setIdleTTL(0))
|
|
425
|
+
|
|
426
|
+
const atoms: FieldAtoms = {
|
|
427
|
+
valueAtom,
|
|
428
|
+
initialValueAtom,
|
|
429
|
+
touchedAtom,
|
|
430
|
+
errorAtom,
|
|
431
|
+
isDirtyAtom,
|
|
432
|
+
validationAtom,
|
|
433
|
+
displayErrorAtom,
|
|
434
|
+
shouldValidateAtom,
|
|
435
|
+
triggerValidationAtom
|
|
436
|
+
}
|
|
437
|
+
fieldAtomsRegistry.set(fieldPath, atoms)
|
|
438
|
+
fieldSchemaRegistry.set(fieldPath, schema)
|
|
439
|
+
return atoms
|
|
440
|
+
}
|
|
303
441
|
|
|
304
|
-
const resetValidationAtoms = (ctx: { set: <R, W
|
|
442
|
+
const resetValidationAtoms = (ctx: { set: <R, W,>(atom: Atom.Writable<R, W>, value: W) => void }) => {
|
|
305
443
|
for (const validationAtom of validationAtomsRegistry.values()) {
|
|
306
|
-
ctx.set(validationAtom, Atom.Reset)
|
|
444
|
+
ctx.set(validationAtom, Atom.Reset)
|
|
307
445
|
}
|
|
308
|
-
validationAtomsRegistry.clear()
|
|
309
|
-
fieldAtomsRegistry.clear()
|
|
310
|
-
|
|
446
|
+
validationAtomsRegistry.clear()
|
|
447
|
+
fieldAtomsRegistry.clear()
|
|
448
|
+
publicFieldAtomsRegistry.clear()
|
|
449
|
+
validationSchemaRegistry.clear()
|
|
450
|
+
fieldSchemaRegistry.clear()
|
|
451
|
+
isDirtyAtomsRegistry.clear()
|
|
452
|
+
}
|
|
311
453
|
|
|
312
454
|
const submitAtom = runtime
|
|
313
455
|
.fn<SubmitArgs>()(
|
|
314
456
|
(args, get) =>
|
|
315
457
|
Effect.gen(function*() {
|
|
316
|
-
const state = get(stateAtom)
|
|
317
|
-
if (Option.isNone(state)) return yield* Effect.die("Form not initialized")
|
|
318
|
-
const values = state.value.values
|
|
319
|
-
get.set(errorsAtom, new Map())
|
|
458
|
+
const state = get(stateAtom)
|
|
459
|
+
if (Option.isNone(state)) return yield* Effect.die("Form not initialized")
|
|
460
|
+
const values = state.value.values
|
|
461
|
+
get.set(errorsAtom, new Map())
|
|
320
462
|
const decoded = yield* pipe(
|
|
321
463
|
Schema.decodeUnknown(combinedSchema, { errors: "all" })(values) as Effect.Effect<
|
|
322
464
|
Field.DecodedFromFields<TFields>,
|
|
@@ -325,106 +467,106 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
|
|
|
325
467
|
>,
|
|
326
468
|
Effect.tapError((parseError) =>
|
|
327
469
|
Effect.sync(() => {
|
|
328
|
-
const routedErrors = Validation.routeErrorsWithSource(parseError)
|
|
329
|
-
get.set(errorsAtom, routedErrors)
|
|
330
|
-
get.set(stateAtom, Option.some(operations.createSubmitState(state.value)))
|
|
470
|
+
const routedErrors = Validation.routeErrorsWithSource(parseError)
|
|
471
|
+
get.set(errorsAtom, routedErrors)
|
|
472
|
+
get.set(stateAtom, Option.some(operations.createSubmitState(state.value)))
|
|
331
473
|
})
|
|
332
|
-
)
|
|
333
|
-
)
|
|
334
|
-
const submitState = operations.createSubmitState(state.value)
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
const submitState = operations.createSubmitState(state.value)
|
|
335
477
|
get.set(
|
|
336
478
|
stateAtom,
|
|
337
479
|
Option.some({
|
|
338
480
|
...submitState,
|
|
339
|
-
lastSubmittedValues: Option.some({ encoded: values, decoded })
|
|
340
|
-
})
|
|
341
|
-
)
|
|
342
|
-
const result = config.onSubmit(args, { decoded, encoded: values, get })
|
|
481
|
+
lastSubmittedValues: Option.some({ encoded: values, decoded })
|
|
482
|
+
})
|
|
483
|
+
)
|
|
484
|
+
const result = config.onSubmit(args, { decoded, encoded: values, get })
|
|
343
485
|
if (Effect.isEffect(result)) {
|
|
344
|
-
return yield* result as Effect.Effect<A, E, R
|
|
486
|
+
return yield* result as Effect.Effect<A, E, R>
|
|
345
487
|
}
|
|
346
|
-
return result as A
|
|
488
|
+
return result as A
|
|
347
489
|
}),
|
|
348
|
-
config.reactivityKeys ? { reactivityKeys: config.reactivityKeys } : undefined
|
|
490
|
+
config.reactivityKeys ? { reactivityKeys: config.reactivityKeys } : undefined
|
|
349
491
|
)
|
|
350
|
-
.pipe(Atom.setIdleTTL(0)) as Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError
|
|
492
|
+
.pipe(Atom.setIdleTTL(0)) as Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError>
|
|
351
493
|
|
|
352
494
|
const fieldRefs = Object.fromEntries(
|
|
353
|
-
Object.keys(fields).map((key) => [key, FormBuilder.makeFieldRef(key)])
|
|
354
|
-
) as FieldRefs<TFields
|
|
495
|
+
Object.keys(fields).map((key) => [key, FormBuilder.makeFieldRef(key)])
|
|
496
|
+
) as FieldRefs<TFields>
|
|
355
497
|
|
|
356
498
|
const operations: FormOperations<TFields> = {
|
|
357
499
|
createInitialState: (defaultValues) => ({
|
|
358
500
|
values: defaultValues,
|
|
359
501
|
initialValues: defaultValues,
|
|
360
502
|
lastSubmittedValues: Option.none(),
|
|
361
|
-
touched: Field.createTouchedRecord(fields, false) as { readonly [K in keyof TFields]: boolean
|
|
503
|
+
touched: Field.createTouchedRecord(fields, false) as { readonly [K in keyof TFields]: boolean },
|
|
362
504
|
submitCount: 0,
|
|
363
|
-
dirtyFields: new Set()
|
|
505
|
+
dirtyFields: new Set()
|
|
364
506
|
}),
|
|
365
507
|
|
|
366
508
|
createResetState: (state) => ({
|
|
367
509
|
values: state.initialValues,
|
|
368
510
|
initialValues: state.initialValues,
|
|
369
511
|
lastSubmittedValues: Option.none(),
|
|
370
|
-
touched: Field.createTouchedRecord(fields, false) as { readonly [K in keyof TFields]: boolean
|
|
512
|
+
touched: Field.createTouchedRecord(fields, false) as { readonly [K in keyof TFields]: boolean },
|
|
371
513
|
submitCount: 0,
|
|
372
|
-
dirtyFields: new Set()
|
|
514
|
+
dirtyFields: new Set()
|
|
373
515
|
}),
|
|
374
516
|
|
|
375
517
|
createSubmitState: (state) => ({
|
|
376
518
|
...state,
|
|
377
|
-
touched: Field.createTouchedRecord(fields, true) as { readonly [K in keyof TFields]: boolean
|
|
378
|
-
submitCount: state.submitCount + 1
|
|
519
|
+
touched: Field.createTouchedRecord(fields, true) as { readonly [K in keyof TFields]: boolean },
|
|
520
|
+
submitCount: state.submitCount + 1
|
|
379
521
|
}),
|
|
380
522
|
|
|
381
523
|
setFieldValue: (state, fieldPath, value) => {
|
|
382
|
-
const newValues = setNestedValue(state.values, fieldPath, value)
|
|
383
|
-
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, newValues, fieldPath)
|
|
524
|
+
const newValues = setNestedValue(state.values, fieldPath, value)
|
|
525
|
+
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, newValues, fieldPath)
|
|
384
526
|
return {
|
|
385
527
|
...state,
|
|
386
528
|
values: newValues as Field.EncodedFromFields<TFields>,
|
|
387
|
-
dirtyFields: newDirtyFields
|
|
388
|
-
}
|
|
529
|
+
dirtyFields: newDirtyFields
|
|
530
|
+
}
|
|
389
531
|
},
|
|
390
532
|
|
|
391
533
|
setFormValues: (state, values) => {
|
|
392
|
-
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, values, "")
|
|
534
|
+
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, values, "")
|
|
393
535
|
return {
|
|
394
536
|
...state,
|
|
395
537
|
values,
|
|
396
|
-
dirtyFields: newDirtyFields
|
|
397
|
-
}
|
|
538
|
+
dirtyFields: newDirtyFields
|
|
539
|
+
}
|
|
398
540
|
},
|
|
399
541
|
|
|
400
542
|
setFieldTouched: (state, fieldPath, touched) => ({
|
|
401
543
|
...state,
|
|
402
|
-
touched: setNestedValue(state.touched, fieldPath, touched) as { readonly [K in keyof TFields]: boolean
|
|
544
|
+
touched: setNestedValue(state.touched, fieldPath, touched) as { readonly [K in keyof TFields]: boolean }
|
|
403
545
|
}),
|
|
404
546
|
|
|
405
547
|
appendArrayItem: (state, arrayPath, itemSchema, value) => {
|
|
406
|
-
const newItem = value ?? Field.getDefaultFromSchema(itemSchema)
|
|
407
|
-
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
408
|
-
const newItems = [...currentItems, newItem]
|
|
548
|
+
const newItem = value ?? Field.getDefaultFromSchema(itemSchema)
|
|
549
|
+
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown>
|
|
550
|
+
const newItems = [...currentItems, newItem]
|
|
409
551
|
return {
|
|
410
552
|
...state,
|
|
411
553
|
values: setNestedValue(state.values, arrayPath, newItems) as Field.EncodedFromFields<TFields>,
|
|
412
|
-
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems)
|
|
413
|
-
}
|
|
554
|
+
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems)
|
|
555
|
+
}
|
|
414
556
|
},
|
|
415
557
|
|
|
416
558
|
removeArrayItem: (state, arrayPath, index) => {
|
|
417
|
-
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
418
|
-
const newItems = currentItems.filter((_, i) => i !== index)
|
|
559
|
+
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown>
|
|
560
|
+
const newItems = currentItems.filter((_, i) => i !== index)
|
|
419
561
|
return {
|
|
420
562
|
...state,
|
|
421
563
|
values: setNestedValue(state.values, arrayPath, newItems) as Field.EncodedFromFields<TFields>,
|
|
422
|
-
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems)
|
|
423
|
-
}
|
|
564
|
+
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems)
|
|
565
|
+
}
|
|
424
566
|
},
|
|
425
567
|
|
|
426
568
|
swapArrayItems: (state, arrayPath, indexA, indexB) => {
|
|
427
|
-
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
569
|
+
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown>
|
|
428
570
|
if (
|
|
429
571
|
indexA < 0 ||
|
|
430
572
|
indexA >= currentItems.length ||
|
|
@@ -432,21 +574,21 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
|
|
|
432
574
|
indexB >= currentItems.length ||
|
|
433
575
|
indexA === indexB
|
|
434
576
|
) {
|
|
435
|
-
return state
|
|
577
|
+
return state
|
|
436
578
|
}
|
|
437
|
-
const newItems = [...currentItems]
|
|
438
|
-
const temp = newItems[indexA]
|
|
439
|
-
newItems[indexA] = newItems[indexB]
|
|
440
|
-
newItems[indexB] = temp
|
|
579
|
+
const newItems = [...currentItems]
|
|
580
|
+
const temp = newItems[indexA]
|
|
581
|
+
newItems[indexA] = newItems[indexB]
|
|
582
|
+
newItems[indexB] = temp
|
|
441
583
|
return {
|
|
442
584
|
...state,
|
|
443
585
|
values: setNestedValue(state.values, arrayPath, newItems) as Field.EncodedFromFields<TFields>,
|
|
444
|
-
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems)
|
|
445
|
-
}
|
|
586
|
+
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems)
|
|
587
|
+
}
|
|
446
588
|
},
|
|
447
589
|
|
|
448
590
|
moveArrayItem: (state, arrayPath, fromIndex, toIndex) => {
|
|
449
|
-
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
591
|
+
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown>
|
|
450
592
|
if (
|
|
451
593
|
fromIndex < 0 ||
|
|
452
594
|
fromIndex >= currentItems.length ||
|
|
@@ -454,116 +596,257 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
|
|
|
454
596
|
toIndex > currentItems.length ||
|
|
455
597
|
fromIndex === toIndex
|
|
456
598
|
) {
|
|
457
|
-
return state
|
|
599
|
+
return state
|
|
458
600
|
}
|
|
459
|
-
const newItems = [...currentItems]
|
|
460
|
-
const [item] = newItems.splice(fromIndex, 1)
|
|
461
|
-
newItems.splice(toIndex, 0, item)
|
|
601
|
+
const newItems = [...currentItems]
|
|
602
|
+
const [item] = newItems.splice(fromIndex, 1)
|
|
603
|
+
newItems.splice(toIndex, 0, item)
|
|
462
604
|
return {
|
|
463
605
|
...state,
|
|
464
606
|
values: setNestedValue(state.values, arrayPath, newItems) as Field.EncodedFromFields<TFields>,
|
|
465
|
-
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems)
|
|
466
|
-
}
|
|
607
|
+
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems)
|
|
608
|
+
}
|
|
467
609
|
},
|
|
468
610
|
|
|
469
611
|
revertToLastSubmit: (state) => {
|
|
470
612
|
if (Option.isNone(state.lastSubmittedValues)) {
|
|
471
|
-
return state
|
|
613
|
+
return state
|
|
472
614
|
}
|
|
473
615
|
|
|
474
|
-
const lastEncoded = state.lastSubmittedValues.value.encoded
|
|
616
|
+
const lastEncoded = state.lastSubmittedValues.value.encoded
|
|
475
617
|
if (state.values === lastEncoded) {
|
|
476
|
-
return state
|
|
618
|
+
return state
|
|
477
619
|
}
|
|
478
620
|
|
|
479
|
-
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, lastEncoded, "")
|
|
621
|
+
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, lastEncoded, "")
|
|
480
622
|
|
|
481
623
|
return {
|
|
482
624
|
...state,
|
|
483
625
|
values: lastEncoded,
|
|
484
|
-
dirtyFields: newDirtyFields
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
626
|
+
dirtyFields: newDirtyFields
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
488
630
|
|
|
489
631
|
const resetAtom = Atom.fnSync<void>()(
|
|
490
632
|
(_: void, get) => {
|
|
491
|
-
const state = get(stateAtom)
|
|
492
|
-
if (Option.isNone(state)) return
|
|
493
|
-
get.set(stateAtom, Option.some(operations.createResetState(state.value)))
|
|
494
|
-
get.set(errorsAtom, new Map())
|
|
495
|
-
resetValidationAtoms(get)
|
|
496
|
-
get.set(submitAtom, Atom.Reset)
|
|
633
|
+
const state = get(stateAtom)
|
|
634
|
+
if (Option.isNone(state)) return
|
|
635
|
+
get.set(stateAtom, Option.some(operations.createResetState(state.value)))
|
|
636
|
+
get.set(errorsAtom, new Map())
|
|
637
|
+
resetValidationAtoms(get)
|
|
638
|
+
get.set(submitAtom, Atom.Reset)
|
|
497
639
|
},
|
|
498
|
-
{ initialValue: undefined as void }
|
|
499
|
-
).pipe(Atom.setIdleTTL(0))
|
|
640
|
+
{ initialValue: undefined as void }
|
|
641
|
+
).pipe(Atom.setIdleTTL(0))
|
|
500
642
|
|
|
501
643
|
const revertToLastSubmitAtom = Atom.fnSync<void>()(
|
|
502
644
|
(_: void, get) => {
|
|
503
|
-
const state = get(stateAtom)
|
|
504
|
-
if (Option.isNone(state)) return
|
|
505
|
-
get.set(stateAtom, Option.some(operations.revertToLastSubmit(state.value)))
|
|
506
|
-
get.set(errorsAtom, new Map())
|
|
645
|
+
const state = get(stateAtom)
|
|
646
|
+
if (Option.isNone(state)) return
|
|
647
|
+
get.set(stateAtom, Option.some(operations.revertToLastSubmit(state.value)))
|
|
648
|
+
get.set(errorsAtom, new Map())
|
|
507
649
|
},
|
|
508
|
-
{ initialValue: undefined as void }
|
|
509
|
-
).pipe(Atom.setIdleTTL(0))
|
|
510
|
-
|
|
511
|
-
const setValuesAtom = Atom.fnSync<
|
|
512
|
-
(
|
|
513
|
-
const state = get(stateAtom)
|
|
514
|
-
if (Option.isNone(state)) return
|
|
515
|
-
|
|
516
|
-
|
|
650
|
+
{ initialValue: undefined as void }
|
|
651
|
+
).pipe(Atom.setIdleTTL(0))
|
|
652
|
+
|
|
653
|
+
const setValuesAtom = Atom.fnSync<SetValuesArg<TFields>>()(
|
|
654
|
+
(update, get) => {
|
|
655
|
+
const state = get(stateAtom)
|
|
656
|
+
if (Option.isNone(state)) return
|
|
657
|
+
const values = typeof update === "function"
|
|
658
|
+
? (update as (prev: Field.EncodedFromFields<TFields>) => Field.EncodedFromFields<TFields>)(state.value.values)
|
|
659
|
+
: update
|
|
660
|
+
get.set(stateAtom, Option.some(operations.setFormValues(state.value, values)))
|
|
661
|
+
get.set(errorsAtom, new Map())
|
|
517
662
|
},
|
|
518
|
-
{ initialValue: undefined as void }
|
|
519
|
-
).pipe(Atom.setIdleTTL(0))
|
|
663
|
+
{ initialValue: undefined as void }
|
|
664
|
+
).pipe(Atom.setIdleTTL(0))
|
|
520
665
|
|
|
521
|
-
const setValueAtomsRegistry = createWeakRegistry<Atom.Writable<void, any>>()
|
|
666
|
+
const setValueAtomsRegistry = createWeakRegistry<Atom.Writable<void, any>>()
|
|
522
667
|
|
|
523
|
-
const setValue = <S
|
|
524
|
-
const cached = setValueAtomsRegistry.get(field.key)
|
|
525
|
-
if (cached) return cached
|
|
668
|
+
const setValue = <S,>(field: FormBuilder.FieldRef<S>): Atom.Writable<void, S | ((prev: S) => S)> => {
|
|
669
|
+
const cached = setValueAtomsRegistry.get(field.key)
|
|
670
|
+
if (cached) return cached
|
|
526
671
|
|
|
527
672
|
const atom = Atom.fnSync<S | ((prev: S) => S)>()(
|
|
528
673
|
(update, get) => {
|
|
529
|
-
const state = get(stateAtom)
|
|
530
|
-
if (Option.isNone(state)) return
|
|
674
|
+
const state = get(stateAtom)
|
|
675
|
+
if (Option.isNone(state)) return
|
|
531
676
|
|
|
532
|
-
const currentValue = getNestedValue(state.value.values, field.key) as S
|
|
533
|
-
const newValue = typeof update === "function" ? (update as (prev: S) => S)(currentValue) : update
|
|
677
|
+
const currentValue = getNestedValue(state.value.values, field.key) as S
|
|
678
|
+
const newValue = typeof update === "function" ? (update as (prev: S) => S)(currentValue) : update
|
|
534
679
|
|
|
535
|
-
get.set(stateAtom, Option.some(operations.setFieldValue(state.value, field.key, newValue)))
|
|
680
|
+
get.set(stateAtom, Option.some(operations.setFieldValue(state.value, field.key, newValue)))
|
|
536
681
|
// Don't clear errors - display logic handles showing/hiding based on source + validation state
|
|
537
682
|
},
|
|
538
|
-
{ initialValue: undefined as void }
|
|
539
|
-
).pipe(Atom.setIdleTTL(0))
|
|
683
|
+
{ initialValue: undefined as void }
|
|
684
|
+
).pipe(Atom.setIdleTTL(0))
|
|
685
|
+
|
|
686
|
+
setValueAtomsRegistry.set(field.key, atom)
|
|
687
|
+
return atom
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const getFieldIsDirty = (field: FormBuilder.FieldRef<any>): Atom.Atom<boolean> => {
|
|
691
|
+
const cached = fieldAtomsRegistry.get(field.key)
|
|
692
|
+
if (cached) return cached.isDirtyAtom
|
|
693
|
+
|
|
694
|
+
const existing = isDirtyAtomsRegistry.get(field.key)
|
|
695
|
+
if (existing) return existing
|
|
696
|
+
|
|
697
|
+
const atom = Atom.readable((get) =>
|
|
698
|
+
isPathOrParentDirty(
|
|
699
|
+
Option.match(get(stateAtom), {
|
|
700
|
+
onNone: () => new Set<string>(),
|
|
701
|
+
onSome: (state) => state.dirtyFields
|
|
702
|
+
}),
|
|
703
|
+
field.key
|
|
704
|
+
)
|
|
705
|
+
).pipe(Atom.setIdleTTL(0))
|
|
706
|
+
|
|
707
|
+
isDirtyAtomsRegistry.set(field.key, atom)
|
|
708
|
+
return atom
|
|
709
|
+
}
|
|
540
710
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
711
|
+
const getFieldAtoms = <S,>(field: FormBuilder.FieldRef<S>): PublicFieldAtoms<S> => {
|
|
712
|
+
const cached = publicFieldAtomsRegistry.get(field.key)
|
|
713
|
+
if (cached) return cached as PublicFieldAtoms<S>
|
|
544
714
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (existing) return existing as Atom.Atom<Option.Option<S>>;
|
|
715
|
+
const schema = fieldSchemasByKey.get(field.key)
|
|
716
|
+
if (!schema) throw new Error(`No schema found for field "${field.key}"`)
|
|
548
717
|
|
|
549
|
-
const
|
|
718
|
+
const internal = getOrCreateFieldAtoms(field.key, schema)
|
|
719
|
+
|
|
720
|
+
const value = Atom.readable((get) =>
|
|
550
721
|
Option.map(get(stateAtom), (state) => getNestedValue(state.values, field.key) as S)
|
|
551
|
-
).pipe(Atom.setIdleTTL(0))
|
|
722
|
+
).pipe(Atom.setIdleTTL(0))
|
|
723
|
+
|
|
724
|
+
const error = Atom.readable((get) =>
|
|
725
|
+
Option.match(get(stateAtom), {
|
|
726
|
+
onNone: () => Option.none<string>(),
|
|
727
|
+
onSome: () => get(internal.displayErrorAtom)
|
|
728
|
+
})
|
|
729
|
+
).pipe(Atom.setIdleTTL(0))
|
|
730
|
+
|
|
731
|
+
const isDirty = getFieldIsDirty(field)
|
|
732
|
+
|
|
733
|
+
const isTouched = Atom.readable((get) =>
|
|
734
|
+
Option.match(get(stateAtom), {
|
|
735
|
+
onNone: () => false,
|
|
736
|
+
onSome: (state) => (getNestedValue(state.touched, field.key) ?? false) as boolean
|
|
737
|
+
})
|
|
738
|
+
).pipe(Atom.setIdleTTL(0))
|
|
739
|
+
|
|
740
|
+
const isValidating = Atom.readable((get) => get(internal.validationAtom).waiting).pipe(Atom.setIdleTTL(0))
|
|
552
741
|
|
|
553
|
-
|
|
554
|
-
return safeAtom;
|
|
555
|
-
};
|
|
742
|
+
const setValueAtom = setValue(field)
|
|
556
743
|
|
|
557
|
-
|
|
558
|
-
|
|
744
|
+
const setTouchedAtom = Atom.fnSync<boolean>()(
|
|
745
|
+
(touched, get) => {
|
|
746
|
+
const state = get(stateAtom)
|
|
747
|
+
if (Option.isNone(state)) return
|
|
748
|
+
get.set(stateAtom, Option.some(operations.setFieldTouched(state.value, field.key, touched)))
|
|
749
|
+
},
|
|
750
|
+
{ initialValue: undefined as void }
|
|
751
|
+
).pipe(Atom.setIdleTTL(0))
|
|
752
|
+
|
|
753
|
+
const bundle: PublicFieldAtoms<S> = {
|
|
754
|
+
value,
|
|
755
|
+
error,
|
|
756
|
+
isDirty,
|
|
757
|
+
isTouched,
|
|
758
|
+
isValidating,
|
|
759
|
+
setValue: setValueAtom,
|
|
760
|
+
setTouched: setTouchedAtom
|
|
761
|
+
}
|
|
762
|
+
publicFieldAtomsRegistry.set(field.key, bundle as PublicFieldAtoms<unknown>)
|
|
763
|
+
return bundle
|
|
764
|
+
}
|
|
559
765
|
|
|
560
766
|
const mountAtom = Atom.readable((get) => {
|
|
561
|
-
get(stateAtom)
|
|
562
|
-
get(errorsAtom)
|
|
563
|
-
get(submitAtom)
|
|
564
|
-
}).pipe(Atom.setIdleTTL(0))
|
|
767
|
+
get(stateAtom)
|
|
768
|
+
get(errorsAtom)
|
|
769
|
+
get(submitAtom)
|
|
770
|
+
}).pipe(Atom.setIdleTTL(0))
|
|
771
|
+
|
|
772
|
+
const keepAliveActiveAtom = Atom.make(false).pipe(Atom.setIdleTTL(0))
|
|
773
|
+
|
|
774
|
+
const autoSubmitAtom: Atom.Atom<void> = parsedMode.autoSubmit && parsedMode.validation === "onChange"
|
|
775
|
+
? Atom.readable((get) => {
|
|
776
|
+
const initialState = get.once(stateAtom)
|
|
777
|
+
let lastValues: unknown = Option.isSome(initialState)
|
|
778
|
+
? initialState.value.values
|
|
779
|
+
: null
|
|
780
|
+
let pendingChanges = false
|
|
781
|
+
let wasSubmitting = false
|
|
782
|
+
let timeout: ReturnType<typeof setTimeout> | undefined
|
|
783
|
+
|
|
784
|
+
const debounceMs = parsedMode.debounce
|
|
785
|
+
|
|
786
|
+
const triggerSubmit = () => {
|
|
787
|
+
if (get.once(submitAtom).waiting) {
|
|
788
|
+
pendingChanges = true
|
|
789
|
+
return
|
|
790
|
+
}
|
|
791
|
+
get.set(submitAtom as Atom.Writable<any, any>, undefined)
|
|
792
|
+
}
|
|
565
793
|
|
|
566
|
-
|
|
794
|
+
const debouncedSubmit = () => {
|
|
795
|
+
if (debounceMs !== null && debounceMs > 0) {
|
|
796
|
+
if (timeout !== undefined) clearTimeout(timeout)
|
|
797
|
+
timeout = setTimeout(() => {
|
|
798
|
+
timeout = undefined
|
|
799
|
+
triggerSubmit()
|
|
800
|
+
}, debounceMs)
|
|
801
|
+
} else {
|
|
802
|
+
triggerSubmit()
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
get.addFinalizer(() => {
|
|
807
|
+
if (timeout !== undefined) clearTimeout(timeout)
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
get.subscribe(stateAtom, () => {
|
|
811
|
+
const state = get.once(stateAtom)
|
|
812
|
+
if (Option.isNone(state)) return
|
|
813
|
+
const currentValues = state.value.values
|
|
814
|
+
if (currentValues === lastValues) return
|
|
815
|
+
lastValues = currentValues
|
|
816
|
+
|
|
817
|
+
const submitResult = get.once(submitAtom)
|
|
818
|
+
if (submitResult.waiting) {
|
|
819
|
+
pendingChanges = true
|
|
820
|
+
} else {
|
|
821
|
+
debouncedSubmit()
|
|
822
|
+
}
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
get.subscribe(submitAtom, () => {
|
|
826
|
+
const result = get.once(submitAtom)
|
|
827
|
+
const isSubmitting = result.waiting
|
|
828
|
+
if (wasSubmitting && !isSubmitting) {
|
|
829
|
+
if (pendingChanges) {
|
|
830
|
+
pendingChanges = false
|
|
831
|
+
debouncedSubmit()
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
wasSubmitting = isSubmitting
|
|
835
|
+
})
|
|
836
|
+
}).pipe(Atom.setIdleTTL(0))
|
|
837
|
+
: Atom.readable(() => {}).pipe(Atom.setIdleTTL(0))
|
|
838
|
+
|
|
839
|
+
const onBlurSubmitAtom: Atom.Writable<void, void> = parsedMode.autoSubmit && parsedMode.validation === "onBlur"
|
|
840
|
+
? Atom.fnSync<void>()((_: void, get) => {
|
|
841
|
+
if (get(submitAtom).waiting) return
|
|
842
|
+
const stateOption = get(stateAtom)
|
|
843
|
+
if (Option.isNone(stateOption)) return
|
|
844
|
+
const { lastSubmittedValues, values } = stateOption.value
|
|
845
|
+
if (Option.isSome(lastSubmittedValues) && values === lastSubmittedValues.value.encoded) return
|
|
846
|
+
get.set(submitAtom as Atom.Writable<any, any>, undefined)
|
|
847
|
+
}, { initialValue: undefined as void }).pipe(Atom.setIdleTTL(0))
|
|
848
|
+
: Atom.fnSync<void>()((_: void) => {}, { initialValue: undefined as void })
|
|
849
|
+
.pipe(Atom.setIdleTTL(0))
|
|
567
850
|
|
|
568
851
|
return {
|
|
569
852
|
stateAtom,
|
|
@@ -588,10 +871,10 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
|
|
|
588
871
|
resetAtom,
|
|
589
872
|
revertToLastSubmitAtom,
|
|
590
873
|
setValuesAtom,
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
874
|
+
getFieldAtoms,
|
|
875
|
+
autoSubmitAtom,
|
|
876
|
+
onBlurSubmitAtom,
|
|
594
877
|
mountAtom,
|
|
595
|
-
keepAliveActiveAtom
|
|
596
|
-
} as FormAtoms<TFields, R, A, E, SubmitArgs
|
|
597
|
-
}
|
|
878
|
+
keepAliveActiveAtom
|
|
879
|
+
} as FormAtoms<TFields, R, A, E, SubmitArgs>
|
|
880
|
+
}
|