@homebound/form-state 3.0.0 → 3.1.1

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 CHANGED
@@ -138,12 +138,12 @@ For submit forms, the expectation is that you should:
138
138
  If you do this, you should not have to call `commitChanges` manually, because code like:
139
139
 
140
140
  ```ts
141
- onClick: async () => {
142
- const input = formState.changedValue;
143
- await saveAuthor(input);
144
- // checks if formState.dirty is true before closing
145
- closeModal();
146
- };
141
+ onClick: async () => {
142
+ const input = formState.changedValue;
143
+ await saveAuthor(input);
144
+ // checks if formState.dirty is true before closing
145
+ closeModal();
146
+ };
147
147
  ```
148
148
 
149
149
  Will "just work" because the control flow will be:
package/dist/index.d.ts CHANGED
@@ -43,7 +43,12 @@ interface FieldState<V> {
43
43
  readonly isNewEntity: boolean;
44
44
  rules: Rule<V>[];
45
45
  readonly errors: string[];
46
- /** Returns a subset of V with only the changed values. Currently not observable. */
46
+ /**
47
+ * Returns a subset of V with only the changed values.
48
+ *
49
+ * Reading this also records that the value is probably being put on the wire, so that a later
50
+ * `refreshing` set can tell a server ack of this value apart from a stale cache refresh.
51
+ */
47
52
  readonly changedValue: V;
48
53
  /** Focuses the field. Disables changes from `ObjectState.set` calls. */
49
54
  focus(): void;
@@ -64,6 +69,14 @@ interface FieldState<V> {
64
69
  commitChanges(): void;
65
70
  /** Creates a new FieldState with a transformation of the value, i.e. string to int, or feet to inches. */
66
71
  adapt<V2>(adapter: ValueAdapter<V, V2>): FieldState<V2>;
72
+ /**
73
+ * Releases any mobx reactions this field created.
74
+ *
75
+ * This only matters when wrapping a mobx observable (i.e. a store or class instance): each field
76
+ * subscribes to its slot on the store so that external mutations can trigger auto-save, and that
77
+ * subscription keeps the field alive until it is disposed. Plain objects create no reactions.
78
+ */
79
+ dispose(): void;
67
80
  }
68
81
  /**
69
82
  * Allows changing a type in the formState (like a string) to a different type in the UI (like a number).
@@ -344,7 +357,7 @@ declare class ObjectConfigBuilder<T> {
344
357
  /** Provides a fluent DSL for building up a value's config. */
345
358
  declare class ValueFieldConfigBuilder<V> {
346
359
  private config;
347
- /** Marks the field as required. */
360
+ /** Marks the field as required, keeping any rules that were already added. */
348
361
  req(): this;
349
362
  /** Marks the field as read only. */
350
363
  readOnly(): this;
@@ -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":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/fields/fragmentField.ts","../src/rules.ts","../src/fields/valueField.ts","../src/fields/listField.ts","../src/temporal.ts","../src/useFormState.ts","../src/utils.ts","../src/fields/objectField.ts","../src/config.ts","../src/configBuilders.ts","../src/useFormStates.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { action, computed, isObservable, makeAutoObservable, observable, reaction, toJS } from "mobx";
1
+ import { action, computed, createAtom, isObservable, makeAutoObservable, reaction, toJS } from "mobx";
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";
@@ -43,11 +43,16 @@ function areSupportedTemporalValuesEqual(a, b) {
43
43
  const bIsTemporal = isSupportedTemporal(b);
44
44
  if (!aIsTemporal && !bIsTemporal) return;
45
45
  if (!aIsTemporal || !bIsTemporal || a[Symbol.toStringTag] !== b[Symbol.toStringTag]) return false;
46
+ if (isTemporalPlainDate(a)) return a.equals(b);
46
47
  return a.equals(b);
47
48
  }
48
49
  function isSupportedTemporalTag(value) {
49
50
  return value === temporalPlainDateTag || value === temporalZonedDateTimeTag;
50
51
  }
52
+ /** Narrows a supported Temporal value to `Temporal.PlainDate` by its well-known tag. */
53
+ function isTemporalPlainDate(value) {
54
+ return value[Symbol.toStringTag] === temporalPlainDateTag;
55
+ }
51
56
  //#endregion
52
57
  //#region src/fields/deepEquals.ts
53
58
  /**
@@ -65,7 +70,7 @@ function deepEquals(a, b, visited = /* @__PURE__ */ new Set()) {
65
70
  if (aType === "number" && isNaN(a) && isNaN(b)) return true;
66
71
  return a === b;
67
72
  }
68
- if (hasToJSON$1(a) || hasToJSON$1(b)) return deepEquals(hasToJSON$1(a) ? a.toJSON() : a, hasToJSON$1(b) ? b.toJSON() : b, visited);
73
+ if (hasToJSON(a) || hasToJSON(b)) return deepEquals(hasToJSON(a) ? a.toJSON() : a, hasToJSON(b) ? b.toJSON() : b, visited);
69
74
  if (Array.isArray(a) !== Array.isArray(b)) return false;
70
75
  const aKeys = Object.keys(a), bKeys = Object.keys(b);
71
76
  if (aKeys.length !== bKeys.length) return false;
@@ -76,7 +81,7 @@ function deepEquals(a, b, visited = /* @__PURE__ */ new Set()) {
76
81
  break;
77
82
  }
78
83
  if (a[key] && a[key] instanceof Object) {
79
- if (visited.has(a[key])) break;
84
+ if (visited.has(a[key])) continue;
80
85
  visited.add(a[key]);
81
86
  }
82
87
  if (!deepEquals(a[key], b[key], visited)) {
@@ -86,13 +91,12 @@ function deepEquals(a, b, visited = /* @__PURE__ */ new Set()) {
86
91
  }
87
92
  return equal;
88
93
  }
89
- function hasToJSON$1(o) {
94
+ /** Returns whether `o` is an object with a `toJSON` method, i.e. a Date or a custom value type. */
95
+ function hasToJSON(o) {
90
96
  return !!(o && typeof o === "object" && "toJSON" in o);
91
97
  }
92
98
  //#endregion
93
99
  //#region src/useFormState.ts
94
- let isAutoSaving$1 = false;
95
- let pendingAutoSave = false;
96
100
  /**
97
101
  * Creates a formState instance for editing in a form.
98
102
  */
@@ -107,25 +111,25 @@ function useFormState(opts) {
107
111
  const isWrappingMobxProxy = !isPlainObject(firstInitValue);
108
112
  const dep = isInput(init) ? init.onlyOnce ? [] : makeArray(init.input) : isQuery(init) ? [init.query.data, init.query.loading] : [];
109
113
  const form = useMemo(() => {
114
+ let isAutoSaving = false;
115
+ let pendingAutoSave = false;
110
116
  function maybeAutoSave() {
111
- if (isAutoSaving$1 === "in-flight") pendingAutoSave = true;
112
- else if (isAutoSaving$1 === "queued") return;
113
- if (autoSaveRef.current && form.dirty && !isAutoSaving$1) {
117
+ if (isAutoSaving === "in-flight") pendingAutoSave = true;
118
+ else if (isAutoSaving === "queued") return;
119
+ if (autoSaveRef.current && form.dirty && !isAutoSaving) {
114
120
  if (!form.valid) {
115
121
  console.debug("Skipping auto-save b/c form is invalid: ", form.errors);
116
122
  return;
117
123
  }
118
- isAutoSaving$1 = "queued";
124
+ isAutoSaving = "queued";
119
125
  setTimeout(async () => {
120
126
  try {
121
127
  form._isAutoSaving = true;
122
128
  const promise = autoSaveRef.current(form);
123
- isAutoSaving$1 = "in-flight";
129
+ isAutoSaving = "in-flight";
124
130
  await promise;
125
- } catch (e) {
126
- throw e;
127
131
  } finally {
128
- isAutoSaving$1 = false;
132
+ isAutoSaving = false;
129
133
  form._isAutoSaving = false;
130
134
  if (pendingAutoSave) {
131
135
  pendingAutoSave = false;
@@ -155,6 +159,11 @@ function useFormState(opts) {
155
159
  firstRunRef.current = true;
156
160
  return form;
157
161
  }, [config, ...isWrappingMobxProxy ? dep : []]);
162
+ const prevFormRef = useRef(void 0);
163
+ useEffect(() => {
164
+ if (prevFormRef.current && prevFormRef.current !== form) prevFormRef.current.dispose();
165
+ prevFormRef.current = form;
166
+ }, [form]);
158
167
  useEffect(() => {
159
168
  if (firstRunRef.current || isWrappingMobxProxy) {
160
169
  firstRunRef.current = false;
@@ -184,9 +193,6 @@ function makeArray(input) {
184
193
  }
185
194
  //#endregion
186
195
  //#region src/utils.ts
187
- function fail(message) {
188
- throw new Error(message || "Failed");
189
- }
190
196
  function assertNever(x) {
191
197
  throw new Error("Unexpected object: " + x);
192
198
  }
@@ -257,9 +263,6 @@ function areEqual(a, b, strictOrder) {
257
263
  }
258
264
  return a === b;
259
265
  }
260
- function hasToJSON(o) {
261
- return !!(o && typeof o === "object" && "toJSON" in o);
262
- }
263
266
  /** Normalizes values into stable object-hash-safe data using our existing `toJSON` semantics. */
264
267
  function normalizeHashValue(value, seen = /* @__PURE__ */ new WeakMap()) {
265
268
  if (hasToJSON(value)) return normalizeHashValue(value.toJSON(), seen);
@@ -309,10 +312,11 @@ function getAllPropertyNames(obj) {
309
312
  //#endregion
310
313
  //#region src/fields/valueField.ts
311
314
  function newValueFieldState(parentCopy, parentInstance, parentState, key, rules, isIdKey, isDeleteKey, isReadOnlyKey, isLocalOnly, computed, readOnly, strictOrder, maybeAutoSave) {
312
- const _tick = observable({ value: 1 });
313
- const _originalValueTick = observable({ value: 1 });
315
+ const valueAtom = createAtom(`${String(key)}.value`);
316
+ const originalValueAtom = createAtom(`${String(key)}.originalValue`);
314
317
  let hasInflightChangedValue = false;
315
318
  let lastChangedValue = void 0;
319
+ let disposeStoreReaction = void 0;
316
320
  const field = {
317
321
  key,
318
322
  touched: false,
@@ -328,7 +332,8 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
328
332
  _isLocalOnly: isLocalOnly,
329
333
  rules,
330
334
  get value() {
331
- const value = _tick.value > 0 ? parentInstance[key] : fail();
335
+ valueAtom.reportObserved();
336
+ const value = parentInstance[key];
332
337
  return value === null && isEmpty(parentCopy[key]) ? void 0 : value;
333
338
  },
334
339
  set value(v) {
@@ -363,13 +368,7 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
363
368
  return this.value;
364
369
  },
365
370
  get valid() {
366
- const opts = {
367
- value: this.value,
368
- key,
369
- originalValue: this.originalValue,
370
- object: parentState()
371
- };
372
- return this.rules.every((r) => r(opts) === void 0);
371
+ return this.errors.length === 0;
373
372
  },
374
373
  get errors() {
375
374
  const opts = {
@@ -400,18 +399,18 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
400
399
  },
401
400
  set(value, opts = {}) {
402
401
  if (this.readOnly && !opts.resetting && !opts.refreshing) throw new Error(`${String(key)} is currently readOnly`);
403
- if (opts.refreshing && this.dirty) {
404
- const isAckingUnset = this.value === null && (value === null || value === void 0);
405
- const acceptServerAck = hasInflightChangedValue && this.value === lastChangedValue && value !== this.originalValue;
406
- if (!areEqual(this.value, value, strictOrder) && !isAckingUnset && !acceptServerAck) return;
407
- } else if (computed && (opts.resetting || opts.refreshing)) return;
408
402
  const keepNull = !isEmpty(this.originalValue) && isEmpty(value) && !opts.refreshing;
409
403
  const coerceEmptyList = value && value instanceof Array && value.length === 0 && isEmpty(this.originalValue);
410
404
  const newValue = keepNull ? null : isEmpty(value) || coerceEmptyList ? void 0 : value;
405
+ if (opts.refreshing && this.dirty) {
406
+ const isAckingUnset = this.value === null && newValue === void 0;
407
+ const acceptServerAck = hasInflightChangedValue && this.value === lastChangedValue && !areEqual(newValue, this.originalValue, strictOrder);
408
+ if (!areEqual(this.value, newValue, strictOrder) && !isAckingUnset && !acceptServerAck) return;
409
+ } else if (computed && (opts.resetting || opts.refreshing)) return;
411
410
  hasInflightChangedValue = false;
412
411
  const changed = !areEqual(newValue, this.value, strictOrder);
413
412
  parentInstance[key] = newValue;
414
- _tick.value++;
413
+ valueAtom.reportChanged();
415
414
  if (opts.refreshing) this.originalValue = newValue;
416
415
  if (!this._focused && !opts.refreshing && !opts.resetting && this.dirty && changed && opts.autoSave !== false) this.maybeAutoSave();
417
416
  },
@@ -427,13 +426,18 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
427
426
  else this.originalValue = this.value;
428
427
  this.touched = false;
429
428
  },
429
+ dispose() {
430
+ disposeStoreReaction?.();
431
+ disposeStoreReaction = void 0;
432
+ },
430
433
  get originalValue() {
431
- const value = _originalValueTick.value > -1 ? parentCopy[key] : parentCopy[key];
434
+ originalValueAtom.reportObserved();
435
+ const value = parentCopy[key];
432
436
  return value === null ? void 0 : value;
433
437
  },
434
438
  set originalValue(v) {
435
439
  if (!(v === void 0 && !(key in parentCopy))) parentCopy[key] = v;
436
- _originalValueTick.value++;
440
+ originalValueAtom.reportChanged();
437
441
  },
438
442
  maybeTrim() {
439
443
  if (typeof this.value === "string") {
@@ -443,7 +447,7 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
443
447
  }
444
448
  }
445
449
  };
446
- if (isObservable(parentInstance)) reaction(() => parentInstance[key], () => {
450
+ if (isObservable(parentInstance)) disposeStoreReaction = reaction(() => parentInstance[key], () => {
447
451
  if (!parentState().focused) maybeAutoSave();
448
452
  });
449
453
  return field;
@@ -481,7 +485,7 @@ function adapt(field, adapter) {
481
485
  //#endregion
482
486
  //#region src/fields/fragmentField.ts
483
487
  function newFragmentField(parentInstance, key) {
484
- const _tick = observable({ value: 1 });
488
+ const valueAtom = createAtom(`${key}.value`);
485
489
  let value = parentInstance[key];
486
490
  delete parentInstance[key];
487
491
  return makeAutoObservable({
@@ -507,17 +511,18 @@ function newFragmentField(parentInstance, key) {
507
511
  maybeAutoSave: () => {},
508
512
  commitChanges: () => {},
509
513
  revertChanges: () => {},
514
+ dispose: () => {},
510
515
  get value() {
511
516
  if (key in parentInstance) {
512
517
  value = parentInstance[key];
513
518
  delete parentInstance[key];
514
519
  }
515
- _tick.value > 0 || fail();
520
+ valueAtom.reportObserved();
516
521
  return value;
517
522
  },
518
523
  set value(v) {
519
524
  value = v;
520
- _tick.value++;
525
+ valueAtom.reportChanged();
521
526
  },
522
527
  set(value) {
523
528
  this.value = value;
@@ -532,9 +537,8 @@ function newFragmentField(parentInstance, key) {
532
537
  function newListFieldState(parentCopy, parentInstance, parentState, key, rules, listConfig, config, strictOrder, maybeAutoSave, deepExhaustive) {
533
538
  const rowMap = /* @__PURE__ */ new Map();
534
539
  const addedRows = /* @__PURE__ */ new Set();
535
- const _tick = observable({ value: 1 });
536
- const _originalValueTick = observable({ value: 1 });
537
- const _childTick = observable({ value: 1 });
540
+ const valueAtom = createAtom(`${String(key)}.value`);
541
+ const originalValueAtom = createAtom(`${String(key)}.originalValue`);
538
542
  const copyMap = /* @__PURE__ */ new Map();
539
543
  (parentCopy[key] ?? []).forEach((copy, i) => {
540
544
  copyMap.set(copy, parentInstance[key][i]);
@@ -558,7 +562,9 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
558
562
  const list = {
559
563
  key,
560
564
  get value() {
561
- return _tick.value > 0 && _childTick.value > 0 ? parentInstance[key] ?? [] : fail();
565
+ this.rows.forEach((r) => r.value);
566
+ valueAtom.reportObserved();
567
+ return parentInstance[key] ?? [];
562
568
  },
563
569
  _kind: "list",
564
570
  _focused: false,
@@ -602,7 +608,7 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
602
608
  else return !original.every((e) => current.includes(copyMap.get(e)));
603
609
  },
604
610
  get rows() {
605
- if (_tick.value < 0) fail();
611
+ valueAtom.reportObserved();
606
612
  return (parentInstance[key] || []).map((child) => getOrCreateChildState(child, { skipSet: true }));
607
613
  },
608
614
  get touched() {
@@ -613,18 +619,13 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
613
619
  },
614
620
  rules,
615
621
  get valid() {
616
- const opts = {
617
- value: this.rows,
618
- key,
619
- originalValue: this.originalValue,
620
- object: parentState()
621
- };
622
- const collectionValid = this.rules.every((r) => r(opts) === void 0);
622
+ const collectionValid = this.errors.length === 0;
623
623
  const entriesValid = this.rows.filter((r) => !r._considerDeleted()).every((r) => r.valid);
624
624
  return collectionValid && entriesValid;
625
625
  },
626
+ /** Returns the errors from our own collection-level rules (not the rows' errors). */
626
627
  get errors() {
627
- if (_tick.value < 0) fail();
628
+ valueAtom.reportObserved();
628
629
  const opts = {
629
630
  value: this.rows,
630
631
  key,
@@ -691,11 +692,11 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
691
692
  mergedItems.push(childState.value);
692
693
  }
693
694
  parentInstance[key] = mergedItems;
694
- _tick.value++;
695
+ valueAtom.reportChanged();
695
696
  this.setOriginalValue(incomingItems);
696
697
  } else {
697
698
  parentInstance[key] = (values ?? []).map((child) => getOrCreateChildState(child, opts).value);
698
- _tick.value++;
699
+ valueAtom.reportChanged();
699
700
  if (opts.refreshing) this.setOriginalValue();
700
701
  }
701
702
  },
@@ -705,7 +706,7 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
705
706
  addedRows.add(value);
706
707
  this.ensureSet();
707
708
  this.value.splice(typeof spliceIndex === "number" ? spliceIndex : this.value.length, 0, childState.value);
708
- _tick.value++;
709
+ valueAtom.reportChanged();
709
710
  maybeAutoSave();
710
711
  },
711
712
  remove(indexOrValue) {
@@ -715,7 +716,7 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
715
716
  const index = this.value.findIndex((v) => v === indexOrValue);
716
717
  if (index > -1) this.value.splice(index, 1);
717
718
  }
718
- _tick.value++;
719
+ valueAtom.reportChanged();
719
720
  maybeAutoSave();
720
721
  },
721
722
  revertChanges() {
@@ -727,26 +728,29 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
727
728
  this.rows.forEach((r) => r.commitChanges());
728
729
  this.setOriginalValue();
729
730
  this.touched = false;
730
- _tick.value++;
731
+ valueAtom.reportChanged();
732
+ },
733
+ /** Disposes every row's ObjectState, including rows that were removed from the list. */
734
+ dispose() {
735
+ new Set(rowMap.values()).forEach((row) => row.dispose());
731
736
  },
732
737
  get originalValue() {
733
- return (_originalValueTick.value > -1 ? parentCopy[key] : parentCopy[key]) ?? [];
738
+ originalValueAtom.reportObserved();
739
+ return parentCopy[key] ?? [];
734
740
  },
735
741
  setOriginalValue(incomingItems) {
736
742
  parentCopy[key] = incomingItems ?? this.rows.map((r) => r.originalValue);
737
743
  parentCopy[key].forEach((copy, i) => {
738
744
  copyMap.set(copy, parentInstance[key][i]);
739
745
  });
740
- _originalValueTick.value++;
746
+ originalValueAtom.reportChanged();
741
747
  },
742
748
  ensureSet() {
743
749
  if (!parentInstance[key]) parentInstance[key] = [];
744
- _tick.value++;
750
+ valueAtom.reportChanged();
745
751
  }
746
752
  };
747
- const proxy = makeAutoObservable(list, { value: computed({ equals: () => false }) });
748
- reaction(() => proxy.rows.map((r) => r.value), () => _childTick.value++);
749
- return proxy;
753
+ return makeAutoObservable(list, { value: computed({ equals: () => false }) });
750
754
  }
751
755
  function hashValue(value) {
752
756
  return hash(value);
@@ -795,7 +799,6 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
795
799
  else throw new Error(`Invalid type value ${config.type}`);
796
800
  return [key, field];
797
801
  });
798
- const _tick = observable({ value: 1 });
799
802
  const fieldNames = Object.keys(objectConfig);
800
803
  function getFields(proxyThis) {
801
804
  return fieldNames.map((name) => proxyThis[name]);
@@ -804,7 +807,7 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
804
807
  ...Object.fromEntries(fieldStates),
805
808
  key,
806
809
  get value() {
807
- _tick.value > 0 || fail();
810
+ getFields(this).forEach((f) => f.value);
808
811
  return instance;
809
812
  },
810
813
  set value(value) {
@@ -892,6 +895,9 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
892
895
  if (idField && idField.value !== void 0) result[idField.key] = idField.value;
893
896
  return result;
894
897
  },
898
+ dispose() {
899
+ getFields(this).forEach((f) => f.dispose());
900
+ },
895
901
  get originalValue() {
896
902
  getFields(proxy).map((f) => f.originalValue);
897
903
  return originalCopy;
@@ -914,7 +920,6 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
914
920
  value: computed({ equals: () => false }),
915
921
  originalValue: computed({ equals: () => false })
916
922
  });
917
- reaction(() => getFields(proxy).map((f) => f.value), () => _tick.value++);
918
923
  return proxy;
919
924
  }
920
925
  //#endregion
@@ -948,7 +953,7 @@ const f = {
948
953
  },
949
954
  /** A shorthand for creating a computed value. */
950
955
  computed() {
951
- return this.value().computed();
956
+ return f.value().computed();
952
957
  }
953
958
  };
954
959
  /** Provides a fluent DSL for building up an object's config. */
@@ -974,9 +979,9 @@ var ValueFieldConfigBuilder = class {
974
979
  type: "value",
975
980
  rules: []
976
981
  };
977
- /** Marks the field as required. */
982
+ /** Marks the field as required, keeping any rules that were already added. */
978
983
  req() {
979
- this.config.rules = [required];
984
+ (this.config.rules ??= []).push(required);
980
985
  return this;
981
986
  }
982
987
  /** Marks the field as read only. */
@@ -1047,7 +1052,14 @@ var ListFieldConfigBuilder = class {
1047
1052
  function useFormStates(opts) {
1048
1053
  const { config, autoSave, getId, map, addRules, readOnly = false } = opts;
1049
1054
  const objectStateCache = useMemo(() => ({}), [config]);
1055
+ const prevCacheRef = useRef(objectStateCache);
1056
+ useEffect(() => {
1057
+ const prevCache = prevCacheRef.current;
1058
+ if (prevCache !== objectStateCache) Object.values(prevCache).forEach((entry) => entry[0].dispose());
1059
+ prevCacheRef.current = objectStateCache;
1060
+ }, [objectStateCache]);
1050
1061
  const pendingAutoSaves = useRef(/* @__PURE__ */ new Set());
1062
+ const isAutoSaving = useRef(false);
1051
1063
  const autoSaveRef = useRef(autoSave);
1052
1064
  autoSaveRef.current = autoSave;
1053
1065
  const readOnlyRef = useRef(readOnly);
@@ -1062,19 +1074,17 @@ function useFormStates(opts) {
1062
1074
  return;
1063
1075
  }
1064
1076
  const { current: pending } = pendingAutoSaves;
1065
- if (isAutoSaving) {
1077
+ if (isAutoSaving.current) {
1066
1078
  pending.add(form);
1067
1079
  return;
1068
1080
  }
1069
1081
  try {
1070
- isAutoSaving = true;
1082
+ isAutoSaving.current = true;
1071
1083
  await new Promise((resolve) => setTimeout(resolve, 0));
1072
1084
  pending.delete(form);
1073
1085
  await autoSaveRef.current(form);
1074
- } catch (e) {
1075
- throw e;
1076
1086
  } finally {
1077
- isAutoSaving = false;
1087
+ isAutoSaving.current = false;
1078
1088
  if (pending.size > 0) {
1079
1089
  const first = pending.values().next().value;
1080
1090
  pending.delete(first);
@@ -1102,7 +1112,6 @@ function useFormStates(opts) {
1102
1112
  return form;
1103
1113
  }, [objectStateCache, config]) };
1104
1114
  }
1105
- let isAutoSaving = false;
1106
1115
  //#endregion
1107
1116
  export { createObjectState, f, fragment, required, useFormState, useFormStates };
1108
1117
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["hasToJSON","isAutoSaving"],"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 return a.equals(b);\n}\n\nfunction isSupportedTemporalTag(value: unknown): value is SupportedTemporalTag {\n return value === temporalPlainDateTag || value === temporalZonedDateTimeTag;\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])) break;\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\nfunction 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// 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.\nlet 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.\nlet pendingAutoSave = false;\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 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 let maybeError: undefined | string;\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 } catch (e) {\n maybeError = String(e);\n throw e;\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 // 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 } 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>\n | ListFieldConfig<any>\n | ValueFieldConfig<any>\n | 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\nexport function hasToJSON(o?: unknown): o is { toJSON(): void } {\n return !!(o && typeof o === \"object\" && \"toJSON\" in o);\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 { isObservable, observable, 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, fail, 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 /** Returns a subset of V with only the changed values. Currently not observable. */\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\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],\n // which itself is not a proxy, we use this as our \"value changed\" trigger.\n const _tick = observable({ value: 1 });\n const _originalValueTick = observable({ value: 1 });\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\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 get reactivity from parentInstance[key]\n const value = _tick.value > 0 ? parentInstance[key] : fail();\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 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 const opts = { value: this.value, key: key as string, originalValue: this.originalValue, object: parentState() };\n return this.rules.every((r) => r(opts as any) === undefined);\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 (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 && (value === null || value === 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 value !== this.originalValue;\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, value, 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 // 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 hasInflightChangedValue = false;\n\n // Set the value on our parent object\n const changed = !areEqual(newValue, this.value, strictOrder);\n parentInstance[key] = newValue!;\n _tick.value++;\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 get originalValue(): V {\n // A dummy check to for reactivity around our non-proxy value\n const value = _originalValueTick.value > -1 ? parentCopy[key] : 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 _originalValueTick.value++;\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 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 { makeAutoObservable, observable } from \"mobx\";\nimport { type FieldStateInternal } from \"src/fields/valueField\";\nimport { fail } from \"src/utils\";\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 // We always return the same `instance` field from our `value` method, but\n // we want to pretend that it's observable, so use a tick to force it.\n const _tick = observable({ value: 1 });\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\n get value() {\n // Watch for our parentInstance changing\n if (key in parentInstance) {\n value = parentInstance[key];\n delete parentInstance[key];\n }\n _tick.value > 0 || fail();\n return value;\n },\n\n set value(v: T[K]) {\n value = v;\n _tick.value++;\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, makeAutoObservable, observable, reaction } from \"mobx\";\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 { fail, groupBy, isNotUndefined, normalizeHashValue } from \"src/utils\";\nimport hash from \"object-hash\";\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 const _tick = observable({ value: 1 });\n const _originalValueTick = observable({ value: 1 });\n const _childTick = observable({ value: 1 });\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 return _tick.value > 0 && _childTick.value > 0 ? ((parentInstance[key] ?? []) as any as U[]) : fail();\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 // It's unclear why we need to access _tick.value here, b/c calling `this.value` should\n // transitively register us as a dependency on it\n if (_tick.value < 0) fail();\n // Avoid using `this.value` to avoid registering `_childTick` as a dependency\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 value = this.rows;\n // TODO Passing `originalCopy || []` is probably not 100% right\n const opts = { value, key: key as string, originalValue: this.originalValue, object: parentState() };\n const collectionValid = this.rules.every((r) => r(opts as any) === undefined);\n const entriesValid = this.rows.filter((r) => !(r as any)._considerDeleted()).every((r) => r.valid);\n return collectionValid && entriesValid;\n },\n\n get errors(): string[] {\n if (_tick.value < 0) fail();\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 _tick.value++;\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 _tick.value++;\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 _tick.value++;\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 _tick.value++;\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 _tick.value++;\n },\n\n get originalValue(): U[] {\n // A dummy check to for reactivity around our non-proxy value\n const value = _originalValueTick.value > -1 ? parentCopy[key] : 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 _originalValueTick.value++;\n },\n\n ensureSet() {\n if (!parentInstance[key]) {\n (parentInstance as any)[key] = [];\n }\n _tick.value++;\n },\n };\n\n const proxy = makeAutoObservable(list, {\n // See other makeAutoObservable comment\n value: computed({ equals: () => false }),\n }) as any;\n\n // Any time a row's value changes, percolate that to our `.value` (so the callers to our\n // `.value` will rerun given the value they saw has deeply changed.)\n reaction(\n () => proxy.rows.map((r: any) => r.value),\n () => _childTick.value++,\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, observable, reaction } 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 type SetOpts,\n newValueFieldState,\n} from \"src/fields/valueField\";\nimport { areEqual, type Builtin, deepClone, fail } 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>\n | ObjectFieldConfig<any>\n | ListFieldConfig<any>\n | 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 // We always return the same `instance` field from our `value` method, but\n // we want to pretend that it's observable, so use a tick to force it.\n const _tick = observable({ value: 1 });\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 _tick.value > 0 || fail();\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 // asdf\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 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 // Any time a field changes, percolate that change up to us\n reaction(\n () => getFields(proxy).map((f) => f.value),\n () => _tick.value++,\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 this.value().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. */\n req(): this {\n this.config.rules = [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, 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 // 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 // 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) {\n pending.add(form);\n return;\n }\n let maybeError: undefined | string;\n try {\n isAutoSaving = 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 } catch (e) {\n maybeError = String(e);\n throw e;\n } finally {\n isAutoSaving = 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\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.\nlet isAutoSaving = false;\n"],"mappings":"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"}
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/form-state",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,14 +28,10 @@
28
28
  "lint": "oxlint --type-aware",
29
29
  "lint:fix": "oxlint --type-aware --fix",
30
30
  "storybook": "storybook dev -p 9000",
31
- "format": "prettier --loglevel warn --write \"**/*.{ts,tsx,css,md}\"",
31
+ "format": "prettier --log-level warn --write \"**/*.{ts,tsx,css,md}\"",
32
+ "prepare": "husky",
32
33
  "semantic-release": "semantic-release"
33
34
  },
34
- "husky": {
35
- "hooks": {
36
- "pre-commit": "lint-staged --allow-empty"
37
- }
38
- },
39
35
  "lint-staged": {
40
36
  "src/**/*.{ts,tsx,css,md}": "prettier --write"
41
37
  },
@@ -43,46 +39,45 @@
43
39
  "@types/object-hash": "^3.0.6",
44
40
  "is-plain-object": "^5.1.0",
45
41
  "object-hash": "^3.0.0",
46
- "temporal-polyfill": "^0.3.2"
42
+ "temporal-polyfill": "^1.0.4"
47
43
  },
48
44
  "peerDependencies": {
49
45
  "mobx": ">=6",
50
46
  "react": ">=16"
51
47
  },
52
48
  "devDependencies": {
53
- "@homebound/eslint-config": "^4.0.0",
49
+ "@homebound/eslint-config": "^5.0.0",
54
50
  "@homebound/rtl-utils": "^3.0.2",
55
51
  "@homebound/tsconfig": "^1.2.0",
56
- "@semantic-release/exec": "^6.0.3",
57
- "@semantic-release/git": "^10.0.1",
58
- "@storybook/addon-essentials": "^8.6.14",
59
- "@storybook/addon-links": "^8.6.14",
60
- "@storybook/react": "^8.6.14",
61
- "@storybook/react-vite": "^8.6.14",
62
- "@storybook/types": "^8.6.14",
52
+ "@semantic-release/exec": "^7.1.0",
53
+ "@semantic-release/git": "^11.0.1",
54
+ "@storybook/addon-docs": "^10.6.0",
55
+ "@storybook/addon-links": "^10.6.0",
56
+ "@storybook/react": "^10.6.0",
57
+ "@storybook/react-vite": "^10.6.0",
63
58
  "@testing-library/dom": "^10.4.1",
64
59
  "@testing-library/jest-dom": "^7.0.1",
65
60
  "@testing-library/react": "^16.3.3",
66
61
  "@types/jsdom": "^30",
67
62
  "@types/react": "^19.2.18",
68
63
  "@types/react-dom": "^19.2.7",
69
- "conventional-changelog-conventionalcommits": "^8.0.0",
70
- "husky": "^3.1.0",
64
+ "conventional-changelog-conventionalcommits": "^9.3.1",
65
+ "husky": "^9.1.7",
71
66
  "jsdom": "^30.0.1",
72
- "lint-staged": "^15.2.10",
67
+ "lint-staged": "^17.5.0",
73
68
  "mobx": "^7.0.3",
74
69
  "mobx-react": "^10.0.2",
75
- "oxlint": "~1.78.0",
70
+ "oxlint": "^1.82.0",
76
71
  "oxlint-tsgolint": "^7.0.2001",
77
- "prettier": "3.3.3",
78
- "prettier-plugin-organize-imports": "^4.1.0",
72
+ "prettier": "^3.9.6",
73
+ "prettier-plugin-organize-imports": "^4.3.0",
79
74
  "react": "^19.2.8",
80
75
  "react-dom": "^19.2.8",
81
- "semantic-release": "^24.1.2",
82
- "storybook": "^8.6.14",
76
+ "semantic-release": "^25.0.9",
77
+ "storybook": "^10.6.0",
83
78
  "tsdown": "^0.23.0",
84
79
  "typescript": "^6.0.3",
85
- "vite": "^6.4.3",
80
+ "vite": "^8.2.2",
86
81
  "vitest": "^5.0.0"
87
82
  },
88
83
  "packageManager": "yarn@4.18.0"
@@ -96,16 +96,16 @@ Enums and nested ids map to `"value"` fields holding the `.code`/`.id` string
96
96
 
97
97
  ### `type: "value"` options (all optional)
98
98
 
99
- | option | meaning | notes |
100
- | ------------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
101
- | `rules: [required, ...]` | validation rules (see [Validation](#validation)) | the common case |
102
- | `readOnly: boolean` | field starts read-only; can be flipped at runtime | often `readOnly: !canEdit` |
103
- | `computed: true` | backed by a MobX computed; excluded from `reset` | for derived/aggregate fields |
104
- | `isDeleteKey: true` | this bool marks a list row as deleted (excluded from list validity) | e.g. `delete: { type: "value", isDeleteKey: true }` |
105
- | `isReadOnlyKey: true` | truthy value makes the whole containing entity read-only | rare |
106
- | `isIdKey: boolean` | marks the id field (auto-true for `id`); always kept in `changedValue` | rely on the default for a field named `id` |
99
+ | option | meaning | notes |
100
+ | ------------------------ | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
101
+ | `rules: [required, ...]` | validation rules (see [Validation](#validation)) | the common case |
102
+ | `readOnly: boolean` | field starts read-only; can be flipped at runtime | often `readOnly: !canEdit` |
103
+ | `computed: true` | backed by a MobX computed; excluded from `reset` | for derived/aggregate fields |
104
+ | `isDeleteKey: true` | this bool marks a list row as deleted (excluded from list validity) | e.g. `delete: { type: "value", isDeleteKey: true }` |
105
+ | `isReadOnlyKey: true` | truthy value makes the whole containing entity read-only | rare |
106
+ | `isIdKey: boolean` | marks the id field (auto-true for `id`); always kept in `changedValue` | rely on the default for a field named `id` |
107
107
  | `isLocalOnly: true` | ignore in dirty checks / never send | for FE-only fields; alternatively add them to `T` and destructure out of `changedValue` |
108
- | `strictOrder: false` | array order doesn't affect dirty/changed | rare |
108
+ | `strictOrder: false` | array order doesn't affect dirty/changed | rare |
109
109
 
110
110
  ### `type: "list"` (nested collections)
111
111