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