@lucas-barake/effect-form 0.13.0 → 0.15.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/cjs/Field.js +0 -67
- package/dist/cjs/Field.js.map +1 -1
- package/dist/cjs/FormAtoms.js +6 -26
- package/dist/cjs/FormAtoms.js.map +1 -1
- package/dist/cjs/FormBuilder.js +0 -66
- package/dist/cjs/FormBuilder.js.map +1 -1
- package/dist/cjs/Mode.js +0 -9
- package/dist/cjs/Mode.js.map +1 -1
- package/dist/cjs/Path.js +0 -34
- package/dist/cjs/Path.js.map +1 -1
- package/dist/cjs/Validation.js +0 -30
- package/dist/cjs/Validation.js.map +1 -1
- package/dist/cjs/internal/dirty.js +0 -16
- package/dist/cjs/internal/dirty.js.map +1 -1
- package/dist/cjs/internal/weak-registry.js +0 -11
- package/dist/cjs/internal/weak-registry.js.map +1 -1
- package/dist/dts/Field.d.ts +0 -99
- package/dist/dts/Field.d.ts.map +1 -1
- package/dist/dts/FormAtoms.d.ts +1 -54
- package/dist/dts/FormAtoms.d.ts.map +1 -1
- package/dist/dts/FormBuilder.d.ts +3 -152
- package/dist/dts/FormBuilder.d.ts.map +1 -1
- package/dist/dts/Mode.d.ts +0 -33
- package/dist/dts/Mode.d.ts.map +1 -1
- package/dist/dts/Path.d.ts +0 -34
- package/dist/dts/Path.d.ts.map +1 -1
- package/dist/dts/Validation.d.ts +0 -37
- package/dist/dts/Validation.d.ts.map +1 -1
- package/dist/dts/index.d.ts +17 -19
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/dirty.d.ts +0 -10
- package/dist/dts/internal/dirty.d.ts.map +1 -1
- package/dist/dts/internal/weak-registry.d.ts +8 -6
- package/dist/dts/internal/weak-registry.d.ts.map +1 -1
- package/dist/esm/Field.js +0 -66
- package/dist/esm/Field.js.map +1 -1
- package/dist/esm/FormAtoms.js +7 -27
- package/dist/esm/FormAtoms.js.map +1 -1
- package/dist/esm/FormBuilder.js +0 -66
- package/dist/esm/FormBuilder.js.map +1 -1
- package/dist/esm/Mode.js +0 -8
- package/dist/esm/Mode.js.map +1 -1
- package/dist/esm/Path.js +0 -34
- package/dist/esm/Path.js.map +1 -1
- package/dist/esm/Validation.js +0 -29
- package/dist/esm/Validation.js.map +1 -1
- package/dist/esm/index.js +17 -19
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/dirty.js +0 -15
- package/dist/esm/internal/dirty.js.map +1 -1
- package/dist/esm/internal/weak-registry.js +0 -11
- package/dist/esm/internal/weak-registry.js.map +1 -1
- package/package.json +1 -1
- package/src/Field.ts +0 -99
- package/src/FormAtoms.ts +277 -320
- package/src/FormBuilder.ts +0 -172
- package/src/Mode.ts +0 -33
- package/src/Path.ts +0 -35
- package/src/Validation.ts +0 -41
- package/src/index.ts +22 -19
- package/src/internal/dirty.ts +0 -15
- package/src/internal/weak-registry.ts +0 -17
package/src/FormAtoms.ts
CHANGED
|
@@ -1,105 +1,86 @@
|
|
|
1
|
-
import * as Atom from "@effect-atom/atom/Atom"
|
|
2
|
-
import * as Effect from "effect/Effect"
|
|
3
|
-
import { pipe } from "effect/Function"
|
|
4
|
-
import * as Option from "effect/Option"
|
|
5
|
-
import type * as ParseResult from "effect/ParseResult"
|
|
6
|
-
import * as Schema from "effect/Schema"
|
|
7
|
-
import * as Field from "./Field.js"
|
|
8
|
-
import * as FormBuilder from "./FormBuilder.js"
|
|
9
|
-
import { recalculateDirtyFieldsForArray, recalculateDirtySubtree } from "./internal/dirty.js"
|
|
10
|
-
import { createWeakRegistry, type WeakRegistry } from "./internal/weak-registry.js"
|
|
11
|
-
import { getNestedValue, setNestedValue } from "./Path.js"
|
|
12
|
-
import * as Validation from "./Validation.js"
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Atoms for a single field.
|
|
16
|
-
*
|
|
17
|
-
* @category Models
|
|
18
|
-
*/
|
|
1
|
+
import * as Atom from "@effect-atom/atom/Atom";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import { pipe } from "effect/Function";
|
|
4
|
+
import * as Option from "effect/Option";
|
|
5
|
+
import type * as ParseResult from "effect/ParseResult";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import * as Field from "./Field.js";
|
|
8
|
+
import * as FormBuilder from "./FormBuilder.js";
|
|
9
|
+
import { recalculateDirtyFieldsForArray, recalculateDirtySubtree } from "./internal/dirty.js";
|
|
10
|
+
import { createWeakRegistry, type WeakRegistry } from "./internal/weak-registry.js";
|
|
11
|
+
import { getNestedValue, isPathOrParentDirty, setNestedValue } from "./Path.js";
|
|
12
|
+
import * as Validation from "./Validation.js";
|
|
13
|
+
|
|
19
14
|
export interface FieldAtoms {
|
|
20
|
-
readonly valueAtom: Atom.Writable<unknown, unknown
|
|
21
|
-
readonly initialValueAtom: Atom.Atom<unknown
|
|
22
|
-
readonly touchedAtom: Atom.Writable<boolean, boolean
|
|
23
|
-
readonly errorAtom: Atom.Atom<Option.Option<Validation.ErrorEntry
|
|
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>;
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
/**
|
|
27
|
-
* Configuration for creating form atoms.
|
|
28
|
-
*
|
|
29
|
-
* @category Models
|
|
30
|
-
*/
|
|
31
22
|
export interface FormAtomsConfig<TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = void> {
|
|
32
|
-
readonly runtime: Atom.AtomRuntime<R, any
|
|
33
|
-
readonly formBuilder: FormBuilder.FormBuilder<TFields, R
|
|
23
|
+
readonly runtime: Atom.AtomRuntime<R, any>;
|
|
24
|
+
readonly formBuilder: FormBuilder.FormBuilder<TFields, R>;
|
|
34
25
|
readonly onSubmit: (
|
|
35
26
|
args: SubmitArgs,
|
|
36
27
|
ctx: {
|
|
37
|
-
readonly decoded: Field.DecodedFromFields<TFields
|
|
38
|
-
readonly encoded: Field.EncodedFromFields<TFields
|
|
39
|
-
readonly get: Atom.FnContext
|
|
28
|
+
readonly decoded: Field.DecodedFromFields<TFields>;
|
|
29
|
+
readonly encoded: Field.EncodedFromFields<TFields>;
|
|
30
|
+
readonly get: Atom.FnContext;
|
|
40
31
|
},
|
|
41
|
-
) => A | Effect.Effect<A, E, R
|
|
32
|
+
) => A | Effect.Effect<A, E, R>;
|
|
42
33
|
}
|
|
43
34
|
|
|
44
|
-
/**
|
|
45
|
-
* Maps field names to their type-safe Field references for setValue operations.
|
|
46
|
-
*
|
|
47
|
-
* @category Models
|
|
48
|
-
*/
|
|
49
35
|
export type FieldRefs<TFields extends Field.FieldsRecord> = {
|
|
50
|
-
readonly [K in keyof TFields]: TFields[K] extends Field.FieldDef<any, infer S>
|
|
51
|
-
FormBuilder.FieldRef<Schema.Schema.Encoded<S>>
|
|
52
|
-
: TFields[K] extends Field.ArrayFieldDef<any, infer S>
|
|
53
|
-
FormBuilder.FieldRef<ReadonlyArray<Schema.Schema.Encoded<S>>>
|
|
54
|
-
: never
|
|
55
|
-
}
|
|
36
|
+
readonly [K in keyof TFields]: TFields[K] extends Field.FieldDef<any, infer S>
|
|
37
|
+
? FormBuilder.FieldRef<Schema.Schema.Encoded<S>>
|
|
38
|
+
: TFields[K] extends Field.ArrayFieldDef<any, infer S>
|
|
39
|
+
? FormBuilder.FieldRef<ReadonlyArray<Schema.Schema.Encoded<S>>>
|
|
40
|
+
: never;
|
|
41
|
+
};
|
|
56
42
|
|
|
57
|
-
/**
|
|
58
|
-
* The complete form atoms infrastructure.
|
|
59
|
-
*
|
|
60
|
-
* @category Models
|
|
61
|
-
*/
|
|
62
43
|
export interface FormAtoms<TFields extends Field.FieldsRecord, R, A = void, E = never, SubmitArgs = void> {
|
|
63
44
|
readonly stateAtom: Atom.Writable<
|
|
64
45
|
Option.Option<FormBuilder.FormState<TFields>>,
|
|
65
46
|
Option.Option<FormBuilder.FormState<TFields>>
|
|
66
|
-
|
|
67
|
-
readonly errorsAtom: Atom.Writable<Map<string, Validation.ErrorEntry>, Map<string, Validation.ErrorEntry
|
|
68
|
-
readonly rootErrorAtom: Atom.Atom<Option.Option<string
|
|
69
|
-
readonly valuesAtom: Atom.Atom<Option.Option<Field.EncodedFromFields<TFields
|
|
70
|
-
readonly dirtyFieldsAtom: Atom.Atom<ReadonlySet<string
|
|
71
|
-
readonly isDirtyAtom: Atom.Atom<boolean
|
|
72
|
-
readonly submitCountAtom: Atom.Atom<number
|
|
73
|
-
readonly lastSubmittedValuesAtom: Atom.Atom<Option.Option<FormBuilder.SubmittedValues<TFields
|
|
74
|
-
readonly changedSinceSubmitFieldsAtom: Atom.Atom<ReadonlySet<string
|
|
75
|
-
readonly hasChangedSinceSubmitAtom: Atom.Atom<boolean
|
|
47
|
+
>;
|
|
48
|
+
readonly errorsAtom: Atom.Writable<Map<string, Validation.ErrorEntry>, Map<string, Validation.ErrorEntry>>;
|
|
49
|
+
readonly rootErrorAtom: Atom.Atom<Option.Option<string>>;
|
|
50
|
+
readonly valuesAtom: Atom.Atom<Option.Option<Field.EncodedFromFields<TFields>>>;
|
|
51
|
+
readonly dirtyFieldsAtom: Atom.Atom<ReadonlySet<string>>;
|
|
52
|
+
readonly isDirtyAtom: Atom.Atom<boolean>;
|
|
53
|
+
readonly submitCountAtom: Atom.Atom<number>;
|
|
54
|
+
readonly lastSubmittedValuesAtom: Atom.Atom<Option.Option<FormBuilder.SubmittedValues<TFields>>>;
|
|
55
|
+
readonly changedSinceSubmitFieldsAtom: Atom.Atom<ReadonlySet<string>>;
|
|
56
|
+
readonly hasChangedSinceSubmitAtom: Atom.Atom<boolean>;
|
|
76
57
|
|
|
77
|
-
readonly submitAtom: Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError
|
|
58
|
+
readonly submitAtom: Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError>;
|
|
78
59
|
|
|
79
|
-
readonly combinedSchema: Schema.Schema<Field.DecodedFromFields<TFields>, Field.EncodedFromFields<TFields>, R
|
|
60
|
+
readonly combinedSchema: Schema.Schema<Field.DecodedFromFields<TFields>, Field.EncodedFromFields<TFields>, R>;
|
|
80
61
|
|
|
81
|
-
readonly fieldRefs: FieldRefs<TFields
|
|
62
|
+
readonly fieldRefs: FieldRefs<TFields>;
|
|
82
63
|
|
|
83
|
-
readonly validationAtomsRegistry: WeakRegistry<Atom.AtomResultFn<unknown, void, ParseResult.ParseError
|
|
84
|
-
readonly fieldAtomsRegistry: WeakRegistry<FieldAtoms
|
|
64
|
+
readonly validationAtomsRegistry: WeakRegistry<Atom.AtomResultFn<unknown, void, ParseResult.ParseError>>;
|
|
65
|
+
readonly fieldAtomsRegistry: WeakRegistry<FieldAtoms>;
|
|
85
66
|
|
|
86
67
|
readonly getOrCreateValidationAtom: (
|
|
87
68
|
fieldPath: string,
|
|
88
69
|
schema: Schema.Schema.Any,
|
|
89
|
-
) => Atom.AtomResultFn<unknown, void, ParseResult.ParseError
|
|
70
|
+
) => Atom.AtomResultFn<unknown, void, ParseResult.ParseError>;
|
|
90
71
|
|
|
91
|
-
readonly getOrCreateFieldAtoms: (fieldPath: string) => FieldAtoms
|
|
72
|
+
readonly getOrCreateFieldAtoms: (fieldPath: string) => FieldAtoms;
|
|
92
73
|
|
|
93
|
-
readonly resetValidationAtoms: (ctx: { set: <R, W>(atom: Atom.Writable<R, W>, value: W) => void }) => void
|
|
74
|
+
readonly resetValidationAtoms: (ctx: { set: <R, W>(atom: Atom.Writable<R, W>, value: W) => void; }) => void;
|
|
94
75
|
|
|
95
|
-
readonly operations: FormOperations<TFields
|
|
76
|
+
readonly operations: FormOperations<TFields>;
|
|
96
77
|
|
|
97
|
-
readonly resetAtom: Atom.Writable<void, void
|
|
98
|
-
readonly revertToLastSubmitAtom: Atom.Writable<void, void
|
|
99
|
-
readonly setValuesAtom: Atom.Writable<void, Field.EncodedFromFields<TFields
|
|
100
|
-
readonly setValue: <S>(field: FormBuilder.FieldRef<S>) => Atom.Writable<void, S | ((prev: S) => S)
|
|
78
|
+
readonly resetAtom: Atom.Writable<void, void>;
|
|
79
|
+
readonly revertToLastSubmitAtom: Atom.Writable<void, void>;
|
|
80
|
+
readonly setValuesAtom: Atom.Writable<void, Field.EncodedFromFields<TFields>>;
|
|
81
|
+
readonly setValue: <S>(field: FormBuilder.FieldRef<S>) => Atom.Writable<void, S | ((prev: S) => S)>;
|
|
101
82
|
|
|
102
|
-
readonly getFieldAtom: <S>(field: FormBuilder.FieldRef<S>) => Atom.Atom<Option.Option<S
|
|
83
|
+
readonly getFieldAtom: <S>(field: FormBuilder.FieldRef<S>) => Atom.Atom<Option.Option<S>>;
|
|
103
84
|
|
|
104
85
|
/**
|
|
105
86
|
* Root anchor atom for the form's dependency graph.
|
|
@@ -119,144 +100,110 @@ export interface FormAtoms<TFields extends Field.FieldsRecord, R, A = void, E =
|
|
|
119
100
|
* }
|
|
120
101
|
* ```
|
|
121
102
|
*/
|
|
122
|
-
readonly mountAtom: Atom.Atom<void
|
|
103
|
+
readonly mountAtom: Atom.Atom<void>;
|
|
123
104
|
|
|
124
|
-
readonly keepAliveActiveAtom: Atom.Writable<boolean, boolean
|
|
105
|
+
readonly keepAliveActiveAtom: Atom.Writable<boolean, boolean>;
|
|
125
106
|
}
|
|
126
107
|
|
|
127
|
-
/**
|
|
128
|
-
* Pure state operations for form manipulation.
|
|
129
|
-
*
|
|
130
|
-
* @category Models
|
|
131
|
-
*/
|
|
132
108
|
export interface FormOperations<TFields extends Field.FieldsRecord> {
|
|
133
|
-
readonly createInitialState: (defaultValues: Field.EncodedFromFields<TFields>) => FormBuilder.FormState<TFields
|
|
109
|
+
readonly createInitialState: (defaultValues: Field.EncodedFromFields<TFields>) => FormBuilder.FormState<TFields>;
|
|
134
110
|
|
|
135
|
-
readonly createResetState: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields
|
|
111
|
+
readonly createResetState: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields>;
|
|
136
112
|
|
|
137
|
-
readonly createSubmitState: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields
|
|
113
|
+
readonly createSubmitState: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields>;
|
|
138
114
|
|
|
139
115
|
readonly setFieldValue: (
|
|
140
116
|
state: FormBuilder.FormState<TFields>,
|
|
141
117
|
fieldPath: string,
|
|
142
118
|
value: unknown,
|
|
143
|
-
) => FormBuilder.FormState<TFields
|
|
119
|
+
) => FormBuilder.FormState<TFields>;
|
|
144
120
|
|
|
145
121
|
readonly setFormValues: (
|
|
146
122
|
state: FormBuilder.FormState<TFields>,
|
|
147
123
|
values: Field.EncodedFromFields<TFields>,
|
|
148
|
-
) => FormBuilder.FormState<TFields
|
|
124
|
+
) => FormBuilder.FormState<TFields>;
|
|
149
125
|
|
|
150
126
|
readonly setFieldTouched: (
|
|
151
127
|
state: FormBuilder.FormState<TFields>,
|
|
152
128
|
fieldPath: string,
|
|
153
129
|
touched: boolean,
|
|
154
|
-
) => FormBuilder.FormState<TFields
|
|
130
|
+
) => FormBuilder.FormState<TFields>;
|
|
155
131
|
|
|
156
132
|
readonly appendArrayItem: (
|
|
157
133
|
state: FormBuilder.FormState<TFields>,
|
|
158
134
|
arrayPath: string,
|
|
159
135
|
itemSchema: Schema.Schema.Any,
|
|
160
136
|
value?: unknown,
|
|
161
|
-
) => FormBuilder.FormState<TFields
|
|
137
|
+
) => FormBuilder.FormState<TFields>;
|
|
162
138
|
|
|
163
139
|
readonly removeArrayItem: (
|
|
164
140
|
state: FormBuilder.FormState<TFields>,
|
|
165
141
|
arrayPath: string,
|
|
166
142
|
index: number,
|
|
167
|
-
) => FormBuilder.FormState<TFields
|
|
143
|
+
) => FormBuilder.FormState<TFields>;
|
|
168
144
|
|
|
169
145
|
readonly swapArrayItems: (
|
|
170
146
|
state: FormBuilder.FormState<TFields>,
|
|
171
147
|
arrayPath: string,
|
|
172
148
|
indexA: number,
|
|
173
149
|
indexB: number,
|
|
174
|
-
) => FormBuilder.FormState<TFields
|
|
150
|
+
) => FormBuilder.FormState<TFields>;
|
|
175
151
|
|
|
176
152
|
readonly moveArrayItem: (
|
|
177
153
|
state: FormBuilder.FormState<TFields>,
|
|
178
154
|
arrayPath: string,
|
|
179
155
|
fromIndex: number,
|
|
180
156
|
toIndex: number,
|
|
181
|
-
) => FormBuilder.FormState<TFields
|
|
157
|
+
) => FormBuilder.FormState<TFields>;
|
|
182
158
|
|
|
183
|
-
|
|
184
|
-
* Reverts values to the last submitted state.
|
|
185
|
-
* No-op if form has never been submitted or is already in sync.
|
|
186
|
-
*/
|
|
187
|
-
readonly revertToLastSubmit: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields>
|
|
159
|
+
readonly revertToLastSubmit: (state: FormBuilder.FormState<TFields>) => FormBuilder.FormState<TFields>;
|
|
188
160
|
}
|
|
189
161
|
|
|
190
|
-
/**
|
|
191
|
-
* Creates the complete form atoms infrastructure.
|
|
192
|
-
*
|
|
193
|
-
* @example
|
|
194
|
-
* ```ts
|
|
195
|
-
* import * as FormAtoms from "@lucas-barake/effect-form/FormAtoms"
|
|
196
|
-
* import * as Form from "@lucas-barake/effect-form"
|
|
197
|
-
* import * as Atom from "@effect-atom/atom/Atom"
|
|
198
|
-
* import * as Layer from "effect/Layer"
|
|
199
|
-
*
|
|
200
|
-
* const runtime = Atom.runtime(Layer.empty)
|
|
201
|
-
*
|
|
202
|
-
* const loginForm = FormBuilder.empty
|
|
203
|
-
* .addField(FormBuilder.makeField("email", Schema.String))
|
|
204
|
-
* .addField(FormBuilder.makeField("password", Schema.String))
|
|
205
|
-
*
|
|
206
|
-
* const atoms = FormAtoms.make({
|
|
207
|
-
* runtime,
|
|
208
|
-
* formBuilder: loginForm,
|
|
209
|
-
* parsedMode: { validation: "onChange", debounce: 300, autoSubmit: false }
|
|
210
|
-
* })
|
|
211
|
-
* ```
|
|
212
|
-
*
|
|
213
|
-
* @category Constructors
|
|
214
|
-
*/
|
|
215
162
|
export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = void>(
|
|
216
163
|
config: FormAtomsConfig<TFields, R, A, E, SubmitArgs>,
|
|
217
164
|
): FormAtoms<TFields, R, A, E, SubmitArgs> => {
|
|
218
|
-
const { formBuilder, runtime } = config
|
|
219
|
-
const { fields } = formBuilder
|
|
165
|
+
const { formBuilder, runtime } = config;
|
|
166
|
+
const { fields } = formBuilder;
|
|
220
167
|
|
|
221
|
-
const combinedSchema = FormBuilder.buildSchema(formBuilder)
|
|
168
|
+
const combinedSchema = FormBuilder.buildSchema(formBuilder);
|
|
222
169
|
|
|
223
|
-
const stateAtom = Atom.make(Option.none<FormBuilder.FormState<TFields>>()).pipe(Atom.setIdleTTL(0))
|
|
224
|
-
const errorsAtom = Atom.make<Map<string, Validation.ErrorEntry>>(new Map()).pipe(Atom.setIdleTTL(0))
|
|
170
|
+
const stateAtom = Atom.make(Option.none<FormBuilder.FormState<TFields>>()).pipe(Atom.setIdleTTL(0));
|
|
171
|
+
const errorsAtom = Atom.make<Map<string, Validation.ErrorEntry>>(new Map()).pipe(Atom.setIdleTTL(0));
|
|
225
172
|
|
|
226
173
|
const rootErrorAtom = Atom.readable((get) => {
|
|
227
|
-
const errors = get(errorsAtom)
|
|
228
|
-
const entry = errors.get("")
|
|
229
|
-
return entry ? Option.some(entry.message) : Option.none<string>()
|
|
230
|
-
}).pipe(Atom.setIdleTTL(0))
|
|
174
|
+
const errors = get(errorsAtom);
|
|
175
|
+
const entry = errors.get("");
|
|
176
|
+
return entry ? Option.some(entry.message) : Option.none<string>();
|
|
177
|
+
}).pipe(Atom.setIdleTTL(0));
|
|
231
178
|
|
|
232
179
|
const valuesAtom = Atom.readable((get) => Option.map(get(stateAtom), (state) => state.values)).pipe(
|
|
233
180
|
Atom.setIdleTTL(0),
|
|
234
|
-
)
|
|
181
|
+
);
|
|
235
182
|
|
|
236
183
|
const dirtyFieldsAtom = Atom.readable((get) =>
|
|
237
184
|
Option.match(get(stateAtom), {
|
|
238
185
|
onNone: () => new Set<string>(),
|
|
239
186
|
onSome: (state) => state.dirtyFields,
|
|
240
187
|
})
|
|
241
|
-
).pipe(Atom.setIdleTTL(0))
|
|
188
|
+
).pipe(Atom.setIdleTTL(0));
|
|
242
189
|
|
|
243
190
|
const isDirtyAtom = Atom.readable((get) =>
|
|
244
191
|
Option.match(get(stateAtom), {
|
|
245
192
|
onNone: () => false,
|
|
246
193
|
onSome: (state) => state.dirtyFields.size > 0,
|
|
247
194
|
})
|
|
248
|
-
).pipe(Atom.setIdleTTL(0))
|
|
195
|
+
).pipe(Atom.setIdleTTL(0));
|
|
249
196
|
|
|
250
197
|
const submitCountAtom = Atom.readable((get) =>
|
|
251
198
|
Option.match(get(stateAtom), {
|
|
252
199
|
onNone: () => 0,
|
|
253
200
|
onSome: (state) => state.submitCount,
|
|
254
201
|
})
|
|
255
|
-
).pipe(Atom.setIdleTTL(0))
|
|
202
|
+
).pipe(Atom.setIdleTTL(0));
|
|
256
203
|
|
|
257
204
|
const lastSubmittedValuesAtom = Atom.readable((get) =>
|
|
258
205
|
Option.flatMap(get(stateAtom), (state) => state.lastSubmittedValues)
|
|
259
|
-
).pipe(Atom.setIdleTTL(0))
|
|
206
|
+
).pipe(Atom.setIdleTTL(0));
|
|
260
207
|
|
|
261
208
|
const changedSinceSubmitFieldsAtom = Atom.readable((get) =>
|
|
262
209
|
Option.match(get(stateAtom), {
|
|
@@ -267,136 +214,147 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
|
|
|
267
214
|
onSome: (lastSubmitted) => recalculateDirtySubtree(new Set(), lastSubmitted.encoded, state.values, ""),
|
|
268
215
|
}),
|
|
269
216
|
})
|
|
270
|
-
).pipe(Atom.setIdleTTL(0))
|
|
217
|
+
).pipe(Atom.setIdleTTL(0));
|
|
271
218
|
|
|
272
219
|
const hasChangedSinceSubmitAtom = Atom.readable((get) =>
|
|
273
220
|
Option.match(get(stateAtom), {
|
|
274
221
|
onNone: () => false,
|
|
275
222
|
onSome: (state) => {
|
|
276
|
-
if (Option.isNone(state.lastSubmittedValues)) return false
|
|
277
|
-
if (state.values === state.lastSubmittedValues.value.encoded) return false
|
|
278
|
-
return get(changedSinceSubmitFieldsAtom).size > 0
|
|
223
|
+
if (Option.isNone(state.lastSubmittedValues)) return false;
|
|
224
|
+
if (state.values === state.lastSubmittedValues.value.encoded) return false;
|
|
225
|
+
return get(changedSinceSubmitFieldsAtom).size > 0;
|
|
279
226
|
},
|
|
280
227
|
})
|
|
281
|
-
).pipe(Atom.setIdleTTL(0))
|
|
228
|
+
).pipe(Atom.setIdleTTL(0));
|
|
282
229
|
|
|
283
|
-
const validationAtomsRegistry = createWeakRegistry<Atom.AtomResultFn<unknown, void, ParseResult.ParseError>>()
|
|
284
|
-
const fieldAtomsRegistry = createWeakRegistry<FieldAtoms>()
|
|
285
|
-
const publicFieldAtomRegistry = createWeakRegistry<Atom.Atom<Option.Option<unknown>>>()
|
|
230
|
+
const validationAtomsRegistry = createWeakRegistry<Atom.AtomResultFn<unknown, void, ParseResult.ParseError>>();
|
|
231
|
+
const fieldAtomsRegistry = createWeakRegistry<FieldAtoms>();
|
|
232
|
+
const publicFieldAtomRegistry = createWeakRegistry<Atom.Atom<Option.Option<unknown>>>();
|
|
286
233
|
|
|
287
234
|
const getOrCreateValidationAtom = (
|
|
288
235
|
fieldPath: string,
|
|
289
236
|
schema: Schema.Schema.Any,
|
|
290
237
|
): Atom.AtomResultFn<unknown, void, ParseResult.ParseError> => {
|
|
291
|
-
const existing = validationAtomsRegistry.get(fieldPath)
|
|
292
|
-
if (existing) return existing
|
|
238
|
+
const existing = validationAtomsRegistry.get(fieldPath);
|
|
239
|
+
if (existing) return existing;
|
|
293
240
|
|
|
294
|
-
const validationAtom = runtime
|
|
295
|
-
|
|
296
|
-
Schema.decodeUnknown(schema)(value) as Effect.Effect<unknown, ParseResult.ParseError, R>,
|
|
297
|
-
Effect.asVoid,
|
|
241
|
+
const validationAtom = runtime
|
|
242
|
+
.fn<unknown>()((value: unknown) =>
|
|
243
|
+
pipe(Schema.decodeUnknown(schema)(value) as Effect.Effect<unknown, ParseResult.ParseError, R>, Effect.asVoid)
|
|
298
244
|
)
|
|
299
|
-
|
|
245
|
+
.pipe(Atom.setIdleTTL(0)) as Atom.AtomResultFn<unknown, void, ParseResult.ParseError>;
|
|
300
246
|
|
|
301
|
-
validationAtomsRegistry.set(fieldPath, validationAtom)
|
|
302
|
-
return validationAtom
|
|
303
|
-
}
|
|
247
|
+
validationAtomsRegistry.set(fieldPath, validationAtom);
|
|
248
|
+
return validationAtom;
|
|
249
|
+
};
|
|
304
250
|
|
|
305
251
|
const getOrCreateFieldAtoms = (fieldPath: string): FieldAtoms => {
|
|
306
|
-
const existing = fieldAtomsRegistry.get(fieldPath)
|
|
307
|
-
if (existing) return existing
|
|
252
|
+
const existing = fieldAtomsRegistry.get(fieldPath);
|
|
253
|
+
if (existing) return existing;
|
|
308
254
|
|
|
309
255
|
const valueAtom = Atom.writable(
|
|
310
256
|
(get) => getNestedValue(Option.getOrThrow(get(stateAtom)).values, fieldPath),
|
|
311
257
|
(ctx, value) => {
|
|
312
|
-
const currentState = Option.getOrThrow(ctx.get(stateAtom))
|
|
313
|
-
ctx.set(stateAtom, Option.some(operations.setFieldValue(currentState, fieldPath, value)))
|
|
258
|
+
const currentState = Option.getOrThrow(ctx.get(stateAtom));
|
|
259
|
+
ctx.set(stateAtom, Option.some(operations.setFieldValue(currentState, fieldPath, value)));
|
|
314
260
|
},
|
|
315
|
-
).pipe(Atom.setIdleTTL(0))
|
|
261
|
+
).pipe(Atom.setIdleTTL(0));
|
|
316
262
|
|
|
317
|
-
const initialValueAtom = Atom.readable(
|
|
318
|
-
|
|
319
|
-
).pipe(Atom.setIdleTTL(0))
|
|
263
|
+
const initialValueAtom = Atom.readable((get) =>
|
|
264
|
+
getNestedValue(Option.getOrThrow(get(stateAtom)).initialValues, fieldPath)
|
|
265
|
+
).pipe(Atom.setIdleTTL(0));
|
|
320
266
|
|
|
321
267
|
const touchedAtom = Atom.writable(
|
|
322
268
|
(get) => (getNestedValue(Option.getOrThrow(get(stateAtom)).touched, fieldPath) ?? false) as boolean,
|
|
323
269
|
(ctx, value) => {
|
|
324
|
-
const currentState = Option.getOrThrow(ctx.get(stateAtom))
|
|
270
|
+
const currentState = Option.getOrThrow(ctx.get(stateAtom));
|
|
325
271
|
ctx.set(
|
|
326
272
|
stateAtom,
|
|
327
273
|
Option.some({
|
|
328
274
|
...currentState,
|
|
329
275
|
touched: setNestedValue(currentState.touched, fieldPath, value),
|
|
330
276
|
}),
|
|
331
|
-
)
|
|
277
|
+
);
|
|
332
278
|
},
|
|
333
|
-
).pipe(Atom.setIdleTTL(0))
|
|
279
|
+
).pipe(Atom.setIdleTTL(0));
|
|
334
280
|
|
|
335
281
|
const errorAtom = Atom.readable((get) => {
|
|
336
|
-
const errors = get(errorsAtom)
|
|
337
|
-
const entry = errors.get(fieldPath)
|
|
338
|
-
return entry ? Option.some(entry) : Option.none<Validation.ErrorEntry>()
|
|
339
|
-
}).pipe(Atom.setIdleTTL(0))
|
|
282
|
+
const errors = get(errorsAtom);
|
|
283
|
+
const entry = errors.get(fieldPath);
|
|
284
|
+
return entry ? Option.some(entry) : Option.none<Validation.ErrorEntry>();
|
|
285
|
+
}).pipe(Atom.setIdleTTL(0));
|
|
286
|
+
|
|
287
|
+
const isDirtyAtom = Atom.readable((get) =>
|
|
288
|
+
isPathOrParentDirty(
|
|
289
|
+
Option.match(get(stateAtom), {
|
|
290
|
+
onNone: () => new Set<string>(),
|
|
291
|
+
onSome: (state) => state.dirtyFields,
|
|
292
|
+
}),
|
|
293
|
+
fieldPath,
|
|
294
|
+
)
|
|
295
|
+
).pipe(Atom.setIdleTTL(0));
|
|
340
296
|
|
|
341
|
-
const atoms: FieldAtoms = { valueAtom, initialValueAtom, touchedAtom, errorAtom }
|
|
342
|
-
fieldAtomsRegistry.set(fieldPath, atoms)
|
|
343
|
-
return atoms
|
|
344
|
-
}
|
|
297
|
+
const atoms: FieldAtoms = { valueAtom, initialValueAtom, touchedAtom, errorAtom, isDirtyAtom };
|
|
298
|
+
fieldAtomsRegistry.set(fieldPath, atoms);
|
|
299
|
+
return atoms;
|
|
300
|
+
};
|
|
345
301
|
|
|
346
|
-
const resetValidationAtoms = (ctx: { set: <R, W>(atom: Atom.Writable<R, W>, value: W) => void }) => {
|
|
302
|
+
const resetValidationAtoms = (ctx: { set: <R, W>(atom: Atom.Writable<R, W>, value: W) => void; }) => {
|
|
347
303
|
for (const validationAtom of validationAtomsRegistry.values()) {
|
|
348
|
-
ctx.set(validationAtom, Atom.Reset)
|
|
304
|
+
ctx.set(validationAtom, Atom.Reset);
|
|
349
305
|
}
|
|
350
|
-
validationAtomsRegistry.clear()
|
|
351
|
-
fieldAtomsRegistry.clear()
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const submitAtom = runtime
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
Effect.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
306
|
+
validationAtomsRegistry.clear();
|
|
307
|
+
fieldAtomsRegistry.clear();
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const submitAtom = runtime
|
|
311
|
+
.fn<SubmitArgs>()((args, get) =>
|
|
312
|
+
Effect.gen(function*() {
|
|
313
|
+
const state = get(stateAtom);
|
|
314
|
+
if (Option.isNone(state)) return yield* Effect.die("Form not initialized");
|
|
315
|
+
const values = state.value.values;
|
|
316
|
+
get.set(errorsAtom, new Map());
|
|
317
|
+
const decoded = yield* pipe(
|
|
318
|
+
Schema.decodeUnknown(combinedSchema, { errors: "all" })(values) as Effect.Effect<
|
|
319
|
+
Field.DecodedFromFields<TFields>,
|
|
320
|
+
ParseResult.ParseError,
|
|
321
|
+
R
|
|
322
|
+
>,
|
|
323
|
+
Effect.tapError((parseError) =>
|
|
324
|
+
Effect.sync(() => {
|
|
325
|
+
const routedErrors = Validation.routeErrorsWithSource(parseError);
|
|
326
|
+
get.set(errorsAtom, routedErrors);
|
|
327
|
+
get.set(stateAtom, Option.some(operations.createSubmitState(state.value)));
|
|
328
|
+
})
|
|
329
|
+
),
|
|
330
|
+
);
|
|
331
|
+
const submitState = operations.createSubmitState(state.value);
|
|
332
|
+
get.set(
|
|
333
|
+
stateAtom,
|
|
334
|
+
Option.some({
|
|
335
|
+
...submitState,
|
|
336
|
+
lastSubmittedValues: Option.some({ encoded: values, decoded }),
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
339
|
+
const result = config.onSubmit(args, { decoded, encoded: values, get });
|
|
340
|
+
if (Effect.isEffect(result)) {
|
|
341
|
+
return yield* result as Effect.Effect<A, E, R>;
|
|
342
|
+
}
|
|
343
|
+
return result as A;
|
|
344
|
+
})
|
|
345
|
+
)
|
|
346
|
+
.pipe(Atom.setIdleTTL(0)) as Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError>;
|
|
389
347
|
|
|
390
348
|
const fieldRefs = Object.fromEntries(
|
|
391
349
|
Object.keys(fields).map((key) => [key, FormBuilder.makeFieldRef(key)]),
|
|
392
|
-
) as FieldRefs<TFields
|
|
350
|
+
) as FieldRefs<TFields>;
|
|
393
351
|
|
|
394
352
|
const operations: FormOperations<TFields> = {
|
|
395
353
|
createInitialState: (defaultValues) => ({
|
|
396
354
|
values: defaultValues,
|
|
397
355
|
initialValues: defaultValues,
|
|
398
356
|
lastSubmittedValues: Option.none(),
|
|
399
|
-
touched: Field.createTouchedRecord(fields, false) as { readonly [K in keyof TFields]: boolean },
|
|
357
|
+
touched: Field.createTouchedRecord(fields, false) as { readonly [K in keyof TFields]: boolean; },
|
|
400
358
|
submitCount: 0,
|
|
401
359
|
dirtyFields: new Set(),
|
|
402
360
|
}),
|
|
@@ -405,201 +363,200 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
|
|
|
405
363
|
values: state.initialValues,
|
|
406
364
|
initialValues: state.initialValues,
|
|
407
365
|
lastSubmittedValues: Option.none(),
|
|
408
|
-
touched: Field.createTouchedRecord(fields, false) as { readonly [K in keyof TFields]: boolean },
|
|
366
|
+
touched: Field.createTouchedRecord(fields, false) as { readonly [K in keyof TFields]: boolean; },
|
|
409
367
|
submitCount: 0,
|
|
410
368
|
dirtyFields: new Set(),
|
|
411
369
|
}),
|
|
412
370
|
|
|
413
371
|
createSubmitState: (state) => ({
|
|
414
372
|
...state,
|
|
415
|
-
touched: Field.createTouchedRecord(fields, true) as { readonly [K in keyof TFields]: boolean },
|
|
373
|
+
touched: Field.createTouchedRecord(fields, true) as { readonly [K in keyof TFields]: boolean; },
|
|
416
374
|
submitCount: state.submitCount + 1,
|
|
417
375
|
}),
|
|
418
376
|
|
|
419
377
|
setFieldValue: (state, fieldPath, value) => {
|
|
420
|
-
const newValues = setNestedValue(state.values, fieldPath, value)
|
|
421
|
-
const newDirtyFields = recalculateDirtySubtree(
|
|
422
|
-
state.dirtyFields,
|
|
423
|
-
state.initialValues,
|
|
424
|
-
newValues,
|
|
425
|
-
fieldPath,
|
|
426
|
-
)
|
|
378
|
+
const newValues = setNestedValue(state.values, fieldPath, value);
|
|
379
|
+
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, newValues, fieldPath);
|
|
427
380
|
return {
|
|
428
381
|
...state,
|
|
429
382
|
values: newValues as Field.EncodedFromFields<TFields>,
|
|
430
383
|
dirtyFields: newDirtyFields,
|
|
431
|
-
}
|
|
384
|
+
};
|
|
432
385
|
},
|
|
433
386
|
|
|
434
387
|
setFormValues: (state, values) => {
|
|
435
|
-
const newDirtyFields = recalculateDirtySubtree(
|
|
436
|
-
state.dirtyFields,
|
|
437
|
-
state.initialValues,
|
|
438
|
-
values,
|
|
439
|
-
"",
|
|
440
|
-
)
|
|
388
|
+
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, values, "");
|
|
441
389
|
return {
|
|
442
390
|
...state,
|
|
443
391
|
values,
|
|
444
392
|
dirtyFields: newDirtyFields,
|
|
445
|
-
}
|
|
393
|
+
};
|
|
446
394
|
},
|
|
447
395
|
|
|
448
396
|
setFieldTouched: (state, fieldPath, touched) => ({
|
|
449
397
|
...state,
|
|
450
|
-
touched: setNestedValue(state.touched, fieldPath, touched) as { readonly [K in keyof TFields]: boolean },
|
|
398
|
+
touched: setNestedValue(state.touched, fieldPath, touched) as { readonly [K in keyof TFields]: boolean; },
|
|
451
399
|
}),
|
|
452
400
|
|
|
453
401
|
appendArrayItem: (state, arrayPath, itemSchema, value) => {
|
|
454
|
-
const newItem = value ?? Field.getDefaultFromSchema(itemSchema)
|
|
455
|
-
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
456
|
-
const newItems = [...currentItems, newItem]
|
|
402
|
+
const newItem = value ?? Field.getDefaultFromSchema(itemSchema);
|
|
403
|
+
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown>;
|
|
404
|
+
const newItems = [...currentItems, newItem];
|
|
457
405
|
return {
|
|
458
406
|
...state,
|
|
459
407
|
values: setNestedValue(state.values, arrayPath, newItems) as Field.EncodedFromFields<TFields>,
|
|
460
408
|
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems),
|
|
461
|
-
}
|
|
409
|
+
};
|
|
462
410
|
},
|
|
463
411
|
|
|
464
412
|
removeArrayItem: (state, arrayPath, index) => {
|
|
465
|
-
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
466
|
-
const newItems = currentItems.filter((_, i) => i !== index)
|
|
413
|
+
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown>;
|
|
414
|
+
const newItems = currentItems.filter((_, i) => i !== index);
|
|
467
415
|
return {
|
|
468
416
|
...state,
|
|
469
417
|
values: setNestedValue(state.values, arrayPath, newItems) as Field.EncodedFromFields<TFields>,
|
|
470
418
|
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems),
|
|
471
|
-
}
|
|
419
|
+
};
|
|
472
420
|
},
|
|
473
421
|
|
|
474
422
|
swapArrayItems: (state, arrayPath, indexA, indexB) => {
|
|
475
|
-
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
423
|
+
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown>;
|
|
476
424
|
if (
|
|
477
|
-
indexA < 0 ||
|
|
478
|
-
|
|
425
|
+
indexA < 0 ||
|
|
426
|
+
indexA >= currentItems.length ||
|
|
427
|
+
indexB < 0 ||
|
|
428
|
+
indexB >= currentItems.length ||
|
|
479
429
|
indexA === indexB
|
|
480
430
|
) {
|
|
481
|
-
return state
|
|
431
|
+
return state;
|
|
482
432
|
}
|
|
483
|
-
const newItems = [...currentItems]
|
|
484
|
-
const temp = newItems[indexA]
|
|
485
|
-
newItems[indexA] = newItems[indexB]
|
|
486
|
-
newItems[indexB] = temp
|
|
433
|
+
const newItems = [...currentItems];
|
|
434
|
+
const temp = newItems[indexA];
|
|
435
|
+
newItems[indexA] = newItems[indexB];
|
|
436
|
+
newItems[indexB] = temp;
|
|
487
437
|
return {
|
|
488
438
|
...state,
|
|
489
439
|
values: setNestedValue(state.values, arrayPath, newItems) as Field.EncodedFromFields<TFields>,
|
|
490
440
|
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems),
|
|
491
|
-
}
|
|
441
|
+
};
|
|
492
442
|
},
|
|
493
443
|
|
|
494
444
|
moveArrayItem: (state, arrayPath, fromIndex, toIndex) => {
|
|
495
|
-
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
445
|
+
const currentItems = (getNestedValue(state.values, arrayPath) ?? []) as ReadonlyArray<unknown>;
|
|
496
446
|
if (
|
|
497
|
-
fromIndex < 0 ||
|
|
498
|
-
|
|
447
|
+
fromIndex < 0 ||
|
|
448
|
+
fromIndex >= currentItems.length ||
|
|
449
|
+
toIndex < 0 ||
|
|
450
|
+
toIndex > currentItems.length ||
|
|
499
451
|
fromIndex === toIndex
|
|
500
452
|
) {
|
|
501
|
-
return state
|
|
453
|
+
return state;
|
|
502
454
|
}
|
|
503
|
-
const newItems = [...currentItems]
|
|
504
|
-
const [item] = newItems.splice(fromIndex, 1)
|
|
505
|
-
newItems.splice(toIndex, 0, item)
|
|
455
|
+
const newItems = [...currentItems];
|
|
456
|
+
const [item] = newItems.splice(fromIndex, 1);
|
|
457
|
+
newItems.splice(toIndex, 0, item);
|
|
506
458
|
return {
|
|
507
459
|
...state,
|
|
508
460
|
values: setNestedValue(state.values, arrayPath, newItems) as Field.EncodedFromFields<TFields>,
|
|
509
461
|
dirtyFields: recalculateDirtyFieldsForArray(state.dirtyFields, state.initialValues, arrayPath, newItems),
|
|
510
|
-
}
|
|
462
|
+
};
|
|
511
463
|
},
|
|
512
464
|
|
|
513
465
|
revertToLastSubmit: (state) => {
|
|
514
466
|
if (Option.isNone(state.lastSubmittedValues)) {
|
|
515
|
-
return state
|
|
467
|
+
return state;
|
|
516
468
|
}
|
|
517
469
|
|
|
518
|
-
const lastEncoded = state.lastSubmittedValues.value.encoded
|
|
470
|
+
const lastEncoded = state.lastSubmittedValues.value.encoded;
|
|
519
471
|
if (state.values === lastEncoded) {
|
|
520
|
-
return state
|
|
472
|
+
return state;
|
|
521
473
|
}
|
|
522
474
|
|
|
523
|
-
const newDirtyFields = recalculateDirtySubtree(
|
|
524
|
-
state.dirtyFields,
|
|
525
|
-
state.initialValues,
|
|
526
|
-
lastEncoded,
|
|
527
|
-
"",
|
|
528
|
-
)
|
|
475
|
+
const newDirtyFields = recalculateDirtySubtree(state.dirtyFields, state.initialValues, lastEncoded, "");
|
|
529
476
|
|
|
530
477
|
return {
|
|
531
478
|
...state,
|
|
532
479
|
values: lastEncoded,
|
|
533
480
|
dirtyFields: newDirtyFields,
|
|
534
|
-
}
|
|
481
|
+
};
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const resetAtom = Atom.fnSync<void>()(
|
|
486
|
+
(_: void, get) => {
|
|
487
|
+
const state = get(stateAtom);
|
|
488
|
+
if (Option.isNone(state)) return;
|
|
489
|
+
get.set(stateAtom, Option.some(operations.createResetState(state.value)));
|
|
490
|
+
get.set(errorsAtom, new Map());
|
|
491
|
+
resetValidationAtoms(get);
|
|
492
|
+
get.set(submitAtom, Atom.Reset);
|
|
493
|
+
},
|
|
494
|
+
{ initialValue: undefined as void },
|
|
495
|
+
).pipe(Atom.setIdleTTL(0));
|
|
496
|
+
|
|
497
|
+
const revertToLastSubmitAtom = Atom.fnSync<void>()(
|
|
498
|
+
(_: void, get) => {
|
|
499
|
+
const state = get(stateAtom);
|
|
500
|
+
if (Option.isNone(state)) return;
|
|
501
|
+
get.set(stateAtom, Option.some(operations.revertToLastSubmit(state.value)));
|
|
502
|
+
get.set(errorsAtom, new Map());
|
|
535
503
|
},
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
get.set(stateAtom, Option.some(operations.revertToLastSubmit(state.value)))
|
|
551
|
-
get.set(errorsAtom, new Map())
|
|
552
|
-
}, { initialValue: undefined as void }).pipe(Atom.setIdleTTL(0))
|
|
553
|
-
|
|
554
|
-
const setValuesAtom = Atom.fnSync<Field.EncodedFromFields<TFields>>()((_values, get) => {
|
|
555
|
-
const state = get(stateAtom)
|
|
556
|
-
if (Option.isNone(state)) return
|
|
557
|
-
get.set(stateAtom, Option.some(operations.setFormValues(state.value, _values)))
|
|
558
|
-
get.set(errorsAtom, new Map())
|
|
559
|
-
}, { initialValue: undefined as void }).pipe(Atom.setIdleTTL(0))
|
|
560
|
-
|
|
561
|
-
const setValueAtomsRegistry = createWeakRegistry<Atom.Writable<void, any>>()
|
|
504
|
+
{ initialValue: undefined as void },
|
|
505
|
+
).pipe(Atom.setIdleTTL(0));
|
|
506
|
+
|
|
507
|
+
const setValuesAtom = Atom.fnSync<Field.EncodedFromFields<TFields>>()(
|
|
508
|
+
(_values, get) => {
|
|
509
|
+
const state = get(stateAtom);
|
|
510
|
+
if (Option.isNone(state)) return;
|
|
511
|
+
get.set(stateAtom, Option.some(operations.setFormValues(state.value, _values)));
|
|
512
|
+
get.set(errorsAtom, new Map());
|
|
513
|
+
},
|
|
514
|
+
{ initialValue: undefined as void },
|
|
515
|
+
).pipe(Atom.setIdleTTL(0));
|
|
516
|
+
|
|
517
|
+
const setValueAtomsRegistry = createWeakRegistry<Atom.Writable<void, any>>();
|
|
562
518
|
|
|
563
519
|
const setValue = <S>(field: FormBuilder.FieldRef<S>): Atom.Writable<void, S | ((prev: S) => S)> => {
|
|
564
|
-
const cached = setValueAtomsRegistry.get(field.key)
|
|
565
|
-
if (cached) return cached
|
|
520
|
+
const cached = setValueAtomsRegistry.get(field.key);
|
|
521
|
+
if (cached) return cached;
|
|
566
522
|
|
|
567
|
-
const atom = Atom.fnSync<S | ((prev: S) => S)>()(
|
|
568
|
-
|
|
569
|
-
|
|
523
|
+
const atom = Atom.fnSync<S | ((prev: S) => S)>()(
|
|
524
|
+
(update, get) => {
|
|
525
|
+
const state = get(stateAtom);
|
|
526
|
+
if (Option.isNone(state)) return;
|
|
570
527
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
? (update as (prev: S) => S)(currentValue)
|
|
574
|
-
: update
|
|
528
|
+
const currentValue = getNestedValue(state.value.values, field.key) as S;
|
|
529
|
+
const newValue = typeof update === "function" ? (update as (prev: S) => S)(currentValue) : update;
|
|
575
530
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
531
|
+
get.set(stateAtom, Option.some(operations.setFieldValue(state.value, field.key, newValue)));
|
|
532
|
+
// Don't clear errors - display logic handles showing/hiding based on source + validation state
|
|
533
|
+
},
|
|
534
|
+
{ initialValue: undefined as void },
|
|
535
|
+
).pipe(Atom.setIdleTTL(0));
|
|
579
536
|
|
|
580
|
-
setValueAtomsRegistry.set(field.key, atom)
|
|
581
|
-
return atom
|
|
582
|
-
}
|
|
537
|
+
setValueAtomsRegistry.set(field.key, atom);
|
|
538
|
+
return atom;
|
|
539
|
+
};
|
|
583
540
|
|
|
584
541
|
const getFieldAtom = <S>(field: FormBuilder.FieldRef<S>): Atom.Atom<Option.Option<S>> => {
|
|
585
|
-
const existing = publicFieldAtomRegistry.get(field.key)
|
|
586
|
-
if (existing) return existing as Atom.Atom<Option.Option<S
|
|
542
|
+
const existing = publicFieldAtomRegistry.get(field.key);
|
|
543
|
+
if (existing) return existing as Atom.Atom<Option.Option<S>>;
|
|
587
544
|
|
|
588
545
|
const safeAtom = Atom.readable((get) =>
|
|
589
546
|
Option.map(get(stateAtom), (state) => getNestedValue(state.values, field.key) as S)
|
|
590
|
-
).pipe(Atom.setIdleTTL(0))
|
|
547
|
+
).pipe(Atom.setIdleTTL(0));
|
|
591
548
|
|
|
592
|
-
publicFieldAtomRegistry.set(field.key, safeAtom)
|
|
593
|
-
return safeAtom
|
|
594
|
-
}
|
|
549
|
+
publicFieldAtomRegistry.set(field.key, safeAtom);
|
|
550
|
+
return safeAtom;
|
|
551
|
+
};
|
|
595
552
|
|
|
596
553
|
const mountAtom = Atom.readable((get) => {
|
|
597
|
-
get(stateAtom)
|
|
598
|
-
get(errorsAtom)
|
|
599
|
-
get(submitAtom)
|
|
600
|
-
}).pipe(Atom.setIdleTTL(0))
|
|
554
|
+
get(stateAtom);
|
|
555
|
+
get(errorsAtom);
|
|
556
|
+
get(submitAtom);
|
|
557
|
+
}).pipe(Atom.setIdleTTL(0));
|
|
601
558
|
|
|
602
|
-
const keepAliveActiveAtom = Atom.make(false).pipe(Atom.setIdleTTL(0))
|
|
559
|
+
const keepAliveActiveAtom = Atom.make(false).pipe(Atom.setIdleTTL(0));
|
|
603
560
|
|
|
604
561
|
return {
|
|
605
562
|
stateAtom,
|
|
@@ -628,5 +585,5 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
|
|
|
628
585
|
getFieldAtom,
|
|
629
586
|
mountAtom,
|
|
630
587
|
keepAliveActiveAtom,
|
|
631
|
-
} as FormAtoms<TFields, R, A, E, SubmitArgs
|
|
632
|
-
}
|
|
588
|
+
} as FormAtoms<TFields, R, A, E, SubmitArgs>;
|
|
589
|
+
};
|