@homebound/form-state 2.33.0 → 3.1.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.
Files changed (59) hide show
  1. package/README.md +6 -6
  2. package/dist/index.d.ts +450 -9
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +1118 -14
  5. package/dist/index.js.map +1 -0
  6. package/package.json +42 -50
  7. package/skills/form-state/SKILL.md +9 -9
  8. package/dist/FormStateApp.d.ts +0 -5
  9. package/dist/FormStateApp.js +0 -54
  10. package/dist/FormStateApp.test.d.ts +0 -1
  11. package/dist/FormStateApp.test.js +0 -26
  12. package/dist/config.d.ts +0 -78
  13. package/dist/config.js +0 -2
  14. package/dist/configBuilders.d.ts +0 -67
  15. package/dist/configBuilders.js +0 -115
  16. package/dist/configBuilders.test.d.ts +0 -1
  17. package/dist/configBuilders.test.js +0 -65
  18. package/dist/fields/deepEquals.d.ts +0 -7
  19. package/dist/fields/deepEquals.js +0 -53
  20. package/dist/fields/fragmentField.d.ts +0 -4
  21. package/dist/fields/fragmentField.js +0 -58
  22. package/dist/fields/listField.d.ts +0 -11
  23. package/dist/fields/listField.js +0 -355
  24. package/dist/fields/objectField.d.ts +0 -63
  25. package/dist/fields/objectField.js +0 -277
  26. package/dist/fields/valueField.d.ts +0 -81
  27. package/dist/fields/valueField.js +0 -244
  28. package/dist/formState.stories.d.ts +0 -4
  29. package/dist/formState.stories.js +0 -12
  30. package/dist/formState.test.d.ts +0 -11
  31. package/dist/formState.test.js +0 -2109
  32. package/dist/formStateDomain.d.ts +0 -47
  33. package/dist/formStateDomain.js +0 -28
  34. package/dist/mobx-behavior.test.d.ts +0 -1
  35. package/dist/mobx-behavior.test.js +0 -51
  36. package/dist/proxies.d.ts +0 -5
  37. package/dist/proxies.js +0 -20
  38. package/dist/rules.d.ts +0 -10
  39. package/dist/rules.js +0 -11
  40. package/dist/setupTests.d.ts +0 -1
  41. package/dist/setupTests.js +0 -15
  42. package/dist/stackoverlfow.d.ts +0 -17
  43. package/dist/stackoverlfow.js +0 -17
  44. package/dist/temporal.d.ts +0 -6
  45. package/dist/temporal.js +0 -34
  46. package/dist/temporal.test.d.ts +0 -1
  47. package/dist/temporal.test.js +0 -97
  48. package/dist/useFormState.d.ts +0 -82
  49. package/dist/useFormState.js +0 -158
  50. package/dist/useFormState.test.d.ts +0 -1
  51. package/dist/useFormState.test.js +0 -787
  52. package/dist/useFormStates.d.ts +0 -68
  53. package/dist/useFormStates.js +0 -101
  54. package/dist/useFormStates.test.d.ts +0 -1
  55. package/dist/useFormStates.test.js +0 -223
  56. package/dist/utils.d.ts +0 -52
  57. package/dist/utils.js +0 -198
  58. package/dist/utils.test.d.ts +0 -1
  59. package/dist/utils.test.js +0 -102
package/README.md CHANGED
@@ -138,12 +138,12 @@ For submit forms, the expectation is that you should:
138
138
  If you do this, you should not have to call `commitChanges` manually, because code like:
139
139
 
140
140
  ```ts
141
- onClick: async () => {
142
- const input = formState.changedValue;
143
- await saveAuthor(input);
144
- // checks if formState.dirty is true before closing
145
- closeModal();
146
- };
141
+ onClick: async () => {
142
+ const input = formState.changedValue;
143
+ await saveAuthor(input);
144
+ // checks if formState.dirty is true before closing
145
+ closeModal();
146
+ };
147
147
  ```
148
148
 
