@lucas-barake/effect-form-react 0.20.0 → 0.22.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/FormReact.d.ts → FormReact.d.ts} +22 -33
- package/dist/FormReact.d.ts.map +1 -0
- package/dist/FormReact.js +238 -0
- package/dist/FormReact.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -15
- package/src/FormReact.tsx +252 -470
- package/src/index.ts +2 -8
- package/dist/cjs/FormReact.js +0 -402
- package/dist/cjs/FormReact.js.map +0 -1
- package/dist/cjs/index.js +0 -42
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/internal/use-debounced.js +0 -56
- package/dist/cjs/internal/use-debounced.js.map +0 -1
- package/dist/dts/FormReact.d.ts.map +0 -1
- package/dist/dts/index.d.ts +0 -9
- package/dist/dts/index.d.ts.map +0 -1
- package/dist/dts/internal/use-debounced.d.ts +0 -2
- package/dist/dts/internal/use-debounced.d.ts.map +0 -1
- package/dist/esm/FormReact.js +0 -375
- package/dist/esm/FormReact.js.map +0 -1
- package/dist/esm/index.js +0 -9
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/internal/use-debounced.js +0 -29
- package/dist/esm/internal/use-debounced.js.map +0 -1
- package/dist/esm/package.json +0 -4
- package/src/internal/use-debounced.ts +0 -39
package/src/FormReact.tsx
CHANGED
|
@@ -1,71 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import * as
|
|
10
|
-
import
|
|
11
|
-
import type * as
|
|
12
|
-
import
|
|
13
|
-
import * as
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export interface FieldState<E> {
|
|
27
|
-
readonly value: E;
|
|
28
|
-
readonly onChange: (value: E) => void;
|
|
29
|
-
readonly onBlur: () => void;
|
|
30
|
-
readonly error: Option.Option<string>;
|
|
31
|
-
readonly isTouched: boolean;
|
|
32
|
-
readonly isValidating: boolean;
|
|
33
|
-
readonly isDirty: boolean;
|
|
1
|
+
import { RegistryContext, useAtom, useAtomMount, useAtomSet, useAtomValue } from "@effect-atom/atom-react"
|
|
2
|
+
import * as Atom from "@effect-atom/atom/Atom"
|
|
3
|
+
import type * as Registry from "@effect-atom/atom/Registry"
|
|
4
|
+
import { Field, FormAtoms } from "@lucas-barake/effect-form"
|
|
5
|
+
import type { FieldState as FieldStateModule, Mode } from "@lucas-barake/effect-form"
|
|
6
|
+
import type * as FormBuilder from "@lucas-barake/effect-form/FormBuilder"
|
|
7
|
+
import { getNestedValue } from "@lucas-barake/effect-form/Path"
|
|
8
|
+
import type * as Effect from "effect/Effect"
|
|
9
|
+
import * as Layer from "effect/Layer"
|
|
10
|
+
import * as Option from "effect/Option"
|
|
11
|
+
import type * as ParseResult from "effect/ParseResult"
|
|
12
|
+
import type * as Schema from "effect/Schema"
|
|
13
|
+
import * as React from "react"
|
|
14
|
+
import { createContext, useContext } from "react"
|
|
15
|
+
|
|
16
|
+
export type FieldValue<T,> = FieldStateModule.FieldValue<T>
|
|
17
|
+
|
|
18
|
+
export type FieldState<E,> = FieldStateModule.FieldState<E>
|
|
19
|
+
|
|
20
|
+
export type ArrayFieldOperations<TItem,> = FieldStateModule.ArrayFieldOperations<TItem>
|
|
21
|
+
|
|
22
|
+
export interface FieldComponentProps<E, P = Record<string, never>,> {
|
|
23
|
+
readonly field: FieldState<E>
|
|
24
|
+
readonly props: P
|
|
34
25
|
}
|
|
35
26
|
|
|
36
|
-
export
|
|
37
|
-
readonly field: FieldState<E>;
|
|
38
|
-
readonly props: P;
|
|
39
|
-
}
|
|
27
|
+
export type FieldComponent<T, P = Record<string, never>,> = React.FC<FieldComponentProps<FieldValue<T>, P>>
|
|
40
28
|
|
|
41
|
-
export type
|
|
29
|
+
export type ExtractExtraProps<C,> = C extends React.FC<FieldComponentProps<any, infer P>> ? P : Record<string, never>
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
type StructFieldsFromSchema<S,> = S extends Schema.Struct<infer Fields> ? Fields
|
|
32
|
+
: S extends { readonly from: infer From } ? StructFieldsFromSchema<From>
|
|
33
|
+
: never
|
|
44
34
|
|
|
45
|
-
export type ArrayItemComponentMap<S extends Schema.Schema.Any
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
export type ArrayItemComponentMap<S extends Schema.Schema.Any,> = StructFieldsFromSchema<S> extends
|
|
36
|
+
Schema.Struct.Fields ? {
|
|
37
|
+
readonly [K in keyof StructFieldsFromSchema<S>]: StructFieldsFromSchema<S>[K] extends Schema.Schema.Any
|
|
38
|
+
? React.FC<FieldComponentProps<Schema.Schema.Encoded<StructFieldsFromSchema<S>[K]>, any>>
|
|
39
|
+
: never
|
|
49
40
|
}
|
|
50
|
-
: React.FC<FieldComponentProps<Schema.Schema.Encoded<S>, any
|
|
41
|
+
: React.FC<FieldComponentProps<Schema.Schema.Encoded<S>, any>>
|
|
51
42
|
|
|
52
|
-
export type FieldComponentMap<TFields extends Field.FieldsRecord
|
|
43
|
+
export type FieldComponentMap<TFields extends Field.FieldsRecord,> = {
|
|
53
44
|
readonly [K in keyof TFields]: TFields[K] extends Field.FieldDef<any, infer S>
|
|
54
45
|
? React.FC<FieldComponentProps<Schema.Schema.Encoded<S>, any>>
|
|
55
46
|
: TFields[K] extends Field.ArrayFieldDef<any, infer S> ? ArrayItemComponentMap<S>
|
|
56
|
-
: never
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export type FieldRefs<TFields extends Field.FieldsRecord> = FormAtoms.FieldRefs<TFields>;
|
|
60
|
-
|
|
61
|
-
export interface ArrayFieldOperations<TItem> {
|
|
62
|
-
readonly items: ReadonlyArray<TItem>;
|
|
63
|
-
readonly append: (value?: TItem) => void;
|
|
64
|
-
readonly remove: (index: number) => void;
|
|
65
|
-
readonly swap: (indexA: number, indexB: number) => void;
|
|
66
|
-
readonly move: (from: number, to: number) => void;
|
|
47
|
+
: never
|
|
67
48
|
}
|
|
68
49
|
|
|
50
|
+
export type FieldRefs<TFields extends Field.FieldsRecord,> = FormAtoms.FieldRefs<TFields>
|
|
51
|
+
|
|
69
52
|
export type BuiltForm<
|
|
70
53
|
TFields extends Field.FieldsRecord,
|
|
71
54
|
R,
|
|
@@ -74,407 +57,307 @@ export type BuiltForm<
|
|
|
74
57
|
SubmitArgs = void,
|
|
75
58
|
CM extends FieldComponentMap<TFields> = FieldComponentMap<TFields>,
|
|
76
59
|
> = {
|
|
77
|
-
readonly values: Atom.Atom<Option.Option<Field.EncodedFromFields<TFields
|
|
78
|
-
readonly isDirty: Atom.Atom<boolean
|
|
79
|
-
readonly hasChangedSinceSubmit: Atom.Atom<boolean
|
|
80
|
-
readonly lastSubmittedValues: Atom.Atom<Option.Option<FormBuilder.SubmittedValues<TFields
|
|
81
|
-
readonly submitCount: Atom.Atom<number
|
|
60
|
+
readonly values: Atom.Atom<Option.Option<Field.EncodedFromFields<TFields>>>
|
|
61
|
+
readonly isDirty: Atom.Atom<boolean>
|
|
62
|
+
readonly hasChangedSinceSubmit: Atom.Atom<boolean>
|
|
63
|
+
readonly lastSubmittedValues: Atom.Atom<Option.Option<FormBuilder.SubmittedValues<TFields>>>
|
|
64
|
+
readonly submitCount: Atom.Atom<number>
|
|
82
65
|
|
|
83
|
-
readonly schema: Schema.Schema<Field.DecodedFromFields<TFields>, Field.EncodedFromFields<TFields>, R
|
|
84
|
-
readonly fields: FieldRefs<TFields
|
|
66
|
+
readonly schema: Schema.Schema<Field.DecodedFromFields<TFields>, Field.EncodedFromFields<TFields>, R>
|
|
67
|
+
readonly fields: FieldRefs<TFields>
|
|
85
68
|
|
|
86
69
|
readonly Initialize: React.FC<{
|
|
87
|
-
readonly defaultValues: Field.EncodedFromFields<TFields
|
|
88
|
-
readonly children: React.ReactNode
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
readonly submit: Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError
|
|
92
|
-
readonly reset: Atom.Writable<void, void
|
|
93
|
-
readonly revertToLastSubmit: Atom.Writable<void, void
|
|
94
|
-
readonly setValues: Atom.Writable<void,
|
|
95
|
-
readonly
|
|
96
|
-
|
|
97
|
-
readonly
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
type FieldComponents<TFields extends Field.FieldsRecord, CM extends FieldComponentMap<TFields>> = {
|
|
70
|
+
readonly defaultValues: Field.EncodedFromFields<TFields>
|
|
71
|
+
readonly children: React.ReactNode
|
|
72
|
+
}>
|
|
73
|
+
|
|
74
|
+
readonly submit: Atom.AtomResultFn<SubmitArgs, A, E | ParseResult.ParseError>
|
|
75
|
+
readonly reset: Atom.Writable<void, void>
|
|
76
|
+
readonly revertToLastSubmit: Atom.Writable<void, void>
|
|
77
|
+
readonly setValues: Atom.Writable<void, FormAtoms.SetValuesArg<TFields>>
|
|
78
|
+
readonly getFieldAtoms: <S,>(field: FormBuilder.FieldRef<S>) => FormAtoms.PublicFieldAtoms<S>
|
|
79
|
+
|
|
80
|
+
readonly mount: Atom.Atom<void>
|
|
81
|
+
readonly KeepAlive: React.FC
|
|
82
|
+
} & FieldComponents<TFields, CM>
|
|
83
|
+
|
|
84
|
+
type FieldComponents<TFields extends Field.FieldsRecord, CM extends FieldComponentMap<TFields>,> = {
|
|
104
85
|
readonly [K in keyof TFields]: TFields[K] extends Field.FieldDef<any, any> ? React.FC<ExtractExtraProps<CM[K]>>
|
|
105
86
|
: TFields[K] extends Field.ArrayFieldDef<any, infer S>
|
|
106
87
|
? ArrayFieldComponent<S, ExtractArrayItemExtraProps<CM[K], S>>
|
|
107
|
-
: never
|
|
108
|
-
}
|
|
88
|
+
: never
|
|
89
|
+
}
|
|
109
90
|
|
|
110
|
-
type ExtractArrayItemExtraProps<CM, S extends Schema.Schema.Any
|
|
111
|
-
|
|
91
|
+
type ExtractArrayItemExtraProps<CM, S extends Schema.Schema.Any,> = StructFieldsFromSchema<S> extends
|
|
92
|
+
Schema.Struct.Fields ? {
|
|
93
|
+
readonly [K in keyof StructFieldsFromSchema<S>]: CM extends { readonly [P in K]: infer C } ? ExtractExtraProps<C>
|
|
94
|
+
: never
|
|
95
|
+
}
|
|
112
96
|
: CM extends React.FC<FieldComponentProps<any, infer P>> ? P
|
|
113
|
-
: never
|
|
97
|
+
: never
|
|
114
98
|
|
|
115
|
-
type ArrayFieldComponent<S extends Schema.Schema.Any, ExtraPropsMap
|
|
99
|
+
type ArrayFieldComponent<S extends Schema.Schema.Any, ExtraPropsMap,> =
|
|
116
100
|
& React.FC<{
|
|
117
|
-
readonly children: (ops: ArrayFieldOperations<Schema.Schema.Encoded<S>>) => React.ReactNode
|
|
101
|
+
readonly children: (ops: ArrayFieldOperations<Schema.Schema.Encoded<S>>) => React.ReactNode
|
|
118
102
|
}>
|
|
119
103
|
& {
|
|
120
104
|
readonly Item: React.FC<{
|
|
121
|
-
readonly index: number
|
|
122
|
-
readonly children: React.ReactNode | ((props: { readonly remove: () => void
|
|
123
|
-
}
|
|
105
|
+
readonly index: number
|
|
106
|
+
readonly children: React.ReactNode | ((props: { readonly remove: () => void }) => React.ReactNode)
|
|
107
|
+
}>
|
|
124
108
|
}
|
|
125
|
-
& (S extends Schema.Struct
|
|
126
|
-
readonly [K in keyof
|
|
127
|
-
ExtraPropsMap extends { readonly [P in K]: infer EP
|
|
128
|
-
|
|
109
|
+
& (StructFieldsFromSchema<S> extends Schema.Struct.Fields ? {
|
|
110
|
+
readonly [K in keyof StructFieldsFromSchema<S>]: React.FC<
|
|
111
|
+
ExtraPropsMap extends { readonly [P in K]: infer EP } ? EP : Record<string, never>
|
|
112
|
+
>
|
|
129
113
|
}
|
|
130
|
-
: unknown)
|
|
114
|
+
: unknown)
|
|
131
115
|
|
|
132
116
|
interface ArrayItemContextValue {
|
|
133
|
-
readonly index: number
|
|
134
|
-
readonly parentPath: string
|
|
117
|
+
readonly index: number
|
|
118
|
+
readonly parentPath: string
|
|
135
119
|
}
|
|
136
120
|
|
|
137
|
-
const ArrayItemContext = createContext<ArrayItemContextValue | null>(null)
|
|
138
|
-
const AutoSubmitContext = createContext<(() => void) | null>(null);
|
|
121
|
+
const ArrayItemContext = createContext<ArrayItemContextValue | null>(null)
|
|
139
122
|
|
|
140
|
-
const makeFieldComponent = <S extends Schema.Schema.Any, P
|
|
123
|
+
const makeFieldComponent = <S extends Schema.Schema.Any, P,>(
|
|
141
124
|
fieldKey: string,
|
|
142
125
|
fieldDef: Field.FieldDef<string, S>,
|
|
143
|
-
|
|
144
|
-
submitCountAtom: Atom.Atom<number>,
|
|
145
|
-
parsedMode: Mode.ParsedMode,
|
|
146
|
-
getOrCreateValidationAtom: (
|
|
147
|
-
fieldPath: string,
|
|
148
|
-
schema: Schema.Schema.Any,
|
|
149
|
-
) => Atom.AtomResultFn<unknown, void, ParseResult.ParseError>,
|
|
150
|
-
getOrCreateFieldAtoms: (fieldPath: string) => FormAtoms.FieldAtoms,
|
|
126
|
+
getOrCreateFieldAtoms: (fieldPath: string, schema: Schema.Schema.Any) => FormAtoms.FieldAtoms,
|
|
151
127
|
Component: React.FC<FieldComponentProps<Schema.Schema.Encoded<S>, P>>,
|
|
128
|
+
onBlurSubmitAtom: Atom.Writable<void, void>
|
|
152
129
|
): React.FC<P> => {
|
|
153
130
|
const FieldComponent: React.FC<P> = (extraProps) => {
|
|
154
|
-
const arrayCtx = useContext(ArrayItemContext)
|
|
155
|
-
const
|
|
156
|
-
const fieldPath = arrayCtx ? `${arrayCtx.parentPath}.${fieldKey}` : fieldKey;
|
|
157
|
-
|
|
158
|
-
const { errorAtom, isDirtyAtom, touchedAtom, valueAtom } = React.useMemo(
|
|
159
|
-
() => getOrCreateFieldAtoms(fieldPath),
|
|
160
|
-
[fieldPath],
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
const [value, setValue] = useAtom(valueAtom) as [Schema.Schema.Encoded<S>, (v: unknown) => void];
|
|
164
|
-
const [isTouched, setTouched] = useAtom(touchedAtom);
|
|
165
|
-
const storedError = useAtomValue(errorAtom);
|
|
166
|
-
const submitCount = useAtomValue(submitCountAtom);
|
|
131
|
+
const arrayCtx = useContext(ArrayItemContext)
|
|
132
|
+
const fieldPath = arrayCtx ? `${arrayCtx.parentPath}.${fieldKey}` : fieldKey
|
|
167
133
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
134
|
+
const fieldAtoms = React.useMemo(
|
|
135
|
+
() => getOrCreateFieldAtoms(fieldPath, fieldDef.schema),
|
|
136
|
+
[fieldPath]
|
|
137
|
+
)
|
|
171
138
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
if (prevValueRef.current === value) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
prevValueRef.current = value;
|
|
139
|
+
const [value, setValue] = useAtom(fieldAtoms.valueAtom) as [Schema.Schema.Encoded<S>, (v: unknown) => void]
|
|
140
|
+
const [isTouched, setTouched] = useAtom(fieldAtoms.touchedAtom)
|
|
141
|
+
const displayError = useAtomValue(fieldAtoms.displayErrorAtom)
|
|
142
|
+
const isDirty = useAtomValue(fieldAtoms.isDirtyAtom)
|
|
143
|
+
const isValidating = useAtomValue(fieldAtoms.validationAtom).waiting
|
|
144
|
+
const setOnBlurSubmit = useAtomSet(onBlurSubmitAtom)
|
|
182
145
|
|
|
183
|
-
|
|
184
|
-
(parsedMode.validation === "onBlur" && isTouched) ||
|
|
185
|
-
(parsedMode.validation === "onSubmit" && submitCount > 0);
|
|
186
|
-
|
|
187
|
-
if (shouldValidate) {
|
|
188
|
-
validate(value);
|
|
189
|
-
}
|
|
190
|
-
}, [value, isTouched, submitCount, validate]);
|
|
191
|
-
|
|
192
|
-
const livePerFieldError: Option.Option<string> = React.useMemo(() => {
|
|
193
|
-
if (validationResult._tag === "Failure") {
|
|
194
|
-
const parseError = Cause.failureOption(validationResult.cause);
|
|
195
|
-
if (Option.isSome(parseError) && ParseResult.isParseError(parseError.value)) {
|
|
196
|
-
return Validation.extractFirstError(parseError.value);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return Option.none();
|
|
200
|
-
}, [validationResult]);
|
|
201
|
-
|
|
202
|
-
const isValidating = validationResult.waiting;
|
|
203
|
-
|
|
204
|
-
const validationError: Option.Option<string> = React.useMemo(() => {
|
|
205
|
-
if (Option.isSome(livePerFieldError)) {
|
|
206
|
-
return livePerFieldError;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (Option.isSome(storedError)) {
|
|
210
|
-
// Hide field-sourced errors when validation passes or is pending (async gap).
|
|
211
|
-
// Refinement errors persist until re-submit - they can't be cleared by typing.
|
|
212
|
-
const shouldHideStoredError = storedError.value.source === "field" &&
|
|
213
|
-
(validationResult._tag === "Success" || isValidating);
|
|
214
|
-
|
|
215
|
-
if (shouldHideStoredError) {
|
|
216
|
-
return Option.none();
|
|
217
|
-
}
|
|
218
|
-
return Option.some(storedError.value.message);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return Option.none();
|
|
222
|
-
}, [livePerFieldError, storedError, validationResult, isValidating]);
|
|
146
|
+
useAtomMount(fieldAtoms.triggerValidationAtom)
|
|
223
147
|
|
|
224
148
|
const onChange = React.useCallback(
|
|
225
|
-
(newValue: Schema.Schema.Encoded<S>) =>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
[setValue],
|
|
229
|
-
);
|
|
149
|
+
(newValue: Schema.Schema.Encoded<S>) => setValue(newValue),
|
|
150
|
+
[setValue]
|
|
151
|
+
)
|
|
230
152
|
|
|
231
153
|
const onBlur = React.useCallback(() => {
|
|
232
|
-
setTouched(true)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
autoSubmitOnBlur?.();
|
|
237
|
-
}, [setTouched, validate, value, autoSubmitOnBlur]);
|
|
238
|
-
|
|
239
|
-
const isDirty = useAtomValue(isDirtyAtom);
|
|
240
|
-
const shouldShowError = parsedMode.validation === "onChange"
|
|
241
|
-
? isDirty || submitCount > 0
|
|
242
|
-
: parsedMode.validation === "onBlur"
|
|
243
|
-
? isTouched || submitCount > 0
|
|
244
|
-
: submitCount > 0;
|
|
154
|
+
setTouched(true)
|
|
155
|
+
setOnBlurSubmit()
|
|
156
|
+
}, [setTouched, setOnBlurSubmit])
|
|
245
157
|
|
|
246
|
-
const fieldState
|
|
158
|
+
const fieldState = React.useMemo(
|
|
247
159
|
() => ({
|
|
248
160
|
value,
|
|
249
161
|
onChange,
|
|
250
162
|
onBlur,
|
|
251
|
-
error:
|
|
163
|
+
error: displayError,
|
|
252
164
|
isTouched,
|
|
253
165
|
isValidating,
|
|
254
|
-
isDirty
|
|
166
|
+
isDirty
|
|
255
167
|
}),
|
|
256
|
-
[value, onChange, onBlur,
|
|
257
|
-
)
|
|
168
|
+
[value, onChange, onBlur, displayError, isTouched, isValidating, isDirty]
|
|
169
|
+
)
|
|
258
170
|
|
|
259
|
-
return <Component field={fieldState} props={extraProps}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
};
|
|
171
|
+
return <Component field={fieldState} props={extraProps} />
|
|
172
|
+
}
|
|
173
|
+
return React.memo(FieldComponent) as React.FC<P>
|
|
174
|
+
}
|
|
264
175
|
|
|
265
|
-
const makeArrayFieldComponent = <S extends Schema.Schema.Any
|
|
176
|
+
const makeArrayFieldComponent = <S extends Schema.Schema.Any,>(
|
|
266
177
|
fieldKey: string,
|
|
267
178
|
def: Field.ArrayFieldDef<string, S>,
|
|
268
179
|
stateAtom: Atom.Writable<Option.Option<FormBuilder.FormState<any>>, Option.Option<FormBuilder.FormState<any>>>,
|
|
269
|
-
|
|
270
|
-
submitCountAtom: Atom.Atom<number>,
|
|
271
|
-
dirtyFieldsAtom: Atom.Atom<ReadonlySet<string>>,
|
|
272
|
-
parsedMode: Mode.ParsedMode,
|
|
273
|
-
getOrCreateValidationAtom: (
|
|
274
|
-
fieldPath: string,
|
|
275
|
-
schema: Schema.Schema.Any,
|
|
276
|
-
) => Atom.AtomResultFn<unknown, void, ParseResult.ParseError>,
|
|
277
|
-
getOrCreateFieldAtoms: (fieldPath: string) => FormAtoms.FieldAtoms,
|
|
180
|
+
getOrCreateFieldAtoms: (fieldPath: string, schema: Schema.Schema.Any) => FormAtoms.FieldAtoms,
|
|
278
181
|
operations: FormAtoms.FormOperations<any>,
|
|
279
182
|
componentMap: ArrayItemComponentMap<S>,
|
|
183
|
+
onBlurSubmitAtom: Atom.Writable<void, void>
|
|
280
184
|
): ArrayFieldComponent<S, any> => {
|
|
281
|
-
const isStructSchema = AST.isTypeLiteral(def.itemSchema.ast);
|
|
282
|
-
|
|
283
185
|
const ArrayWrapper: React.FC<{
|
|
284
|
-
readonly children: (ops: ArrayFieldOperations<Schema.Schema.Encoded<S>>) => React.ReactNode
|
|
186
|
+
readonly children: (ops: ArrayFieldOperations<Schema.Schema.Encoded<S>>) => React.ReactNode
|
|
285
187
|
}> = ({ children }) => {
|
|
286
|
-
const arrayCtx = useContext(ArrayItemContext)
|
|
287
|
-
const [formStateOption, setFormState] = useAtom(stateAtom)
|
|
288
|
-
const formState = Option.getOrThrow(formStateOption)
|
|
188
|
+
const arrayCtx = useContext(ArrayItemContext)
|
|
189
|
+
const [formStateOption, setFormState] = useAtom(stateAtom)
|
|
190
|
+
const formState = Option.getOrThrow(formStateOption)
|
|
289
191
|
|
|
290
|
-
const fieldPath = arrayCtx ? `${arrayCtx.parentPath}.${fieldKey}` : fieldKey
|
|
192
|
+
const fieldPath = arrayCtx ? `${arrayCtx.parentPath}.${fieldKey}` : fieldKey
|
|
291
193
|
const items = React.useMemo(
|
|
292
194
|
() => (getNestedValue(formState.values, fieldPath) ?? []) as ReadonlyArray<Schema.Schema.Encoded<S>>,
|
|
293
|
-
[formState.values, fieldPath]
|
|
294
|
-
)
|
|
195
|
+
[formState.values, fieldPath]
|
|
196
|
+
)
|
|
295
197
|
|
|
296
198
|
const append = React.useCallback(
|
|
297
199
|
(value?: Schema.Schema.Encoded<S>) => {
|
|
298
200
|
setFormState((prev) => {
|
|
299
|
-
if (Option.isNone(prev)) return prev
|
|
300
|
-
return Option.some(operations.appendArrayItem(prev.value, fieldPath, def.itemSchema, value))
|
|
301
|
-
})
|
|
201
|
+
if (Option.isNone(prev)) return prev
|
|
202
|
+
return Option.some(operations.appendArrayItem(prev.value, fieldPath, def.itemSchema, value))
|
|
203
|
+
})
|
|
302
204
|
},
|
|
303
|
-
[fieldPath, setFormState]
|
|
304
|
-
)
|
|
205
|
+
[fieldPath, setFormState]
|
|
206
|
+
)
|
|
305
207
|
|
|
306
208
|
const remove = React.useCallback(
|
|
307
209
|
(index: number) => {
|
|
308
210
|
setFormState((prev) => {
|
|
309
|
-
if (Option.isNone(prev)) return prev
|
|
310
|
-
return Option.some(operations.removeArrayItem(prev.value, fieldPath, index))
|
|
311
|
-
})
|
|
211
|
+
if (Option.isNone(prev)) return prev
|
|
212
|
+
return Option.some(operations.removeArrayItem(prev.value, fieldPath, index))
|
|
213
|
+
})
|
|
312
214
|
},
|
|
313
|
-
[fieldPath, setFormState]
|
|
314
|
-
)
|
|
215
|
+
[fieldPath, setFormState]
|
|
216
|
+
)
|
|
315
217
|
|
|
316
218
|
const swap = React.useCallback(
|
|
317
219
|
(indexA: number, indexB: number) => {
|
|
318
220
|
setFormState((prev) => {
|
|
319
|
-
if (Option.isNone(prev)) return prev
|
|
320
|
-
return Option.some(operations.swapArrayItems(prev.value, fieldPath, indexA, indexB))
|
|
321
|
-
})
|
|
221
|
+
if (Option.isNone(prev)) return prev
|
|
222
|
+
return Option.some(operations.swapArrayItems(prev.value, fieldPath, indexA, indexB))
|
|
223
|
+
})
|
|
322
224
|
},
|
|
323
|
-
[fieldPath, setFormState]
|
|
324
|
-
)
|
|
225
|
+
[fieldPath, setFormState]
|
|
226
|
+
)
|
|
325
227
|
|
|
326
228
|
const move = React.useCallback(
|
|
327
229
|
(from: number, to: number) => {
|
|
328
230
|
setFormState((prev) => {
|
|
329
|
-
if (Option.isNone(prev)) return prev
|
|
330
|
-
return Option.some(operations.moveArrayItem(prev.value, fieldPath, from, to))
|
|
331
|
-
})
|
|
231
|
+
if (Option.isNone(prev)) return prev
|
|
232
|
+
return Option.some(operations.moveArrayItem(prev.value, fieldPath, from, to))
|
|
233
|
+
})
|
|
332
234
|
},
|
|
333
|
-
[fieldPath, setFormState]
|
|
334
|
-
)
|
|
235
|
+
[fieldPath, setFormState]
|
|
236
|
+
)
|
|
335
237
|
|
|
336
|
-
return <>{children({ items, append, remove, swap, move })}
|
|
337
|
-
}
|
|
238
|
+
return <>{children({ items, append, remove, swap, move })}</>
|
|
239
|
+
}
|
|
338
240
|
|
|
339
241
|
const ItemWrapper: React.FC<{
|
|
340
|
-
readonly index: number
|
|
341
|
-
readonly children: React.ReactNode | ((props: { readonly remove: () => void
|
|
242
|
+
readonly index: number
|
|
243
|
+
readonly children: React.ReactNode | ((props: { readonly remove: () => void }) => React.ReactNode)
|
|
342
244
|
}> = ({ children, index }) => {
|
|
343
|
-
const arrayCtx = useContext(ArrayItemContext)
|
|
344
|
-
const setFormState = useAtomSet(stateAtom)
|
|
245
|
+
const arrayCtx = useContext(ArrayItemContext)
|
|
246
|
+
const setFormState = useAtomSet(stateAtom)
|
|
345
247
|
|
|
346
|
-
const parentPath = arrayCtx ? `${arrayCtx.parentPath}.${fieldKey}` : fieldKey
|
|
347
|
-
const itemPath = `${parentPath}[${index}]
|
|
248
|
+
const parentPath = arrayCtx ? `${arrayCtx.parentPath}.${fieldKey}` : fieldKey
|
|
249
|
+
const itemPath = `${parentPath}[${index}]`
|
|
348
250
|
|
|
349
251
|
const remove = React.useCallback(() => {
|
|
350
252
|
setFormState((prev) => {
|
|
351
|
-
if (Option.isNone(prev)) return prev
|
|
352
|
-
return Option.some(operations.removeArrayItem(prev.value, parentPath, index))
|
|
353
|
-
})
|
|
354
|
-
}, [parentPath, index, setFormState])
|
|
253
|
+
if (Option.isNone(prev)) return prev
|
|
254
|
+
return Option.some(operations.removeArrayItem(prev.value, parentPath, index))
|
|
255
|
+
})
|
|
256
|
+
}, [parentPath, index, setFormState])
|
|
355
257
|
|
|
356
258
|
return (
|
|
357
259
|
<ArrayItemContext.Provider value={{ index, parentPath: itemPath }}>
|
|
358
260
|
{typeof children === "function" ? children({ remove }) : children}
|
|
359
261
|
</ArrayItemContext.Provider>
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const itemFieldComponents: Record<string, React.FC> = {}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
for (const
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
itemFieldComponents[itemKey] = makeFieldComponent(
|
|
373
|
-
itemKey,
|
|
374
|
-
itemDef,
|
|
375
|
-
errorsAtom,
|
|
376
|
-
submitCountAtom,
|
|
377
|
-
parsedMode,
|
|
378
|
-
getOrCreateValidationAtom,
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const itemFieldComponents: Record<string, React.FC> = {}
|
|
266
|
+
|
|
267
|
+
const subFieldDefs = Field.extractStructFieldDefs(def.itemSchema)
|
|
268
|
+
if (subFieldDefs) {
|
|
269
|
+
for (const subDef of subFieldDefs) {
|
|
270
|
+
const itemComponent = (componentMap as Record<string, React.FC<FieldComponentProps<any, any>>>)[subDef.key]
|
|
271
|
+
itemFieldComponents[subDef.key] = makeFieldComponent(
|
|
272
|
+
subDef.key,
|
|
273
|
+
subDef,
|
|
379
274
|
getOrCreateFieldAtoms,
|
|
380
275
|
itemComponent,
|
|
381
|
-
|
|
276
|
+
onBlurSubmitAtom
|
|
277
|
+
)
|
|
382
278
|
}
|
|
383
279
|
}
|
|
384
280
|
|
|
385
281
|
const properties: Record<string, unknown> = {
|
|
386
282
|
Item: ItemWrapper,
|
|
387
|
-
...itemFieldComponents
|
|
388
|
-
}
|
|
283
|
+
...itemFieldComponents
|
|
284
|
+
}
|
|
389
285
|
|
|
390
286
|
return new Proxy(ArrayWrapper, {
|
|
391
287
|
get(target, prop) {
|
|
392
288
|
if (prop in properties) {
|
|
393
|
-
return properties[prop as string]
|
|
289
|
+
return properties[prop as string]
|
|
394
290
|
}
|
|
395
|
-
return Reflect.get(target, prop)
|
|
396
|
-
}
|
|
397
|
-
}) as ArrayFieldComponent<S, any
|
|
398
|
-
}
|
|
291
|
+
return Reflect.get(target, prop)
|
|
292
|
+
}
|
|
293
|
+
}) as ArrayFieldComponent<S, any>
|
|
294
|
+
}
|
|
399
295
|
|
|
400
|
-
const makeFieldComponents = <TFields extends Field.FieldsRecord, CM extends FieldComponentMap<TFields
|
|
296
|
+
const makeFieldComponents = <TFields extends Field.FieldsRecord, CM extends FieldComponentMap<TFields>,>(
|
|
401
297
|
fields: TFields,
|
|
402
298
|
stateAtom: Atom.Writable<
|
|
403
299
|
Option.Option<FormBuilder.FormState<TFields>>,
|
|
404
300
|
Option.Option<FormBuilder.FormState<TFields>>
|
|
405
301
|
>,
|
|
406
|
-
|
|
407
|
-
submitCountAtom: Atom.Atom<number>,
|
|
408
|
-
dirtyFieldsAtom: Atom.Atom<ReadonlySet<string>>,
|
|
409
|
-
parsedMode: Mode.ParsedMode,
|
|
410
|
-
getOrCreateValidationAtom: (
|
|
411
|
-
fieldPath: string,
|
|
412
|
-
schema: Schema.Schema.Any,
|
|
413
|
-
) => Atom.AtomResultFn<unknown, void, ParseResult.ParseError>,
|
|
414
|
-
getOrCreateFieldAtoms: (fieldPath: string) => FormAtoms.FieldAtoms,
|
|
302
|
+
getOrCreateFieldAtoms: (fieldPath: string, schema: Schema.Schema.Any) => FormAtoms.FieldAtoms,
|
|
415
303
|
operations: FormAtoms.FormOperations<TFields>,
|
|
416
304
|
componentMap: CM,
|
|
305
|
+
onBlurSubmitAtom: Atom.Writable<void, void>
|
|
417
306
|
): FieldComponents<TFields, CM> => {
|
|
418
|
-
const components: Record<string, any> = {}
|
|
307
|
+
const components: Record<string, any> = {}
|
|
419
308
|
|
|
420
309
|
for (const [key, def] of Object.entries(fields)) {
|
|
421
310
|
if (Field.isArrayFieldDef(def)) {
|
|
422
|
-
const arrayComponentMap = (componentMap as Record<string, any>)[key]
|
|
311
|
+
const arrayComponentMap = (componentMap as Record<string, any>)[key]
|
|
423
312
|
components[key] = makeArrayFieldComponent(
|
|
424
313
|
key,
|
|
425
314
|
def as Field.ArrayFieldDef<string, Schema.Schema.Any>,
|
|
426
315
|
stateAtom,
|
|
427
|
-
errorsAtom,
|
|
428
|
-
submitCountAtom,
|
|
429
|
-
dirtyFieldsAtom,
|
|
430
|
-
parsedMode,
|
|
431
|
-
getOrCreateValidationAtom,
|
|
432
316
|
getOrCreateFieldAtoms,
|
|
433
317
|
operations,
|
|
434
318
|
arrayComponentMap,
|
|
435
|
-
|
|
319
|
+
onBlurSubmitAtom
|
|
320
|
+
)
|
|
436
321
|
} else if (Field.isFieldDef(def)) {
|
|
437
|
-
const fieldComponent = (componentMap as Record<string, React.FC<FieldComponentProps<any, any>>>)[key]
|
|
322
|
+
const fieldComponent = (componentMap as Record<string, React.FC<FieldComponentProps<any, any>>>)[key]
|
|
438
323
|
components[key] = makeFieldComponent(
|
|
439
324
|
key,
|
|
440
325
|
def,
|
|
441
|
-
errorsAtom,
|
|
442
|
-
submitCountAtom,
|
|
443
|
-
parsedMode,
|
|
444
|
-
getOrCreateValidationAtom,
|
|
445
326
|
getOrCreateFieldAtoms,
|
|
446
327
|
fieldComponent,
|
|
447
|
-
|
|
328
|
+
onBlurSubmitAtom
|
|
329
|
+
)
|
|
448
330
|
}
|
|
449
331
|
}
|
|
450
332
|
|
|
451
|
-
return components as FieldComponents<TFields, CM
|
|
452
|
-
}
|
|
333
|
+
return components as FieldComponents<TFields, CM>
|
|
334
|
+
}
|
|
453
335
|
|
|
454
336
|
export const make: {
|
|
455
337
|
<
|
|
456
338
|
TFields extends Field.FieldsRecord,
|
|
339
|
+
R extends Registry.AtomRegistry,
|
|
457
340
|
A,
|
|
458
341
|
E,
|
|
459
342
|
SubmitArgs = void,
|
|
460
343
|
CM extends FieldComponentMap<TFields> = FieldComponentMap<TFields>,
|
|
461
344
|
>(
|
|
462
|
-
self: FormBuilder.FormBuilder<TFields,
|
|
345
|
+
self: FormBuilder.FormBuilder<TFields, R>,
|
|
463
346
|
options: {
|
|
464
|
-
readonly runtime?: Atom.AtomRuntime<
|
|
465
|
-
readonly fields: CM
|
|
466
|
-
readonly mode?: SubmitArgs extends void ? Mode.FormMode : Mode.FormModeWithoutAutoSubmit
|
|
467
|
-
readonly reactivityKeys?: ReadonlyArray<unknown> | Readonly<Record<string, ReadonlyArray<unknown>>> | undefined
|
|
347
|
+
readonly runtime?: Atom.AtomRuntime<any, any>
|
|
348
|
+
readonly fields: CM
|
|
349
|
+
readonly mode?: SubmitArgs extends void ? Mode.FormMode : Mode.FormModeWithoutAutoSubmit
|
|
350
|
+
readonly reactivityKeys?: ReadonlyArray<unknown> | Readonly<Record<string, ReadonlyArray<unknown>>> | undefined
|
|
468
351
|
readonly onSubmit: (
|
|
469
352
|
args: SubmitArgs,
|
|
470
353
|
ctx: {
|
|
471
|
-
readonly decoded: Field.DecodedFromFields<TFields
|
|
472
|
-
readonly encoded: Field.EncodedFromFields<TFields
|
|
473
|
-
readonly get: Atom.FnContext
|
|
474
|
-
}
|
|
475
|
-
) => A | Effect.Effect<A, E,
|
|
476
|
-
}
|
|
477
|
-
): BuiltForm<TFields,
|
|
354
|
+
readonly decoded: Field.DecodedFromFields<TFields>
|
|
355
|
+
readonly encoded: Field.EncodedFromFields<TFields>
|
|
356
|
+
readonly get: Atom.FnContext
|
|
357
|
+
}
|
|
358
|
+
) => A | Effect.Effect<A, E, R>
|
|
359
|
+
}
|
|
360
|
+
): BuiltForm<TFields, R, A, E, SubmitArgs, CM>
|
|
478
361
|
|
|
479
362
|
<
|
|
480
363
|
TFields extends Field.FieldsRecord,
|
|
@@ -487,200 +370,101 @@ export const make: {
|
|
|
487
370
|
>(
|
|
488
371
|
self: FormBuilder.FormBuilder<TFields, R>,
|
|
489
372
|
options: {
|
|
490
|
-
readonly runtime: Atom.AtomRuntime<R, ER
|
|
491
|
-
readonly fields: CM
|
|
492
|
-
readonly mode?: SubmitArgs extends void ? Mode.FormMode : Mode.FormModeWithoutAutoSubmit
|
|
493
|
-
readonly reactivityKeys?: ReadonlyArray<unknown> | Readonly<Record<string, ReadonlyArray<unknown>>> | undefined
|
|
373
|
+
readonly runtime: Atom.AtomRuntime<R, ER>
|
|
374
|
+
readonly fields: CM
|
|
375
|
+
readonly mode?: SubmitArgs extends void ? Mode.FormMode : Mode.FormModeWithoutAutoSubmit
|
|
376
|
+
readonly reactivityKeys?: ReadonlyArray<unknown> | Readonly<Record<string, ReadonlyArray<unknown>>> | undefined
|
|
494
377
|
readonly onSubmit: (
|
|
495
378
|
args: SubmitArgs,
|
|
496
379
|
ctx: {
|
|
497
|
-
readonly decoded: Field.DecodedFromFields<TFields
|
|
498
|
-
readonly encoded: Field.EncodedFromFields<TFields
|
|
499
|
-
readonly get: Atom.FnContext
|
|
500
|
-
}
|
|
501
|
-
) => A | Effect.Effect<A, E, R
|
|
502
|
-
}
|
|
503
|
-
): BuiltForm<TFields, R, A, E, SubmitArgs, CM
|
|
380
|
+
readonly decoded: Field.DecodedFromFields<TFields>
|
|
381
|
+
readonly encoded: Field.EncodedFromFields<TFields>
|
|
382
|
+
readonly get: Atom.FnContext
|
|
383
|
+
}
|
|
384
|
+
) => A | Effect.Effect<A, E, R>
|
|
385
|
+
}
|
|
386
|
+
): BuiltForm<TFields, R, A, E, SubmitArgs, CM>
|
|
504
387
|
} = (self: any, options: any): any => {
|
|
505
|
-
const { fields: components, mode, onSubmit, runtime: providedRuntime, reactivityKeys } = options
|
|
506
|
-
const runtime = providedRuntime ?? Atom.runtime(Layer.empty)
|
|
507
|
-
const
|
|
508
|
-
const { fields } = self;
|
|
388
|
+
const { fields: components, mode, onSubmit, runtime: providedRuntime, reactivityKeys } = options
|
|
389
|
+
const runtime = providedRuntime ?? Atom.runtime(Layer.empty)
|
|
390
|
+
const { fields } = self
|
|
509
391
|
|
|
510
392
|
const formAtoms = FormAtoms.make({
|
|
511
393
|
formBuilder: self,
|
|
512
394
|
runtime,
|
|
513
395
|
onSubmit,
|
|
514
396
|
reactivityKeys,
|
|
515
|
-
|
|
397
|
+
mode
|
|
398
|
+
})
|
|
516
399
|
|
|
517
400
|
const {
|
|
401
|
+
autoSubmitAtom,
|
|
518
402
|
combinedSchema,
|
|
519
|
-
dirtyFieldsAtom,
|
|
520
|
-
errorsAtom,
|
|
521
403
|
fieldRefs,
|
|
522
|
-
|
|
523
|
-
getFieldValue,
|
|
404
|
+
getFieldAtoms,
|
|
524
405
|
getOrCreateFieldAtoms,
|
|
525
|
-
getOrCreateValidationAtom,
|
|
526
406
|
hasChangedSinceSubmitAtom,
|
|
527
407
|
isDirtyAtom,
|
|
528
408
|
keepAliveActiveAtom,
|
|
529
409
|
lastSubmittedValuesAtom,
|
|
530
410
|
mountAtom,
|
|
411
|
+
onBlurSubmitAtom,
|
|
531
412
|
operations,
|
|
532
413
|
resetAtom,
|
|
533
414
|
revertToLastSubmitAtom,
|
|
534
415
|
rootErrorAtom,
|
|
535
|
-
setValue,
|
|
536
416
|
setValuesAtom,
|
|
537
417
|
stateAtom,
|
|
538
418
|
submitAtom,
|
|
539
419
|
submitCountAtom,
|
|
540
|
-
valuesAtom
|
|
541
|
-
} = formAtoms
|
|
420
|
+
valuesAtom
|
|
421
|
+
} = formAtoms
|
|
542
422
|
|
|
543
423
|
const InitializeComponent: React.FC<{
|
|
544
|
-
readonly defaultValues: any
|
|
545
|
-
readonly children: React.ReactNode
|
|
424
|
+
readonly defaultValues: any
|
|
425
|
+
readonly children: React.ReactNode
|
|
546
426
|
}> = ({ children, defaultValues }) => {
|
|
547
|
-
const registry = React.useContext(RegistryContext)
|
|
548
|
-
const state = useAtomValue(stateAtom)
|
|
549
|
-
const setFormState = useAtomSet(stateAtom)
|
|
550
|
-
const
|
|
551
|
-
const isInitializedRef = React.useRef(false);
|
|
552
|
-
const [isInitialized, setIsInitialized] = React.useState(false);
|
|
427
|
+
const registry = React.useContext(RegistryContext)
|
|
428
|
+
const state = useAtomValue(stateAtom)
|
|
429
|
+
const setFormState = useAtomSet(stateAtom)
|
|
430
|
+
const [isInitialized, setIsInitialized] = React.useState(false)
|
|
553
431
|
|
|
554
432
|
React.useEffect(() => {
|
|
555
|
-
const isKeptAlive = registry.get(keepAliveActiveAtom)
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (!isKeptAlive) {
|
|
559
|
-
setFormState(Option.some(operations.createInitialState(defaultValues)));
|
|
560
|
-
} else if (Option.isNone(currentState)) {
|
|
561
|
-
setFormState(Option.some(operations.createInitialState(defaultValues)));
|
|
433
|
+
const isKeptAlive = registry.get(keepAliveActiveAtom)
|
|
434
|
+
if (!isKeptAlive || Option.isNone(registry.get(stateAtom))) {
|
|
435
|
+
setFormState(Option.some(operations.createInitialState(defaultValues)))
|
|
562
436
|
}
|
|
437
|
+
setIsInitialized(true)
|
|
438
|
+
}, [registry])
|
|
563
439
|
|
|
564
|
-
|
|
565
|
-
setIsInitialized(true);
|
|
566
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only
|
|
567
|
-
}, [registry]);
|
|
440
|
+
useAtomMount(autoSubmitAtom)
|
|
568
441
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
},
|
|
575
|
-
parsedMode.autoSubmit && parsedMode.validation === "onChange" ? parsedMode.debounce : null,
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
579
|
-
// Auto-Submit Coordination
|
|
580
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
581
|
-
// Two-subscription model to avoid infinite loop:
|
|
582
|
-
// - Stream 1 reacts to value changes (reference equality), triggers or queues submit
|
|
583
|
-
// - Stream 2 reacts to submit completion, flushes queued changes
|
|
584
|
-
//
|
|
585
|
-
// Single subscription to stateAtom cannot distinguish value changes from submit
|
|
586
|
-
// metadata updates (submitCount, lastSubmittedValues).
|
|
587
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
588
|
-
|
|
589
|
-
const lastValuesRef = React.useRef<unknown>(null);
|
|
590
|
-
const pendingChangesRef = React.useRef(false);
|
|
591
|
-
const wasSubmittingRef = React.useRef(false);
|
|
592
|
-
|
|
593
|
-
useAtomSubscribe(
|
|
594
|
-
stateAtom,
|
|
595
|
-
React.useCallback(() => {
|
|
596
|
-
if (!isInitializedRef.current) return;
|
|
597
|
-
|
|
598
|
-
const state = registry.get(stateAtom);
|
|
599
|
-
if (Option.isNone(state)) return;
|
|
600
|
-
const currentValues = state.value.values;
|
|
601
|
-
|
|
602
|
-
// Reference equality filters out submit metadata changes.
|
|
603
|
-
// Works because setFieldValue creates new values object (immutable update).
|
|
604
|
-
if (currentValues === lastValuesRef.current) return;
|
|
605
|
-
lastValuesRef.current = currentValues;
|
|
606
|
-
|
|
607
|
-
if (!parsedMode.autoSubmit || parsedMode.validation !== "onChange") return;
|
|
608
|
-
|
|
609
|
-
const submitResult = registry.get(submitAtom);
|
|
610
|
-
if (submitResult.waiting) {
|
|
611
|
-
pendingChangesRef.current = true;
|
|
612
|
-
} else {
|
|
613
|
-
debouncedAutoSubmit();
|
|
614
|
-
}
|
|
615
|
-
}, [debouncedAutoSubmit, registry]),
|
|
616
|
-
{ immediate: false },
|
|
617
|
-
);
|
|
618
|
-
|
|
619
|
-
useAtomSubscribe(
|
|
620
|
-
submitAtom,
|
|
621
|
-
React.useCallback(
|
|
622
|
-
(result) => {
|
|
623
|
-
if (!parsedMode.autoSubmit || parsedMode.validation !== "onChange") return;
|
|
624
|
-
|
|
625
|
-
const isSubmitting = result.waiting;
|
|
626
|
-
const wasSubmitting = wasSubmittingRef.current;
|
|
627
|
-
wasSubmittingRef.current = isSubmitting;
|
|
628
|
-
|
|
629
|
-
// Flush queued changes when submit completes
|
|
630
|
-
if (wasSubmitting && !isSubmitting) {
|
|
631
|
-
if (pendingChangesRef.current) {
|
|
632
|
-
pendingChangesRef.current = false;
|
|
633
|
-
debouncedAutoSubmit();
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
},
|
|
637
|
-
[debouncedAutoSubmit],
|
|
638
|
-
),
|
|
639
|
-
{ immediate: false },
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
const onBlurAutoSubmit = React.useCallback(() => {
|
|
643
|
-
if (!parsedMode.autoSubmit || parsedMode.validation !== "onBlur") return;
|
|
644
|
-
|
|
645
|
-
const stateOption = registry.get(stateAtom);
|
|
646
|
-
if (Option.isNone(stateOption)) return;
|
|
647
|
-
|
|
648
|
-
const { lastSubmittedValues, values } = stateOption.value;
|
|
649
|
-
if (Option.isSome(lastSubmittedValues) && values === lastSubmittedValues.value.encoded) return;
|
|
650
|
-
|
|
651
|
-
callSubmit(undefined);
|
|
652
|
-
}, [registry, callSubmit]);
|
|
653
|
-
|
|
654
|
-
if (!isInitialized) return null;
|
|
655
|
-
if (Option.isNone(state)) return null;
|
|
656
|
-
|
|
657
|
-
return <AutoSubmitContext.Provider value={onBlurAutoSubmit}>{children}</AutoSubmitContext.Provider>;
|
|
658
|
-
};
|
|
442
|
+
if (!isInitialized) return null
|
|
443
|
+
if (Option.isNone(state)) return null
|
|
444
|
+
|
|
445
|
+
return <>{children}</>
|
|
446
|
+
}
|
|
659
447
|
|
|
660
448
|
const fieldComponents = makeFieldComponents(
|
|
661
449
|
fields,
|
|
662
450
|
stateAtom,
|
|
663
|
-
errorsAtom,
|
|
664
|
-
submitCountAtom,
|
|
665
|
-
dirtyFieldsAtom,
|
|
666
|
-
parsedMode,
|
|
667
|
-
getOrCreateValidationAtom,
|
|
668
451
|
getOrCreateFieldAtoms,
|
|
669
452
|
operations,
|
|
670
453
|
components,
|
|
671
|
-
|
|
454
|
+
onBlurSubmitAtom
|
|
455
|
+
)
|
|
672
456
|
|
|
673
457
|
const KeepAlive: React.FC = () => {
|
|
674
|
-
const setKeepAliveActive = useAtomSet(keepAliveActiveAtom)
|
|
458
|
+
const setKeepAliveActive = useAtomSet(keepAliveActiveAtom)
|
|
675
459
|
|
|
676
460
|
React.useLayoutEffect(() => {
|
|
677
|
-
setKeepAliveActive(true)
|
|
678
|
-
return () => setKeepAliveActive(false)
|
|
679
|
-
}, [setKeepAliveActive])
|
|
461
|
+
setKeepAliveActive(true)
|
|
462
|
+
return () => setKeepAliveActive(false)
|
|
463
|
+
}, [setKeepAliveActive])
|
|
680
464
|
|
|
681
|
-
useAtomMount(mountAtom)
|
|
682
|
-
return null
|
|
683
|
-
}
|
|
465
|
+
useAtomMount(mountAtom)
|
|
466
|
+
return null
|
|
467
|
+
}
|
|
684
468
|
|
|
685
469
|
return {
|
|
686
470
|
values: valuesAtom,
|
|
@@ -696,11 +480,9 @@ export const make: {
|
|
|
696
480
|
reset: resetAtom,
|
|
697
481
|
revertToLastSubmit: revertToLastSubmitAtom,
|
|
698
482
|
setValues: setValuesAtom,
|
|
699
|
-
|
|
700
|
-
getFieldValue,
|
|
701
|
-
getFieldIsDirty,
|
|
483
|
+
getFieldAtoms,
|
|
702
484
|
mount: mountAtom,
|
|
703
485
|
KeepAlive,
|
|
704
|
-
...fieldComponents
|
|
705
|
-
}
|
|
706
|
-
}
|
|
486
|
+
...fieldComponents
|
|
487
|
+
}
|
|
488
|
+
}
|