@lucas-barake/effect-form-react 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/FormReact.d.ts → FormReact.d.ts} +23 -32
- 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 +253 -468
- package/src/index.ts +2 -8
- package/dist/cjs/FormReact.js +0 -400
- 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 -373
- 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,406 +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, Field.EncodedFromFields<TFields
|
|
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, Field.EncodedFromFields<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
|
|
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
|
|
467
351
|
readonly onSubmit: (
|
|
468
352
|
args: SubmitArgs,
|
|
469
353
|
ctx: {
|
|
470
|
-
readonly decoded: Field.DecodedFromFields<TFields
|
|
471
|
-
readonly encoded: Field.EncodedFromFields<TFields
|
|
472
|
-
readonly get: Atom.FnContext
|
|
473
|
-
}
|
|
474
|
-
) => A | Effect.Effect<A, E,
|
|
475
|
-
}
|
|
476
|
-
): 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>
|
|
477
361
|
|
|
478
362
|
<
|
|
479
363
|
TFields extends Field.FieldsRecord,
|
|
@@ -486,198 +370,101 @@ export const make: {
|
|
|
486
370
|
>(
|
|
487
371
|
self: FormBuilder.FormBuilder<TFields, R>,
|
|
488
372
|
options: {
|
|
489
|
-
readonly runtime: Atom.AtomRuntime<R, ER
|
|
490
|
-
readonly fields: CM
|
|
491
|
-
readonly mode?: SubmitArgs extends void ? Mode.FormMode : Mode.FormModeWithoutAutoSubmit
|
|
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
|
|
492
377
|
readonly onSubmit: (
|
|
493
378
|
args: SubmitArgs,
|
|
494
379
|
ctx: {
|
|
495
|
-
readonly decoded: Field.DecodedFromFields<TFields
|
|
496
|
-
readonly encoded: Field.EncodedFromFields<TFields
|
|
497
|
-
readonly get: Atom.FnContext
|
|
498
|
-
}
|
|
499
|
-
) => A | Effect.Effect<A, E, R
|
|
500
|
-
}
|
|
501
|
-
): 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>
|
|
502
387
|
} = (self: any, options: any): any => {
|
|
503
|
-
const { fields: components, mode, onSubmit, runtime: providedRuntime } = options
|
|
504
|
-
const runtime = providedRuntime ?? Atom.runtime(Layer.empty)
|
|
505
|
-
const
|
|
506
|
-
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
|
|
507
391
|
|
|
508
392
|
const formAtoms = FormAtoms.make({
|
|
509
393
|
formBuilder: self,
|
|
510
394
|
runtime,
|
|
511
395
|
onSubmit,
|
|
512
|
-
|
|
396
|
+
reactivityKeys,
|
|
397
|
+
mode
|
|
398
|
+
})
|
|
513
399
|
|
|
514
400
|
const {
|
|
401
|
+
autoSubmitAtom,
|
|
515
402
|
combinedSchema,
|
|
516
|
-
dirtyFieldsAtom,
|
|
517
|
-
errorsAtom,
|
|
518
403
|
fieldRefs,
|
|
519
|
-
|
|
520
|
-
getFieldValue,
|
|
404
|
+
getFieldAtoms,
|
|
521
405
|
getOrCreateFieldAtoms,
|
|
522
|
-
getOrCreateValidationAtom,
|
|
523
406
|
hasChangedSinceSubmitAtom,
|
|
524
407
|
isDirtyAtom,
|
|
525
408
|
keepAliveActiveAtom,
|
|
526
409
|
lastSubmittedValuesAtom,
|
|
527
410
|
mountAtom,
|
|
411
|
+
onBlurSubmitAtom,
|
|
528
412
|
operations,
|
|
529
413
|
resetAtom,
|
|
530
414
|
revertToLastSubmitAtom,
|
|
531
415
|
rootErrorAtom,
|
|
532
|
-
setValue,
|
|
533
416
|
setValuesAtom,
|
|
534
417
|
stateAtom,
|
|
535
418
|
submitAtom,
|
|
536
419
|
submitCountAtom,
|
|
537
|
-
valuesAtom
|
|
538
|
-
} = formAtoms
|
|
420
|
+
valuesAtom
|
|
421
|
+
} = formAtoms
|
|
539
422
|
|
|
540
423
|
const InitializeComponent: React.FC<{
|
|
541
|
-
readonly defaultValues: any
|
|
542
|
-
readonly children: React.ReactNode
|
|
424
|
+
readonly defaultValues: any
|
|
425
|
+
readonly children: React.ReactNode
|
|
543
426
|
}> = ({ children, defaultValues }) => {
|
|
544
|
-
const registry = React.useContext(RegistryContext)
|
|
545
|
-
const state = useAtomValue(stateAtom)
|
|
546
|
-
const setFormState = useAtomSet(stateAtom)
|
|
547
|
-
const
|
|
548
|
-
const isInitializedRef = React.useRef(false);
|
|
549
|
-
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)
|
|
550
431
|
|
|
551
432
|
React.useEffect(() => {
|
|
552
|
-
const isKeptAlive = registry.get(keepAliveActiveAtom)
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (!isKeptAlive) {
|
|
556
|
-
setFormState(Option.some(operations.createInitialState(defaultValues)));
|
|
557
|
-
} else if (Option.isNone(currentState)) {
|
|
558
|
-
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)))
|
|
559
436
|
}
|
|
437
|
+
setIsInitialized(true)
|
|
438
|
+
}, [registry])
|
|
560
439
|
|
|
561
|
-
|
|
562
|
-
setIsInitialized(true);
|
|
563
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only
|
|
564
|
-
}, [registry]);
|
|
440
|
+
useAtomMount(autoSubmitAtom)
|
|
565
441
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
},
|
|
572
|
-
parsedMode.autoSubmit && parsedMode.validation === "onChange" ? parsedMode.debounce : null,
|
|
573
|
-
);
|
|
574
|
-
|
|
575
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
576
|
-
// Auto-Submit Coordination
|
|
577
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
578
|
-
// Two-subscription model to avoid infinite loop:
|
|
579
|
-
// - Stream 1 reacts to value changes (reference equality), triggers or queues submit
|
|
580
|
-
// - Stream 2 reacts to submit completion, flushes queued changes
|
|
581
|
-
//
|
|
582
|
-
// Single subscription to stateAtom cannot distinguish value changes from submit
|
|
583
|
-
// metadata updates (submitCount, lastSubmittedValues).
|
|
584
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
585
|
-
|
|
586
|
-
const lastValuesRef = React.useRef<unknown>(null);
|
|
587
|
-
const pendingChangesRef = React.useRef(false);
|
|
588
|
-
const wasSubmittingRef = React.useRef(false);
|
|
589
|
-
|
|
590
|
-
useAtomSubscribe(
|
|
591
|
-
stateAtom,
|
|
592
|
-
React.useCallback(() => {
|
|
593
|
-
if (!isInitializedRef.current) return;
|
|
594
|
-
|
|
595
|
-
const state = registry.get(stateAtom);
|
|
596
|
-
if (Option.isNone(state)) return;
|
|
597
|
-
const currentValues = state.value.values;
|
|
598
|
-
|
|
599
|
-
// Reference equality filters out submit metadata changes.
|
|
600
|
-
// Works because setFieldValue creates new values object (immutable update).
|
|
601
|
-
if (currentValues === lastValuesRef.current) return;
|
|
602
|
-
lastValuesRef.current = currentValues;
|
|
603
|
-
|
|
604
|
-
if (!parsedMode.autoSubmit || parsedMode.validation !== "onChange") return;
|
|
605
|
-
|
|
606
|
-
const submitResult = registry.get(submitAtom);
|
|
607
|
-
if (submitResult.waiting) {
|
|
608
|
-
pendingChangesRef.current = true;
|
|
609
|
-
} else {
|
|
610
|
-
debouncedAutoSubmit();
|
|
611
|
-
}
|
|
612
|
-
}, [debouncedAutoSubmit, registry]),
|
|
613
|
-
{ immediate: false },
|
|
614
|
-
);
|
|
615
|
-
|
|
616
|
-
useAtomSubscribe(
|
|
617
|
-
submitAtom,
|
|
618
|
-
React.useCallback(
|
|
619
|
-
(result) => {
|
|
620
|
-
if (!parsedMode.autoSubmit || parsedMode.validation !== "onChange") return;
|
|
621
|
-
|
|
622
|
-
const isSubmitting = result.waiting;
|
|
623
|
-
const wasSubmitting = wasSubmittingRef.current;
|
|
624
|
-
wasSubmittingRef.current = isSubmitting;
|
|
625
|
-
|
|
626
|
-
// Flush queued changes when submit completes
|
|
627
|
-
if (wasSubmitting && !isSubmitting) {
|
|
628
|
-
if (pendingChangesRef.current) {
|
|
629
|
-
pendingChangesRef.current = false;
|
|
630
|
-
debouncedAutoSubmit();
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
},
|
|
634
|
-
[debouncedAutoSubmit],
|
|
635
|
-
),
|
|
636
|
-
{ immediate: false },
|
|
637
|
-
);
|
|
638
|
-
|
|
639
|
-
const onBlurAutoSubmit = React.useCallback(() => {
|
|
640
|
-
if (!parsedMode.autoSubmit || parsedMode.validation !== "onBlur") return;
|
|
641
|
-
|
|
642
|
-
const stateOption = registry.get(stateAtom);
|
|
643
|
-
if (Option.isNone(stateOption)) return;
|
|
644
|
-
|
|
645
|
-
const { lastSubmittedValues, values } = stateOption.value;
|
|
646
|
-
if (Option.isSome(lastSubmittedValues) && values === lastSubmittedValues.value.encoded) return;
|
|
647
|
-
|
|
648
|
-
callSubmit(undefined);
|
|
649
|
-
}, [registry, callSubmit]);
|
|
650
|
-
|
|
651
|
-
if (!isInitialized) return null;
|
|
652
|
-
if (Option.isNone(state)) return null;
|
|
653
|
-
|
|
654
|
-
return <AutoSubmitContext.Provider value={onBlurAutoSubmit}>{children}</AutoSubmitContext.Provider>;
|
|
655
|
-
};
|
|
442
|
+
if (!isInitialized) return null
|
|
443
|
+
if (Option.isNone(state)) return null
|
|
444
|
+
|
|
445
|
+
return <>{children}</>
|
|
446
|
+
}
|
|
656
447
|
|
|
657
448
|
const fieldComponents = makeFieldComponents(
|
|
658
449
|
fields,
|
|
659
450
|
stateAtom,
|
|
660
|
-
errorsAtom,
|
|
661
|
-
submitCountAtom,
|
|
662
|
-
dirtyFieldsAtom,
|
|
663
|
-
parsedMode,
|
|
664
|
-
getOrCreateValidationAtom,
|
|
665
451
|
getOrCreateFieldAtoms,
|
|
666
452
|
operations,
|
|
667
453
|
components,
|
|
668
|
-
|
|
454
|
+
onBlurSubmitAtom
|
|
455
|
+
)
|
|
669
456
|
|
|
670
457
|
const KeepAlive: React.FC = () => {
|
|
671
|
-
const setKeepAliveActive = useAtomSet(keepAliveActiveAtom)
|
|
458
|
+
const setKeepAliveActive = useAtomSet(keepAliveActiveAtom)
|
|
672
459
|
|
|
673
460
|
React.useLayoutEffect(() => {
|
|
674
|
-
setKeepAliveActive(true)
|
|
675
|
-
return () => setKeepAliveActive(false)
|
|
676
|
-
}, [setKeepAliveActive])
|
|
461
|
+
setKeepAliveActive(true)
|
|
462
|
+
return () => setKeepAliveActive(false)
|
|
463
|
+
}, [setKeepAliveActive])
|
|
677
464
|
|
|
678
|
-
useAtomMount(mountAtom)
|
|
679
|
-
return null
|
|
680
|
-
}
|
|
465
|
+
useAtomMount(mountAtom)
|
|
466
|
+
return null
|
|
467
|
+
}
|
|
681
468
|
|
|
682
469
|
return {
|
|
683
470
|
values: valuesAtom,
|
|
@@ -693,11 +480,9 @@ export const make: {
|
|
|
693
480
|
reset: resetAtom,
|
|
694
481
|
revertToLastSubmit: revertToLastSubmitAtom,
|
|
695
482
|
setValues: setValuesAtom,
|
|
696
|
-
|
|
697
|
-
getFieldValue,
|
|
698
|
-
getFieldIsDirty,
|
|
483
|
+
getFieldAtoms,
|
|
699
484
|
mount: mountAtom,
|
|
700
485
|
KeepAlive,
|
|
701
|
-
...fieldComponents
|
|
702
|
-
}
|
|
703
|
-
}
|
|
486
|
+
...fieldComponents
|
|
487
|
+
}
|
|
488
|
+
}
|