149
149
  Will "just work" because the control flow will be:
package/dist/index.d.ts CHANGED
@@ -1,9 +1,450 @@
1
- export { f } from "./configBuilders";
2
- export { type FragmentField } from "./fields/fragmentField";
3
- export { type ListFieldState } from "./fields/listField";
4
- export { createObjectState, type Fragment, type ObjectState, fragment } from "./fields/objectField";
5
- export { type FieldState, type ValueAdapter } from "./fields/valueField";
6
- export { required, type Rule } from "./rules";
7
- export { type FormObjectState, useFormState } from "./useFormState";
8
- export { useFormStates } from "./useFormStates";
9
- export { type ObjectConfig } from "./config";
1
+ import { Temporal } from "temporal-polyfill";
2
+ //#region src/fields/fragmentField.d.ts
3
+ interface FragmentField<V> {
4
+ value: V;
5
+ }
6
+ //#endregion
7
+ //#region src/rules.d.ts
8
+ /** A validation rule, given the value and name, return the error string if valid, or undefined if valid. */
9
+ type Rule<V> = (opts: {
10
+ value: V;
11
+ key: string;
12
+ originalValue: V;
13
+ }) => string | undefined;
14
+ /** A rule that validates `value` is not `undefined`, `null`, or empty string. */
15
+ export declare const required: <V>({ value: v }: {
16
+ value: V;
17
+ }) => string | undefined;
18
+ //#endregion
19
+ //#region src/fields/valueField.d.ts
20
+ /**
21
+ * Form state for a primitive field in the form, i.e. its value but also touched/validation/etc. state.
22
+ *
23
+ * This API also provides hooks for form elements to call into, i.e. `blur()` and `set(...)` that will
24
+ * update the field state and re-render, i.e. when including in an `ObjectState`-typed literal that is
25
+ * an mobx `useLocalObservable`/observable.
26
+ *
27
+ * Note that `V` will always have `null | undefined` added to it by `FieldStates`, b/c most form fields
28
+ * i.e. text boxes, can always be cleared out/deleted.
29
+ */
30
+ interface FieldState<V> {
31
+ /** The key in the parent object, i.e. `firstName` in `author: { firstName: string }`. */
32
+ readonly key: string;
33
+ /** The current value of the field. */
34
+ value: V;
35
+ readonly originalValue: V;
36
+ touched: boolean;
37
+ readOnly: boolean;
38
+ loading: boolean;
39
+ readonly required: boolean;
40
+ readonly dirty: boolean;
41
+ readonly valid: boolean;
42
+ readonly focused: boolean;
43
+ readonly isNewEntity: boolean;
44
+ rules: Rule<V>[];
45
+ readonly errors: string[];
46
+ /**
47
+ * Returns a subset of V with only the changed values.
48
+ *
49
+ * Reading this also records that the value is probably being put on the wire, so that a later
50
+ * `refreshing` set can tell a server ack of this value apart from a stale cache refresh.
51
+ */
52
+ readonly changedValue: V;
53
+ /** Focuses the field. Disables changes from `ObjectState.set` calls. */
54
+ focus(): void;
55
+ /** Blur marks the field as touched and triggers an auto-save. */
56
+ blur(): void;
57
+ /** Triggers an auto-save; the caller (i.e. useFormState) should still dirty & valid check. */
58
+ maybeAutoSave(): void;
59
+ set(value: V, opts?: SetOpts): void;
60
+ /** Reverts back to the original value and resets dirty/touched. */
61
+ revertChanges(): void;
62
+ /**
63
+ * Accepts the current changed value (if any) as the original and resets dirty/touched.
64
+ *
65
+ * @deprecated Use `formState.update(ackedServerValue)` instead. `commitChanges` marks all current
66
+ * changes as saved, which risks discarding a user's in-flight edits. Updating from the server ack gives
67
+ * form-state a field-by-field audit of which changes were successfully saved.
68
+ */
69
+ commitChanges(): void;
70
+ /** Creates a new FieldState with a transformation of the value, i.e. string to int, or feet to inches. */
71
+ adapt<V2>(adapter: ValueAdapter<V, V2>): FieldState<V2>;
72
+ /**
73
+ * Releases any mobx reactions this field created.
74
+ *
75
+ * This only matters when wrapping a mobx observable (i.e. a store or class instance): each field
76
+ * subscribes to its slot on the store so that external mutations can trigger auto-save, and that
77
+ * subscription keeps the field alive until it is disposed. Plain objects create no reactions.
78
+ */
79
+ dispose(): void;
80
+ }
81
+ /**
82
+ * Allows changing a type in the formState (like a string) to a different type in the UI (like a number).
83
+ *
84
+ * Or doing unit of measure conversions within the same type, like from meters to feet.
85
+ */
86
+ interface ValueAdapter<V, V2 = V> {
87
+ /** Converts the original FieldState's value `V` into new `V2` type. */
88
+ toValue(value: V): V2;
89
+ /** Converts the adapted FieldState's value `V2` back into the original `V` type. */
90
+ fromValue(value: V2): V;
91
+ }
92
+ /** Public options for our `set` command. */
93
+ interface SetOpts {
94
+ /** Whether this `set` should trigger an auto-save; defaults to true. */
95
+ autoSave?: boolean;
96
+ }
97
+ //#endregion
98
+ //#region src/fields/listField.d.ts
99
+ /** Form state for list of children, i.e. `U` is a `Book` in a form with a `books: Book[]`. */
100
+ interface ListFieldState<U> extends FieldState<U[]> {
101
+ readonly rows: ReadonlyArray<ObjectState<U>>;
102
+ add(value: U, index?: number): void;
103
+ remove(indexOrValue: number | U): void;
104
+ }
105
+ //#endregion
106
+ //#region src/temporal.d.ts
107
+ type SupportedTemporal = Temporal.PlainDate | Temporal.ZonedDateTime;
108
+ //#endregion
109
+ //#region src/useFormState.d.ts
110
+ type Query<I> = {
111
+ data: I;
112
+ loading: boolean;
113
+ error?: any;
114
+ };
115
+ type InputAndMap<T, I> = {
116
+ input: I;
117
+ map?: (input: Exclude<I, null | undefined>) => T;
118
+ ifUndefined?: T;
119
+ onlyOnce?: boolean;
120
+ };
121
+ type QueryAndMap<T, I> = {
122
+ query: Query<I>;
123
+ map: (input: Exclude<I, null | undefined>) => T;
124
+ ifUndefined?: T;
125
+ };
126
+ /**
127
+ * The opts has for `useFormState`.
128
+ *
129
+ * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*
130
+ * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query
131
+ */
132
+ type UseFormStateOpts<T, I> = {
133
+ /** The form configuration, should be a module-level const or useMemo'd. */
134
+ config: ObjectConfig<T>;
135
+ /**
136
+ * Provides the form's initial value.
137
+ *
138
+ * User's can either:
139
+ *
140
+ * - Provide no initial value (don't set `init` at all)
141
+ * - Provide an initial value that already matches the form type `T` (i.e. set `init: data`)
142
+ * - Provide an initial value from an object that _almost_ matches the form type `T`,
143
+ * but needs to be mapped from it's input type `I` to the form type
144
+ * (i.e. set `init: { input: data, map: (data) => ...}`).
145
+ *
146
+ * The value of using the 3rd option is that: a) we internally `useMemo` on the identity of the
147
+ * `init.input` (i.e. a response from an Apollo hook) and don't require the `map` function
148
+ * to have a stable identity, and also b) we will null-check/undefined-check `init.input` and
149
+ * only call `init.map` if it's set, otherwise we'll use `init.ifDefined` or `{}`, saving you
150
+ * from having to null check within your `init.map` function.
151
+ */
152
+ init?: InputAndMap<T, I> | QueryAndMap<T, I>;
153
+ /**
154
+ * A hook to add custom, cross-field validation rules that can be difficult to setup directly in the config DSL.
155
+ *
156
+ * Does not need to be stable/useMemo'd.
157
+ */
158
+ addRules?: (state: ObjectState<T>) => void;
159
+ /** Whether the form should be read only, when changed it won't re-create the whole form. */
160
+ readOnly?: boolean;
161
+ /**
162
+ * Whether the form is loading.
163
+ *
164
+ * Note that we also will infer loading from `init.input === undefined` or `init.query.loading`,
165
+ * so you only need to set this directly if you're not using either of those conventions.
166
+ */
167
+ loading?: boolean;
168
+ /**
169
+ * Fired when the form should auto-save, i.e. after a) blur and b) all fields are valid.
170
+ *
171
+ * Does not need to be stable/useMemo'd.
172
+ */
173
+ autoSave?: (state: ObjectState<T>) => Promise<unknown>;
174
+ };
175
+ type FormObjectState<T, I> = ObjectState<T> & {
176
+ /**
177
+ * Refreshes the form with a server-acked value, i.e. `form.update(savedAuthor)`.
178
+ *
179
+ * This is intended for save callbacks that need `form.dirty` to become `false` immediately, before the
180
+ * normal query/cache refresh has a chance to rerender `useFormState`. Pass the value returned by the
181
+ * server so the form can map it through `init.map` and then do a field-by-field refresh of only the
182
+ * changes the server has acknowledged.
183
+ */
184
+ update(input: Exclude<I, null | undefined>): void;
185
+ };
186
+ /**
187
+ * Creates a formState instance for editing in a form.
188
+ */
189
+ export declare function useFormState<T, I>(opts: UseFormStateOpts<T, I>): FormObjectState<T, I>;
190
+ //#endregion
191
+ //#region src/utils.d.ts
192
+ type Builtin = Date | Function | SupportedTemporal | Uint8Array | string | number | boolean;
193
+ type OmitIf<Base, Condition> = Pick<Base, { [Key in keyof Base]: Base[Key] extends Condition ? never : Key; }[keyof Base]>;
194
+ //#endregion
195
+ //#region src/fields/objectField.d.ts
196
+ /**
197
+ * Wraps a given input/on-the-wire type `T` for editing in a form.
198
+ *
199
+ * We basically mimic every field in `T` (i.e. `firstName`, `lastName`, etc.) but decorate them
200
+ * with form-specific state like `touched`, `dirty`, `errors`, etc.
201
+ *
202
+ * The intent is that, after ensuring all fields are `valid`/etc., callers can take the
203
+ * result of this `objectState.value` (or `objectState.originalValue` for the non-proxy version) and
204
+ * have exactly the on-the-wire type `T` that they need to submit to the backend, without doing the
205
+ * manual mapping of "data that was in the form controls" into "data that the backend wants".
206
+ *
207
+ * Note that this can be hierarchical by having a field of `ListFieldState` that
208
+ * themselves each wrap an `ObjectState`, i.e.:
209
+ *
210
+ * ```
211
+ * ObjectState for author
212
+ * - firstName: FieldState
213
+ * - lastName: FieldState
214
+ * - rows: ListFieldState
215
+ * - [0]: ObjectState for book 1
216
+ * - title: FieldState
217
+ * - [1]: ObjectState for book 2
218
+ * - title: FieldState
219
+ * ```
220
+ */
221
+ type ObjectState<T> = FieldStates<T> & FieldState<T> & {
222
+ /** Sets the state of fields in `state`. */
223
+ set(state: Partial<T>, opts?: SetOpts): void;
224
+ /** Returns whether the object can be saved, i.e. is valid, but also as a side-effect marks touched. */
225
+ canSave(): boolean;
226
+ };
227
+ declare const fragmentSym: unique symbol;
228
+ type Fragment<V> = V & {
229
+ [fragmentSym]: true;
230
+ };
231
+ export declare function fragment<V>(value: V): Fragment<V>;
232
+ /** For a given input type `T`, decorate each field into the "field state" type that holds our form-relevant state, i.e. valid/touched/etc. */
233
+ type FieldStates<T> = { [K in keyof T]-?: T[K] extends Fragment<infer V> ? FragmentField<V> : T[K] extends Array<infer U> | null | undefined ? [U] extends [Builtin] ? FieldState<T[K]> : ListFieldState<U> : T[K] extends Builtin | null | undefined ? FieldState<T[K]> : ObjectState<T[K]>; };
234
+ /**
235
+ * Creates a new `ObjectState` for a given form object `T` given config rules in `config`.
236
+ *
237
+ * The returned `ObjectState` can be used in a mobx `useLocalObservable` to drive an
238
+ * interactive form that tracks the current valid/touched/etc. state of both each
239
+ * individual fields as well as the top-level form/object itself.
240
+ */
241
+ export declare function createObjectState<T>(config: ObjectConfig<T>, instance: T, opts?: {
242
+ maybeAutoSave?: () => void;
243
+ }): ObjectState<T>;
244
+ //#endregion
245
+ //#region src/config.d.ts
246
+ /**
247
+ * Config rules for each field in `T` that we're editing in a form.
248
+ *
249
+ * Basically every field is either a value/primitive or a list, and this `ObjectConfig` lets
250
+ * the caller define field-specific behavior, i.e. validation rules.
251
+ */
252
+ type ObjectConfig<T> = { [P in keyof OmitIf<T, Function>]: T[P] extends Fragment<infer V> ? FragmentFieldConfig : T[P] extends Array<infer U> | null | undefined ? U extends Builtin ? ValueFieldConfig<T[P]> : ListFieldConfig<U> : ValueFieldConfig<T[P]> | ObjectFieldConfig<T[P]>; };
253
+ /** Field configuration for an opaque value that we don't actually want to include. */
254
+ type FragmentFieldConfig = {
255
+ type: "fragment";
256
+ };
257
+ /** Field configuration for primitive values, i.e. strings/numbers/Dates/user-defined types. */
258
+ type ValueFieldConfig<V> = {
259
+ type: "value";
260
+ rules?: Rule<V | null | undefined>[];
261
+ /**
262
+ * If true, marks this field as the id, which will be used for things like "always include in changedValue".
263
+ *
264
+ * Defaults to true for fields named `id`.
265
+ */
266
+ isIdKey?: boolean;
267
+ /** If true, and this value is used on an entity in a list, the entity won't count towards the list validity. */
268
+ isDeleteKey?: boolean;
269
+ /** If true, the entity that contains this value will be treated as read only. */
270
+ isReadOnlyKey?: boolean;
271
+ /** If true, we ignore field in dirty checks. */
272
+ isLocalOnly?: boolean;
273
+ /**
274
+ * Marks a field as being backed by a mobx class computed field.
275
+ *
276
+ * Note that it might still be settable (some computed have setters), but we do
277
+ * exclude from the `reset` operation, i.e. we assume resetting other non-computed fields
278
+ * will effectively reset this field as well.
279
+ */
280
+ computed?: boolean;
281
+ /** Marks a field as being initially read-only, i.e. `field.readOnly = true/false` can change this default. */
282
+ readOnly?: boolean;
283
+ /** Marks an array field to be order agnostic when determining changed/dirty states, defaults true. */
284
+ strictOrder?: false;
285
+ };
286
+ /** Field configuration for list values, i.e. `U` is `Book` in a form with `books: Book[]`. */
287
+ type ListFieldConfig<U> = {
288
+ type: "list";
289
+ /** Rules that can run on the full list of children. */
290
+ rules?: Rule<readonly ObjectState<U>[]>[];
291
+ /** Config for each child's form state, i.e. each book. */
292
+ config: ObjectConfig<U>;
293
+ /**
294
+ * What the server-side update behavior is for this collection.
295
+ *
296
+ * When exhaustive, we include all rows in `changedValue` so that we
297
+ * don't orhan unchanged rows.
298
+ *
299
+ * When incremental, we only include changed rows in `changedValue`.
300
+ *
301
+ * When deep-exhaustive, we include all rows in `changedValue` and also
302
+ * all fields in the child entities, i.e. not just placeholder `{ id: "b:1" }`
303
+ * values for otherwise-unchanged children.
304
+ *
305
+ * Defaults to `exhaustive` b/c that is the safest and also Joist's
306
+ * default behavior.
307
+ */
308
+ update?: "exhaustive" | "incremental" | "deep-exhaustive";
309
+ /** Set to not consider the order of the list when evaluating changed/dirty states, defaults true. */
310
+ strictOrder?: false;
311
+ };
312
+ type ObjectFieldConfig<U> = {
313
+ type: "object";
314
+ /** Marks an object as a reference, which means we'll only include it's `id` in `changedValue` output. */
315
+ reference?: boolean;
316
+ /** Config for the child's form state, i.e. each book. */
317
+ config: ObjectConfig<U>;
318
+ };
319
+ //#endregion
320
+ //#region src/configBuilders.d.ts
321
+ /**
322
+ * Provides a Zod-ish API for building form configs.
323
+ */
324
+ export declare const f: {
325
+ /** Creates the top-level config for a form, like an author or book. */
326
+ config<T>(fields: ObjectConfigBuilderFields<T>): ObjectConfig<T>;
327
+ /** Creates the config DSL for an object, like an author or book. */
328
+ object<T>(fields: ObjectConfigBuilderFields<T>): ObjectConfigBuilder<T>;
329
+ /** Creates the config DSL for an list of objects, like an author's list of books. */
330
+ list<U>(fields: ObjectConfigBuilderFields<U>): ListFieldConfigBuilder<U>;
331
+ /** Creates the config DSL for a primitive value, like a first name or phone number. */
332
+ value<V>(): ValueFieldConfigBuilder<V>;
333
+ /** A shortcut for creating a child object with a single `id` key. */
334
+ reference<V extends {
335
+ id?: unknown;
336
+ }>(fields?: ObjectConfigBuilderFields<V>): ObjectConfigBuilder<V>;
337
+ /** A shorthand for creating a computed value. */
338
+ computed<V>(): ValueFieldConfigBuilder<V>;
339
+ };
340
+ /**
341
+ * Defines the fields that should be passed into `f.object({ ... })` for fhe form type `T`.
342
+ *
343
+ * This is basically a map of `key` in `T` to `f.value()`, `f.list()`, or `f.object()`
344
+ * DSL objects that define the runtime structure/fields of the form.
345
+ */
346
+ type ObjectConfigBuilderFields<T> = { [P in keyof OmitIf<T, Function>]: T[P] extends Fragment<infer V> ? FragmentFieldConfigBuilder : T[P] extends Array<infer U> | null | undefined ? U extends Builtin ? ValueFieldConfigBuilder<T[P]> : ListFieldConfigBuilder<U> : ValueFieldConfigBuilder<T[P]> | ObjectConfigBuilder<T[P]>; };
347
+ interface FragmentFieldConfigBuilder {
348
+ build(): FragmentFieldConfig;
349
+ }
350
+ /** Provides a fluent DSL for building up an object's config. */
351
+ declare class ObjectConfigBuilder<T> {
352
+ private config;
353
+ constructor(fields: ObjectConfigBuilderFields<T>);
354
+ ref(): this;
355
+ build(): ObjectFieldConfig<T>;
356
+ }
357
+ /** Provides a fluent DSL for building up a value's config. */
358
+ declare class ValueFieldConfigBuilder<V> {
359
+ private config;
360
+ /** Marks the field as required, keeping any rules that were already added. */
361
+ req(): this;
362
+ /** Marks the field as read only. */
363
+ readOnly(): this;
364
+ /** Marks the field as local only. */
365
+ localOnly(): this;
366
+ /** Appends `rules` to the field's validation rules. */
367
+ rules(rules: Rule<V | null | undefined>[]): this;
368
+ /** Marks the field as computed. */
369
+ computed(): this;
370
+ build(): ValueFieldConfig<V>;
371
+ }
372
+ /** Provides a fluent DSL for building up a list's config. */
373
+ declare class ListFieldConfigBuilder<U> {
374
+ private config;
375
+ constructor(fields: ObjectConfigBuilderFields<U>);
376
+ /** Appends `rule` to the field's validation rules. */
377
+ rule(rule: Rule<readonly ObjectState<U>[]>): this;
378
+ /** Appends `rules` to the field's validation rules. */
379
+ rules(rules: Rule<readonly ObjectState<U>[]>[]): this;
380
+ build(): ListFieldConfig<U>;
381
+ }
382
+ //#endregion
383
+ //#region src/useFormStates.d.ts
384
+ /**
385
+ * The opts has for `useFormStates`.
386
+ *
387
+ * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*
388
+ * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query
389
+ */
390
+ type UseFormStatesOpts<T, I> = {
391
+ /**
392
+ * The config to use for each form state.
393
+ *
394
+ * Should be stable/useMemo'd.
395
+ */
396
+ config: ObjectConfig<T>;
397
+ /**
398
+ * Fired for each individual `ObjectState` when it's had a value change.
399
+ *
400
+ * Does not need to be stable/useMemo'd.
401
+ */
402
+ autoSave?: (state: ObjectState<T>) => Promise<void>;
403
+ /**
404
+ * A hook to add custom, cross-field validation rules that can be difficult to setup directly in the config DSL.
405
+ *
406
+ * This will be called once-per `ObjectState` instance, and so is effectively a `useEffect` hook with
407
+ * a `[config, objectState]` dependency.
408
+ *
409
+ * Does not need to be stable/useMemo'd.
410
+ */
411
+ addRules?: (state: ObjectState<T>) => void;
412
+ /**
413
+ * Given an input to `getFormState`, returns the identity value that we'll cache that value's form state on.
414
+ *
415
+ * Does not need to be stable/useMemo'd.
416
+ */
417
+ getId: (v: I) => string;
418
+ /**
419
+ * Maps an input to `getFormState` to the actual form shape `T`.
420
+ *
421
+ * Does not need to be stable/useMemo'd.
422
+ */
423
+ map?: (input: Exclude<I, null | undefined>) => T;
424
+ /**
425
+ * Sets all `ObjectState`s to readOnly.
426
+ */
427
+ readOnly?: boolean;
428
+ };
429
+ type UseFormStatesHook<T, I> = {
430
+ getFormState: (input: I, opts?: {
431
+ readOnly?: boolean;
432
+ }) => ObjectState<T>;
433
+ };
434
+ /**
435
+ * A hook to manage many "mini-forms" on a single page, typically one form per row
436
+ * in a table.
437
+ *
438
+ * This hook basically provides the page/table with a cache, so each table row naively ask "what's
439
+ * the form state for this given row's data?" and get back a new-or-existing `ObjectState` instance
440
+ * that, if already existing, still has any of the user's WIP changes.
441
+ *
442
+ * Each mini-form/row can have its own autoSave calls, independent of the other rows.
443
+ *
444
+ * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*
445
+ * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query
446
+ */
447
+ export declare function useFormStates<T, I = T>(opts: UseFormStatesOpts<T, I>): UseFormStatesHook<T, I>;
448
+ //#endregion
449
+ export type { FieldState, FormObjectState, Fragment, FragmentField, ListFieldState, ObjectConfig, ObjectState, Rule, ValueAdapter };
450
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/fields/fragmentField.ts","../src/rules.ts","../src/fields/valueField.ts","../src/fields/listField.ts","../src/temporal.ts","../src/useFormState.ts","../src/utils.ts","../src/fields/objectField.ts","../src/config.ts","../src/configBuilders.ts","../src/useFormStates.ts"],"mappings":"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"}