@homebound/form-state 3.1.1 → 3.1.3
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/README.md +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +189 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,6 +127,14 @@ With these in place, we will correctly handle interleaved edits/saves, i.e.:
|
|
|
127
127
|
5. The `init.map` updates `formState` to realize `firstName` is no longer dirty, but `lastName` keeps its WIP change
|
|
128
128
|
6. formState will trigger a 2nd `autoSave` for just the `lastName` change
|
|
129
129
|
|
|
130
|
+
Server acks must be applied explicitly:
|
|
131
|
+
|
|
132
|
+
- Use updated `init.input`/query data (usually happens for free via Apollo/React Query cache refreshes), or
|
|
133
|
+
- Call `formState.update(ackedInput)` explicitly (i.e. in a callback before navigating, to prevent "Are you sure you want to leave?" prompts)
|
|
134
|
+
- Do not use `commitChanges()` to acknowledge saves: it also commits edits that may have never been submitted.
|
|
135
|
+
|
|
136
|
+
form-state applies several heuristics to avoid dropping any WIP edits the user made between the initial request and the server's response.
|
|
137
|
+
|
|
130
138
|
### Submit forms
|
|
131
139
|
|
|
132
140
|
For submit forms, the expectation is that you should:
|
package/dist/index.d.ts
CHANGED
|
@@ -170,7 +170,7 @@ type UseFormStateOpts<T, I> = {
|
|
|
170
170
|
*
|
|
171
171
|
* Does not need to be stable/useMemo'd.
|
|
172
172
|
*/
|
|
173
|
-
autoSave?: (state: ObjectState<T>) => Promise<
|
|
173
|
+
autoSave?: (state: ObjectState<T>) => Promise<void>;
|
|
174
174
|
};
|
|
175
175
|
type FormObjectState<T, I> = ObjectState<T> & {
|
|
176
176
|
/**
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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":";;UAGiB,cAAc;EAC7B,OAAO;;;;;KCDG,KAAK,MAAM;EAAQ,OAAO;EAAG;EAAa,eAAe;;;qBAKxD,WAAmB,KAAC,OAAA;EAAkB,OAAO;;;;;;;;;;;;;;
|
|
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":";;UAGiB,cAAc;EAC7B,OAAO;;;;;KCDG,KAAK,MAAM;EAAQ,OAAO;EAAG;EAAa,eAAe;;;qBAKxD,WAAmB,KAAC,OAAA;EAAkB,OAAO;;;;;;;;;;;;;;UCUzC,WAAW;;WAEjB;;EAET,OAAO;WACE,eAAe;EACxB;EACA;EACA;WACS;WACA;WACA;WACA;WACA;EACT,OAAO,KAAK;WACH;;;;;;;WAOA,cAAc;;EAEvB;;EAEA;;EAEA;EACA,IAAI,OAAO,GAAG,OAAO;;EAErB;;;;;;;;EAQA;;EAEA,MAAM,IAAI,SAAS,aAAa,GAAG,MAAM,WAAW;;;;;;;;EAQpD;;;;;;;UAQe,aAAa,GAAG,KAAK;;EAEpC,QAAQ,OAAO,IAAI;;EAEnB,UAAU,OAAO,KAAK;;;UAIP;;EAEf;;;;;UCtEe,eAAe,WAAW,WAAW;WAC3C,MAAM,cAAc,YAAY;EAEzC,IAAI,OAAO,GAAG;EAEd,OAAO,uBAAuB;;;;KCNpB,oBAAoB,SAAS,YAAY,SAAS;;;KCNlD,MAAM;EAAO,MAAM;EAAG;EAAkB;;KAExC,YAAY,GAAG;EACzB,OAAO;EACP,OAAO,OAAO,QAAQ,yBAAyB;EAC/C,cAAc;EACd;;KAGU,YAAY,GAAG;EACzB,OAAO,MAAM;EACb,MAAM,OAAO,QAAQ,yBAAyB;EAC9C,cAAc;;;;;;;;KASJ,iBAAiB,GAAG;;EAE9B,QAAQ,aAAa;;;;;;;;;;;;;;;;;;EAmBrB,OAAO,YAAY,GAAG,KAAK,YAAY,GAAG;;;;;;EAO1C,YAAY,OAAO,YAAY;;EAG/B;;;;;;;EAQA;;;;;;EAOA,YAAY,OAAO,YAAY,OAAO;;KAG5B,gBAAgB,GAAG,KAAK,YAAY;;;;;;;;;EAS9C,OAAO,OAAO,QAAQ;;;;;wBAMR,aAAa,GAAG,GAAG,MAAM,iBAAiB,GAAG,KAAK,gBAAgB,GAAG;;;KChFzE,UAAU,OAAO,WAAW,oBAAoB;KAgBhD,OAAO,MAAM,aAAa,KACpC,SAEG,aAAa,OAAO,KAAK,aAAa,oBAAoB,aACrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;KCYE,YAAY,KAEtB,YAAY,KAEV,WAAW;;EAET,IAAI,OAAO,QAAQ,IAAI,OAAO;;EAG9B;;cASA;KAEM,SAAS,KAAK;GAAO;;wBAEjB,SAAS,GAAG,OAAO,IAAI,SAAS;;KAK3C,YAAY,QACd,WAAW,MAAM,EAAE,WAAW,eAAe,KAC1C,cAAc,KACd,EAAE,WAAW,YAAY,yBACtB,YAAY,WACX,WAAW,EAAE,MACb,eAAe,KACjB,EAAE,WAAW,6BACX,WAAW,EAAE,MACb,YAAY,EAAE;;;;;;;;wBAUR,kBAAkB,GAChC,QAAQ,aAAa,IACrB,UAAU,GACV;EAAQ;IACP,YAAY;;;;;;;;;KCrFH,aAAa,QAQtB,WAAW,OAAO,GAAG,YAAY,EAAE,WAAW,eAAe,KAC1D,sBACA,EAAE,WAAW,YAAY,wBACvB,UAAU,UACR,iBAAiB,EAAE,MACnB,gBAAgB,KAClB,iBAAiB,EAAE,MAAM,kBAAkB,EAAE;;KAIzC;EACV;;;KAIU,iBAAiB;EAC3B;EACA,QAAQ,KAAK;;;;;;EAMb;;EAEA;;EAEA;;EAEA;;;;;;;;EAQA;;EAEA;;EAEA;;;KAIU,gBAAgB;EAC1B;;EAEA,QAAQ,cAAc,YAAY;;EAElC,QAAQ,aAAa;;;;;;;;;;;;;;;;EAgBrB;;EAEA;;KAGU,kBAAkB;EAC5B;;EAEA;;EAEA,QAAQ,aAAa;;;;;;;qBChFV;;EAEJ,OAAA,GAAC,QAAU,0BAA0B,KAAK,aAAa;;EAKvD,OAAA,GAAC,QAAU,0BAA0B,KAAK,oBAAoB;;EAKhE,KAAA,GAAC,QAAU,0BAA0B,KAAK,uBAAuB;;EAKhE,MAAA,MAAM,wBAAwB;;EAK1B,UAAA;IAAY;KAAc,SAAW,0BAA0B,KAAK,oBAAoB;;EAKzF,SAAA,MAAM,wBAAwB;;;;;;;;KAW7B,0BAA0B,QACnC,WAAW,OAAO,GAAG,YAAY,EAAE,WAAW,eAAe,KAC1D,6BACA,EAAE,WAAW,YAAY,wBACvB,UAAU,UACR,wBAAwB,EAAE,MAC1B,uBAAuB,KACzB,wBAAwB,EAAE,MAAM,oBAAoB,EAAE;UAG7C;EACf,SAAS;;;cAIE,oBAAoB;UACvB;EAEI,YAAA,QAAQ,0BAA0B;EAM9C;EAKA,SAAS,kBAAkB;;;cAMhB,wBAAwB;UAC3B;;EAGR;;EAMA;;EAMA;;EAMA,MAAM,OAAO,KAAK;;EAMlB;EAKA,SAAS,iBAAiB;;;cAMf,uBAAuB;UAC1B;EAEI,YAAA,QAAQ,0BAA0B;;EAS9C,KAAK,MAAM,cAAc,YAAY;;EAMrC,MAAM,OAAO,cAAc,YAAY;EAKvC,SAAS,gBAAgB;;;;;;;;;;KCvItB,kBAAkB,GAAG;;;;;;EAMxB,QAAQ,aAAa;;;;;;EAOrB,YAAY,OAAO,YAAY,OAAO;;;;;;;;;EAUtC,YAAY,OAAO,YAAY;;;;;;EAO/B,QAAQ,GAAG;;;;;;EAOX,OAAO,OAAO,QAAQ,yBAAyB;;;;EAK/C;;KAGG,kBAAkB,GAAG;EACxB,eAAe,OAAO,GAAG;IAAS;QAAyB,YAAY;;;;;;;;;;;;;;;wBAgBzD,cAAc,GAAG,IAAI,GAAG,MAAM,kBAAkB,GAAG,KAAK,kBAAkB,GAAG"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,58 @@ import { action, computed, createAtom, isObservable, makeAutoObservable, reactio
|
|
|
2
2
|
import { isPlainObject } from "is-plain-object";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import hash from "object-hash";
|
|
5
|
+
//#region src/fields/subscriptions.ts
|
|
6
|
+
/**
|
|
7
|
+
* Tracks reversible subscriptions to observable instances supplied by callers.
|
|
8
|
+
*
|
|
9
|
+
* In development, React StrictMode replays effects as setup -> cleanup -> setup while keeping the
|
|
10
|
+
* same form instances. Cleanup must release external-store reactions so they cannot retain an
|
|
11
|
+
* unmounted form or trigger autosave. Replayed setup must reconnect those reactions without
|
|
12
|
+
* replacing the form, losing edits, or resetting acknowledgement state. Hooks therefore construct
|
|
13
|
+
* inactive forms, activate them in effect setup, and deactivate them in cleanup. An abandoned
|
|
14
|
+
* initial render never acquires subscriptions that would have no effect cleanup to release them.
|
|
15
|
+
*
|
|
16
|
+
* Only value fields wrapping external MobX observables own these reactions. Object and list fields
|
|
17
|
+
* coordinate their children's subscriptions, including cached list rows. This lifecycle does not
|
|
18
|
+
* manage form-state's own atoms, observable properties, or computed values.
|
|
19
|
+
*
|
|
20
|
+
* Demand-driven subscriptions would mean "autosave only while someone observes this field."
|
|
21
|
+
* Our hooks instead mean "autosave while this hook owns the form," including cached fields that
|
|
22
|
+
* no component observes. That is why these reactions follow the owning effect's lifecycle rather
|
|
23
|
+
* than MobX's observer count.
|
|
24
|
+
*
|
|
25
|
+
* Keep lifecycle operations off the public field objects and exported form API. The WeakMap also
|
|
26
|
+
* lets inactive forms be collected without requiring a separate registry cleanup.
|
|
27
|
+
*/
|
|
28
|
+
const subscriptions = /* @__PURE__ */ new WeakMap();
|
|
29
|
+
/** Registers a field's reversible subscription lifecycle, initially inactive. */
|
|
30
|
+
function registerSubscriptions(state, lifecycle) {
|
|
31
|
+
subscriptions.set(state, {
|
|
32
|
+
...lifecycle,
|
|
33
|
+
active: false
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Connects a field tree once, without replacing its values or acknowledgement state. */
|
|
37
|
+
function activateSubscriptions(state) {
|
|
38
|
+
const lifecycle = subscriptions.get(state);
|
|
39
|
+
if (lifecycle && !lifecycle.active) {
|
|
40
|
+
lifecycle.active = true;
|
|
41
|
+
lifecycle.activate();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Disconnects a field tree synchronously; a later effect setup can reconnect it. */
|
|
45
|
+
function deactivateSubscriptions(state) {
|
|
46
|
+
const lifecycle = subscriptions.get(state);
|
|
47
|
+
if (lifecycle?.active) {
|
|
48
|
+
lifecycle.active = false;
|
|
49
|
+
lifecycle.deactivate();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Reports whether this field tree currently owns active subscriptions. */
|
|
53
|
+
function subscriptionsActive(state) {
|
|
54
|
+
return subscriptions.get(state)?.active ?? false;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
5
57
|
//#region src/proxies.ts
|
|
6
58
|
/**
|
|
7
59
|
* Creates a new combined object with keys in `overrides` taking precedence, and then
|
|
@@ -110,10 +162,14 @@ function useFormState(opts) {
|
|
|
110
162
|
const [firstInitValue] = useState(() => initValue(config, init));
|
|
111
163
|
const isWrappingMobxProxy = !isPlainObject(firstInitValue);
|
|
112
164
|
const dep = isInput(init) ? init.onlyOnce ? [] : makeArray(init.input) : isQuery(init) ? [init.query.data, init.query.loading] : [];
|
|
113
|
-
const
|
|
165
|
+
const state = useMemo(() => {
|
|
114
166
|
let isAutoSaving = false;
|
|
115
167
|
let pendingAutoSave = false;
|
|
116
168
|
function maybeAutoSave() {
|
|
169
|
+
if (!subscriptionsActive(form)) {
|
|
170
|
+
pendingAutoSave = true;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
117
173
|
if (isAutoSaving === "in-flight") pendingAutoSave = true;
|
|
118
174
|
else if (isAutoSaving === "queued") return;
|
|
119
175
|
if (autoSaveRef.current && form.dirty && !isAutoSaving) {
|
|
@@ -124,6 +180,10 @@ function useFormState(opts) {
|
|
|
124
180
|
isAutoSaving = "queued";
|
|
125
181
|
setTimeout(async () => {
|
|
126
182
|
try {
|
|
183
|
+
if (!subscriptionsActive(form) || !autoSaveRef.current) {
|
|
184
|
+
pendingAutoSave = true;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
127
187
|
form._isAutoSaving = true;
|
|
128
188
|
const promise = autoSaveRef.current(form);
|
|
129
189
|
isAutoSaving = "in-flight";
|
|
@@ -131,7 +191,7 @@ function useFormState(opts) {
|
|
|
131
191
|
} finally {
|
|
132
192
|
isAutoSaving = false;
|
|
133
193
|
form._isAutoSaving = false;
|
|
134
|
-
if (pendingAutoSave) {
|
|
194
|
+
if (pendingAutoSave && subscriptionsActive(form)) {
|
|
135
195
|
pendingAutoSave = false;
|
|
136
196
|
setTimeout(maybeAutoSave, 0);
|
|
137
197
|
}
|
|
@@ -140,7 +200,7 @@ function useFormState(opts) {
|
|
|
140
200
|
}
|
|
141
201
|
}
|
|
142
202
|
const value = firstRunRef.current ? firstInitValue : initValue(config, init);
|
|
143
|
-
const form =
|
|
203
|
+
const form = createInactiveObjectState(config, value, { maybeAutoSave });
|
|
144
204
|
form.update = function update(input) {
|
|
145
205
|
const init = initRef.current;
|
|
146
206
|
const updateInit = isInput(init) ? {
|
|
@@ -156,22 +216,31 @@ function useFormState(opts) {
|
|
|
156
216
|
form.readOnly = readOnly;
|
|
157
217
|
setLoading(form, opts);
|
|
158
218
|
(addRules || (() => {}))(form);
|
|
159
|
-
|
|
160
|
-
|
|
219
|
+
return {
|
|
220
|
+
form,
|
|
221
|
+
lastInitDeps: dep,
|
|
222
|
+
resumeAutoSave() {
|
|
223
|
+
if (pendingAutoSave) {
|
|
224
|
+
pendingAutoSave = false;
|
|
225
|
+
maybeAutoSave();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
161
229
|
}, [config, ...isWrappingMobxProxy ? dep : []]);
|
|
162
|
-
const
|
|
230
|
+
const { form } = state;
|
|
163
231
|
useEffect(() => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
232
|
+
activateSubscriptions(state.form);
|
|
233
|
+
state.resumeAutoSave();
|
|
234
|
+
return () => deactivateSubscriptions(state.form);
|
|
235
|
+
}, [state]);
|
|
167
236
|
useEffect(() => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
237
|
+
firstRunRef.current = false;
|
|
238
|
+
const sameInit = dep.length === state.lastInitDeps.length && dep.every((value, i) => Object.is(value, state.lastInitDeps[i]));
|
|
239
|
+
if (isWrappingMobxProxy || sameInit) return;
|
|
240
|
+
state.lastInitDeps = dep;
|
|
172
241
|
setLoading(form, opts);
|
|
173
242
|
form.set(initValue(config, init), { refreshing: true });
|
|
174
|
-
}, [
|
|
243
|
+
}, [state, ...dep]);
|
|
175
244
|
useEffect(() => {
|
|
176
245
|
form.readOnly = readOnly;
|
|
177
246
|
if (loading !== void 0) form.loading = loading;
|
|
@@ -315,8 +384,9 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
315
384
|
const valueAtom = createAtom(`${String(key)}.value`);
|
|
316
385
|
const originalValueAtom = createAtom(`${String(key)}.originalValue`);
|
|
317
386
|
let hasInflightChangedValue = false;
|
|
318
|
-
let
|
|
387
|
+
let lastSubmittedValue = void 0;
|
|
319
388
|
let disposeStoreReaction = void 0;
|
|
389
|
+
let lastObservedValue = parentInstance[key];
|
|
320
390
|
const field = {
|
|
321
391
|
key,
|
|
322
392
|
touched: false,
|
|
@@ -363,7 +433,7 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
363
433
|
},
|
|
364
434
|
get changedValue() {
|
|
365
435
|
hasInflightChangedValue = true;
|
|
366
|
-
|
|
436
|
+
lastSubmittedValue = this.value;
|
|
367
437
|
if (this.value === void 0 && this.originalValue !== void 0) return null;
|
|
368
438
|
return this.value;
|
|
369
439
|
},
|
|
@@ -402,12 +472,20 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
402
472
|
const keepNull = !isEmpty(this.originalValue) && isEmpty(value) && !opts.refreshing;
|
|
403
473
|
const coerceEmptyList = value && value instanceof Array && value.length === 0 && isEmpty(this.originalValue);
|
|
404
474
|
const newValue = keepNull ? null : isEmpty(value) || coerceEmptyList ? void 0 : value;
|
|
405
|
-
if (opts.refreshing && this.dirty) {
|
|
475
|
+
if (opts.refreshing && (this.dirty || hasInflightChangedValue)) {
|
|
476
|
+
if (hasInflightChangedValue && !areEqual(lastSubmittedValue, this.originalValue, strictOrder) && areEqual(newValue, this.originalValue, strictOrder)) return;
|
|
406
477
|
const isAckingUnset = this.value === null && newValue === void 0;
|
|
407
|
-
const acceptServerAck = hasInflightChangedValue && this.value ===
|
|
408
|
-
if (!areEqual(this.value, newValue, strictOrder) && !isAckingUnset && !acceptServerAck)
|
|
478
|
+
const acceptServerAck = hasInflightChangedValue && this.value === lastSubmittedValue && !areEqual(newValue, this.originalValue, strictOrder);
|
|
479
|
+
if (!areEqual(this.value, newValue, strictOrder) && !isAckingUnset && !acceptServerAck) {
|
|
480
|
+
if (hasInflightChangedValue && !areEqual(newValue, this.originalValue, strictOrder)) {
|
|
481
|
+
this.originalValue = newValue;
|
|
482
|
+
hasInflightChangedValue = false;
|
|
483
|
+
if (!this._focused) maybeAutoSave();
|
|
484
|
+
}
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
409
487
|
} else if (computed && (opts.resetting || opts.refreshing)) return;
|
|
410
|
-
hasInflightChangedValue = false;
|
|
488
|
+
if (opts.refreshing || opts.resetting) hasInflightChangedValue = false;
|
|
411
489
|
const changed = !areEqual(newValue, this.value, strictOrder);
|
|
412
490
|
parentInstance[key] = newValue;
|
|
413
491
|
valueAtom.reportChanged();
|
|
@@ -427,8 +505,7 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
427
505
|
this.touched = false;
|
|
428
506
|
},
|
|
429
507
|
dispose() {
|
|
430
|
-
|
|
431
|
-
disposeStoreReaction = void 0;
|
|
508
|
+
deactivateSubscriptions(field);
|
|
432
509
|
},
|
|
433
510
|
get originalValue() {
|
|
434
511
|
originalValueAtom.reportObserved();
|
|
@@ -447,10 +524,27 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
447
524
|
}
|
|
448
525
|
}
|
|
449
526
|
};
|
|
450
|
-
if (isObservable(parentInstance))
|
|
451
|
-
|
|
527
|
+
if (isObservable(parentInstance)) registerSubscriptions(field, {
|
|
528
|
+
activate() {
|
|
529
|
+
disposeStoreReaction = reaction(() => parentInstance[key], (value) => {
|
|
530
|
+
lastObservedValue = value;
|
|
531
|
+
saveExternalChange();
|
|
532
|
+
});
|
|
533
|
+
const value = parentInstance[key];
|
|
534
|
+
const changed = !Object.is(lastObservedValue, value);
|
|
535
|
+
lastObservedValue = value;
|
|
536
|
+
if (changed) saveExternalChange();
|
|
537
|
+
},
|
|
538
|
+
deactivate() {
|
|
539
|
+
disposeStoreReaction?.();
|
|
540
|
+
disposeStoreReaction = void 0;
|
|
541
|
+
}
|
|
452
542
|
});
|
|
453
543
|
return field;
|
|
544
|
+
/** Saves external mutations unless a focused field is still accumulating edits. */
|
|
545
|
+
function saveExternalChange() {
|
|
546
|
+
if (!parentState().focused) maybeAutoSave();
|
|
547
|
+
}
|
|
454
548
|
}
|
|
455
549
|
/**
|
|
456
550
|
* Returns a proxy that looks exactly like the original `field`, in terms of valid/touched/errors/etc., but
|
|
@@ -556,6 +650,7 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
556
650
|
if (!childState) {
|
|
557
651
|
childState = newObjectState(config, parentState, void 0, list, child, void 0, maybeAutoSave, listConfig.update === "deep-exhaustive");
|
|
558
652
|
rowMap.set(child, childState);
|
|
653
|
+
if (subscriptionsActive(list)) activateSubscriptions(childState);
|
|
559
654
|
}
|
|
560
655
|
return childState;
|
|
561
656
|
}
|
|
@@ -732,7 +827,7 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
732
827
|
},
|
|
733
828
|
/** Disposes every row's ObjectState, including rows that were removed from the list. */
|
|
734
829
|
dispose() {
|
|
735
|
-
|
|
830
|
+
deactivateSubscriptions(list);
|
|
736
831
|
},
|
|
737
832
|
get originalValue() {
|
|
738
833
|
originalValueAtom.reportObserved();
|
|
@@ -750,7 +845,16 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
750
845
|
valueAtom.reportChanged();
|
|
751
846
|
}
|
|
752
847
|
};
|
|
753
|
-
|
|
848
|
+
const proxy = makeAutoObservable(list, { value: computed({ equals: () => false }) });
|
|
849
|
+
registerSubscriptions(proxy, {
|
|
850
|
+
activate() {
|
|
851
|
+
rowMap.forEach((row) => activateSubscriptions(row));
|
|
852
|
+
},
|
|
853
|
+
deactivate() {
|
|
854
|
+
rowMap.forEach((row) => deactivateSubscriptions(row));
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
return proxy;
|
|
754
858
|
}
|
|
755
859
|
function hashValue(value) {
|
|
756
860
|
return hash(value);
|
|
@@ -768,6 +872,12 @@ function fragment(value) {
|
|
|
768
872
|
* individual fields as well as the top-level form/object itself.
|
|
769
873
|
*/
|
|
770
874
|
function createObjectState(config, instance, opts = {}) {
|
|
875
|
+
const form = createInactiveObjectState(config, instance, opts);
|
|
876
|
+
activateSubscriptions(form);
|
|
877
|
+
return form;
|
|
878
|
+
}
|
|
879
|
+
/** Builds a hook-owned form without subscriptions that could escape an abandoned render. */
|
|
880
|
+
function createInactiveObjectState(config, instance, opts = {}) {
|
|
771
881
|
const noop = () => {};
|
|
772
882
|
return newObjectState({
|
|
773
883
|
type: "object",
|
|
@@ -896,7 +1006,7 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
|
|
|
896
1006
|
return result;
|
|
897
1007
|
},
|
|
898
1008
|
dispose() {
|
|
899
|
-
|
|
1009
|
+
deactivateSubscriptions(proxy);
|
|
900
1010
|
},
|
|
901
1011
|
get originalValue() {
|
|
902
1012
|
getFields(proxy).map((f) => f.originalValue);
|
|
@@ -920,6 +1030,14 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
|
|
|
920
1030
|
value: computed({ equals: () => false }),
|
|
921
1031
|
originalValue: computed({ equals: () => false })
|
|
922
1032
|
});
|
|
1033
|
+
registerSubscriptions(proxy, {
|
|
1034
|
+
activate() {
|
|
1035
|
+
fieldStates.forEach((entry) => activateSubscriptions(entry[1]));
|
|
1036
|
+
},
|
|
1037
|
+
deactivate() {
|
|
1038
|
+
fieldStates.forEach((entry) => deactivateSubscriptions(entry[1]));
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
923
1041
|
return proxy;
|
|
924
1042
|
}
|
|
925
1043
|
//#endregion
|
|
@@ -1051,55 +1169,78 @@ var ListFieldConfigBuilder = class {
|
|
|
1051
1169
|
*/
|
|
1052
1170
|
function useFormStates(opts) {
|
|
1053
1171
|
const { config, autoSave, getId, map, addRules, readOnly = false } = opts;
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const pendingAutoSaves = useRef(/* @__PURE__ */ new Set());
|
|
1172
|
+
const cache = useMemo(() => ({
|
|
1173
|
+
config,
|
|
1174
|
+
forms: {},
|
|
1175
|
+
active: false,
|
|
1176
|
+
pending: /* @__PURE__ */ new Map()
|
|
1177
|
+
}), [config]);
|
|
1178
|
+
const currentCacheRef = useRef(cache);
|
|
1062
1179
|
const isAutoSaving = useRef(false);
|
|
1063
1180
|
const autoSaveRef = useRef(autoSave);
|
|
1064
1181
|
autoSaveRef.current = autoSave;
|
|
1065
1182
|
const readOnlyRef = useRef(readOnly);
|
|
1066
1183
|
readOnlyRef.current = readOnly;
|
|
1184
|
+
useEffect(() => {
|
|
1185
|
+
currentCacheRef.current = cache;
|
|
1186
|
+
cache.active = true;
|
|
1187
|
+
Object.values(cache.forms).forEach((entry) => activateSubscriptions(entry[0]));
|
|
1188
|
+
Array.from(cache.pending.values()).forEach((maybeAutosave) => void maybeAutosave());
|
|
1189
|
+
return () => {
|
|
1190
|
+
cache.active = false;
|
|
1191
|
+
Object.values(cache.forms).forEach((entry) => deactivateSubscriptions(entry[0]));
|
|
1192
|
+
};
|
|
1193
|
+
}, [cache]);
|
|
1067
1194
|
return { getFormState: useCallback((input, opts = {}) => {
|
|
1195
|
+
const { config, forms: objectStateCache } = cache;
|
|
1068
1196
|
const existing = objectStateCache[getId(input)];
|
|
1069
1197
|
let form = existing?.[0];
|
|
1070
|
-
|
|
1198
|
+
/**
|
|
1199
|
+
* Serializes this form's saves with the other rows owned by the hook.
|
|
1200
|
+
*
|
|
1201
|
+
* Each retry retains its cache so a config replacement cannot revive a released form.
|
|
1202
|
+
*/
|
|
1203
|
+
async function maybeAutoSave() {
|
|
1204
|
+
if (!cache.active) {
|
|
1205
|
+
cache.pending.set(form, maybeAutoSave);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1071
1208
|
if (autoSaveRef.current && form.dirty) {
|
|
1072
1209
|
if (!form.valid) {
|
|
1073
1210
|
console.debug("Skipping auto-save b/c form is invalid: ", form.errors);
|
|
1211
|
+
cache.pending.delete(form);
|
|
1074
1212
|
return;
|
|
1075
1213
|
}
|
|
1076
|
-
const { current: pending } = pendingAutoSaves;
|
|
1077
1214
|
if (isAutoSaving.current) {
|
|
1078
|
-
pending.
|
|
1215
|
+
cache.pending.set(form, maybeAutoSave);
|
|
1079
1216
|
return;
|
|
1080
1217
|
}
|
|
1081
1218
|
try {
|
|
1082
1219
|
isAutoSaving.current = true;
|
|
1083
1220
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1084
|
-
pending.delete(form);
|
|
1085
|
-
await autoSaveRef.current(form);
|
|
1221
|
+
cache.pending.delete(form);
|
|
1222
|
+
if (cache.active && autoSaveRef.current) await autoSaveRef.current(form);
|
|
1223
|
+
else cache.pending.set(form, maybeAutoSave);
|
|
1086
1224
|
} finally {
|
|
1087
1225
|
isAutoSaving.current = false;
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
pending.delete(first);
|
|
1091
|
-
|
|
1226
|
+
const currentCache = currentCacheRef.current;
|
|
1227
|
+
if (currentCache.active) for (const [first, maybeAutosave] of currentCache.pending) {
|
|
1228
|
+
currentCache.pending.delete(first);
|
|
1229
|
+
if (!first.dirty || !first.valid) continue;
|
|
1230
|
+
await maybeAutosave();
|
|
1231
|
+
break;
|
|
1092
1232
|
}
|
|
1093
1233
|
}
|
|
1094
|
-
}
|
|
1234
|
+
} else cache.pending.delete(form);
|
|
1095
1235
|
}
|
|
1096
1236
|
if (!form) {
|
|
1097
|
-
form =
|
|
1237
|
+
form = createInactiveObjectState(config, initValue(config, {
|
|
1098
1238
|
map,
|
|
1099
1239
|
input
|
|
1100
|
-
}), { maybeAutoSave
|
|
1240
|
+
}), { maybeAutoSave });
|
|
1101
1241
|
if (addRules) addRules(form);
|
|
1102
1242
|
objectStateCache[getId(input)] = [form, input];
|
|
1243
|
+
if (cache.active) activateSubscriptions(form);
|
|
1103
1244
|
}
|
|
1104
1245
|
if (existing && existing[1] !== input) {
|
|
1105
1246
|
form.set(initValue(config, {
|
|
@@ -1110,7 +1251,7 @@ function useFormStates(opts) {
|
|
|
1110
1251
|
}
|
|
1111
1252
|
form.readOnly = readOnlyRef.current || !!opts.readOnly;
|
|
1112
1253
|
return form;
|
|
1113
|
-
}, [
|
|
1254
|
+
}, [cache]) };
|
|
1114
1255
|
}
|
|
1115
1256
|
//#endregion
|
|
1116
1257
|
export { createObjectState, f, fragment, required, useFormState, useFormStates };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/proxies.ts","../src/rules.ts","../src/temporal.ts","../src/fields/deepEquals.ts","../src/useFormState.ts","../src/utils.ts","../src/fields/valueField.ts","../src/fields/fragmentField.ts","../src/fields/listField.ts","../src/fields/objectField.ts","../src/configBuilders.ts","../src/useFormStates.ts"],"sourcesContent":["/**\n * Creates a new combined object with keys in `overrides` taking precedence, and then\n * any other keys falling back to `delegate`.\n */\nexport function newDelegateProxy<T extends object, O extends object>(delegate: T, overrides: O): Omit<T, keyof O> & O {\n function pickTarget(key: keyof any) {\n return Reflect.has(overrides, key) ? overrides : delegate;\n }\n return new Proxy(delegate, {\n get(object, key) {\n return Reflect.get(pickTarget(key), key);\n },\n\n set(object, key, value) {\n return Reflect.set(pickTarget(key), key, value);\n },\n }) as any;\n}\n","import { action } from \"mobx\";\n\n/** A validation rule, given the value and name, return the error string if valid, or undefined if valid. */\nexport type Rule<V> = (opts: { value: V; key: string; originalValue: V }) => string | undefined;\n\n/** A rule that validates `value` is not `undefined`, `null`, or empty string. */\n// We pre-emptively make this a mobx action so that it's identity doesn't change when proxied\n// and breaks our ability to do `rules.some(r => r === required)`.\nexport const required = action(<V>({ value: v }: { value: V }): string | undefined => {\n const isEmptyString = typeof v === \"string\" ? v.trim() === \"\" : false;\n return v !== undefined && v !== null && !isEmptyString ? undefined : \"Required\";\n});\n","// Use `import type` (not `import { type Temporal }`) so that, with verbatimModuleSyntax, the bundle does not keep\n// a side-effect `import \"temporal-polyfill\"`; form-state only needs the types and never loads the polyfill.\nimport type { Temporal } from \"temporal-polyfill\";\n\n// Use Temporal's well-known tags for runtime detection instead of `instanceof` because this\n// library can receive values from native Temporal, a polyfill, another copy of a polyfill,\n// or another realm. Tag checks are stable across those boundaries, while `instanceof` is not.\nconst temporalPlainDateTag = \"Temporal.PlainDate\";\nconst temporalZonedDateTimeTag = \"Temporal.ZonedDateTime\";\n\n// Narrow the supported Temporal surface area to the exact value types we want form-state to treat\n// like builtins, so unsupported Temporal types still fall through to existing behavior.\ntype SupportedTemporalTag = typeof temporalPlainDateTag | typeof temporalZonedDateTimeTag;\n\nexport type SupportedTemporal = Temporal.PlainDate | Temporal.ZonedDateTime;\n\n/** Detects the Temporal value types that form-state treats as builtins. */\nexport function isSupportedTemporal(value: unknown): value is SupportedTemporal {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Record<PropertyKey, unknown>;\n return (\n isSupportedTemporalTag(record[Symbol.toStringTag]) &&\n typeof record.equals === \"function\" &&\n typeof record.toJSON === \"function\"\n );\n}\n\n/** Compares supported Temporal values with Temporal's `.equals(...)` semantics. */\nexport function areSupportedTemporalValuesEqual(a: unknown, b: unknown): boolean | undefined {\n const aIsTemporal = isSupportedTemporal(a);\n const bIsTemporal = isSupportedTemporal(b);\n if (!aIsTemporal && !bIsTemporal) {\n return undefined;\n }\n if (!aIsTemporal || !bIsTemporal || a[Symbol.toStringTag] !== b[Symbol.toStringTag]) {\n return false;\n }\n // The tags match, so `b` is the same Temporal type as `a`; narrow `a` so that TS accepts `b` for its `equals`.\n if (isTemporalPlainDate(a)) {\n return a.equals(b as Temporal.PlainDate);\n }\n return a.equals(b as Temporal.ZonedDateTime);\n}\n\nfunction isSupportedTemporalTag(value: unknown): value is SupportedTemporalTag {\n return value === temporalPlainDateTag || value === temporalZonedDateTimeTag;\n}\n\n/** Narrows a supported Temporal value to `Temporal.PlainDate` by its well-known tag. */\nfunction isTemporalPlainDate(value: SupportedTemporal): value is Temporal.PlainDate {\n return value[Symbol.toStringTag] === temporalPlainDateTag;\n}\n","import { areSupportedTemporalValuesEqual } from \"src/temporal\";\n\n/**\n * Our own `deepEquals` because either `fast-deep-equals` or `dequal` or etc actually\n * handle cyclic data structures, despite ChatGTP's assertions/hallucinations.\n *\n * Ported from https://github.com/KoryNunn/cyclic-deep-equal which is ISC.\n */\nexport function deepEquals(a: any, b: any, visited: Set<any> = new Set()): boolean {\n const temporalEquals = areSupportedTemporalValuesEqual(a, b);\n if (temporalEquals !== undefined) return temporalEquals;\n\n const aType = typeof a;\n if (aType !== typeof b) return false;\n\n if (a == null || b == null || !(aType === \"object\" || aType === \"function\")) {\n if (aType === \"number\" && isNaN(a) && isNaN(b)) return true;\n return a === b;\n }\n\n if (hasToJSON(a) || hasToJSON(b)) {\n const a1 = hasToJSON(a) ? a.toJSON() : a;\n const b1 = hasToJSON(b) ? b.toJSON() : b;\n return deepEquals(a1, b1, visited);\n }\n\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n\n const aKeys = Object.keys(a),\n bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n\n let equal = true;\n\n for (const key of aKeys) {\n if (!(key in b)) {\n equal = false;\n break;\n }\n if (a[key] && a[key] instanceof Object) {\n if (visited.has(a[key])) continue;\n visited.add(a[key]);\n }\n if (!deepEquals(a[key], b[key], visited)) {\n equal = false;\n break;\n }\n }\n\n return equal;\n}\n\n/** Returns whether `o` is an object with a `toJSON` method, i.e. a Date or a custom value type. */\nexport function hasToJSON(o?: unknown): o is { toJSON(): unknown } {\n return !!(o && typeof o === \"object\" && \"toJSON\" in o);\n}\n","import { isPlainObject } from \"is-plain-object\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { type ObjectConfig } from \"src/config\";\nimport { type ObjectState, type ObjectStateInternal, createObjectState } from \"src/fields/objectField\";\nimport { initValue, isInput, isQuery } from \"./utils\";\n\n// A structural match for useQuery\nexport type Query<I> = { data: I; loading: boolean; error?: any };\n\nexport type InputAndMap<T, I> = {\n input: I;\n map?: (input: Exclude<I, null | undefined>) => T;\n ifUndefined?: T;\n onlyOnce?: boolean;\n};\n\nexport type QueryAndMap<T, I> = {\n query: Query<I>;\n map: (input: Exclude<I, null | undefined>) => T;\n ifUndefined?: T;\n};\n\n/**\n * The opts has for `useFormState`.\n *\n * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*\n * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query\n */\nexport type UseFormStateOpts<T, I> = {\n /** The form configuration, should be a module-level const or useMemo'd. */\n config: ObjectConfig<T>;\n\n /**\n * Provides the form's initial value.\n *\n * User's can either:\n *\n * - Provide no initial value (don't set `init` at all)\n * - Provide an initial value that already matches the form type `T` (i.e. set `init: data`)\n * - Provide an initial value from an object that _almost_ matches the form type `T`,\n * but needs to be mapped from it's input type `I` to the form type\n * (i.e. set `init: { input: data, map: (data) => ...}`).\n *\n * The value of using the 3rd option is that: a) we internally `useMemo` on the identity of the\n * `init.input` (i.e. a response from an Apollo hook) and don't require the `map` function\n * to have a stable identity, and also b) we will null-check/undefined-check `init.input` and\n * only call `init.map` if it's set, otherwise we'll use `init.ifDefined` or `{}`, saving you\n * from having to null check within your `init.map` function.\n */\n init?: InputAndMap<T, I> | QueryAndMap<T, I>;\n\n /**\n * A hook to add custom, cross-field validation rules that can be difficult to setup directly in the config DSL.\n *\n * Does not need to be stable/useMemo'd.\n */\n addRules?: (state: ObjectState<T>) => void;\n\n /** Whether the form should be read only, when changed it won't re-create the whole form. */\n readOnly?: boolean;\n\n /**\n * Whether the form is loading.\n *\n * Note that we also will infer loading from `init.input === undefined` or `init.query.loading`,\n * so you only need to set this directly if you're not using either of those conventions.\n */\n loading?: boolean;\n\n /**\n * Fired when the form should auto-save, i.e. after a) blur and b) all fields are valid.\n *\n * Does not need to be stable/useMemo'd.\n */\n autoSave?: (state: ObjectState<T>) => Promise<unknown>;\n};\n\nexport type FormObjectState<T, I> = ObjectState<T> & {\n /**\n * Refreshes the form with a server-acked value, i.e. `form.update(savedAuthor)`.\n *\n * This is intended for save callbacks that need `form.dirty` to become `false` immediately, before the\n * normal query/cache refresh has a chance to rerender `useFormState`. Pass the value returned by the\n * server so the form can map it through `init.map` and then do a field-by-field refresh of only the\n * changes the server has acknowledged.\n */\n update(input: Exclude<I, null | undefined>): void;\n};\n\n/**\n * Creates a formState instance for editing in a form.\n */\nexport function useFormState<T, I>(opts: UseFormStateOpts<T, I>): FormObjectState<T, I> {\n const { config, init, addRules, readOnly = false, loading, autoSave } = opts;\n\n // Use a ref so our memo'ized `onBlur` always see the latest value\n const autoSaveRef = useRef<((state: ObjectState<T>) => void) | undefined>(autoSave);\n autoSaveRef.current = autoSave;\n // Use a ref so our memo'ized `update` always sees the latest init/map\n const initRef = useRef<UseFormStateOpts<T, I>[\"init\"]>(init);\n initRef.current = init;\n\n const firstRunRef = useRef<boolean>(true);\n // This is a little weird, but we need to know ahead of time, before the form useMemo, if we're working with classes/mobx proxies\n const [firstInitValue] = useState(() => initValue(config, init));\n const isWrappingMobxProxy = !isPlainObject(firstInitValue);\n // If they're using init.input, useMemo on it (and it might be an array), otherwise allow the identity of init be unstable\n const dep = isInput(init)\n ? init.onlyOnce\n ? []\n : makeArray(init.input)\n : isQuery(init)\n ? [init.query.data, init.query.loading]\n : [];\n\n const form = useMemo(\n () => {\n // If the user's autoSave hook makes some last-minute `.set` calls to sneak\n // in some business logic right before their GraphQL mutation call, ignore it\n // so that we don't infinite loop.\n //\n // This is per-form (not module-level) so that one form's in-flight save does\n // not swallow the auto-save of an unrelated form on the same page.\n let isAutoSaving: \"queued\" | \"in-flight\" | false = false;\n\n // `pendingAutoSave` is a flag for determining if we need to immediately call `maybeAutoSave` again after the initial Promise finishes\n // This could happen if a field triggers auto-save while another field's auto-save is already in progress.\n let pendingAutoSave = false;\n\n function maybeAutoSave() {\n if (isAutoSaving === \"in-flight\") {\n pendingAutoSave = true;\n } else if (isAutoSaving === \"queued\") {\n // If we've queued up an auto-save, that means we haven't called the `autoSave(...)` function yet, so\n // we assume that the scheduled-but-not-yet-invoked invocation will find both it's original change, and\n // also this potentially new change, and put them both onto the wire as a single operation.\n // So we can just ignore this call.\n return;\n }\n\n // Don't use canSave() because we don't want to set touched for all the fields\n if (autoSaveRef.current && form.dirty && !isAutoSaving) {\n // It's very frustrating to not know why the form is savings, to go ahead and log these\n if (!form.valid) {\n console.debug(\"Skipping auto-save b/c form is invalid: \", form.errors);\n return;\n }\n isAutoSaving = \"queued\";\n // We use setTimeout as a cheap way to wait until the end of the current event listener\n setTimeout(async () => {\n try {\n // Tell commitChanges to blow up.\n (form as any)._isAutoSaving = true;\n // We technically don't flip to in-flight until after the call in case the\n // user's autoSave function itself wants to call a .set (which would call `maybeAutoSave`,\n // and we don't want it to see `isAutoSaving=in-flight` and think it \"missed the boat\",\n // and so schedule an unnecessary follow-up autosave.)\n const promise = autoSaveRef.current!(form);\n isAutoSaving = \"in-flight\";\n await promise;\n } finally {\n isAutoSaving = false;\n (form as any)._isAutoSaving = false;\n if (pendingAutoSave) {\n pendingAutoSave = false;\n // Push out the follow-up by 1 tick to allow refreshes to happen to potentially\n // un-dirty the just-saved data (e.g. if we run right away, the caller's maybeAutoSave\n // will see a form.changedValue that thinks the just-saved data is still dirty).\n setTimeout(maybeAutoSave, 0);\n }\n }\n }, 0);\n }\n }\n const value = firstRunRef.current ? firstInitValue : initValue(config, init);\n const form = createObjectState(config, value, { maybeAutoSave }) as FormObjectState<T, I>;\n form.update = function update(input) {\n const init = initRef.current;\n const updateInit = isInput(init)\n ? { ...init, input }\n : isQuery(init)\n ? { input, map: init.map, ifUndefined: init.ifUndefined }\n : { input };\n (form as unknown as ObjectStateInternal<T>).set(initValue(config, updateInit), {\n refreshing: true,\n });\n };\n form.readOnly = readOnly;\n setLoading(form, opts);\n // The identity of `addRules` is not stable, but assume that it is for better UX.\n (addRules || (() => {}))(form);\n firstRunRef.current = true;\n return form;\n },\n // For this useMemo, we (almost) never re-run so that we can have a stable `form` identity across query refreshes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [config, ...(isWrappingMobxProxy ? dep : [])],\n );\n\n // When `init` changes while wrapping a mobx proxy, the useMemo above builds a new form, so dispose the\n // superseded one and its fields stop watching the old store. We deliberately never dispose the *current*\n // form (i.e. in an effect cleanup), because StrictMode re-runs effects against a still-mounted form.\n const prevFormRef = useRef<FormObjectState<T, I> | undefined>(undefined);\n useEffect(() => {\n if (prevFormRef.current && prevFormRef.current !== form) prevFormRef.current.dispose();\n prevFormRef.current = form;\n }, [form]);\n\n // We use useEffect so that any mutations to the proxies, which will call `setState`s on any observers to\n // queue their components' render), don't happen during our render, per https://fb.me/setstate-in-render.\n useEffect(() => {\n // Ignore the 1st run b/c our 1st useMemo already initialized `form` with the current `init` value.\n // Also for mobx proxies, we recreate a new form-state every time init changes, so that our\n // fields fundamentally pointing to the right proxy. So just defer to the ^ useMemo.\n if (firstRunRef.current || isWrappingMobxProxy) {\n firstRunRef.current = false;\n return;\n }\n setLoading(form, opts);\n // Note: maybe someday we want to watch the `id` value and notice if it's changing (i.e. 2 -> 3),\n // and treat this more as a reset than a refresh, b/c the id changing means it's probably\n // not \"the cache refreshed after a mutation save\" but \"the cache changed b/c of changing\n // rows in the table that our side panel's form has open/is focused on\" (which ideally would\n // be treated as a full component remount by having an `key` field somewhere in the parent\n // component, but it's unlikely the user will always remember to do this).\n (form as any).set(initValue(config, init), { refreshing: true });\n // Only re-run when the `init` values change; `config` and `opts` are treated as stable, like in the useMemo above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [form, ...dep]);\n\n // Use useEffect so that we don't touch the form.init proxy during a render\n useEffect(() => {\n form.readOnly = readOnly;\n if (loading !== undefined) {\n form.loading = loading;\n }\n }, [form, readOnly, loading]);\n\n return form;\n}\n\nfunction setLoading(form: ObjectState<any>, opts: UseFormStateOpts<any, any>) {\n const { loading, init } = opts;\n if (loading !== undefined) {\n // Prefer the explicit/top-level opts.loading if it's set\n form.loading = loading;\n } else if (isInput(init) && !init.ifUndefined) {\n // Otherwise, check for `init.input`\n form.loading = init.input === undefined;\n } else if (isQuery(init)) {\n // Or `query.loading`\n form.loading = init.query.loading;\n }\n}\n\nfunction makeArray(input: any): any[] {\n return Array.isArray(input) ? input : [input];\n}\n","import { isPlainObject } from \"is-plain-object\";\nimport { isObservable, toJS } from \"mobx\";\nimport {\n type FragmentFieldConfig,\n type ListFieldConfig,\n type ObjectConfig,\n type ObjectFieldConfig,\n type ValueFieldConfig,\n} from \"src/config\";\nimport { deepEquals, hasToJSON } from \"src/fields/deepEquals\";\nimport { areSupportedTemporalValuesEqual, type SupportedTemporal } from \"src/temporal\";\nimport { type InputAndMap, type QueryAndMap, type UseFormStateOpts } from \"src/useFormState\";\n\nexport type Builtin = Date | Function | SupportedTemporal | Uint8Array | string | number | boolean;\n\nexport type Primitive = undefined | null | boolean | string | number | Function | Date | { toJSON(): any };\n\n/** Makes the keys in `T` required while keeping the values undefined. */\nexport type DeepRequired<T> = T extends Primitive\n ? T\n : {\n [P in keyof Required<T>]: T[P] extends Array<infer U>\n ? Array<DeepRequired<U>>\n : T[P] extends ReadonlyArray<infer U2>\n ? ReadonlyArray<DeepRequired<U2>>\n : DeepRequired<T[P]>;\n };\n\n// Inverse of SubType: https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c\nexport type OmitIf<Base, Condition> = Pick<\n Base,\n {\n [Key in keyof Base]: Base[Key] extends Condition ? never : Key;\n }[keyof Base]\n>;\n\nexport function fail(message?: string): never {\n throw new Error(message || \"Failed\");\n}\n\nexport function assertNever(x: never): never {\n throw new Error(\"Unexpected object: \" + x);\n}\n\n/** Introspects the `init` prop to see if it has a `map` function/etc. and returns the form value. */\nexport function initValue<T>(config: ObjectConfig<T>, init: any): T {\n let value: any;\n if (isInput(init)) {\n value = init.input ? (init.map ? init.map(init.input) : init.input) : init.ifUndefined;\n } else if (isQuery(init)) {\n value = init.query.data ? init.map(init.query.data) : init.ifUndefined;\n } else if (init === undefined) {\n // allow completely undefined init\n } else {\n throw new Error(\"init must have an input or query key\");\n }\n // Given our form config, pick out only the subset of fields out of `value` (unless it's a mobx class)\n return pickFields(config, value ?? {}) as T;\n}\n\nexport function isInput<T, I>(init: UseFormStateOpts<T, I>[\"init\"]): init is InputAndMap<T, I> {\n return !!init && typeof init === \"object\" && \"input\" in init;\n}\n\nexport function isQuery<T, I>(init: UseFormStateOpts<T, I>[\"init\"]): init is QueryAndMap<T, I> {\n return !!init && typeof init === \"object\" && \"query\" in init && \"map\" in init;\n}\n\n/**\n * Picks ony fields out of `instance` that are in the `formConfig`.\n *\n * This is useful for creating a form input from a GraphQL query result,\n * but ignoring extra data in the query that we don't want/need in the form.\n *\n * (Especially because the form.value will become our mutation's input type\n * that goes on the wire, we don't want superfluous data in it.)\n */\nexport function pickFields<T, I>(\n formConfig: ObjectConfig<T>,\n instance: I,\n): { [K in keyof T]: K extends keyof I ? I[K] : never } {\n // If the caller is using classes, i.e. with their own custom observable behavior, then just use as-is\n if (!isPlainObject(instance)) {\n return instance as any;\n }\n return Object.fromEntries(\n Object.entries(formConfig).map(([key, _keyConfig]) => {\n const keyConfig = _keyConfig as any as\n ObjectFieldConfig<any> | ListFieldConfig<any> | ValueFieldConfig<any> | FragmentFieldConfig;\n const value = (instance as any)[key];\n if (keyConfig.type === \"object\") {\n if (value) {\n return [key, pickFields(keyConfig.config, value)];\n } else {\n return [key, value];\n }\n } else if (keyConfig.type === \"list\") {\n if (isObservable(value)) {\n // If we hit an observable array, leave it as the existing proxy so the our\n // ListFieldState will react to changes in the original array.\n return [key, value];\n } else if (value) {\n return [key, (value as any[]).map((u) => pickFields(keyConfig.config, u))];\n } else {\n return [key, value];\n }\n } else if (keyConfig.type === \"value\") {\n return [key, value];\n } else if (keyConfig.type === \"fragment\") {\n return [key, value];\n } else {\n return assertNever(keyConfig);\n }\n }),\n ) as any;\n}\n\nexport function isNotUndefined<T>(value: T | undefined): value is T {\n return value !== undefined;\n}\n\nexport function isEmpty(value: any): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n/**\n * An equals that does deep-ish equality.\n *\n * We only do non-identity equals for:\n *\n * - \"plain\" objects that have no custom prototype/i.e. are object literals\n * - objects that implement `toJSON`\n * - arrays\n */\nexport function areEqual<T>(a?: T, b?: T, strictOrder?: boolean): boolean {\n const temporalEquals = areSupportedTemporalValuesEqual(a, b);\n if (temporalEquals !== undefined) {\n return temporalEquals;\n }\n if (isPlainObject(a)) {\n return deepEquals(toJS(a), toJS(b));\n }\n if (hasToJSON(a) || hasToJSON(b)) {\n const a1 = hasToJSON(a) ? a.toJSON() : a;\n const b1 = hasToJSON(b) ? b.toJSON() : b;\n return deepEquals(a1, b1);\n }\n if (a && b && a instanceof Array && b instanceof Array) {\n if (strictOrder !== false) {\n return deepEquals(a, b);\n }\n if (a.length !== b.length) return false;\n return a.every((a1) => b.some((b1) => areEqual(a1, b1)));\n }\n return a === b;\n}\n\n/** Normalizes values into stable object-hash-safe data using our existing `toJSON` semantics. */\nexport function normalizeHashValue(value: unknown, seen: WeakMap<object, unknown> = new WeakMap()): unknown {\n if (hasToJSON(value)) {\n return normalizeHashValue(value.toJSON(), seen);\n }\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n return seen.get(value);\n }\n const result: unknown[] = [];\n seen.set(value, result);\n value.forEach((entry) => result.push(normalizeHashValue(entry, seen)));\n return result;\n }\n if (value && typeof value === \"object\" && isPlainObject(value)) {\n if (seen.has(value)) {\n return seen.get(value);\n }\n const result: Record<string, unknown> = {};\n seen.set(value, result);\n Object.entries(value as Record<string, unknown>).forEach(([key, entry]) => {\n result[key] = normalizeHashValue(entry, seen);\n });\n return result;\n }\n return value;\n}\n\n/** Make a clone of `obj`, but only recurse into POJOs and Arrays...and stores. */\nexport function deepClone<T>(obj: T, map = new WeakMap()): T {\n if (obj && typeof obj === \"object\" && (isPlainObject(obj) || Array.isArray(obj) || isObservable(obj))) {\n if (map.has(obj)) return map.get(obj);\n const result = Array.isArray(obj) ? [] : {};\n map.set(obj, result);\n Object.assign(result, ...getAllPropertyNames(obj).map((key) => ({ [key]: deepClone((obj as any)[key], map) })));\n return result as T;\n } else {\n return obj;\n }\n}\n\nexport function groupBy<T, Y = T, K = string>(list: readonly T[], fn: (x: T) => K, valueFn?: (x: T) => Y): Map<K, Y[]> {\n const result = new Map<K, Y[]>();\n list.forEach((o) => {\n const group = fn(o);\n if (!result.has(group)) {\n result.set(group, []);\n }\n result.get(group)!.push(valueFn === undefined ? (o as any as Y) : valueFn(o));\n });\n return result;\n}\n\n/** Returns all property names, including mobx computeds (non-enumerable) & inherited. */\nfunction getAllPropertyNames(obj: unknown): string[] {\n const proto = Object.getPrototypeOf(obj);\n // Don't crawl up into Object.prototype, or into arrays/observable arrays\n const inherited = proto && proto !== Object.prototype && !Array.isArray(obj) ? getAllPropertyNames(proto) : [];\n return [...new Set(Object.getOwnPropertyNames(obj).concat(inherited))];\n}\n","import { isPlainObject } from \"is-plain-object\";\nimport { createAtom, isObservable, reaction, toJS } from \"mobx\";\nimport { type ObjectState } from \"src/fields/objectField\";\nimport { newDelegateProxy } from \"src/proxies\";\nimport { type Rule, required } from \"src/rules\";\nimport { areEqual, isEmpty, isNotUndefined } from \"src/utils\";\n\n/**\n * Form state for a primitive field in the form, i.e. its value but also touched/validation/etc. state.\n *\n * This API also provides hooks for form elements to call into, i.e. `blur()` and `set(...)` that will\n * update the field state and re-render, i.e. when including in an `ObjectState`-typed literal that is\n * an mobx `useLocalObservable`/observable.\n *\n * Note that `V` will always have `null | undefined` added to it by `FieldStates`, b/c most form fields\n * i.e. text boxes, can always be cleared out/deleted.\n */\nexport interface FieldState<V> {\n /** The key in the parent object, i.e. `firstName` in `author: { firstName: string }`. */\n readonly key: string;\n /** The current value of the field. */\n value: V;\n readonly originalValue: V;\n touched: boolean;\n readOnly: boolean;\n loading: boolean;\n readonly required: boolean;\n readonly dirty: boolean;\n readonly valid: boolean;\n readonly focused: boolean;\n readonly isNewEntity: boolean;\n rules: Rule<V>[];\n readonly errors: string[];\n /**\n * Returns a subset of V with only the changed values.\n *\n * Reading this also records that the value is probably being put on the wire, so that a later\n * `refreshing` set can tell a server ack of this value apart from a stale cache refresh.\n */\n readonly changedValue: V;\n /** Focuses the field. Disables changes from `ObjectState.set` calls. */\n focus(): void;\n /** Blur marks the field as touched and triggers an auto-save. */\n blur(): void;\n /** Triggers an auto-save; the caller (i.e. useFormState) should still dirty & valid check. */\n maybeAutoSave(): void;\n set(value: V, opts?: SetOpts): void;\n /** Reverts back to the original value and resets dirty/touched. */\n revertChanges(): void;\n /**\n * Accepts the current changed value (if any) as the original and resets dirty/touched.\n *\n * @deprecated Use `formState.update(ackedServerValue)` instead. `commitChanges` marks all current\n * changes as saved, which risks discarding a user's in-flight edits. Updating from the server ack gives\n * form-state a field-by-field audit of which changes were successfully saved.\n */\n commitChanges(): void;\n /** Creates a new FieldState with a transformation of the value, i.e. string to int, or feet to inches. */\n adapt<V2>(adapter: ValueAdapter<V, V2>): FieldState<V2>;\n /**\n * Releases any mobx reactions this field created.\n *\n * This only matters when wrapping a mobx observable (i.e. a store or class instance): each field\n * subscribes to its slot on the store so that external mutations can trigger auto-save, and that\n * subscription keeps the field alive until it is disposed. Plain objects create no reactions.\n */\n dispose(): void;\n}\n\n/**\n * Allows changing a type in the formState (like a string) to a different type in the UI (like a number).\n *\n * Or doing unit of measure conversions within the same type, like from meters to feet.\n */\nexport interface ValueAdapter<V, V2 = V> {\n /** Converts the original FieldState's value `V` into new `V2` type. */\n toValue(value: V): V2;\n /** Converts the adapted FieldState's value `V2` back into the original `V` type. */\n fromValue(value: V2): V;\n}\n\n/** Public options for our `set` command. */\nexport interface SetOpts {\n /** Whether this `set` should trigger an auto-save; defaults to true. */\n autoSave?: boolean;\n}\n\n/** Internal `.set` opts for conditionally that form-state internally percolates. */\nexport interface InternalSetOpts extends SetOpts {\n /** `resetting` is the code calling `.reset()` to revert to original values. */\n resetting?: boolean;\n /** `refreshing` is when `useFormState` sees a new value. */\n refreshing?: boolean;\n}\n\nexport interface FieldStateInternal<T, V> extends FieldState<V> {\n set(value: V, opts?: InternalSetOpts): void;\n _isIdKey: boolean;\n _isDeleteKey: boolean;\n _isReadOnlyKey: boolean;\n _isLocalOnly: boolean;\n}\n\nexport function newValueFieldState<T, K extends keyof T>(\n parentCopy: T,\n parentInstance: T,\n parentState: () => ObjectState<T>,\n key: K,\n rules: Rule<T[K] | null | undefined>[],\n isIdKey: boolean,\n isDeleteKey: boolean,\n isReadOnlyKey: boolean,\n isLocalOnly: boolean,\n computed: boolean,\n readOnly: boolean,\n strictOrder: boolean,\n maybeAutoSave: () => void,\n): FieldState<T[K] | null | undefined> {\n type V = T[K];\n\n // Because we read/write the value directly back into parentInstance[key] (and the\n // original into parentCopy[key]), which are not proxies, we use atoms as our\n // \"value changed\" triggers: getters call `reportObserved`, writes call `reportChanged`.\n const valueAtom = createAtom(`${String(key)}.value`);\n const originalValueAtom = createAtom(`${String(key)}.originalValue`);\n // Track \"this is probably what we put into a mutation to the server\" to allow\n // use to better accept server acks/responses that change our initial submission.\n //\n // I.e. we submit `firstName: bob` and the server acks `firstName: Bob`, we should\n // accept it if we can tell we haven't changed `bob` since our initial submission.\n let hasInflightChangedValue = false;\n let lastChangedValue: V | undefined = undefined;\n // Set below if we're wrapping a mobx observable, see `dispose`\n let disposeStoreReaction: (() => void) | undefined = undefined;\n\n const field = {\n key: key as string,\n\n touched: false,\n\n /** Current readOnly value. */\n _readOnly: readOnly || false,\n _loading: false,\n _kind: \"value\",\n // Expose so computed can be skipped in changedValue\n _computed: computed,\n _focused: false,\n\n _isIdKey: isIdKey,\n _isDeleteKey: isDeleteKey,\n _isReadOnlyKey: isReadOnlyKey,\n _isLocalOnly: isLocalOnly,\n\n rules,\n\n get value(): V {\n // If we're wrapping a mobx store, then we'll also get reactivity from parentInstance[key]\n valueAtom.reportObserved();\n const value = parentInstance[key];\n // Re-create the `keepNull` logic on sets but for our initial read where our\n // originalValue is null (empty) but we want to expose it as undefined for\n // consistency of \"empty-ness\" to our UI components.\n return value === null && isEmpty(parentCopy[key]) ? (undefined as any) : value;\n },\n\n set value(v: V) {\n this.set(v);\n },\n\n get focused(): boolean {\n return this._focused;\n },\n\n get dirty(): boolean {\n return !areEqual(this.originalValue, this.value, strictOrder);\n },\n\n /** Returns whether this field is readOnly, although if our parent is readOnly then it trumps. */\n get readOnly(): boolean {\n return parentState().readOnly || this._readOnly;\n },\n\n /** Sets the field readOnly, but `loading` will still be or-d with the parent's readOnly state. */\n set readOnly(readOnly: boolean) {\n this._readOnly = readOnly;\n },\n\n /** Returns whether this field is loading, or if our parent is loading. */\n get loading(): boolean {\n return parentState().loading || this._loading;\n },\n\n /** Sets the field loading, but `loading` will still be or-d with the parent's loading state. */\n set loading(loading: boolean) {\n this._loading = loading;\n },\n\n // For primitive fields, the changed value is just the value.\n get changedValue() {\n // Add some hints to let `set(..., { refreshing: true })` accept server-driven changes to our value,\n // if the user hasn't changed it yet-again while the changedValue was in in-flight.\n //\n // Note this is a side effect inside a mobx computed; it stays correct because every `set` calls\n // `valueAtom.reportChanged()`, which invalidates this computed, so the next read re-records the hints.\n hasInflightChangedValue = true;\n lastChangedValue = this.value;\n // Usually if we see a field being unset, we set `parent[key] = null`, but if we're wrapping\n // a mobx observable object, it might have changed to `undefined` without us being able to\n // tell it to be `null` (and potentially break the type contract of `string | undefined`).\n // So detect un-set-ness here and return `null`.\n if (this.value === undefined && this.originalValue !== undefined) return null;\n return this.value;\n },\n\n get valid(): boolean {\n return this.errors.length === 0;\n },\n\n get errors(): string[] {\n const opts = { value: this.value, key: key as string, originalValue: this.originalValue, object: parentState() };\n return this.rules.map((r) => r(opts as any)).filter(isNotUndefined);\n },\n\n get required(): boolean {\n return this.rules.some((rule) => rule === required);\n },\n\n get isNewEntity(): boolean {\n return parentState().isNewEntity;\n },\n\n focus() {\n this._focused = true;\n },\n\n blur() {\n this.maybeAutoSave();\n this._focused = false;\n },\n\n maybeAutoSave() {\n // Now that the user is done editing the field (note maybe w/o blurring, i.e. if they hit enter), we sneak in some trim logic\n this.maybeTrim();\n\n // touched is readonly, but we're allowed to change it\n this.touched = true;\n maybeAutoSave();\n },\n\n set(value: V | null | undefined, opts: InternalSetOpts = {}) {\n if (this.readOnly && !opts.resetting && !opts.refreshing) {\n throw new Error(`${String(key)} is currently readOnly`);\n }\n\n // If the user has deleted/emptied a value that was originally set, keep it as `null`\n // so that our partial update to the backend correctly unsets it.\n const keepNull = !isEmpty(this.originalValue) && isEmpty(value) && !opts.refreshing;\n // If a list of primitives was originally undefined, coerce `[]` to `undefined`\n const coerceEmptyList = value && value instanceof Array && value.length === 0 && isEmpty(this.originalValue);\n const newValue = keepNull ? null : isEmpty(value) || coerceEmptyList ? undefined : value;\n\n if (opts.refreshing && this.dirty) {\n // See if we should ignore the incoming server-side value, to avoid dropping local WIP changes\n const isAckingUnset = this.value === null && newValue === undefined;\n const acceptServerAck =\n hasInflightChangedValue &&\n this.value === lastChangedValue &&\n // If value === originalValue, this is likely a cache refresh firing in-between \"we put the new value\n // on the wire\" and \"the server acked our change\". Granted, this heuristic means that if the server\n // really does reject/rollback our change, we'll ignore it, but atm we don't have a way of differentiating\n // \"this refresh is from a pre-response cache refresh\" vs. \"post-response cache refresh\".\n !areEqual(newValue, this.originalValue, strictOrder);\n // Ignore incoming values if we have changes (this.dirty) unless:\n // - our latest change (this.value) matches the incoming value (value), i.e. the server\n // is exactly acking our change, or\n // - the user hasn't made any changes since `.changedValue` was put on the wire, i.e. the\n // server received our value, but wanted to change it.\n // Compare by content (not `!==`) so array/list-of-primitive values are treated as acked when\n // the server returns the same contents in a new array instance (otherwise the field would\n // stay dirty forever, since reference equality never holds for a fresh array).\n const keepLocalWipChange = !areEqual(this.value, newValue, strictOrder) && !isAckingUnset && !acceptServerAck;\n if (keepLocalWipChange) return;\n } else if (computed && (opts.resetting || opts.refreshing)) {\n // Computeds can't be either reset or refreshed\n return;\n }\n\n hasInflightChangedValue = false;\n\n // Set the value on our parent object\n const changed = !areEqual(newValue, this.value, strictOrder);\n parentInstance[key] = newValue!;\n valueAtom.reportChanged();\n\n if (opts.refreshing) {\n this.originalValue = newValue as any;\n }\n // If we're being set programmatically, i.e. we don't currently have focus,\n // call blur to trigger any auto-saves.\n if (!this._focused && !opts.refreshing && !opts.resetting && this.dirty && changed && opts.autoSave !== false) {\n this.maybeAutoSave();\n }\n },\n\n adapt<V2>(adapter: ValueAdapter<V, V2>): FieldState<V2> {\n return adapt(this, adapter);\n },\n\n revertChanges() {\n if (!computed) {\n this.set(this.originalValue, { resetting: true });\n }\n this.touched = false;\n },\n\n commitChanges() {\n if (isPlainObject(this.originalValue)) {\n this.originalValue = toJS(this.value);\n } else {\n this.originalValue = this.value;\n }\n this.touched = false;\n },\n\n dispose() {\n disposeStoreReaction?.();\n disposeStoreReaction = undefined;\n },\n\n get originalValue(): V {\n originalValueAtom.reportObserved();\n const value = parentCopy[key];\n // Re-create the `keepNull` logic so that `.value` === `.originalValue`\n return value === null ? (undefined as any) : value;\n },\n\n set originalValue(v: V) {\n const canSkip = v === undefined && !(key in (parentCopy as any));\n if (!canSkip) parentCopy[key] = v;\n originalValueAtom.reportChanged();\n },\n\n maybeTrim() {\n if (typeof this.value === \"string\") {\n let newValue: string | undefined = this.value.trim();\n if (newValue === \"\") {\n newValue = undefined;\n }\n if (newValue !== this.value) {\n this.set(newValue as any);\n }\n }\n },\n };\n\n // If we're wrapping a mobx observer, watching for external mutations, i.e. from callers not\n // going through our FieldState.set/FieldState.value setters.\n if (isObservable(parentInstance)) {\n disposeStoreReaction = reaction(\n () => parentInstance[key],\n () => {\n // Don't auto-save on maybe-external mutation if our field, or another other field (potentially a computed),\n // is currently focused, because then it's probably just us doing the writes through `Bound...Field` components.\n if (!parentState().focused) maybeAutoSave();\n },\n );\n }\n\n return field as any;\n}\n\n/**\n * Returns a proxy that looks exactly like the original `field`, in terms of valid/touched/errors/etc., but\n * has any methods that use `V` overridden to use be `V2`.\n *\n * Note that `V2` can be a new type, like string -> number, or just a transformation on the same\n * type, i.e. feet -> inches where both are `number`s.\n */\nfunction adapt<V, V2>(field: FieldState<V>, adapter: ValueAdapter<V, V2>): FieldState<V2> {\n return newDelegateProxy(field, {\n rules: [],\n get value(): V2 {\n return adapter.toValue(field.value);\n },\n set value(v: V2) {\n field.value = adapter.fromValue(v);\n },\n set: (v: V2) => {\n field.value = adapter.fromValue(v);\n },\n get changedValue(): V2 {\n return this.value;\n },\n adapt<V3>(adapter: ValueAdapter<V2, V3>) {\n return adapt(this, adapter);\n },\n get originalValue(): V2 {\n return adapter.toValue(field.originalValue);\n },\n });\n}\n","import { createAtom, makeAutoObservable } from \"mobx\";\nimport { type FieldStateInternal } from \"src/fields/valueField\";\n\nexport interface FragmentField<V> {\n value: V;\n}\n\nexport function newFragmentField<T extends object, K extends keyof T & string>(\n parentInstance: T,\n key: K,\n): FragmentField<T[K]> {\n // `value` lives in a closure variable, not an observable, so we use an atom\n // to make `get value()` observable and `set value()` notify observers.\n const valueAtom = createAtom(`${key}.value`);\n\n // We steal the fragment from our parent, so that it doesn't\n // accidentally end up on the wire\n let value = parentInstance[key];\n delete parentInstance[key];\n\n const obj = {\n key,\n _isIdKey: false,\n _isDeleteKey: false,\n _isReadOnlyKey: false,\n _isLocalOnly: false,\n touched: false,\n valid: true,\n readOnly: true,\n required: false,\n loading: false,\n dirty: false,\n focused: false,\n originalValue: undefined,\n changedValue: undefined,\n errors: [],\n rules: [],\n isNewEntity: false,\n focus: () => {},\n blur: () => {},\n maybeAutoSave: () => {},\n commitChanges: () => {},\n revertChanges: () => {},\n dispose: () => {},\n\n get value() {\n // Watch for our parentInstance changing\n if (key in parentInstance) {\n value = parentInstance[key];\n delete parentInstance[key];\n }\n valueAtom.reportObserved();\n return value;\n },\n\n set value(v: T[K]) {\n value = v;\n valueAtom.reportChanged();\n },\n\n set(value) {\n this.value = value;\n },\n\n adapt(value) {\n throw new Error(\"FragmentField does not support adapt\");\n },\n } satisfies FieldStateInternal<T, any>;\n\n return makeAutoObservable(obj, { value: false });\n}\n","import { computed, createAtom, makeAutoObservable } from \"mobx\";\nimport hash from \"object-hash\";\nimport { type ListFieldConfig, type ObjectFieldConfig } from \"src/config\";\nimport { type ObjectState, type ObjectStateInternal, newObjectState } from \"src/fields/objectField\";\nimport { type FieldState, type InternalSetOpts } from \"src/fields/valueField\";\nimport { type Rule, required } from \"src/rules\";\nimport { groupBy, isNotUndefined, normalizeHashValue } from \"src/utils\";\n\n/** Form state for list of children, i.e. `U` is a `Book` in a form with a `books: Book[]`. */\nexport interface ListFieldState<U> extends FieldState<U[]> {\n readonly rows: ReadonlyArray<ObjectState<U>>;\n\n add(value: U, index?: number): void;\n\n remove(indexOrValue: number | U): void;\n}\n\nexport function newListFieldState<T, K extends keyof T, U>(\n // parentCopy is objectField's deepClone-d `originalCopy` that it/we use for `dirty` checking\n parentCopy: T,\n // parentInstance is objectField's `instance`, that we're currently mutating\n parentInstance: T,\n parentState: () => ObjectState<T>,\n key: K,\n rules: Rule<readonly ObjectState<U>[]>[],\n listConfig: ListFieldConfig<U>,\n config: ObjectFieldConfig<U>,\n strictOrder: boolean,\n maybeAutoSave: () => void,\n deepExhaustive: boolean,\n): ListFieldState<U> {\n // Keep a map of \"item in the parentInstance list\" -> \"that item's ObjectState\"\n const rowMap = new Map<U, ObjectStateInternal<U>>();\n const addedRows = new Set<U>();\n // `parentInstance[key]` and `parentCopy[key]` are not proxies, so we use atoms as our\n // \"rows added/removed\" and \"original value changed\" triggers.\n const valueAtom = createAtom(`${String(key)}.value`);\n const originalValueAtom = createAtom(`${String(key)}.originalValue`);\n\n // When child rows don't have ids (i.e. for new rows that aren't saved yet), we need an id-less\n // \"clone <-> current\" map to tell if we're dirty or not, i.e. whether `parentInstance[key]` has\n // drifted from `parentCopy[key]`\n const copyMap = new Map<U, U>();\n ((parentCopy[key] ?? []) as U[]).forEach((copy, i) => {\n copyMap.set(copy, (parentInstance[key] as any)[i]);\n });\n\n // Given a child POJO (or a copy/clone of a child POJO), return its ObjectState wrapper.\n function getOrCreateChildState(child: U, opts?: InternalSetOpts & { skipSet?: boolean }): ObjectState<U> {\n let childState = rowMap.get(child);\n // If we're being reverted to our originalValue, i.e. values is actually\n // a list of copies, use the copyMap to recover the non-copy original value\n if (!childState && copyMap.has(child)) {\n childState = rowMap.get(copyMap.get(child)!);\n }\n // Look for an existing child (requires having an id key configured)\n if (!childState) {\n for (const [, otherState] of rowMap.entries()) {\n if (otherState.isSameEntity(child)) {\n // If we're being called from `list.rows`, we should be careful to not trigger mutations\n if (!opts?.skipSet) otherState.set(child, opts);\n rowMap.set(child, otherState);\n return otherState;\n }\n }\n }\n // If we didn't have an existing child, just make a new object state\n if (!childState) {\n childState = newObjectState<U>(\n config,\n parentState as any,\n undefined,\n list as any as FieldState<any>,\n child,\n undefined,\n maybeAutoSave,\n // Note: this used to be `... ?? deepExhaustive`, but the left side is a boolean, so the\n // parent's `deepExhaustive` was never inherited here; this keeps that behavior.\n listConfig.update === \"deep-exhaustive\",\n ) as ObjectStateInternal<U>;\n rowMap.set(child, childState);\n }\n return childState;\n }\n\n const list = {\n key: key as string,\n\n // Our fundamental state of wrapped Us\n get value() {\n // Read each row's value so observers of our `value` re-run when a row deeply changes,\n // not just when rows are added/removed (which is what `valueAtom` tracks).\n this.rows.forEach((r) => r.value);\n valueAtom.reportObserved();\n return (parentInstance[key] ?? []) as any as U[];\n },\n\n _kind: \"list\",\n _focused: false,\n _readOnly: false,\n _loading: false,\n\n get readOnly(): boolean {\n return this._readOnly || parentState().readOnly;\n },\n\n set readOnly(readOnly: boolean) {\n this._readOnly = readOnly;\n },\n\n get loading(): boolean {\n return this._loading || parentState().loading;\n },\n\n set loading(loading: boolean) {\n this._loading = loading;\n },\n\n set value(v: U[]) {\n this.set(v);\n },\n\n get dirty(): boolean {\n return this.rows.some((r) => r.dirty) || this.hasChanged();\n },\n\n // Having a new entity is slightly different than being dirty, b/c merely having a placeholder-but-not-changed-yet\n // `isNewEntity` child should not fire autosave (which would happen if we're dirty).\n get hasNewEntity(): boolean {\n return this.rows.some((r) => r.isNewEntity);\n },\n\n get focused(): boolean {\n return this.rows.some((r) => r.focused);\n },\n\n get required(): boolean {\n return this.rules.some((rule) => rule === required);\n },\n\n get isNewEntity(): boolean {\n return parentState().isNewEntity;\n },\n\n // private\n /** Returns whether a row has been added/removed. */\n hasChanged(): boolean {\n const [current, original] = [this.value || [], this.originalValue];\n if (current.length !== original.length) return true;\n if (strictOrder) {\n // With strict order, every copy[i] === original[i] must be true\n return original.some((e, idx) => current[idx] !== copyMap.get(e));\n } else {\n // With loose order, we just want every copy to still have its original somewhere\n return !original.every((e) => current.includes(copyMap.get(e)!));\n }\n },\n\n // And we can derive each value's ObjectState wrapper as needed from the rowMap cache\n get rows(): readonly ObjectState<U>[] {\n // Re-run when rows are added/removed\n valueAtom.reportObserved();\n // Read `parentInstance[key]` directly (not `this.value`), because `value` reads `rows`\n const value = parentInstance[key] as any as U[];\n return (value || []).map((child) => getOrCreateChildState(child, { skipSet: true }));\n },\n\n // TODO Should this be true when all rows are touched?\n get touched() {\n return this.rows.some((r) => r.touched) || this.hasChanged();\n },\n\n set touched(touched: boolean) {\n this.rows.forEach((r) => (r.touched = touched));\n },\n\n rules,\n\n get valid(): boolean {\n const collectionValid = this.errors.length === 0;\n const entriesValid = this.rows.filter((r) => !(r as any)._considerDeleted()).every((r) => r.valid);\n return collectionValid && entriesValid;\n },\n\n /** Returns the errors from our own collection-level rules (not the rows' errors). */\n get errors(): string[] {\n valueAtom.reportObserved();\n // TODO Passing `originalCopy || []` is probably not 100% right\n const opts = { value: this.rows, key: key as string, originalValue: this.originalValue, object: parentState() };\n return this.rules.map((r) => r(opts as any)).filter(isNotUndefined);\n },\n\n get changedValue() {\n const result = [] as any;\n const hasOpKey = Object.keys(listConfig.config).includes(\"op\");\n const hasLegacyOpKey = Object.keys(listConfig.config).some((key) => key === \"delete\" || key === \"remove\");\n const incremental =\n listConfig.update === \"incremental\" ||\n // Implicitly enable incremental mode if we see an op key\n (listConfig.update === undefined && (hasOpKey || hasLegacyOpKey));\n const exhaustive = !incremental;\n this.rows.forEach((r) => {\n if (exhaustive || r.dirty || r.isNewEntity) {\n const changed = r.changedValue;\n // Ensure we have an `op: include` key, following https://joist-orm.io/docs/features/partial-update-apis\n if (incremental && hasOpKey) {\n (changed as any).op ??= \"include\";\n }\n result.push(changed);\n }\n });\n return result;\n },\n\n focus() {\n this._focused = true;\n },\n\n blur() {\n this._focused = false;\n this.maybeAutoSave();\n },\n\n maybeAutoSave() {\n this.touched = true;\n maybeAutoSave();\n },\n\n set(values: U[], opts: InternalSetOpts = {}) {\n if (this.readOnly && !opts.resetting && !opts.refreshing) {\n throw new Error(`${String(key)} is currently readOnly`);\n }\n // Given the values, see if we've got existing child states, and use their\n // value if so. I.e. this covers revertChanges doing `set(originalValue)` and\n // passing us cloned rows from the parentCopy, but `getOrCreateChildState` will\n // use the `copyMap` to recover the non-cloned rows, to avoid promoting the clone\n // into a real row.\n if ((this.dirty || this.hasNewEntity) && opts.refreshing) {\n // When refreshing a dirty list, we need to preserve WIP values\n\n // Start with current list, then merge in incoming changes\n //\n // These will all be POJOs, where `currentItems` existing in our `parentInstance`, and\n // `incomingItems` could really be any of:\n // - an instance from `parentInstance[key]` that user code is passing back in\n // - an instance from `parentCopy[key]` that a cache refresh is passing in\n // - a new instance, either from a cache refresh or user code, that we haven't seen yet\n const currentItems = this.value || [];\n const incomingItems = values || [];\n const mergedItems: U[] = [];\n\n // Index by idKey or a hash of the object, so we don't have to n^2 merging\n const idKey = this.rows.length > 0 && (this.rows[0] as any as ObjectStateInternal).idKey;\n // Look at our child config, i.e. `bookConfig` and find the non-id / non-list keys\n const contentKeys = Object.entries(config.config)\n .filter(([key, cfg]: any) => key !== idKey && cfg.type === \"value\")\n .map(([key]) => key);\n const hashByContent = (item: any) =>\n hashValue(\n normalizeHashValue(\n Object.fromEntries(Object.entries(item as object).filter(([key]) => contentKeys.includes(key))),\n ),\n );\n const hashById = (item: any) => {\n const idValue = idKey ? item[idKey] : undefined;\n return idValue === undefined ? hashByContent(item) : idValue;\n };\n const currentById = groupBy(currentItems, hashById);\n const incomingById = groupBy(incomingItems, hashById);\n // If our local instance doesn't assign in id, when we get results back from the server, strip\n // their id and then see if we're the same based on data. Note that this is a hueristic, and will\n // fail if the client-side has made additional changes to the child after submitting it to the server,\n // or if the server acks back more/different data than the client sent.\n //\n // Only do this if one of our client-side objects is missing an id\n const incomingByContent =\n idKey && currentItems.some((item) => !(item as any)[idKey]) && groupBy(incomingItems, hashByContent);\n\n // Watch for deletions\n const hasOpKey = Object.keys(listConfig.config).includes(\"op\");\n\n // First, process all current items\n for (const currentItem of currentItems) {\n const childState = rowMap.get(currentItem)!; // We'll always have a child state for `currentItems`\n // Try to find a matching item in the incoming data\n const hash = hashById(currentItem);\n const match = (incomingById.get(hash)?.[0] ??\n // Only if we don't have `book[id]` set, look for a match based on our content\n (!!idKey &&\n !(currentItem as any)[idKey] &&\n !!incomingByContent &&\n // If we don't have an id, our `hash` variable will already be the `hashByContent`, so we can reuse it\n incomingByContent?.get(hash)?.[0])) as U | undefined;\n if (match) {\n // Defer to set to handle not nuking WIP changes\n childState.set(match, opts);\n mergedItems.push(childState.value);\n // Once matched, we don't want this to be an addedRow anymore\n addedRows.delete(currentItem);\n } else if (!childState.dirty && !childState.isNewEntity && !addedRows.has(currentItem)) {\n // Local is not dirty/added, and it's not upstream, so let it get removed\n } else if (hasOpKey && (currentItem as any).op === \"delete\") {\n // We were locally marked as deleted, and not finding a match is the server acking that we're gone\n } else if (\n currentItems.length === incomingItems.length &&\n !!idKey &&\n (currentItem as any)[idKey] === undefined\n ) {\n // Assume our newly-assigned id is coming back\n } else {\n // If no incoming, but we're dirty (see above) and not deleted (see above), keep the WIP change\n mergedItems.push(currentItem);\n }\n }\n\n // Then, add any incoming items that don't exist in current\n for (const incomingItem of incomingItems) {\n // Look for both `incoming.id` and the incoming-sans-id for new entities\n const match =\n currentById.get(hashById(incomingItem))?.[0] || currentById.get(hashByContent(incomingItem))?.[0];\n if (!match) {\n // New item from server, add it\n const childState = getOrCreateChildState(incomingItem, opts);\n mergedItems.push(childState.value);\n }\n }\n\n parentInstance[key] = mergedItems as any as T[K];\n valueAtom.reportChanged();\n\n // Set original to not merged...\n this.setOriginalValue(incomingItems);\n } else {\n parentInstance[key] = (values ?? []).map((child) => getOrCreateChildState(child, opts).value) as any as T[K];\n valueAtom.reportChanged();\n // Reset originalCopy so that our dirty checks have the right # of rows.\n if (opts.refreshing) {\n this.setOriginalValue();\n }\n }\n },\n\n add(value: U, spliceIndex?: number): void {\n // This is called by the user, so value should be a non-proxy value we should keep\n const childState = getOrCreateChildState(value) as ObjectStateInternal<U>;\n rowMap.set(value, childState);\n // Let `.set` know this is a new row\n addedRows.add(value);\n this.ensureSet();\n this.value.splice(typeof spliceIndex === \"number\" ? spliceIndex : this.value.length, 0, childState.value);\n valueAtom.reportChanged();\n maybeAutoSave();\n },\n\n remove(indexOrValue: number | U): void {\n this.ensureSet();\n if (typeof indexOrValue === \"number\") {\n this.value.splice(indexOrValue, 1);\n } else {\n const index = this.value.findIndex((v) => v === indexOrValue);\n if (index > -1) {\n this.value.splice(index, 1);\n }\n }\n valueAtom.reportChanged();\n maybeAutoSave();\n },\n\n revertChanges() {\n this.set(this.originalValue, { resetting: true });\n this.rows.forEach((r) => r.revertChanges());\n this.touched = false;\n },\n\n commitChanges() {\n // Tell each child ObjectState to have its fields commit into its parentCopy\n this.rows.forEach((r) => r.commitChanges());\n this.setOriginalValue();\n this.touched = false;\n valueAtom.reportChanged();\n },\n\n /** Disposes every row's ObjectState, including rows that were removed from the list. */\n dispose() {\n new Set(rowMap.values()).forEach((row) => row.dispose());\n },\n\n get originalValue(): U[] {\n originalValueAtom.reportObserved();\n const value = parentCopy[key];\n return value ?? ([] as any);\n },\n\n // This should only be called when value === originalValue\n setOriginalValue(incomingItems?: U[]) {\n // Use the rows' originalValues to update the parentCopy\n parentCopy[key] = incomingItems ?? (this.rows.map((r) => r.originalValue) as any);\n (parentCopy[key] as U[]).forEach((copy, i) => {\n copyMap.set(copy, (parentInstance[key] as any)[i]);\n });\n originalValueAtom.reportChanged();\n },\n\n ensureSet() {\n if (!parentInstance[key]) {\n (parentInstance as any)[key] = [];\n }\n valueAtom.reportChanged();\n },\n };\n\n const proxy = makeAutoObservable(list, {\n // See other makeAutoObservable comment\n value: computed({ equals: () => false }),\n }) as any;\n\n return proxy;\n}\n\nfunction hashValue(value: unknown): string {\n return hash(value as Parameters<typeof hash>[0]);\n}\n","import { computed, isObservable, makeAutoObservable } from \"mobx\";\nimport {\n type FragmentFieldConfig,\n type ListFieldConfig,\n type ObjectConfig,\n type ObjectFieldConfig,\n type ValueFieldConfig,\n} from \"src/config\";\nimport { type FragmentField, newFragmentField } from \"src/fields/fragmentField\";\nimport { type ListFieldState, newListFieldState } from \"src/fields/listField\";\nimport {\n type FieldState,\n type FieldStateInternal,\n type InternalSetOpts,\n newValueFieldState,\n type SetOpts,\n} from \"src/fields/valueField\";\nimport { areEqual, type Builtin, deepClone } from \"src/utils\";\n\n/**\n * Wraps a given input/on-the-wire type `T` for editing in a form.\n *\n * We basically mimic every field in `T` (i.e. `firstName`, `lastName`, etc.) but decorate them\n * with form-specific state like `touched`, `dirty`, `errors`, etc.\n *\n * The intent is that, after ensuring all fields are `valid`/etc., callers can take the\n * result of this `objectState.value` (or `objectState.originalValue` for the non-proxy version) and\n * have exactly the on-the-wire type `T` that they need to submit to the backend, without doing the\n * manual mapping of \"data that was in the form controls\" into \"data that the backend wants\".\n *\n * Note that this can be hierarchical by having a field of `ListFieldState` that\n * themselves each wrap an `ObjectState`, i.e.:\n *\n * ```\n * ObjectState for author\n * - firstName: FieldState\n * - lastName: FieldState\n * - rows: ListFieldState\n * - [0]: ObjectState for book 1\n * - title: FieldState\n * - [1]: ObjectState for book 2\n * - title: FieldState\n * ```\n */\nexport type ObjectState<T> =\n // Add state.field1, state.field2 for each key in T\n FieldStates<T> &\n // Pull in the touched, blur, dirty, etc\n FieldState<T> & {\n /** Sets the state of fields in `state`. */\n set(state: Partial<T>, opts?: SetOpts): void;\n\n /** Returns whether the object can be saved, i.e. is valid, but also as a side-effect marks touched. */\n canSave(): boolean;\n };\n\nexport type ObjectStateInternal<T = unknown> = ObjectState<T> & {\n set(value: T, opts?: InternalSetOpts): void;\n isSameEntity(other: T): boolean;\n idKey: string | undefined;\n};\n\nconst fragmentSym = Symbol(\"fragment\");\n\nexport type Fragment<V> = V & { [fragmentSym]: true };\n\nexport function fragment<V>(value: V): Fragment<V> {\n return value as any;\n}\n\n/** 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. */\ntype FieldStates<T> = {\n [K in keyof T]-?: T[K] extends Fragment<infer V>\n ? FragmentField<V>\n : T[K] extends Array<infer U> | null | undefined\n ? [U] extends [Builtin]\n ? FieldState<T[K]>\n : ListFieldState<U>\n : T[K] extends Builtin | null | undefined\n ? FieldState<T[K]>\n : ObjectState<T[K]>;\n};\n\n/**\n * Creates a new `ObjectState` for a given form object `T` given config rules in `config`.\n *\n * The returned `ObjectState` can be used in a mobx `useLocalObservable` to drive an\n * interactive form that tracks the current valid/touched/etc. state of both each\n * individual fields as well as the top-level form/object itself.\n */\nexport function createObjectState<T>(\n config: ObjectConfig<T>,\n instance: T,\n opts: { maybeAutoSave?: () => void } = {},\n): ObjectState<T> {\n const noop = () => {};\n return newObjectState(\n { type: \"object\", config },\n undefined,\n undefined,\n undefined,\n instance,\n undefined,\n opts.maybeAutoSave || noop,\n false,\n );\n}\n\n/** A more internal version of `createObjectState`. */\nexport function newObjectState<T, P = any>(\n config: ObjectFieldConfig<T>,\n parentState: (() => ObjectState<P>) | undefined,\n parentInstance: P | undefined,\n parentListState: FieldState<any> | undefined,\n instance: T,\n key: keyof T | undefined,\n maybeAutoSave: () => void,\n deepExhaustive: boolean,\n): ObjectState<T> {\n // This is what we return, but we only know it's value until we call `observable`, so\n // we create a mutable variable to capture it so that we can create fields/call their\n // constructors and give them a way to access it later.\n let proxy: ObjectState<T> | undefined = undefined;\n function getObjectState(): ObjectState<T> {\n if (!proxy) {\n throw new Error(\"Race condition\");\n }\n return proxy;\n }\n\n // We directly mutate `instance` as the user edits the form, so keep a deep copy of the POJO.\n // If the user passed us `instance` that was a mobx store/class, this originalValue won't really\n // be a true match (i.e. pass instanceof), but it should be good enough\n const originalCopy: any = deepClone(instance);\n\n const objectConfig = config.config;\n const fieldStates = Object.entries(objectConfig).map(([_key, _config]) => {\n const key = _key as keyof T;\n const config = _config as\n ValueFieldConfig<any> | ObjectFieldConfig<any> | ListFieldConfig<any> | FragmentFieldConfig;\n let field: FieldState<any> | ListFieldState<any> | ObjectState<T> | FragmentField<any>;\n if (config.type === \"value\") {\n field = newValueFieldState(\n originalCopy,\n instance,\n getObjectState,\n key,\n config.rules || [],\n config.isIdKey ||\n // Default the id key to \"id\" unless some other field has isIdKey set\n (key === \"id\" &&\n !(\"isIdKey\" in config) && // Allow setting `isIdKey=false`\n !(Object.entries(objectConfig) as any as [string, ValueFieldConfig<any>][]).some(\n ([other, c]) => other !== key && c.isIdKey,\n )),\n config.isDeleteKey || false,\n config.isReadOnlyKey || false,\n config.isLocalOnly || false,\n config.computed ??\n // If instance is a mobx class, we can detect computeds as they won't be enumerable\n (isObservable(instance) ? !(key in originalCopy) : false),\n config.readOnly ?? false,\n config.strictOrder ?? true,\n maybeAutoSave,\n );\n } else if (config.type === \"list\") {\n field = newListFieldState(\n originalCopy,\n instance,\n getObjectState,\n key,\n config.rules || [],\n config,\n { type: \"object\", config: config.config },\n config.strictOrder ?? true,\n maybeAutoSave,\n deepExhaustive,\n );\n } else if (config.type === \"object\") {\n // Because our objectField will fundamentally want to do `child.firstName.set(...)` or\n // even `child.firstName !== undefined`, etc., we \"simplify\" things by always setting\n // an empty object, for our child valueFields/listFields to use/read from, although\n // we then have to \"oh right ignore {}\" in places like `dirty`.\n if (!instance[key]) {\n instance[key] = {} as any;\n }\n field = newObjectState(\n config,\n getObjectState,\n instance,\n undefined,\n instance[key] as any,\n key,\n maybeAutoSave,\n deepExhaustive,\n ) as any;\n } else if (config.type === \"fragment\") {\n field = newFragmentField(instance as object & T, key as any);\n } else {\n throw new Error(`Invalid type value ${(config as any).type}`);\n }\n return [key, field];\n });\n\n const fieldNames = Object.keys(objectConfig);\n function getFields(proxyThis: any): FieldStateInternal<T, any>[] {\n return fieldNames.map((name) => proxyThis[name]) as FieldStateInternal<T, any>[];\n }\n\n const obj = {\n ...Object.fromEntries(fieldStates),\n\n key,\n\n get value() {\n // We always return the same `instance`, so read each field's value here to make\n // observers of our (identity-stable) `value` re-run when any field deeply changes.\n // (`originalValue` below does the same for `originalCopy`.)\n getFields(this).forEach((f) => f.value);\n return instance;\n },\n\n set value(value) {\n this.set(value);\n },\n\n // private\n _kind: \"object\",\n _readOnly: false,\n _loading: false,\n _isAutoSaving: false,\n\n _considerDeleted(): boolean {\n const deleteField = getFields(this).find((f) => f._isDeleteKey);\n return !!deleteField?.value;\n },\n\n _considerReadOnly(): boolean {\n const readOnlyField = getFields(this).find((f) => f._isReadOnlyKey);\n return !!readOnlyField?.value;\n },\n\n get focused(): boolean {\n return getFields(this).some((f) => f.focused);\n },\n\n get touched(): boolean {\n return getFields(this).some((f) => f.touched);\n },\n\n set touched(touched: boolean) {\n getFields(this).forEach((f) => (f.touched = touched));\n },\n\n get readOnly(): boolean {\n return (\n this._readOnly ||\n this._considerReadOnly() ||\n (parentState && parentState().readOnly) ||\n !!parentListState?.readOnly\n );\n },\n\n set readOnly(readOnly: boolean) {\n this._readOnly = readOnly;\n },\n\n get loading(): boolean {\n return this._loading || (parentState && parentState().loading) || !!parentListState?.loading;\n },\n\n set loading(loading: boolean) {\n this._loading = loading;\n },\n\n get valid(): boolean {\n return getFields(this).every((f) => {\n return f.valid;\n });\n },\n\n get errors(): string[] {\n return getFields(this).flatMap((f) => f.errors.map((e) => `${f.key}: ${e}`));\n },\n\n get dirty(): boolean {\n return (\n getFields(this).some((f) => f.dirty && !(f as FieldStateInternal<any, any>)._isLocalOnly) ||\n // `isUnset` checks if our `parent[key] === undefined`, which can mean \"surely we're dirty\",\n // but as long as we've got some keys actually set\n (this.isUnset() && !areEqual(this.value, {}))\n );\n },\n\n get isNewEntity(): boolean {\n const idField = getFields(this).find((f) => f._isIdKey);\n // If we're a line item w/o an immediate id field, look in our parent\n if (!idField && parentState) {\n return parentState().isNewEntity;\n }\n // If there is no id field, assume we're not new\n return idField !== undefined && (idField.value === null || idField.value === undefined);\n },\n\n canSave(): boolean {\n this.touched = true;\n return this.valid;\n },\n\n // Accepts new values in bulk, i.e. when setting the form initial state from the backend.\n set(value: T, opts: InternalSetOpts = {}) {\n if (this.readOnly && !opts.resetting && !opts.refreshing) {\n throw new Error(`${String(key) || \"formState\"} is currently readOnly`);\n }\n // Restore our instance if we're being reset\n if (value && this.isUnset()) (parentInstance as any)[key] = instance;\n // Delete our instance if we're being unset\n if (!value && parentInstance) (parentInstance as any)[key] = undefined;\n // Otherwise just copy over the fields\n getFields(this).forEach((field) => {\n if (value && typeof value === \"object\" && field.key in value) {\n field.set((value as any)[field.key], opts);\n }\n });\n },\n\n // Resets all fields back to their original values\n revertChanges() {\n getFields(this).forEach((f) => f.revertChanges());\n },\n\n // Saves all current values into _originalValue.\n // Deprecated: use formState.update(ackedServerValue) so saved changes are audited field-by-field.\n commitChanges() {\n if (this._isAutoSaving) {\n // `commitChanges` will mark all WIP changes as committed, which might drop changes if there\n // is an auto-save in-flight, and the user made more changes, that are waiting for their turn\n // on the next auto-save request.\n //\n // Instead of doing a form-wide \"all changes are committed\", autosave forms should use\n // formState.update(ackedServerValue), or init.map/input, to apply the server's latest results\n // within the form. That selectively \"un-dirty\"/commits the just-saved data, but keeps the\n // \"still-dirty\" WIP data alone.\n throw new Error(\n \"When using autoSave, you should not manually call commitChanges, instead have init.map/input update the form state\",\n );\n }\n getFields(this).forEach((f) => f.commitChanges());\n },\n\n // Create a result that is only populated with changed keys\n get changedValue() {\n const result: any = {};\n // If we're a child, like `author.address` that was unset, return null\n if (this.isUnset()) {\n // If we're a reference, always return `{ id: ... }` for easy binding\n const idField = getFields(this).find((f) => f._isIdKey);\n if (idField && config.reference) return { id: null };\n // Otherwise mark the whole child as gone\n return null;\n }\n getFields(this).forEach((f) => {\n if ((f as any)._computed) return;\n // References only include the id key below\n if (config.reference) return;\n // Ignore local-only fields\n if (f._isLocalOnly) return;\n if (\n f.dirty ||\n // If the caller used useFormState.ifUndefined to provide some default values, then those keys may not\n // look dirty, but if we're new we should include them anyway.\n (this.isNewEntity && (f as any)._kind === \"value\" && f.value !== undefined) ||\n // ...or they're non-empty sub-objects\n (this.isNewEntity &&\n (f as any)._kind === \"object\" &&\n // Child objects that are unset will have `changedValue = null`\n f.changedValue &&\n Object.entries(f.changedValue).length > 0) ||\n // ...or they're non-empty sub-lists\n (this.isNewEntity && (f as any)._kind === \"list\" && f.value?.length > 0) ||\n // ...or the parent list is deep-exhaustive\n deepExhaustive\n ) {\n result[f.key] = f.changedValue;\n }\n });\n // Ensure we always have the id for updates to work\n const idField = getFields(this).find((f) => f._isIdKey);\n if (idField && idField.value !== undefined) {\n result[idField.key] = idField.value;\n }\n return result;\n },\n\n dispose() {\n getFields(this).forEach((f) => f.dispose());\n },\n\n get originalValue(): T | undefined {\n getFields(proxy).map((f) => f.originalValue);\n return originalCopy;\n },\n\n // An internal helper method to see if `other` is for \"the same entity\" as our current row\n isSameEntity(other: T): boolean {\n const idField = getFields(this).find((f) => f._isIdKey);\n if (!idField) return false;\n const ourIdValue = idField.value;\n const otherIdValue = (other as any)[idField.key];\n // If the otherIdValue is undefined, it's a new entity so can't be the same as us\n return otherIdValue !== undefined && areEqual(otherIdValue, ourIdValue);\n },\n\n isUnset(): boolean {\n return !!parentInstance && (parentInstance as any)[key] === undefined;\n },\n\n get idKey(): string | undefined {\n return getFields(this).find((f) => f._isIdKey)?.key;\n },\n };\n\n proxy = makeAutoObservable(obj, {\n // Use custom equality on `value` that is _never_ equals. This sounds weird, but\n // because our `value` is always the same `instance` that was passed to `newObjectState`,\n // to mobx this looks like the value never changes, and it will never invoke observers\n // even with our tick-based hacks.\n value: computed({ equals: () => false }),\n originalValue: computed({ equals: () => false }),\n });\n\n return proxy!;\n}\n","import {\n type FragmentFieldConfig,\n type ListFieldConfig,\n type ObjectConfig,\n type ObjectFieldConfig,\n type ValueFieldConfig,\n} from \"src/config\";\nimport { type Fragment, type ObjectState } from \"src/fields/objectField\";\nimport { type Rule, required } from \"src/rules\";\nimport { type Builtin, type OmitIf } from \"src/utils\";\n\n/**\n * Provides a Zod-ish API for building form configs.\n */\nexport const f = {\n /** Creates the top-level config for a form, like an author or book. */\n config<T>(fields: ObjectConfigBuilderFields<T>): ObjectConfig<T> {\n return new ObjectConfigBuilder<T>(fields).build().config;\n },\n\n /** Creates the config DSL for an object, like an author or book. */\n object<T>(fields: ObjectConfigBuilderFields<T>): ObjectConfigBuilder<T> {\n return new ObjectConfigBuilder<T>(fields);\n },\n\n /** Creates the config DSL for an list of objects, like an author's list of books. */\n list<U>(fields: ObjectConfigBuilderFields<U>): ListFieldConfigBuilder<U> {\n return new ListFieldConfigBuilder<U>(fields);\n },\n\n /** Creates the config DSL for a primitive value, like a first name or phone number. */\n value<V>(): ValueFieldConfigBuilder<V> {\n return new ValueFieldConfigBuilder<V>();\n },\n\n /** A shortcut for creating a child object with a single `id` key. */\n reference<V extends { id?: unknown }>(fields?: ObjectConfigBuilderFields<V>): ObjectConfigBuilder<V> {\n return f.object<V>({ id: f.value(), ...fields } as any).ref();\n },\n\n /** A shorthand for creating a computed value. */\n computed<V>(): ValueFieldConfigBuilder<V> {\n return f.value<V>().computed();\n },\n};\n\n/**\n * Defines the fields that should be passed into `f.object({ ... })` for fhe form type `T`.\n *\n * This is basically a map of `key` in `T` to `f.value()`, `f.list()`, or `f.object()`\n * DSL objects that define the runtime structure/fields of the form.\n */\nexport type ObjectConfigBuilderFields<T> = {\n [P in keyof OmitIf<T, Function>]: T[P] extends Fragment<infer V>\n ? FragmentFieldConfigBuilder\n : T[P] extends Array<infer U> | null | undefined\n ? U extends Builtin\n ? ValueFieldConfigBuilder<T[P]>\n : ListFieldConfigBuilder<U>\n : ValueFieldConfigBuilder<T[P]> | ObjectConfigBuilder<T[P]>;\n};\n\nexport interface FragmentFieldConfigBuilder {\n build(): FragmentFieldConfig;\n}\n\n/** Provides a fluent DSL for building up an object's config. */\nexport class ObjectConfigBuilder<T> {\n private config: ObjectFieldConfig<T> = { type: \"object\", config: {} as ObjectConfig<T> };\n\n constructor(fields: ObjectConfigBuilderFields<T>) {\n for (const [key, value] of Object.entries(fields)) {\n (this.config.config as any)[key] = (value as any).build();\n }\n }\n\n ref(): this {\n this.config.reference = true;\n return this;\n }\n\n build(): ObjectFieldConfig<T> {\n return this.config;\n }\n}\n\n/** Provides a fluent DSL for building up a value's config. */\nexport class ValueFieldConfigBuilder<V> {\n private config: ValueFieldConfig<V> = { type: \"value\", rules: [] };\n\n /** Marks the field as required, keeping any rules that were already added. */\n req(): this {\n (this.config.rules ??= []).push(required);\n return this;\n }\n\n /** Marks the field as read only. */\n readOnly(): this {\n this.config.readOnly = true;\n return this;\n }\n\n /** Marks the field as local only. */\n localOnly(): this {\n this.config.isLocalOnly = true;\n return this;\n }\n\n /** Appends `rules` to the field's validation rules. */\n rules(rules: Rule<V | null | undefined>[]): this {\n (this.config.rules ??= []).push(...rules);\n return this;\n }\n\n /** Marks the field as computed. */\n computed(): this {\n this.config.computed = true;\n return this;\n }\n\n build(): ValueFieldConfig<V> {\n return this.config;\n }\n}\n\n/** Provides a fluent DSL for building up a list's config. */\nexport class ListFieldConfigBuilder<U> {\n private config: ListFieldConfig<U>;\n\n constructor(fields: ObjectConfigBuilderFields<U>) {\n const config = {} as any;\n for (const [key, value] of Object.entries(fields)) {\n config[key] = (value as any).build();\n }\n this.config = { type: \"list\", rules: [], config };\n }\n\n /** Appends `rule` to the field's validation rules. */\n rule(rule: Rule<readonly ObjectState<U>[]>): this {\n (this.config.rules ??= []).push(rule);\n return this;\n }\n\n /** Appends `rules` to the field's validation rules. */\n rules(rules: Rule<readonly ObjectState<U>[]>[]): this {\n (this.config.rules ??= []).push(...rules);\n return this;\n }\n\n build(): ListFieldConfig<U> {\n return this.config;\n }\n}\n","import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { type ObjectConfig } from \"src/config\";\nimport { type ObjectState, type ObjectStateInternal, createObjectState } from \"src/fields/objectField\";\nimport { initValue } from \"src/utils\";\n\nexport type ObjectStateCache<T, I> = Record<string, [ObjectState<T>, I]>;\n\n/**\n * The opts has for `useFormStates`.\n *\n * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*\n * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query\n */\ntype UseFormStatesOpts<T, I> = {\n /**\n * The config to use for each form state.\n *\n * Should be stable/useMemo'd.\n */\n config: ObjectConfig<T>;\n\n /**\n * Fired for each individual `ObjectState` when it's had a value change.\n *\n * Does not need to be stable/useMemo'd.\n */\n autoSave?: (state: ObjectState<T>) => Promise<void>;\n\n /**\n * A hook to add custom, cross-field validation rules that can be difficult to setup directly in the config DSL.\n *\n * This will be called once-per `ObjectState` instance, and so is effectively a `useEffect` hook with\n * a `[config, objectState]` dependency.\n *\n * Does not need to be stable/useMemo'd.\n */\n addRules?: (state: ObjectState<T>) => void;\n\n /**\n * Given an input to `getFormState`, returns the identity value that we'll cache that value's form state on.\n *\n * Does not need to be stable/useMemo'd.\n */\n getId: (v: I) => string;\n\n /**\n * Maps an input to `getFormState` to the actual form shape `T`.\n *\n * Does not need to be stable/useMemo'd.\n */\n map?: (input: Exclude<I, null | undefined>) => T;\n\n /**\n * Sets all `ObjectState`s to readOnly.\n */\n readOnly?: boolean;\n};\n\ntype UseFormStatesHook<T, I> = {\n getFormState: (input: I, opts?: { readOnly?: boolean }) => ObjectState<T>;\n};\n\n/**\n * A hook to manage many \"mini-forms\" on a single page, typically one form per row\n * in a table.\n *\n * This hook basically provides the page/table with a cache, so each table row naively ask \"what's\n * the form state for this given row's data?\" and get back a new-or-existing `ObjectState` instance\n * that, if already existing, still has any of the user's WIP changes.\n *\n * Each mini-form/row can have its own autoSave calls, independent of the other rows.\n *\n * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*\n * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query\n */\nexport function useFormStates<T, I = T>(opts: UseFormStatesOpts<T, I>): UseFormStatesHook<T, I> {\n const { config, autoSave, getId, map, addRules, readOnly = false } = opts;\n\n const objectStateCache = useMemo<ObjectStateCache<T, I>>(\n () => ({}),\n // Force resetting the cache if config changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [config],\n );\n // When `config` changes we start a new cache, so dispose the superseded forms and their fields stop\n // watching any mobx stores they wrapped. We never dispose the current cache (i.e. in an effect cleanup),\n // because StrictMode re-runs effects against a still-mounted component.\n const prevCacheRef = useRef(objectStateCache);\n useEffect(() => {\n const prevCache = prevCacheRef.current;\n if (prevCache !== objectStateCache) Object.values(prevCache).forEach((entry) => entry[0].dispose());\n prevCacheRef.current = objectStateCache;\n }, [objectStateCache]);\n // Keep track of ObjectStates that triggered auto-save when a save was already in progress.\n const pendingAutoSaves = useRef<Set<ObjectState<T>>>(new Set());\n // If the user's autoSave hook makes some last-minute `.set` calls to sneak\n // in some business logic right before their GraphQL mutation call, ignore it\n // so that we don't infinite loop.\n //\n // This is per-hook (not module-level) so that one hook's in-flight save does not\n // swallow the auto-saves queued by an unrelated `useFormStates` on the same page.\n const isAutoSaving = useRef(false);\n // Use a ref so our memo'ized `autoSave` always see the latest value\n const autoSaveRef = useRef<UseFormStatesOpts<T, I>[\"autoSave\"]>(autoSave);\n autoSaveRef.current = autoSave;\n // Use a ref b/c we're memod\n const readOnlyRef = useRef<boolean>(readOnly);\n readOnlyRef.current = readOnly;\n\n const getFormState = useCallback<UseFormStatesHook<T, I>[\"getFormState\"]>(\n (input, opts = {}) => {\n const existing = objectStateCache[getId(input)];\n let form = existing?.[0];\n\n async function maybeAutoSave(form: ObjectState<T>) {\n // Don't use form.canSave() because we don't want to set touched for all the fields\n if (autoSaveRef.current && form.dirty) {\n // It's very frustrating to not know why the form is savings, to go ahead and log these\n if (!form.valid) {\n console.debug(\"Skipping auto-save b/c form is invalid: \", form.errors);\n return;\n }\n const { current: pending } = pendingAutoSaves;\n if (isAutoSaving.current) {\n pending.add(form);\n return;\n }\n try {\n isAutoSaving.current = true;\n // See if we have any reactions that want to run (i.e. added by addRules hooks)\n await new Promise((resolve) => setTimeout(resolve, 0));\n // If a reaction re-queued our form during the ^ wait, remove it\n pending.delete(form);\n await autoSaveRef.current(form);\n } finally {\n isAutoSaving.current = false;\n if (pending.size > 0) {\n const first = pending.values().next().value!;\n pending.delete(first);\n await maybeAutoSave(first);\n }\n }\n }\n }\n\n // If it didn't exist, then add to the cache.\n if (!form) {\n form = createObjectState(config, initValue(config, { map, input }), {\n maybeAutoSave: () => maybeAutoSave(form),\n });\n if (addRules) {\n addRules(form);\n }\n objectStateCache[getId(input)] = [form, input];\n }\n\n // If the source of truth changed, then update the existing state and return it.\n if (existing && existing[1] !== input) {\n (form as any as ObjectStateInternal<any>).set(initValue(config, { map, input }), {\n refreshing: true,\n });\n existing[1] = input;\n }\n\n form.readOnly = readOnlyRef.current || !!opts.readOnly;\n\n return form;\n },\n // Allow the user to not stable-ize getId, map, addRules, and autoSave\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [objectStateCache, config],\n );\n\n return { getFormState };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/fields/subscriptions.ts","../src/proxies.ts","../src/rules.ts","../src/temporal.ts","../src/fields/deepEquals.ts","../src/useFormState.ts","../src/utils.ts","../src/fields/valueField.ts","../src/fields/fragmentField.ts","../src/fields/listField.ts","../src/fields/objectField.ts","../src/configBuilders.ts","../src/useFormStates.ts"],"sourcesContent":["type SubscriptionLifecycle = {\n active: boolean;\n activate(): void;\n deactivate(): void;\n};\n\n/**\n * Tracks reversible subscriptions to observable instances supplied by callers.\n *\n * In development, React StrictMode replays effects as setup -> cleanup -> setup while keeping the\n * same form instances. Cleanup must release external-store reactions so they cannot retain an\n * unmounted form or trigger autosave. Replayed setup must reconnect those reactions without\n * replacing the form, losing edits, or resetting acknowledgement state. Hooks therefore construct\n * inactive forms, activate them in effect setup, and deactivate them in cleanup. An abandoned\n * initial render never acquires subscriptions that would have no effect cleanup to release them.\n *\n * Only value fields wrapping external MobX observables own these reactions. Object and list fields\n * coordinate their children's subscriptions, including cached list rows. This lifecycle does not\n * manage form-state's own atoms, observable properties, or computed values.\n *\n * Demand-driven subscriptions would mean \"autosave only while someone observes this field.\"\n * Our hooks instead mean \"autosave while this hook owns the form,\" including cached fields that\n * no component observes. That is why these reactions follow the owning effect's lifecycle rather\n * than MobX's observer count.\n *\n * Keep lifecycle operations off the public field objects and exported form API. The WeakMap also\n * lets inactive forms be collected without requiring a separate registry cleanup.\n */\nconst subscriptions = new WeakMap<object, SubscriptionLifecycle>();\n\n/** Registers a field's reversible subscription lifecycle, initially inactive. */\nexport function registerSubscriptions(state: object, lifecycle: Omit<SubscriptionLifecycle, \"active\">): void {\n subscriptions.set(state, { ...lifecycle, active: false });\n}\n\n/** Connects a field tree once, without replacing its values or acknowledgement state. */\nexport function activateSubscriptions(state: object): void {\n const lifecycle = subscriptions.get(state);\n if (lifecycle && !lifecycle.active) {\n lifecycle.active = true;\n lifecycle.activate();\n }\n}\n\n/** Disconnects a field tree synchronously; a later effect setup can reconnect it. */\nexport function deactivateSubscriptions(state: object): void {\n const lifecycle = subscriptions.get(state);\n if (lifecycle?.active) {\n lifecycle.active = false;\n lifecycle.deactivate();\n }\n}\n\n/** Reports whether this field tree currently owns active subscriptions. */\nexport function subscriptionsActive(state: object): boolean {\n return subscriptions.get(state)?.active ?? false;\n}\n","/**\n * Creates a new combined object with keys in `overrides` taking precedence, and then\n * any other keys falling back to `delegate`.\n */\nexport function newDelegateProxy<T extends object, O extends object>(delegate: T, overrides: O): Omit<T, keyof O> & O {\n function pickTarget(key: keyof any) {\n return Reflect.has(overrides, key) ? overrides : delegate;\n }\n return new Proxy(delegate, {\n get(object, key) {\n return Reflect.get(pickTarget(key), key);\n },\n\n set(object, key, value) {\n return Reflect.set(pickTarget(key), key, value);\n },\n }) as any;\n}\n","import { action } from \"mobx\";\n\n/** A validation rule, given the value and name, return the error string if valid, or undefined if valid. */\nexport type Rule<V> = (opts: { value: V; key: string; originalValue: V }) => string | undefined;\n\n/** A rule that validates `value` is not `undefined`, `null`, or empty string. */\n// We pre-emptively make this a mobx action so that it's identity doesn't change when proxied\n// and breaks our ability to do `rules.some(r => r === required)`.\nexport const required = action(<V>({ value: v }: { value: V }): string | undefined => {\n const isEmptyString = typeof v === \"string\" ? v.trim() === \"\" : false;\n return v !== undefined && v !== null && !isEmptyString ? undefined : \"Required\";\n});\n","// Use `import type` (not `import { type Temporal }`) so that, with verbatimModuleSyntax, the bundle does not keep\n// a side-effect `import \"temporal-polyfill\"`; form-state only needs the types and never loads the polyfill.\nimport type { Temporal } from \"temporal-polyfill\";\n\n// Use Temporal's well-known tags for runtime detection instead of `instanceof` because this\n// library can receive values from native Temporal, a polyfill, another copy of a polyfill,\n// or another realm. Tag checks are stable across those boundaries, while `instanceof` is not.\nconst temporalPlainDateTag = \"Temporal.PlainDate\";\nconst temporalZonedDateTimeTag = \"Temporal.ZonedDateTime\";\n\n// Narrow the supported Temporal surface area to the exact value types we want form-state to treat\n// like builtins, so unsupported Temporal types still fall through to existing behavior.\ntype SupportedTemporalTag = typeof temporalPlainDateTag | typeof temporalZonedDateTimeTag;\n\nexport type SupportedTemporal = Temporal.PlainDate | Temporal.ZonedDateTime;\n\n/** Detects the Temporal value types that form-state treats as builtins. */\nexport function isSupportedTemporal(value: unknown): value is SupportedTemporal {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Record<PropertyKey, unknown>;\n return (\n isSupportedTemporalTag(record[Symbol.toStringTag]) &&\n typeof record.equals === \"function\" &&\n typeof record.toJSON === \"function\"\n );\n}\n\n/** Compares supported Temporal values with Temporal's `.equals(...)` semantics. */\nexport function areSupportedTemporalValuesEqual(a: unknown, b: unknown): boolean | undefined {\n const aIsTemporal = isSupportedTemporal(a);\n const bIsTemporal = isSupportedTemporal(b);\n if (!aIsTemporal && !bIsTemporal) {\n return undefined;\n }\n if (!aIsTemporal || !bIsTemporal || a[Symbol.toStringTag] !== b[Symbol.toStringTag]) {\n return false;\n }\n // The tags match, so `b` is the same Temporal type as `a`; narrow `a` so that TS accepts `b` for its `equals`.\n if (isTemporalPlainDate(a)) {\n return a.equals(b as Temporal.PlainDate);\n }\n return a.equals(b as Temporal.ZonedDateTime);\n}\n\nfunction isSupportedTemporalTag(value: unknown): value is SupportedTemporalTag {\n return value === temporalPlainDateTag || value === temporalZonedDateTimeTag;\n}\n\n/** Narrows a supported Temporal value to `Temporal.PlainDate` by its well-known tag. */\nfunction isTemporalPlainDate(value: SupportedTemporal): value is Temporal.PlainDate {\n return value[Symbol.toStringTag] === temporalPlainDateTag;\n}\n","import { areSupportedTemporalValuesEqual } from \"src/temporal\";\n\n/**\n * Our own `deepEquals` because either `fast-deep-equals` or `dequal` or etc actually\n * handle cyclic data structures, despite ChatGTP's assertions/hallucinations.\n *\n * Ported from https://github.com/KoryNunn/cyclic-deep-equal which is ISC.\n */\nexport function deepEquals(a: any, b: any, visited: Set<any> = new Set()): boolean {\n const temporalEquals = areSupportedTemporalValuesEqual(a, b);\n if (temporalEquals !== undefined) return temporalEquals;\n\n const aType = typeof a;\n if (aType !== typeof b) return false;\n\n if (a == null || b == null || !(aType === \"object\" || aType === \"function\")) {\n if (aType === \"number\" && isNaN(a) && isNaN(b)) return true;\n return a === b;\n }\n\n if (hasToJSON(a) || hasToJSON(b)) {\n const a1 = hasToJSON(a) ? a.toJSON() : a;\n const b1 = hasToJSON(b) ? b.toJSON() : b;\n return deepEquals(a1, b1, visited);\n }\n\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n\n const aKeys = Object.keys(a),\n bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n\n let equal = true;\n\n for (const key of aKeys) {\n if (!(key in b)) {\n equal = false;\n break;\n }\n if (a[key] && a[key] instanceof Object) {\n if (visited.has(a[key])) continue;\n visited.add(a[key]);\n }\n if (!deepEquals(a[key], b[key], visited)) {\n equal = false;\n break;\n }\n }\n\n return equal;\n}\n\n/** Returns whether `o` is an object with a `toJSON` method, i.e. a Date or a custom value type. */\nexport function hasToJSON(o?: unknown): o is { toJSON(): unknown } {\n return !!(o && typeof o === \"object\" && \"toJSON\" in o);\n}\n","import { isPlainObject } from \"is-plain-object\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { type ObjectConfig } from \"src/config\";\nimport { type ObjectState, type ObjectStateInternal, createInactiveObjectState } from \"src/fields/objectField\";\nimport { activateSubscriptions, deactivateSubscriptions, subscriptionsActive } from \"src/fields/subscriptions\";\nimport { initValue, isInput, isQuery } from \"./utils\";\n\n// A structural match for useQuery\nexport type Query<I> = { data: I; loading: boolean; error?: any };\n\nexport type InputAndMap<T, I> = {\n input: I;\n map?: (input: Exclude<I, null | undefined>) => T;\n ifUndefined?: T;\n onlyOnce?: boolean;\n};\n\nexport type QueryAndMap<T, I> = {\n query: Query<I>;\n map: (input: Exclude<I, null | undefined>) => T;\n ifUndefined?: T;\n};\n\n/**\n * The opts has for `useFormState`.\n *\n * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*\n * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query\n */\nexport type UseFormStateOpts<T, I> = {\n /** The form configuration, should be a module-level const or useMemo'd. */\n config: ObjectConfig<T>;\n\n /**\n * Provides the form's initial value.\n *\n * User's can either:\n *\n * - Provide no initial value (don't set `init` at all)\n * - Provide an initial value that already matches the form type `T` (i.e. set `init: data`)\n * - Provide an initial value from an object that _almost_ matches the form type `T`,\n * but needs to be mapped from it's input type `I` to the form type\n * (i.e. set `init: { input: data, map: (data) => ...}`).\n *\n * The value of using the 3rd option is that: a) we internally `useMemo` on the identity of the\n * `init.input` (i.e. a response from an Apollo hook) and don't require the `map` function\n * to have a stable identity, and also b) we will null-check/undefined-check `init.input` and\n * only call `init.map` if it's set, otherwise we'll use `init.ifDefined` or `{}`, saving you\n * from having to null check within your `init.map` function.\n */\n init?: InputAndMap<T, I> | QueryAndMap<T, I>;\n\n /**\n * A hook to add custom, cross-field validation rules that can be difficult to setup directly in the config DSL.\n *\n * Does not need to be stable/useMemo'd.\n */\n addRules?: (state: ObjectState<T>) => void;\n\n /** Whether the form should be read only, when changed it won't re-create the whole form. */\n readOnly?: boolean;\n\n /**\n * Whether the form is loading.\n *\n * Note that we also will infer loading from `init.input === undefined` or `init.query.loading`,\n * so you only need to set this directly if you're not using either of those conventions.\n */\n loading?: boolean;\n\n /**\n * Fired when the form should auto-save, i.e. after a) blur and b) all fields are valid.\n *\n * Does not need to be stable/useMemo'd.\n */\n autoSave?: (state: ObjectState<T>) => Promise<void>;\n};\n\nexport type FormObjectState<T, I> = ObjectState<T> & {\n /**\n * Refreshes the form with a server-acked value, i.e. `form.update(savedAuthor)`.\n *\n * This is intended for save callbacks that need `form.dirty` to become `false` immediately, before the\n * normal query/cache refresh has a chance to rerender `useFormState`. Pass the value returned by the\n * server so the form can map it through `init.map` and then do a field-by-field refresh of only the\n * changes the server has acknowledged.\n */\n update(input: Exclude<I, null | undefined>): void;\n};\n\n/**\n * Creates a formState instance for editing in a form.\n */\nexport function useFormState<T, I>(opts: UseFormStateOpts<T, I>): FormObjectState<T, I> {\n const { config, init, addRules, readOnly = false, loading, autoSave } = opts;\n\n // Use a ref so our memo'ized `onBlur` always see the latest value\n const autoSaveRef = useRef<((state: ObjectState<T>) => void) | undefined>(autoSave);\n autoSaveRef.current = autoSave;\n // Use a ref so our memo'ized `update` always sees the latest init/map\n const initRef = useRef<UseFormStateOpts<T, I>[\"init\"]>(init);\n initRef.current = init;\n\n const firstRunRef = useRef<boolean>(true);\n // This is a little weird, but we need to know ahead of time, before the form useMemo, if we're working with classes/mobx proxies\n const [firstInitValue] = useState(() => initValue(config, init));\n const isWrappingMobxProxy = !isPlainObject(firstInitValue);\n // If they're using init.input, useMemo on it (and it might be an array), otherwise allow the identity of init be unstable\n const dep = isInput(init)\n ? init.onlyOnce\n ? []\n : makeArray(init.input)\n : isQuery(init)\n ? [init.query.data, init.query.loading]\n : [];\n\n const state = useMemo(\n () => {\n // If the user's autoSave hook makes some last-minute `.set` calls to sneak\n // in some business logic right before their GraphQL mutation call, ignore it\n // so that we don't infinite loop.\n //\n // This is per-form (not module-level) so that one form's in-flight save does\n // not swallow the auto-save of an unrelated form on the same page.\n let isAutoSaving: \"queued\" | \"in-flight\" | false = false;\n\n // `pendingAutoSave` is a flag for determining if we need to immediately call `maybeAutoSave` again after the initial Promise finishes\n // This could happen if a field triggers auto-save while another field's auto-save is already in progress.\n let pendingAutoSave = false;\n\n function maybeAutoSave() {\n // When not subscribed (i.e. before or after useEffect setup/cleanup), we must not initiate\n // any auto save requests. So mark the save as pending so a later setup (i.e. StrictMode\n // replay or actual useEffect reconnection) can retry it.\n if (!subscriptionsActive(form)) {\n pendingAutoSave = true;\n return;\n }\n if (isAutoSaving === \"in-flight\") {\n pendingAutoSave = true;\n } else if (isAutoSaving === \"queued\") {\n // If we've queued up an auto-save, that means we haven't called the `autoSave(...)` function yet, so\n // we assume that the scheduled-but-not-yet-invoked invocation will find both it's original change, and\n // also this potentially new change, and put them both onto the wire as a single operation.\n // So we can just ignore this call.\n return;\n }\n\n // Don't use canSave() because we don't want to set touched for all the fields\n if (autoSaveRef.current && form.dirty && !isAutoSaving) {\n // It's very frustrating to not know why the form is savings, to go ahead and log these\n if (!form.valid) {\n console.debug(\"Skipping auto-save b/c form is invalid: \", form.errors);\n return;\n }\n isAutoSaving = \"queued\";\n // We use setTimeout as a cheap way to wait until the end of the current event listener\n setTimeout(async () => {\n try {\n // Cleanup disconnects reactions, but a timer can already be queued.\n if (!subscriptionsActive(form) || !autoSaveRef.current) {\n pendingAutoSave = true;\n return;\n }\n // Tell commitChanges to blow up.\n (form as any)._isAutoSaving = true;\n // We technically don't flip to in-flight until after the call in case the\n // user's autoSave function itself wants to call a .set (which would call `maybeAutoSave`,\n // and we don't want it to see `isAutoSaving=in-flight` and think it \"missed the boat\",\n // and so schedule an unnecessary follow-up autosave.)\n const promise = autoSaveRef.current!(form);\n isAutoSaving = \"in-flight\";\n await promise;\n } finally {\n isAutoSaving = false;\n (form as any)._isAutoSaving = false;\n if (pendingAutoSave && subscriptionsActive(form)) {\n pendingAutoSave = false;\n // Push out the follow-up by 1 tick to allow refreshes to happen to potentially\n // un-dirty the just-saved data (e.g. if we run right away, the caller's maybeAutoSave\n // will see a form.changedValue that thinks the just-saved data is still dirty).\n setTimeout(maybeAutoSave, 0);\n }\n }\n }, 0);\n }\n }\n const value = firstRunRef.current ? firstInitValue : initValue(config, init);\n // Use createInactiveObjectState to defer subscribing the form to any underlying mobx stores\n // until our useEffect subscriptions actually run/are committed, i.e. in case React abandons\n // this render, when we'd not want to leak/leave behind prematurely-connected subscriptions.\n const form = createInactiveObjectState(config, value, { maybeAutoSave }) as FormObjectState<T, I>;\n form.update = function update(input) {\n const init = initRef.current;\n const updateInit = isInput(init)\n ? { ...init, input }\n : isQuery(init)\n ? { input, map: init.map, ifUndefined: init.ifUndefined }\n : { input };\n (form as unknown as ObjectStateInternal<T>).set(initValue(config, updateInit), {\n refreshing: true,\n });\n };\n form.readOnly = readOnly;\n setLoading(form, opts);\n // The identity of `addRules` is not stable, but assume that it is for better UX.\n (addRules || (() => {}))(form);\n return {\n form,\n lastInitDeps: dep,\n resumeAutoSave() {\n if (pendingAutoSave) {\n pendingAutoSave = false;\n maybeAutoSave();\n }\n },\n };\n },\n // For this useMemo, we (almost) never re-run so that we can have a stable `form` identity across query refreshes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [config, ...(isWrappingMobxProxy ? dep : [])],\n );\n const { form } = state;\n\n // StrictMode runs setup, cleanup, then setup again for the same form. Cleanup disconnects its\n // store reactions; setup reconnects them and resumes any saves that were paused.\n useEffect(() => {\n activateSubscriptions(state.form);\n state.resumeAutoSave();\n return () => deactivateSubscriptions(state.form);\n }, [state]);\n\n // We use useEffect so that any mutations to the proxies, which will call `setState`s on any observers to\n // queue their components' render), don't happen during our render, per https://fb.me/setstate-in-render.\n useEffect(() => {\n firstRunRef.current = false;\n // Construction already applied these inputs. Effect replay/reconnection is not a server refresh:\n // applying the same mutable input again would mistake unsaved store edits for acknowledgements.\n // For wrapped class instances, useMemo rebuilds the form so fields point to the new instance.\n const sameInit =\n dep.length === state.lastInitDeps.length && dep.every((value, i) => Object.is(value, state.lastInitDeps[i]));\n if (isWrappingMobxProxy || sameInit) return;\n state.lastInitDeps = dep;\n setLoading(form, opts);\n // Note: maybe someday we want to watch the `id` value and notice if it's changing (i.e. 2 -> 3),\n // and treat this more as a reset than a refresh, b/c the id changing means it's probably\n // not \"the cache refreshed after a mutation save\" but \"the cache changed b/c of changing\n // rows in the table that our side panel's form has open/is focused on\" (which ideally would\n // be treated as a full component remount by having an `key` field somewhere in the parent\n // component, but it's unlikely the user will always remember to do this).\n (form as any).set(initValue(config, init), { refreshing: true });\n // Only re-run when the `init` values change; `config` and `opts` are treated as stable, like in the useMemo above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [state, ...dep]);\n\n // Use useEffect so that we don't touch the form.init proxy during a render\n useEffect(() => {\n form.readOnly = readOnly;\n if (loading !== undefined) {\n form.loading = loading;\n }\n }, [form, readOnly, loading]);\n\n return form;\n}\n\nfunction setLoading(form: ObjectState<any>, opts: UseFormStateOpts<any, any>) {\n const { loading, init } = opts;\n if (loading !== undefined) {\n // Prefer the explicit/top-level opts.loading if it's set\n form.loading = loading;\n } else if (isInput(init) && !init.ifUndefined) {\n // Otherwise, check for `init.input`\n form.loading = init.input === undefined;\n } else if (isQuery(init)) {\n // Or `query.loading`\n form.loading = init.query.loading;\n }\n}\n\nfunction makeArray(input: any): any[] {\n return Array.isArray(input) ? input : [input];\n}\n","import { isPlainObject } from \"is-plain-object\";\nimport { isObservable, toJS } from \"mobx\";\nimport {\n type FragmentFieldConfig,\n type ListFieldConfig,\n type ObjectConfig,\n type ObjectFieldConfig,\n type ValueFieldConfig,\n} from \"src/config\";\nimport { deepEquals, hasToJSON } from \"src/fields/deepEquals\";\nimport { areSupportedTemporalValuesEqual, type SupportedTemporal } from \"src/temporal\";\nimport { type InputAndMap, type QueryAndMap, type UseFormStateOpts } from \"src/useFormState\";\n\nexport type Builtin = Date | Function | SupportedTemporal | Uint8Array | string | number | boolean;\n\nexport type Primitive = undefined | null | boolean | string | number | Function | Date | { toJSON(): any };\n\n/** Makes the keys in `T` required while keeping the values undefined. */\nexport type DeepRequired<T> = T extends Primitive\n ? T\n : {\n [P in keyof Required<T>]: T[P] extends Array<infer U>\n ? Array<DeepRequired<U>>\n : T[P] extends ReadonlyArray<infer U2>\n ? ReadonlyArray<DeepRequired<U2>>\n : DeepRequired<T[P]>;\n };\n\n// Inverse of SubType: https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c\nexport type OmitIf<Base, Condition> = Pick<\n Base,\n {\n [Key in keyof Base]: Base[Key] extends Condition ? never : Key;\n }[keyof Base]\n>;\n\nexport function fail(message?: string): never {\n throw new Error(message || \"Failed\");\n}\n\nexport function assertNever(x: never): never {\n throw new Error(\"Unexpected object: \" + x);\n}\n\n/** Introspects the `init` prop to see if it has a `map` function/etc. and returns the form value. */\nexport function initValue<T>(config: ObjectConfig<T>, init: any): T {\n let value: any;\n if (isInput(init)) {\n value = init.input ? (init.map ? init.map(init.input) : init.input) : init.ifUndefined;\n } else if (isQuery(init)) {\n value = init.query.data ? init.map(init.query.data) : init.ifUndefined;\n } else if (init === undefined) {\n // allow completely undefined init\n } else {\n throw new Error(\"init must have an input or query key\");\n }\n // Given our form config, pick out only the subset of fields out of `value` (unless it's a mobx class)\n return pickFields(config, value ?? {}) as T;\n}\n\nexport function isInput<T, I>(init: UseFormStateOpts<T, I>[\"init\"]): init is InputAndMap<T, I> {\n return !!init && typeof init === \"object\" && \"input\" in init;\n}\n\nexport function isQuery<T, I>(init: UseFormStateOpts<T, I>[\"init\"]): init is QueryAndMap<T, I> {\n return !!init && typeof init === \"object\" && \"query\" in init && \"map\" in init;\n}\n\n/**\n * Picks ony fields out of `instance` that are in the `formConfig`.\n *\n * This is useful for creating a form input from a GraphQL query result,\n * but ignoring extra data in the query that we don't want/need in the form.\n *\n * (Especially because the form.value will become our mutation's input type\n * that goes on the wire, we don't want superfluous data in it.)\n */\nexport function pickFields<T, I>(\n formConfig: ObjectConfig<T>,\n instance: I,\n): { [K in keyof T]: K extends keyof I ? I[K] : never } {\n // If the caller is using classes, i.e. with their own custom observable behavior, then just use as-is\n if (!isPlainObject(instance)) {\n return instance as any;\n }\n return Object.fromEntries(\n Object.entries(formConfig).map(([key, _keyConfig]) => {\n const keyConfig = _keyConfig as any as\n ObjectFieldConfig<any> | ListFieldConfig<any> | ValueFieldConfig<any> | FragmentFieldConfig;\n const value = (instance as any)[key];\n if (keyConfig.type === \"object\") {\n if (value) {\n return [key, pickFields(keyConfig.config, value)];\n } else {\n return [key, value];\n }\n } else if (keyConfig.type === \"list\") {\n if (isObservable(value)) {\n // If we hit an observable array, leave it as the existing proxy so the our\n // ListFieldState will react to changes in the original array.\n return [key, value];\n } else if (value) {\n return [key, (value as any[]).map((u) => pickFields(keyConfig.config, u))];\n } else {\n return [key, value];\n }\n } else if (keyConfig.type === \"value\") {\n return [key, value];\n } else if (keyConfig.type === \"fragment\") {\n return [key, value];\n } else {\n return assertNever(keyConfig);\n }\n }),\n ) as any;\n}\n\nexport function isNotUndefined<T>(value: T | undefined): value is T {\n return value !== undefined;\n}\n\nexport function isEmpty(value: any): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n/**\n * An equals that does deep-ish equality.\n *\n * We only do non-identity equals for:\n *\n * - \"plain\" objects that have no custom prototype/i.e. are object literals\n * - objects that implement `toJSON`\n * - arrays\n */\nexport function areEqual<T>(a?: T, b?: T, strictOrder?: boolean): boolean {\n const temporalEquals = areSupportedTemporalValuesEqual(a, b);\n if (temporalEquals !== undefined) {\n return temporalEquals;\n }\n if (isPlainObject(a)) {\n return deepEquals(toJS(a), toJS(b));\n }\n if (hasToJSON(a) || hasToJSON(b)) {\n const a1 = hasToJSON(a) ? a.toJSON() : a;\n const b1 = hasToJSON(b) ? b.toJSON() : b;\n return deepEquals(a1, b1);\n }\n if (a && b && a instanceof Array && b instanceof Array) {\n if (strictOrder !== false) {\n return deepEquals(a, b);\n }\n if (a.length !== b.length) return false;\n return a.every((a1) => b.some((b1) => areEqual(a1, b1)));\n }\n return a === b;\n}\n\n/** Normalizes values into stable object-hash-safe data using our existing `toJSON` semantics. */\nexport function normalizeHashValue(value: unknown, seen: WeakMap<object, unknown> = new WeakMap()): unknown {\n if (hasToJSON(value)) {\n return normalizeHashValue(value.toJSON(), seen);\n }\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n return seen.get(value);\n }\n const result: unknown[] = [];\n seen.set(value, result);\n value.forEach((entry) => result.push(normalizeHashValue(entry, seen)));\n return result;\n }\n if (value && typeof value === \"object\" && isPlainObject(value)) {\n if (seen.has(value)) {\n return seen.get(value);\n }\n const result: Record<string, unknown> = {};\n seen.set(value, result);\n Object.entries(value as Record<string, unknown>).forEach(([key, entry]) => {\n result[key] = normalizeHashValue(entry, seen);\n });\n return result;\n }\n return value;\n}\n\n/** Make a clone of `obj`, but only recurse into POJOs and Arrays...and stores. */\nexport function deepClone<T>(obj: T, map = new WeakMap()): T {\n if (obj && typeof obj === \"object\" && (isPlainObject(obj) || Array.isArray(obj) || isObservable(obj))) {\n if (map.has(obj)) return map.get(obj);\n const result = Array.isArray(obj) ? [] : {};\n map.set(obj, result);\n Object.assign(result, ...getAllPropertyNames(obj).map((key) => ({ [key]: deepClone((obj as any)[key], map) })));\n return result as T;\n } else {\n return obj;\n }\n}\n\nexport function groupBy<T, Y = T, K = string>(list: readonly T[], fn: (x: T) => K, valueFn?: (x: T) => Y): Map<K, Y[]> {\n const result = new Map<K, Y[]>();\n list.forEach((o) => {\n const group = fn(o);\n if (!result.has(group)) {\n result.set(group, []);\n }\n result.get(group)!.push(valueFn === undefined ? (o as any as Y) : valueFn(o));\n });\n return result;\n}\n\n/** Returns all property names, including mobx computeds (non-enumerable) & inherited. */\nfunction getAllPropertyNames(obj: unknown): string[] {\n const proto = Object.getPrototypeOf(obj);\n // Don't crawl up into Object.prototype, or into arrays/observable arrays\n const inherited = proto && proto !== Object.prototype && !Array.isArray(obj) ? getAllPropertyNames(proto) : [];\n return [...new Set(Object.getOwnPropertyNames(obj).concat(inherited))];\n}\n","import { isPlainObject } from \"is-plain-object\";\nimport { createAtom, isObservable, reaction, toJS } from \"mobx\";\nimport { type ObjectState } from \"src/fields/objectField\";\nimport { deactivateSubscriptions, registerSubscriptions } from \"src/fields/subscriptions\";\nimport { newDelegateProxy } from \"src/proxies\";\nimport { type Rule, required } from \"src/rules\";\nimport { areEqual, isEmpty, isNotUndefined } from \"src/utils\";\n\n/**\n * Form state for a primitive field in the form, i.e. its value but also touched/validation/etc. state.\n *\n * This API also provides hooks for form elements to call into, i.e. `blur()` and `set(...)` that will\n * update the field state and re-render, i.e. when including in an `ObjectState`-typed literal that is\n * an mobx `useLocalObservable`/observable.\n *\n * Note that `V` will always have `null | undefined` added to it by `FieldStates`, b/c most form fields\n * i.e. text boxes, can always be cleared out/deleted.\n */\nexport interface FieldState<V> {\n /** The key in the parent object, i.e. `firstName` in `author: { firstName: string }`. */\n readonly key: string;\n /** The current value of the field. */\n value: V;\n readonly originalValue: V;\n touched: boolean;\n readOnly: boolean;\n loading: boolean;\n readonly required: boolean;\n readonly dirty: boolean;\n readonly valid: boolean;\n readonly focused: boolean;\n readonly isNewEntity: boolean;\n rules: Rule<V>[];\n readonly errors: string[];\n /**\n * Returns a subset of V with only the changed values.\n *\n * Reading this also records that the value is probably being put on the wire, so that a later\n * `refreshing` set can tell a server ack of this value apart from a stale cache refresh.\n */\n readonly changedValue: V;\n /** Focuses the field. Disables changes from `ObjectState.set` calls. */\n focus(): void;\n /** Blur marks the field as touched and triggers an auto-save. */\n blur(): void;\n /** Triggers an auto-save; the caller (i.e. useFormState) should still dirty & valid check. */\n maybeAutoSave(): void;\n set(value: V, opts?: SetOpts): void;\n /** Reverts back to the original value and resets dirty/touched. */\n revertChanges(): void;\n /**\n * Accepts the current changed value (if any) as the original and resets dirty/touched.\n *\n * @deprecated Use `formState.update(ackedServerValue)` instead. `commitChanges` marks all current\n * changes as saved, which risks discarding a user's in-flight edits. Updating from the server ack gives\n * form-state a field-by-field audit of which changes were successfully saved.\n */\n commitChanges(): void;\n /** Creates a new FieldState with a transformation of the value, i.e. string to int, or feet to inches. */\n adapt<V2>(adapter: ValueAdapter<V, V2>): FieldState<V2>;\n /**\n * Releases any mobx reactions this field created.\n *\n * This only matters when wrapping a mobx observable (i.e. a store or class instance): each field\n * subscribes to its slot on the store so that external mutations can trigger auto-save, and that\n * subscription keeps the field alive until it is disposed. Plain objects create no reactions.\n */\n dispose(): void;\n}\n\n/**\n * Allows changing a type in the formState (like a string) to a different type in the UI (like a number).\n *\n * Or doing unit of measure conversions within the same type, like from meters to feet.\n */\nexport interface ValueAdapter<V, V2 = V> {\n /** Converts the original FieldState's value `V` into new `V2` type. */\n toValue(value: V): V2;\n /** Converts the adapted FieldState's value `V2` back into the original `V` type. */\n fromValue(value: V2): V;\n}\n\n/** Public options for our `set` command. */\nexport interface SetOpts {\n /** Whether this `set` should trigger an auto-save; defaults to true. */\n autoSave?: boolean;\n}\n\n/** Internal `.set` opts for conditionally that form-state internally percolates. */\nexport interface InternalSetOpts extends SetOpts {\n /** `resetting` is the code calling `.reset()` to revert to original values. */\n resetting?: boolean;\n /** `refreshing` is when `useFormState` sees a new value. */\n refreshing?: boolean;\n}\n\nexport interface FieldStateInternal<T, V> extends FieldState<V> {\n set(value: V, opts?: InternalSetOpts): void;\n _isIdKey: boolean;\n _isDeleteKey: boolean;\n _isReadOnlyKey: boolean;\n _isLocalOnly: boolean;\n}\n\nexport function newValueFieldState<T, K extends keyof T>(\n parentCopy: T,\n parentInstance: T,\n parentState: () => ObjectState<T>,\n key: K,\n rules: Rule<T[K] | null | undefined>[],\n isIdKey: boolean,\n isDeleteKey: boolean,\n isReadOnlyKey: boolean,\n isLocalOnly: boolean,\n computed: boolean,\n readOnly: boolean,\n strictOrder: boolean,\n maybeAutoSave: () => void,\n): FieldState<T[K] | null | undefined> {\n type V = T[K];\n\n // Because we read/write the value directly back into parentInstance[key] (and the\n // original into parentCopy[key]), which are not proxies, we use atoms as our\n // \"value changed\" triggers: getters call `reportObserved`, writes call `reportChanged`.\n const valueAtom = createAtom(`${String(key)}.value`);\n const originalValueAtom = createAtom(`${String(key)}.originalValue`);\n // Track \"this is probably what we put into a mutation to the server\" to allow\n // us to better accept server acks/responses that change our initial submission.\n //\n // I.e. we submit `firstName: bob` and the server acks `firstName: Bob`, we should\n // accept it if we can tell we haven't changed `bob` since our initial submission.\n let hasInflightChangedValue = false;\n let lastSubmittedValue: V | undefined = undefined;\n // Set below if we're wrapping a mobx observable, see `dispose`\n let disposeStoreReaction: (() => void) | undefined = undefined;\n // Remember what the store contained before subscribing, including across effect reconnects.\n let lastObservedValue = parentInstance[key];\n\n const field = {\n key: key as string,\n\n touched: false,\n\n /** Current readOnly value. */\n _readOnly: readOnly || false,\n _loading: false,\n _kind: \"value\",\n // Expose so computed can be skipped in changedValue\n _computed: computed,\n _focused: false,\n\n _isIdKey: isIdKey,\n _isDeleteKey: isDeleteKey,\n _isReadOnlyKey: isReadOnlyKey,\n _isLocalOnly: isLocalOnly,\n\n rules,\n\n get value(): V {\n // If we're wrapping a mobx store, then we'll also get reactivity from parentInstance[key]\n valueAtom.reportObserved();\n const value = parentInstance[key];\n // Re-create the `keepNull` logic on sets but for our initial read where our\n // originalValue is null (empty) but we want to expose it as undefined for\n // consistency of \"empty-ness\" to our UI components.\n return value === null && isEmpty(parentCopy[key]) ? (undefined as any) : value;\n },\n\n set value(v: V) {\n this.set(v);\n },\n\n get focused(): boolean {\n return this._focused;\n },\n\n get dirty(): boolean {\n return !areEqual(this.originalValue, this.value, strictOrder);\n },\n\n /** Returns whether this field is readOnly, although if our parent is readOnly then it trumps. */\n get readOnly(): boolean {\n return parentState().readOnly || this._readOnly;\n },\n\n /** Sets the field readOnly, but `loading` will still be or-d with the parent's readOnly state. */\n set readOnly(readOnly: boolean) {\n this._readOnly = readOnly;\n },\n\n /** Returns whether this field is loading, or if our parent is loading. */\n get loading(): boolean {\n return parentState().loading || this._loading;\n },\n\n /** Sets the field loading, but `loading` will still be or-d with the parent's loading state. */\n set loading(loading: boolean) {\n this._loading = loading;\n },\n\n // For primitive fields, the changed value is just the value.\n get changedValue() {\n // Add some hints to let `set(..., { refreshing: true })` accept server-driven changes to our value,\n // if the user hasn't changed it yet-again while the changedValue was in in-flight.\n //\n // Note this is a side effect inside a mobx computed; it stays correct because every `set` calls\n // `valueAtom.reportChanged()`, which invalidates this computed, so the next read re-records the hints.\n hasInflightChangedValue = true;\n lastSubmittedValue = this.value;\n // Usually if we see a field being unset, we set `parent[key] = null`, but if we're wrapping\n // a mobx observable object, it might have changed to `undefined` without us being able to\n // tell it to be `null` (and potentially break the type contract of `string | undefined`).\n // So detect un-set-ness here and return `null`.\n if (this.value === undefined && this.originalValue !== undefined) return null;\n return this.value;\n },\n\n get valid(): boolean {\n return this.errors.length === 0;\n },\n\n get errors(): string[] {\n const opts = { value: this.value, key: key as string, originalValue: this.originalValue, object: parentState() };\n return this.rules.map((r) => r(opts as any)).filter(isNotUndefined);\n },\n\n get required(): boolean {\n return this.rules.some((rule) => rule === required);\n },\n\n get isNewEntity(): boolean {\n return parentState().isNewEntity;\n },\n\n focus() {\n this._focused = true;\n },\n\n blur() {\n this.maybeAutoSave();\n this._focused = false;\n },\n\n maybeAutoSave() {\n // Now that the user is done editing the field (note maybe w/o blurring, i.e. if they hit enter), we sneak in some trim logic\n this.maybeTrim();\n\n // touched is readonly, but we're allowed to change it\n this.touched = true;\n maybeAutoSave();\n },\n\n set(value: V | null | undefined, opts: InternalSetOpts = {}) {\n if (this.readOnly && !opts.resetting && !opts.refreshing) {\n throw new Error(`${String(key)} is currently readOnly`);\n }\n\n // If the user has deleted/emptied a value that was originally set, keep it as `null`\n // so that our partial update to the backend correctly unsets it.\n const keepNull = !isEmpty(this.originalValue) && isEmpty(value) && !opts.refreshing;\n // If a list of primitives was originally undefined, coerce `[]` to `undefined`\n const coerceEmptyList = value && value instanceof Array && value.length === 0 && isEmpty(this.originalValue);\n const newValue = keepNull ? null : isEmpty(value) || coerceEmptyList ? undefined : value;\n\n if (opts.refreshing && (this.dirty || hasInflightChangedValue)) {\n // When accepting (refreshing) a new value from the server (i.e. an ack after our mutation\n // _or_ just a cache/query refresh, we can't be sure which), make sure we don't drop any\n // WIP changes the user made after the initial submit.\n //\n // - Initially, originalValue is name=Bob.\n // - The user submits name=Fred, recorded as lastSubmittedValue.\n // - The user edits back to name=Bob, making the field temporarily clean.\n // - Before the acknowledgement, a cache refresh happens with `newValue=Bob`.\n // - Without this early return, we'd accept `value=Bob` and clear hasInflightChangedValue.\n // - When the Fred ack arrives, the field is clean with no inflight flag, so we'd overwrite the\n // user's newer Bob edit with Fred, instead of preserving Bob.\n if (\n hasInflightChangedValue &&\n // Reading changedValue sets the inflight flag even for a clean field, so lastSubmittedValue\n // can still equal originalValue. Only preserve the flag here if the submission differs from originalValue.\n !areEqual(lastSubmittedValue, this.originalValue, strictOrder) &&\n areEqual(newValue, this.originalValue, strictOrder)\n )\n return;\n // See if we should ignore the incoming server-side value, to avoid dropping local WIP changes\n const isAckingUnset = this.value === null && newValue === undefined;\n const acceptServerAck =\n hasInflightChangedValue &&\n this.value === lastSubmittedValue &&\n // If value === originalValue, this is likely a cache refresh firing in-between \"we put the new value\n // on the wire\" and \"the server acked our change\". Granted, this heuristic means that if the server\n // really does reject/rollback our change, we'll ignore it, but atm we don't have a way of differentiating\n // \"this refresh is from a pre-response cache refresh\" vs. \"post-response cache refresh\".\n !areEqual(newValue, this.originalValue, strictOrder);\n // Ignore incoming values if we have changes (this.dirty) unless:\n // - our latest change (this.value) matches the incoming value (value), i.e. the server\n // is exactly acking our change, or\n // - the user hasn't made any changes since `.changedValue` was put on the wire, i.e. the\n // server received our value, but wanted to change it.\n // Compare by content (not `!==`) so array/list-of-primitive values are treated as acked when\n // the server returns the same contents in a new array instance (otherwise the field would\n // stay dirty forever, since reference equality never holds for a fresh array).\n const keepLocalWipChange = !areEqual(this.value, newValue, strictOrder) && !isAckingUnset && !acceptServerAck;\n if (keepLocalWipChange) {\n if (hasInflightChangedValue && !areEqual(newValue, this.originalValue, strictOrder)) {\n // Besides keeping the WIP change, update originalValue to the acked value (we only get here\n // if the current value no longer matches lastSubmittedValue and the server value differs\n // from originalValue).\n // - Initially, originalValue is name=Bob.\n // - The user submits name=Fred, then edits back to name=Bob.\n // - The Fred ack arrives with `newValue=Fred`.\n // - Keep `value=Bob`, but set `originalValue=Fred` and clear the inflight flag.\n // - Bob is now dirty against Fred and needs a follow-up autosave.\n this.originalValue = newValue as V;\n hasInflightChangedValue = false;\n // Bob matched the old originalValue, so its edit may not have queued a save.\n // The previous save may also have settled before this ack. Schedule a save now, or wait for blur.\n if (!this._focused) maybeAutoSave();\n }\n return;\n }\n } else if (computed && (opts.resetting || opts.refreshing)) {\n // Computeds can't be either reset or refreshed\n return;\n }\n\n if (opts.refreshing || opts.resetting) hasInflightChangedValue = false;\n\n // Set the value on our parent object\n const changed = !areEqual(newValue, this.value, strictOrder);\n parentInstance[key] = newValue!;\n valueAtom.reportChanged();\n\n if (opts.refreshing) {\n this.originalValue = newValue as any;\n }\n // If we're being set programmatically, i.e. we don't currently have focus,\n // call blur to trigger any auto-saves.\n if (!this._focused && !opts.refreshing && !opts.resetting && this.dirty && changed && opts.autoSave !== false) {\n this.maybeAutoSave();\n }\n },\n\n adapt<V2>(adapter: ValueAdapter<V, V2>): FieldState<V2> {\n return adapt(this, adapter);\n },\n\n revertChanges() {\n if (!computed) {\n this.set(this.originalValue, { resetting: true });\n }\n this.touched = false;\n },\n\n commitChanges() {\n if (isPlainObject(this.originalValue)) {\n this.originalValue = toJS(this.value);\n } else {\n this.originalValue = this.value;\n }\n this.touched = false;\n },\n\n dispose() {\n deactivateSubscriptions(field);\n },\n\n get originalValue(): V {\n originalValueAtom.reportObserved();\n const value = parentCopy[key];\n // Re-create the `keepNull` logic so that `.value` === `.originalValue`\n return value === null ? (undefined as any) : value;\n },\n\n set originalValue(v: V) {\n const canSkip = v === undefined && !(key in (parentCopy as any));\n if (!canSkip) parentCopy[key] = v;\n originalValueAtom.reportChanged();\n },\n\n maybeTrim() {\n if (typeof this.value === \"string\") {\n let newValue: string | undefined = this.value.trim();\n if (newValue === \"\") {\n newValue = undefined;\n }\n if (newValue !== this.value) {\n this.set(newValue as any);\n }\n }\n },\n };\n\n // If we're wrapping a mobx observer, watching for external mutations, i.e. from callers not\n // going through our FieldState.set/FieldState.value setters.\n if (isObservable(parentInstance)) {\n registerSubscriptions(field, {\n activate() {\n disposeStoreReaction = reaction(\n () => parentInstance[key],\n (value) => {\n lastObservedValue = value;\n saveExternalChange();\n },\n );\n\n // A layout effect or another component can edit the store between render and subscription.\n // Read again after subscribing so that change is saved without resetting the form's baseline.\n const value = parentInstance[key];\n const changed = !Object.is(lastObservedValue, value);\n lastObservedValue = value;\n if (changed) saveExternalChange();\n },\n deactivate() {\n disposeStoreReaction?.();\n disposeStoreReaction = undefined;\n },\n });\n }\n return field as any;\n\n /** Saves external mutations unless a focused field is still accumulating edits. */\n function saveExternalChange() {\n // Don't auto-save on maybe-external mutation if our field, or another other field (potentially a computed),\n // is currently focused, because then it's probably just us doing the writes through `Bound...Field` components.\n if (!parentState().focused) maybeAutoSave();\n }\n}\n\n/**\n * Returns a proxy that looks exactly like the original `field`, in terms of valid/touched/errors/etc., but\n * has any methods that use `V` overridden to use be `V2`.\n *\n * Note that `V2` can be a new type, like string -> number, or just a transformation on the same\n * type, i.e. feet -> inches where both are `number`s.\n */\nfunction adapt<V, V2>(field: FieldState<V>, adapter: ValueAdapter<V, V2>): FieldState<V2> {\n return newDelegateProxy(field, {\n rules: [],\n get value(): V2 {\n return adapter.toValue(field.value);\n },\n set value(v: V2) {\n field.value = adapter.fromValue(v);\n },\n set: (v: V2) => {\n field.value = adapter.fromValue(v);\n },\n get changedValue(): V2 {\n return this.value;\n },\n adapt<V3>(adapter: ValueAdapter<V2, V3>) {\n return adapt(this, adapter);\n },\n get originalValue(): V2 {\n return adapter.toValue(field.originalValue);\n },\n });\n}\n","import { createAtom, makeAutoObservable } from \"mobx\";\nimport { type FieldStateInternal } from \"src/fields/valueField\";\n\nexport interface FragmentField<V> {\n value: V;\n}\n\nexport function newFragmentField<T extends object, K extends keyof T & string>(\n parentInstance: T,\n key: K,\n): FragmentField<T[K]> {\n // `value` lives in a closure variable, not an observable, so we use an atom\n // to make `get value()` observable and `set value()` notify observers.\n const valueAtom = createAtom(`${key}.value`);\n\n // We steal the fragment from our parent, so that it doesn't\n // accidentally end up on the wire\n let value = parentInstance[key];\n delete parentInstance[key];\n\n const obj = {\n key,\n _isIdKey: false,\n _isDeleteKey: false,\n _isReadOnlyKey: false,\n _isLocalOnly: false,\n touched: false,\n valid: true,\n readOnly: true,\n required: false,\n loading: false,\n dirty: false,\n focused: false,\n originalValue: undefined,\n changedValue: undefined,\n errors: [],\n rules: [],\n isNewEntity: false,\n focus: () => {},\n blur: () => {},\n maybeAutoSave: () => {},\n commitChanges: () => {},\n revertChanges: () => {},\n dispose: () => {},\n\n get value() {\n // Watch for our parentInstance changing\n if (key in parentInstance) {\n value = parentInstance[key];\n delete parentInstance[key];\n }\n valueAtom.reportObserved();\n return value;\n },\n\n set value(v: T[K]) {\n value = v;\n valueAtom.reportChanged();\n },\n\n set(value) {\n this.value = value;\n },\n\n adapt(value) {\n throw new Error(\"FragmentField does not support adapt\");\n },\n } satisfies FieldStateInternal<T, any>;\n\n return makeAutoObservable(obj, { value: false });\n}\n","import { computed, createAtom, makeAutoObservable } from \"mobx\";\nimport hash from \"object-hash\";\nimport { type ListFieldConfig, type ObjectFieldConfig } from \"src/config\";\nimport { type ObjectState, type ObjectStateInternal, newObjectState } from \"src/fields/objectField\";\nimport {\n activateSubscriptions,\n deactivateSubscriptions,\n registerSubscriptions,\n subscriptionsActive,\n} from \"src/fields/subscriptions\";\nimport { type FieldState, type InternalSetOpts } from \"src/fields/valueField\";\nimport { type Rule, required } from \"src/rules\";\nimport { groupBy, isNotUndefined, normalizeHashValue } from \"src/utils\";\n\n/** Form state for list of children, i.e. `U` is a `Book` in a form with a `books: Book[]`. */\nexport interface ListFieldState<U> extends FieldState<U[]> {\n readonly rows: ReadonlyArray<ObjectState<U>>;\n\n add(value: U, index?: number): void;\n\n remove(indexOrValue: number | U): void;\n}\n\nexport function newListFieldState<T, K extends keyof T, U>(\n // parentCopy is objectField's deepClone-d `originalCopy` that it/we use for `dirty` checking\n parentCopy: T,\n // parentInstance is objectField's `instance`, that we're currently mutating\n parentInstance: T,\n parentState: () => ObjectState<T>,\n key: K,\n rules: Rule<readonly ObjectState<U>[]>[],\n listConfig: ListFieldConfig<U>,\n config: ObjectFieldConfig<U>,\n strictOrder: boolean,\n maybeAutoSave: () => void,\n deepExhaustive: boolean,\n): ListFieldState<U> {\n // Keep a map of \"item in the parentInstance list\" -> \"that item's ObjectState\"\n const rowMap = new Map<U, ObjectStateInternal<U>>();\n const addedRows = new Set<U>();\n // `parentInstance[key]` and `parentCopy[key]` are not proxies, so we use atoms as our\n // \"rows added/removed\" and \"original value changed\" triggers.\n const valueAtom = createAtom(`${String(key)}.value`);\n const originalValueAtom = createAtom(`${String(key)}.originalValue`);\n\n // When child rows don't have ids (i.e. for new rows that aren't saved yet), we need an id-less\n // \"clone <-> current\" map to tell if we're dirty or not, i.e. whether `parentInstance[key]` has\n // drifted from `parentCopy[key]`\n const copyMap = new Map<U, U>();\n ((parentCopy[key] ?? []) as U[]).forEach((copy, i) => {\n copyMap.set(copy, (parentInstance[key] as any)[i]);\n });\n\n // Given a child POJO (or a copy/clone of a child POJO), return its ObjectState wrapper.\n function getOrCreateChildState(child: U, opts?: InternalSetOpts & { skipSet?: boolean }): ObjectState<U> {\n let childState = rowMap.get(child);\n // If we're being reverted to our originalValue, i.e. values is actually\n // a list of copies, use the copyMap to recover the non-copy original value\n if (!childState && copyMap.has(child)) {\n childState = rowMap.get(copyMap.get(child)!);\n }\n // Look for an existing child (requires having an id key configured)\n if (!childState) {\n for (const [, otherState] of rowMap.entries()) {\n if (otherState.isSameEntity(child)) {\n // If we're being called from `list.rows`, we should be careful to not trigger mutations\n if (!opts?.skipSet) otherState.set(child, opts);\n rowMap.set(child, otherState);\n return otherState;\n }\n }\n }\n // If we didn't have an existing child, just make a new object state\n if (!childState) {\n childState = newObjectState<U>(\n config,\n parentState as any,\n undefined,\n list as any as FieldState<any>,\n child,\n undefined,\n maybeAutoSave,\n // Note: this used to be `... ?? deepExhaustive`, but the left side is a boolean, so the\n // parent's `deepExhaustive` was never inherited here; this keeps that behavior.\n listConfig.update === \"deep-exhaustive\",\n ) as ObjectStateInternal<U>;\n rowMap.set(child, childState);\n if (subscriptionsActive(list)) activateSubscriptions(childState);\n }\n return childState;\n }\n\n const list = {\n key: key as string,\n\n // Our fundamental state of wrapped Us\n get value() {\n // Read each row's value so observers of our `value` re-run when a row deeply changes,\n // not just when rows are added/removed (which is what `valueAtom` tracks).\n this.rows.forEach((r) => r.value);\n valueAtom.reportObserved();\n return (parentInstance[key] ?? []) as any as U[];\n },\n\n _kind: \"list\",\n _focused: false,\n _readOnly: false,\n _loading: false,\n\n get readOnly(): boolean {\n return this._readOnly || parentState().readOnly;\n },\n\n set readOnly(readOnly: boolean) {\n this._readOnly = readOnly;\n },\n\n get loading(): boolean {\n return this._loading || parentState().loading;\n },\n\n set loading(loading: boolean) {\n this._loading = loading;\n },\n\n set value(v: U[]) {\n this.set(v);\n },\n\n get dirty(): boolean {\n return this.rows.some((r) => r.dirty) || this.hasChanged();\n },\n\n // Having a new entity is slightly different than being dirty, b/c merely having a placeholder-but-not-changed-yet\n // `isNewEntity` child should not fire autosave (which would happen if we're dirty).\n get hasNewEntity(): boolean {\n return this.rows.some((r) => r.isNewEntity);\n },\n\n get focused(): boolean {\n return this.rows.some((r) => r.focused);\n },\n\n get required(): boolean {\n return this.rules.some((rule) => rule === required);\n },\n\n get isNewEntity(): boolean {\n return parentState().isNewEntity;\n },\n\n // private\n /** Returns whether a row has been added/removed. */\n hasChanged(): boolean {\n const [current, original] = [this.value || [], this.originalValue];\n if (current.length !== original.length) return true;\n if (strictOrder) {\n // With strict order, every copy[i] === original[i] must be true\n return original.some((e, idx) => current[idx] !== copyMap.get(e));\n } else {\n // With loose order, we just want every copy to still have its original somewhere\n return !original.every((e) => current.includes(copyMap.get(e)!));\n }\n },\n\n // And we can derive each value's ObjectState wrapper as needed from the rowMap cache\n get rows(): readonly ObjectState<U>[] {\n // Re-run when rows are added/removed\n valueAtom.reportObserved();\n // Read `parentInstance[key]` directly (not `this.value`), because `value` reads `rows`\n const value = parentInstance[key] as any as U[];\n return (value || []).map((child) => getOrCreateChildState(child, { skipSet: true }));\n },\n\n // TODO Should this be true when all rows are touched?\n get touched() {\n return this.rows.some((r) => r.touched) || this.hasChanged();\n },\n\n set touched(touched: boolean) {\n this.rows.forEach((r) => (r.touched = touched));\n },\n\n rules,\n\n get valid(): boolean {\n const collectionValid = this.errors.length === 0;\n const entriesValid = this.rows.filter((r) => !(r as any)._considerDeleted()).every((r) => r.valid);\n return collectionValid && entriesValid;\n },\n\n /** Returns the errors from our own collection-level rules (not the rows' errors). */\n get errors(): string[] {\n valueAtom.reportObserved();\n // TODO Passing `originalCopy || []` is probably not 100% right\n const opts = { value: this.rows, key: key as string, originalValue: this.originalValue, object: parentState() };\n return this.rules.map((r) => r(opts as any)).filter(isNotUndefined);\n },\n\n get changedValue() {\n const result = [] as any;\n const hasOpKey = Object.keys(listConfig.config).includes(\"op\");\n const hasLegacyOpKey = Object.keys(listConfig.config).some((key) => key === \"delete\" || key === \"remove\");\n const incremental =\n listConfig.update === \"incremental\" ||\n // Implicitly enable incremental mode if we see an op key\n (listConfig.update === undefined && (hasOpKey || hasLegacyOpKey));\n const exhaustive = !incremental;\n this.rows.forEach((r) => {\n if (exhaustive || r.dirty || r.isNewEntity) {\n const changed = r.changedValue;\n // Ensure we have an `op: include` key, following https://joist-orm.io/docs/features/partial-update-apis\n if (incremental && hasOpKey) {\n (changed as any).op ??= \"include\";\n }\n result.push(changed);\n }\n });\n return result;\n },\n\n focus() {\n this._focused = true;\n },\n\n blur() {\n this._focused = false;\n this.maybeAutoSave();\n },\n\n maybeAutoSave() {\n this.touched = true;\n maybeAutoSave();\n },\n\n set(values: U[], opts: InternalSetOpts = {}) {\n if (this.readOnly && !opts.resetting && !opts.refreshing) {\n throw new Error(`${String(key)} is currently readOnly`);\n }\n // Given the values, see if we've got existing child states, and use their\n // value if so. I.e. this covers revertChanges doing `set(originalValue)` and\n // passing us cloned rows from the parentCopy, but `getOrCreateChildState` will\n // use the `copyMap` to recover the non-cloned rows, to avoid promoting the clone\n // into a real row.\n if ((this.dirty || this.hasNewEntity) && opts.refreshing) {\n // When refreshing a dirty list, we need to preserve WIP values\n\n // Start with current list, then merge in incoming changes\n //\n // These will all be POJOs, where `currentItems` existing in our `parentInstance`, and\n // `incomingItems` could really be any of:\n // - an instance from `parentInstance[key]` that user code is passing back in\n // - an instance from `parentCopy[key]` that a cache refresh is passing in\n // - a new instance, either from a cache refresh or user code, that we haven't seen yet\n const currentItems = this.value || [];\n const incomingItems = values || [];\n const mergedItems: U[] = [];\n\n // Index by idKey or a hash of the object, so we don't have to n^2 merging\n const idKey = this.rows.length > 0 && (this.rows[0] as any as ObjectStateInternal).idKey;\n // Look at our child config, i.e. `bookConfig` and find the non-id / non-list keys\n const contentKeys = Object.entries(config.config)\n .filter(([key, cfg]: any) => key !== idKey && cfg.type === \"value\")\n .map(([key]) => key);\n const hashByContent = (item: any) =>\n hashValue(\n normalizeHashValue(\n Object.fromEntries(Object.entries(item as object).filter(([key]) => contentKeys.includes(key))),\n ),\n );\n const hashById = (item: any) => {\n const idValue = idKey ? item[idKey] : undefined;\n return idValue === undefined ? hashByContent(item) : idValue;\n };\n const currentById = groupBy(currentItems, hashById);\n const incomingById = groupBy(incomingItems, hashById);\n // If our local instance doesn't assign in id, when we get results back from the server, strip\n // their id and then see if we're the same based on data. Note that this is a hueristic, and will\n // fail if the client-side has made additional changes to the child after submitting it to the server,\n // or if the server acks back more/different data than the client sent.\n //\n // Only do this if one of our client-side objects is missing an id\n const incomingByContent =\n idKey && currentItems.some((item) => !(item as any)[idKey]) && groupBy(incomingItems, hashByContent);\n\n // Watch for deletions\n const hasOpKey = Object.keys(listConfig.config).includes(\"op\");\n\n // First, process all current items\n for (const currentItem of currentItems) {\n const childState = rowMap.get(currentItem)!; // We'll always have a child state for `currentItems`\n // Try to find a matching item in the incoming data\n const hash = hashById(currentItem);\n const match = (incomingById.get(hash)?.[0] ??\n // Only if we don't have `book[id]` set, look for a match based on our content\n (!!idKey &&\n !(currentItem as any)[idKey] &&\n !!incomingByContent &&\n // If we don't have an id, our `hash` variable will already be the `hashByContent`, so we can reuse it\n incomingByContent?.get(hash)?.[0])) as U | undefined;\n if (match) {\n // Defer to set to handle not nuking WIP changes\n childState.set(match, opts);\n mergedItems.push(childState.value);\n // Once matched, we don't want this to be an addedRow anymore\n addedRows.delete(currentItem);\n } else if (!childState.dirty && !childState.isNewEntity && !addedRows.has(currentItem)) {\n // Local is not dirty/added, and it's not upstream, so let it get removed\n } else if (hasOpKey && (currentItem as any).op === \"delete\") {\n // We were locally marked as deleted, and not finding a match is the server acking that we're gone\n } else if (\n currentItems.length === incomingItems.length &&\n !!idKey &&\n (currentItem as any)[idKey] === undefined\n ) {\n // Assume our newly-assigned id is coming back\n } else {\n // If no incoming, but we're dirty (see above) and not deleted (see above), keep the WIP change\n mergedItems.push(currentItem);\n }\n }\n\n // Then, add any incoming items that don't exist in current\n for (const incomingItem of incomingItems) {\n // Look for both `incoming.id` and the incoming-sans-id for new entities\n const match =\n currentById.get(hashById(incomingItem))?.[0] || currentById.get(hashByContent(incomingItem))?.[0];\n if (!match) {\n // New item from server, add it\n const childState = getOrCreateChildState(incomingItem, opts);\n mergedItems.push(childState.value);\n }\n }\n\n parentInstance[key] = mergedItems as any as T[K];\n valueAtom.reportChanged();\n\n // Set original to not merged...\n this.setOriginalValue(incomingItems);\n } else {\n parentInstance[key] = (values ?? []).map((child) => getOrCreateChildState(child, opts).value) as any as T[K];\n valueAtom.reportChanged();\n // Reset originalCopy so that our dirty checks have the right # of rows.\n if (opts.refreshing) {\n this.setOriginalValue();\n }\n }\n },\n\n add(value: U, spliceIndex?: number): void {\n // This is called by the user, so value should be a non-proxy value we should keep\n const childState = getOrCreateChildState(value) as ObjectStateInternal<U>;\n rowMap.set(value, childState);\n // Let `.set` know this is a new row\n addedRows.add(value);\n this.ensureSet();\n this.value.splice(typeof spliceIndex === \"number\" ? spliceIndex : this.value.length, 0, childState.value);\n valueAtom.reportChanged();\n maybeAutoSave();\n },\n\n remove(indexOrValue: number | U): void {\n this.ensureSet();\n if (typeof indexOrValue === \"number\") {\n this.value.splice(indexOrValue, 1);\n } else {\n const index = this.value.findIndex((v) => v === indexOrValue);\n if (index > -1) {\n this.value.splice(index, 1);\n }\n }\n valueAtom.reportChanged();\n maybeAutoSave();\n },\n\n revertChanges() {\n this.set(this.originalValue, { resetting: true });\n this.rows.forEach((r) => r.revertChanges());\n this.touched = false;\n },\n\n commitChanges() {\n // Tell each child ObjectState to have its fields commit into its parentCopy\n this.rows.forEach((r) => r.commitChanges());\n this.setOriginalValue();\n this.touched = false;\n valueAtom.reportChanged();\n },\n\n /** Disposes every row's ObjectState, including rows that were removed from the list. */\n dispose() {\n deactivateSubscriptions(list);\n },\n\n get originalValue(): U[] {\n originalValueAtom.reportObserved();\n const value = parentCopy[key];\n return value ?? ([] as any);\n },\n\n // This should only be called when value === originalValue\n setOriginalValue(incomingItems?: U[]) {\n // Use the rows' originalValues to update the parentCopy\n parentCopy[key] = incomingItems ?? (this.rows.map((r) => r.originalValue) as any);\n (parentCopy[key] as U[]).forEach((copy, i) => {\n copyMap.set(copy, (parentInstance[key] as any)[i]);\n });\n originalValueAtom.reportChanged();\n },\n\n ensureSet() {\n if (!parentInstance[key]) {\n (parentInstance as any)[key] = [];\n }\n valueAtom.reportChanged();\n },\n };\n\n const proxy = makeAutoObservable(list, {\n // See other makeAutoObservable comment\n value: computed({ equals: () => false }),\n }) as any;\n\n registerSubscriptions(proxy, {\n activate() {\n rowMap.forEach((row) => activateSubscriptions(row));\n },\n deactivate() {\n rowMap.forEach((row) => deactivateSubscriptions(row));\n },\n });\n return proxy;\n}\n\nfunction hashValue(value: unknown): string {\n return hash(value as Parameters<typeof hash>[0]);\n}\n","import { computed, isObservable, makeAutoObservable } from \"mobx\";\nimport {\n type FragmentFieldConfig,\n type ListFieldConfig,\n type ObjectConfig,\n type ObjectFieldConfig,\n type ValueFieldConfig,\n} from \"src/config\";\nimport { type FragmentField, newFragmentField } from \"src/fields/fragmentField\";\nimport { type ListFieldState, newListFieldState } from \"src/fields/listField\";\nimport { activateSubscriptions, deactivateSubscriptions, registerSubscriptions } from \"src/fields/subscriptions\";\nimport {\n type FieldState,\n type FieldStateInternal,\n type InternalSetOpts,\n newValueFieldState,\n type SetOpts,\n} from \"src/fields/valueField\";\nimport { areEqual, type Builtin, deepClone } from \"src/utils\";\n\n/**\n * Wraps a given input/on-the-wire type `T` for editing in a form.\n *\n * We basically mimic every field in `T` (i.e. `firstName`, `lastName`, etc.) but decorate them\n * with form-specific state like `touched`, `dirty`, `errors`, etc.\n *\n * The intent is that, after ensuring all fields are `valid`/etc., callers can take the\n * result of this `objectState.value` (or `objectState.originalValue` for the non-proxy version) and\n * have exactly the on-the-wire type `T` that they need to submit to the backend, without doing the\n * manual mapping of \"data that was in the form controls\" into \"data that the backend wants\".\n *\n * Note that this can be hierarchical by having a field of `ListFieldState` that\n * themselves each wrap an `ObjectState`, i.e.:\n *\n * ```\n * ObjectState for author\n * - firstName: FieldState\n * - lastName: FieldState\n * - rows: ListFieldState\n * - [0]: ObjectState for book 1\n * - title: FieldState\n * - [1]: ObjectState for book 2\n * - title: FieldState\n * ```\n */\nexport type ObjectState<T> =\n // Add state.field1, state.field2 for each key in T\n FieldStates<T> &\n // Pull in the touched, blur, dirty, etc\n FieldState<T> & {\n /** Sets the state of fields in `state`. */\n set(state: Partial<T>, opts?: SetOpts): void;\n\n /** Returns whether the object can be saved, i.e. is valid, but also as a side-effect marks touched. */\n canSave(): boolean;\n };\n\nexport type ObjectStateInternal<T = unknown> = ObjectState<T> & {\n set(value: T, opts?: InternalSetOpts): void;\n isSameEntity(other: T): boolean;\n idKey: string | undefined;\n};\n\nconst fragmentSym = Symbol(\"fragment\");\n\nexport type Fragment<V> = V & { [fragmentSym]: true };\n\nexport function fragment<V>(value: V): Fragment<V> {\n return value as any;\n}\n\n/** 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. */\ntype FieldStates<T> = {\n [K in keyof T]-?: T[K] extends Fragment<infer V>\n ? FragmentField<V>\n : T[K] extends Array<infer U> | null | undefined\n ? [U] extends [Builtin]\n ? FieldState<T[K]>\n : ListFieldState<U>\n : T[K] extends Builtin | null | undefined\n ? FieldState<T[K]>\n : ObjectState<T[K]>;\n};\n\n/**\n * Creates a new `ObjectState` for a given form object `T` given config rules in `config`.\n *\n * The returned `ObjectState` can be used in a mobx `useLocalObservable` to drive an\n * interactive form that tracks the current valid/touched/etc. state of both each\n * individual fields as well as the top-level form/object itself.\n */\nexport function createObjectState<T>(\n config: ObjectConfig<T>,\n instance: T,\n opts: { maybeAutoSave?: () => void } = {},\n): ObjectState<T> {\n const form = createInactiveObjectState(config, instance, opts);\n activateSubscriptions(form);\n return form;\n}\n\n/** Builds a hook-owned form without subscriptions that could escape an abandoned render. */\nexport function createInactiveObjectState<T>(\n config: ObjectConfig<T>,\n instance: T,\n opts: { maybeAutoSave?: () => void } = {},\n): ObjectState<T> {\n const noop = () => {};\n return newObjectState(\n { type: \"object\", config },\n undefined,\n undefined,\n undefined,\n instance,\n undefined,\n opts.maybeAutoSave || noop,\n false,\n );\n}\n\n/** A more internal version of `createObjectState`. */\nexport function newObjectState<T, P = any>(\n config: ObjectFieldConfig<T>,\n parentState: (() => ObjectState<P>) | undefined,\n parentInstance: P | undefined,\n parentListState: FieldState<any> | undefined,\n instance: T,\n key: keyof T | undefined,\n maybeAutoSave: () => void,\n deepExhaustive: boolean,\n): ObjectState<T> {\n // This is what we return, but we only know it's value until we call `observable`, so\n // we create a mutable variable to capture it so that we can create fields/call their\n // constructors and give them a way to access it later.\n let proxy: ObjectState<T> | undefined = undefined;\n function getObjectState(): ObjectState<T> {\n if (!proxy) {\n throw new Error(\"Race condition\");\n }\n return proxy;\n }\n\n // We directly mutate `instance` as the user edits the form, so keep a deep copy of the POJO.\n // If the user passed us `instance` that was a mobx store/class, this originalValue won't really\n // be a true match (i.e. pass instanceof), but it should be good enough\n const originalCopy: any = deepClone(instance);\n\n const objectConfig = config.config;\n const fieldStates = Object.entries(objectConfig).map(([_key, _config]) => {\n const key = _key as keyof T;\n const config = _config as\n ValueFieldConfig<any> | ObjectFieldConfig<any> | ListFieldConfig<any> | FragmentFieldConfig;\n let field: FieldState<any> | ListFieldState<any> | ObjectState<T> | FragmentField<any>;\n if (config.type === \"value\") {\n field = newValueFieldState(\n originalCopy,\n instance,\n getObjectState,\n key,\n config.rules || [],\n config.isIdKey ||\n // Default the id key to \"id\" unless some other field has isIdKey set\n (key === \"id\" &&\n !(\"isIdKey\" in config) && // Allow setting `isIdKey=false`\n !(Object.entries(objectConfig) as any as [string, ValueFieldConfig<any>][]).some(\n ([other, c]) => other !== key && c.isIdKey,\n )),\n config.isDeleteKey || false,\n config.isReadOnlyKey || false,\n config.isLocalOnly || false,\n config.computed ??\n // If instance is a mobx class, we can detect computeds as they won't be enumerable\n (isObservable(instance) ? !(key in originalCopy) : false),\n config.readOnly ?? false,\n config.strictOrder ?? true,\n maybeAutoSave,\n );\n } else if (config.type === \"list\") {\n field = newListFieldState(\n originalCopy,\n instance,\n getObjectState,\n key,\n config.rules || [],\n config,\n { type: \"object\", config: config.config },\n config.strictOrder ?? true,\n maybeAutoSave,\n deepExhaustive,\n );\n } else if (config.type === \"object\") {\n // Because our objectField will fundamentally want to do `child.firstName.set(...)` or\n // even `child.firstName !== undefined`, etc., we \"simplify\" things by always setting\n // an empty object, for our child valueFields/listFields to use/read from, although\n // we then have to \"oh right ignore {}\" in places like `dirty`.\n if (!instance[key]) {\n instance[key] = {} as any;\n }\n field = newObjectState(\n config,\n getObjectState,\n instance,\n undefined,\n instance[key] as any,\n key,\n maybeAutoSave,\n deepExhaustive,\n ) as any;\n } else if (config.type === \"fragment\") {\n field = newFragmentField(instance as object & T, key as any);\n } else {\n throw new Error(`Invalid type value ${(config as any).type}`);\n }\n return [key, field];\n });\n\n const fieldNames = Object.keys(objectConfig);\n function getFields(proxyThis: any): FieldStateInternal<T, any>[] {\n return fieldNames.map((name) => proxyThis[name]) as FieldStateInternal<T, any>[];\n }\n\n const obj = {\n ...Object.fromEntries(fieldStates),\n\n key,\n\n get value() {\n // We always return the same `instance`, so read each field's value here to make\n // observers of our (identity-stable) `value` re-run when any field deeply changes.\n // (`originalValue` below does the same for `originalCopy`.)\n getFields(this).forEach((f) => f.value);\n return instance;\n },\n\n set value(value) {\n this.set(value);\n },\n\n // private\n _kind: \"object\",\n _readOnly: false,\n _loading: false,\n _isAutoSaving: false,\n\n _considerDeleted(): boolean {\n const deleteField = getFields(this).find((f) => f._isDeleteKey);\n return !!deleteField?.value;\n },\n\n _considerReadOnly(): boolean {\n const readOnlyField = getFields(this).find((f) => f._isReadOnlyKey);\n return !!readOnlyField?.value;\n },\n\n get focused(): boolean {\n return getFields(this).some((f) => f.focused);\n },\n\n get touched(): boolean {\n return getFields(this).some((f) => f.touched);\n },\n\n set touched(touched: boolean) {\n getFields(this).forEach((f) => (f.touched = touched));\n },\n\n get readOnly(): boolean {\n return (\n this._readOnly ||\n this._considerReadOnly() ||\n (parentState && parentState().readOnly) ||\n !!parentListState?.readOnly\n );\n },\n\n set readOnly(readOnly: boolean) {\n this._readOnly = readOnly;\n },\n\n get loading(): boolean {\n return this._loading || (parentState && parentState().loading) || !!parentListState?.loading;\n },\n\n set loading(loading: boolean) {\n this._loading = loading;\n },\n\n get valid(): boolean {\n return getFields(this).every((f) => {\n return f.valid;\n });\n },\n\n get errors(): string[] {\n return getFields(this).flatMap((f) => f.errors.map((e) => `${f.key}: ${e}`));\n },\n\n get dirty(): boolean {\n return (\n getFields(this).some((f) => f.dirty && !(f as FieldStateInternal<any, any>)._isLocalOnly) ||\n // `isUnset` checks if our `parent[key] === undefined`, which can mean \"surely we're dirty\",\n // but as long as we've got some keys actually set\n (this.isUnset() && !areEqual(this.value, {}))\n );\n },\n\n get isNewEntity(): boolean {\n const idField = getFields(this).find((f) => f._isIdKey);\n // If we're a line item w/o an immediate id field, look in our parent\n if (!idField && parentState) {\n return parentState().isNewEntity;\n }\n // If there is no id field, assume we're not new\n return idField !== undefined && (idField.value === null || idField.value === undefined);\n },\n\n canSave(): boolean {\n this.touched = true;\n return this.valid;\n },\n\n // Accepts new values in bulk, i.e. when setting the form initial state from the backend.\n set(value: T, opts: InternalSetOpts = {}) {\n if (this.readOnly && !opts.resetting && !opts.refreshing) {\n throw new Error(`${String(key) || \"formState\"} is currently readOnly`);\n }\n // Restore our instance if we're being reset\n if (value && this.isUnset()) (parentInstance as any)[key] = instance;\n // Delete our instance if we're being unset\n if (!value && parentInstance) (parentInstance as any)[key] = undefined;\n // Otherwise just copy over the fields\n getFields(this).forEach((field) => {\n if (value && typeof value === \"object\" && field.key in value) {\n field.set((value as any)[field.key], opts);\n }\n });\n },\n\n // Resets all fields back to their original values\n revertChanges() {\n getFields(this).forEach((f) => f.revertChanges());\n },\n\n // Saves all current values into _originalValue.\n // Deprecated: use formState.update(ackedServerValue) so saved changes are audited field-by-field.\n commitChanges() {\n if (this._isAutoSaving) {\n // `commitChanges` will mark all WIP changes as committed, which might drop changes if there\n // is an auto-save in-flight, and the user made more changes, that are waiting for their turn\n // on the next auto-save request.\n //\n // Instead of doing a form-wide \"all changes are committed\", autosave forms should use\n // formState.update(ackedServerValue), or init.map/input, to apply the server's latest results\n // within the form. That selectively \"un-dirty\"/commits the just-saved data, but keeps the\n // \"still-dirty\" WIP data alone.\n throw new Error(\n \"When using autoSave, you should not manually call commitChanges, instead have init.map/input update the form state\",\n );\n }\n getFields(this).forEach((f) => f.commitChanges());\n },\n\n // Create a result that is only populated with changed keys\n get changedValue() {\n const result: any = {};\n // If we're a child, like `author.address` that was unset, return null\n if (this.isUnset()) {\n // If we're a reference, always return `{ id: ... }` for easy binding\n const idField = getFields(this).find((f) => f._isIdKey);\n if (idField && config.reference) return { id: null };\n // Otherwise mark the whole child as gone\n return null;\n }\n getFields(this).forEach((f) => {\n if ((f as any)._computed) return;\n // References only include the id key below\n if (config.reference) return;\n // Ignore local-only fields\n if (f._isLocalOnly) return;\n if (\n f.dirty ||\n // If the caller used useFormState.ifUndefined to provide some default values, then those keys may not\n // look dirty, but if we're new we should include them anyway.\n (this.isNewEntity && (f as any)._kind === \"value\" && f.value !== undefined) ||\n // ...or they're non-empty sub-objects\n (this.isNewEntity &&\n (f as any)._kind === \"object\" &&\n // Child objects that are unset will have `changedValue = null`\n f.changedValue &&\n Object.entries(f.changedValue).length > 0) ||\n // ...or they're non-empty sub-lists\n (this.isNewEntity && (f as any)._kind === \"list\" && f.value?.length > 0) ||\n // ...or the parent list is deep-exhaustive\n deepExhaustive\n ) {\n result[f.key] = f.changedValue;\n }\n });\n // Ensure we always have the id for updates to work\n const idField = getFields(this).find((f) => f._isIdKey);\n if (idField && idField.value !== undefined) {\n result[idField.key] = idField.value;\n }\n return result;\n },\n\n dispose() {\n deactivateSubscriptions(proxy!);\n },\n\n get originalValue(): T | undefined {\n getFields(proxy).map((f) => f.originalValue);\n return originalCopy;\n },\n\n // An internal helper method to see if `other` is for \"the same entity\" as our current row\n isSameEntity(other: T): boolean {\n const idField = getFields(this).find((f) => f._isIdKey);\n if (!idField) return false;\n const ourIdValue = idField.value;\n const otherIdValue = (other as any)[idField.key];\n // If the otherIdValue is undefined, it's a new entity so can't be the same as us\n return otherIdValue !== undefined && areEqual(otherIdValue, ourIdValue);\n },\n\n isUnset(): boolean {\n return !!parentInstance && (parentInstance as any)[key] === undefined;\n },\n\n get idKey(): string | undefined {\n return getFields(this).find((f) => f._isIdKey)?.key;\n },\n };\n\n proxy = makeAutoObservable(obj, {\n // Use custom equality on `value` that is _never_ equals. This sounds weird, but\n // because our `value` is always the same `instance` that was passed to `newObjectState`,\n // to mobx this looks like the value never changes, and it will never invoke observers\n // even with our tick-based hacks.\n value: computed({ equals: () => false }),\n originalValue: computed({ equals: () => false }),\n });\n\n // Use the original children: makeAutoObservable can wrap plain value fields in proxies.\n registerSubscriptions(proxy!, {\n activate() {\n fieldStates.forEach((entry) => activateSubscriptions(entry[1] as object));\n },\n deactivate() {\n fieldStates.forEach((entry) => deactivateSubscriptions(entry[1] as object));\n },\n });\n return proxy!;\n}\n","import {\n type FragmentFieldConfig,\n type ListFieldConfig,\n type ObjectConfig,\n type ObjectFieldConfig,\n type ValueFieldConfig,\n} from \"src/config\";\nimport { type Fragment, type ObjectState } from \"src/fields/objectField\";\nimport { type Rule, required } from \"src/rules\";\nimport { type Builtin, type OmitIf } from \"src/utils\";\n\n/**\n * Provides a Zod-ish API for building form configs.\n */\nexport const f = {\n /** Creates the top-level config for a form, like an author or book. */\n config<T>(fields: ObjectConfigBuilderFields<T>): ObjectConfig<T> {\n return new ObjectConfigBuilder<T>(fields).build().config;\n },\n\n /** Creates the config DSL for an object, like an author or book. */\n object<T>(fields: ObjectConfigBuilderFields<T>): ObjectConfigBuilder<T> {\n return new ObjectConfigBuilder<T>(fields);\n },\n\n /** Creates the config DSL for an list of objects, like an author's list of books. */\n list<U>(fields: ObjectConfigBuilderFields<U>): ListFieldConfigBuilder<U> {\n return new ListFieldConfigBuilder<U>(fields);\n },\n\n /** Creates the config DSL for a primitive value, like a first name or phone number. */\n value<V>(): ValueFieldConfigBuilder<V> {\n return new ValueFieldConfigBuilder<V>();\n },\n\n /** A shortcut for creating a child object with a single `id` key. */\n reference<V extends { id?: unknown }>(fields?: ObjectConfigBuilderFields<V>): ObjectConfigBuilder<V> {\n return f.object<V>({ id: f.value(), ...fields } as any).ref();\n },\n\n /** A shorthand for creating a computed value. */\n computed<V>(): ValueFieldConfigBuilder<V> {\n return f.value<V>().computed();\n },\n};\n\n/**\n * Defines the fields that should be passed into `f.object({ ... })` for fhe form type `T`.\n *\n * This is basically a map of `key` in `T` to `f.value()`, `f.list()`, or `f.object()`\n * DSL objects that define the runtime structure/fields of the form.\n */\nexport type ObjectConfigBuilderFields<T> = {\n [P in keyof OmitIf<T, Function>]: T[P] extends Fragment<infer V>\n ? FragmentFieldConfigBuilder\n : T[P] extends Array<infer U> | null | undefined\n ? U extends Builtin\n ? ValueFieldConfigBuilder<T[P]>\n : ListFieldConfigBuilder<U>\n : ValueFieldConfigBuilder<T[P]> | ObjectConfigBuilder<T[P]>;\n};\n\nexport interface FragmentFieldConfigBuilder {\n build(): FragmentFieldConfig;\n}\n\n/** Provides a fluent DSL for building up an object's config. */\nexport class ObjectConfigBuilder<T> {\n private config: ObjectFieldConfig<T> = { type: \"object\", config: {} as ObjectConfig<T> };\n\n constructor(fields: ObjectConfigBuilderFields<T>) {\n for (const [key, value] of Object.entries(fields)) {\n (this.config.config as any)[key] = (value as any).build();\n }\n }\n\n ref(): this {\n this.config.reference = true;\n return this;\n }\n\n build(): ObjectFieldConfig<T> {\n return this.config;\n }\n}\n\n/** Provides a fluent DSL for building up a value's config. */\nexport class ValueFieldConfigBuilder<V> {\n private config: ValueFieldConfig<V> = { type: \"value\", rules: [] };\n\n /** Marks the field as required, keeping any rules that were already added. */\n req(): this {\n (this.config.rules ??= []).push(required);\n return this;\n }\n\n /** Marks the field as read only. */\n readOnly(): this {\n this.config.readOnly = true;\n return this;\n }\n\n /** Marks the field as local only. */\n localOnly(): this {\n this.config.isLocalOnly = true;\n return this;\n }\n\n /** Appends `rules` to the field's validation rules. */\n rules(rules: Rule<V | null | undefined>[]): this {\n (this.config.rules ??= []).push(...rules);\n return this;\n }\n\n /** Marks the field as computed. */\n computed(): this {\n this.config.computed = true;\n return this;\n }\n\n build(): ValueFieldConfig<V> {\n return this.config;\n }\n}\n\n/** Provides a fluent DSL for building up a list's config. */\nexport class ListFieldConfigBuilder<U> {\n private config: ListFieldConfig<U>;\n\n constructor(fields: ObjectConfigBuilderFields<U>) {\n const config = {} as any;\n for (const [key, value] of Object.entries(fields)) {\n config[key] = (value as any).build();\n }\n this.config = { type: \"list\", rules: [], config };\n }\n\n /** Appends `rule` to the field's validation rules. */\n rule(rule: Rule<readonly ObjectState<U>[]>): this {\n (this.config.rules ??= []).push(rule);\n return this;\n }\n\n /** Appends `rules` to the field's validation rules. */\n rules(rules: Rule<readonly ObjectState<U>[]>[]): this {\n (this.config.rules ??= []).push(...rules);\n return this;\n }\n\n build(): ListFieldConfig<U> {\n return this.config;\n }\n}\n","import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { type ObjectConfig } from \"src/config\";\nimport { type ObjectState, type ObjectStateInternal, createInactiveObjectState } from \"src/fields/objectField\";\nimport { activateSubscriptions, deactivateSubscriptions } from \"src/fields/subscriptions\";\nimport { initValue } from \"src/utils\";\n\nexport type ObjectStateCache<T, I> = Record<string, [ObjectState<T>, I]>;\n\n/**\n * The opts has for `useFormStates`.\n *\n * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*\n * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query\n */\ntype UseFormStatesOpts<T, I> = {\n /**\n * The config to use for each form state.\n *\n * Should be stable/useMemo'd.\n */\n config: ObjectConfig<T>;\n\n /**\n * Fired for each individual `ObjectState` when it's had a value change.\n *\n * Does not need to be stable/useMemo'd.\n */\n autoSave?: (state: ObjectState<T>) => Promise<void>;\n\n /**\n * A hook to add custom, cross-field validation rules that can be difficult to setup directly in the config DSL.\n *\n * This will be called once-per `ObjectState` instance, and so is effectively a `useEffect` hook with\n * a `[config, objectState]` dependency.\n *\n * Does not need to be stable/useMemo'd.\n */\n addRules?: (state: ObjectState<T>) => void;\n\n /**\n * Given an input to `getFormState`, returns the identity value that we'll cache that value's form state on.\n *\n * Does not need to be stable/useMemo'd.\n */\n getId: (v: I) => string;\n\n /**\n * Maps an input to `getFormState` to the actual form shape `T`.\n *\n * Does not need to be stable/useMemo'd.\n */\n map?: (input: Exclude<I, null | undefined>) => T;\n\n /**\n * Sets all `ObjectState`s to readOnly.\n */\n readOnly?: boolean;\n};\n\ntype UseFormStatesHook<T, I> = {\n getFormState: (input: I, opts?: { readOnly?: boolean }) => ObjectState<T>;\n};\n\n/**\n * A hook to manage many \"mini-forms\" on a single page, typically one form per row\n * in a table.\n *\n * This hook basically provides the page/table with a cache, so each table row naively ask \"what's\n * the form state for this given row's data?\" and get back a new-or-existing `ObjectState` instance\n * that, if already existing, still has any of the user's WIP changes.\n *\n * Each mini-form/row can have its own autoSave calls, independent of the other rows.\n *\n * @typeparam T the form type, which is usually as close as possible to your *GraphQL input*\n * @typeparam I the *form input* type, which is usually the *GraphQL output* type, i.e. the type of the response from your GraphQL query\n */\nexport function useFormStates<T, I = T>(opts: UseFormStatesOpts<T, I>): UseFormStatesHook<T, I> {\n const { config, autoSave, getId, map, addRules, readOnly = false } = opts;\n\n // Force resetting the cache if config changes.\n const cache = useMemo(\n () => ({\n config,\n forms: {} as ObjectStateCache<T, I>,\n active: false,\n // Keep paused saves with their forms so effect reconnection can resume them.\n pending: new Map<ObjectState<T>, () => Promise<void>>(),\n }),\n [config],\n );\n // A previous config's in-flight request must drain the latest committed cache, not its old queue.\n const currentCacheRef = useRef(cache);\n // If the user's autoSave hook makes some last-minute `.set` calls to sneak\n // in some business logic right before their GraphQL mutation call, ignore it\n // so that we don't infinite loop.\n //\n // This is per-hook (not module-level) so that one hook's in-flight save does not\n // swallow the auto-saves queued by an unrelated `useFormStates` on the same page.\n const isAutoSaving = useRef(false);\n // Use a ref so our memo'ized `autoSave` always see the latest value\n const autoSaveRef = useRef<UseFormStatesOpts<T, I>[\"autoSave\"]>(autoSave);\n autoSaveRef.current = autoSave;\n // Use a ref b/c we're memod\n const readOnlyRef = useRef<boolean>(readOnly);\n readOnlyRef.current = readOnly;\n\n useEffect(() => {\n currentCacheRef.current = cache;\n cache.active = true;\n Object.values(cache.forms).forEach((entry) => activateSubscriptions(entry[0]));\n // Resume any auto saves that were made before/after useEffect connected our subscriptions\n Array.from(cache.pending.values()).forEach((maybeAutosave) => void maybeAutosave());\n return () => {\n // Stop queued requests immediately, without cancelling requests already in flight.\n cache.active = false;\n Object.values(cache.forms).forEach((entry) => deactivateSubscriptions(entry[0]));\n };\n }, [cache]);\n\n const getFormState = useCallback<UseFormStatesHook<T, I>[\"getFormState\"]>(\n (input, opts = {}) => {\n const { config, forms: objectStateCache } = cache;\n const existing = objectStateCache[getId(input)];\n let form = existing?.[0];\n\n /**\n * Serializes this form's saves with the other rows owned by the hook.\n *\n * Each retry retains its cache so a config replacement cannot revive a released form.\n */\n async function maybeAutoSave() {\n if (!cache.active) {\n cache.pending.set(form, maybeAutoSave);\n return;\n }\n // Don't use form.canSave() because we don't want to set touched for all the fields\n if (autoSaveRef.current && form.dirty) {\n // It's very frustrating to not know why the form is savings, to go ahead and log these\n if (!form.valid) {\n console.debug(\"Skipping auto-save b/c form is invalid: \", form.errors);\n cache.pending.delete(form);\n return;\n }\n if (isAutoSaving.current) {\n cache.pending.set(form, maybeAutoSave);\n return;\n }\n try {\n isAutoSaving.current = true;\n // See if we have any reactions that want to run (i.e. added by addRules hooks)\n await new Promise((resolve) => setTimeout(resolve, 0));\n // If a reaction re-queued our form during the ^ wait, remove it\n cache.pending.delete(form);\n if (cache.active && autoSaveRef.current) {\n await autoSaveRef.current(form);\n } else {\n cache.pending.set(form, maybeAutoSave);\n }\n } finally {\n isAutoSaving.current = false;\n // Config replacement can commit a new cache while this request is in flight.\n // Only drain that current cache, and leave its queue intact if effects are disconnected.\n const currentCache = currentCacheRef.current;\n if (currentCache.active) {\n for (const [first, maybeAutosave] of currentCache.pending) {\n currentCache.pending.delete(first);\n if (!first.dirty || !first.valid) continue;\n await maybeAutosave();\n break;\n }\n }\n }\n } else {\n cache.pending.delete(form);\n }\n }\n\n // If it didn't exist, then add to the cache.\n if (!form) {\n form = createInactiveObjectState(config, initValue(config, { map, input }), {\n maybeAutoSave,\n });\n if (addRules) {\n addRules(form);\n }\n objectStateCache[getId(input)] = [form, input];\n if (cache.active) activateSubscriptions(form);\n }\n\n // If the source of truth changed, then update the existing state and return it.\n if (existing && existing[1] !== input) {\n (form as any as ObjectStateInternal<any>).set(initValue(config, { map, input }), {\n refreshing: true,\n });\n existing[1] = input;\n }\n\n form.readOnly = readOnlyRef.current || !!opts.readOnly;\n\n return form;\n },\n // Allow the user to not stable-ize getId, map, addRules, and autoSave\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [cache],\n );\n\n return { getFormState };\n}\n"],"mappings":"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"}
|