@homebound/form-state 2.27.3 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.d.ts CHANGED
@@ -28,6 +28,8 @@ export type ValueFieldConfig<V> = {
28
28
  isDeleteKey?: boolean;
29
29
  /** If true, the entity that contains this value will be treated as read only. */
30
30
  isReadOnlyKey?: boolean;
31
+ /** If true, we ignore field in dirty checks. */
32
+ isLocalOnly?: boolean;
31
33
  /**
32
34
  * Marks a field as being backed by a mobx class computed field.
33
35
  *
@@ -47,6 +47,8 @@ export declare class ValueFieldConfigBuilder<V> {
47
47
  req(): this;
48
48
  /** Marks the field as read only. */
49
49
  readOnly(): this;
50
+ /** Marks the field as local only. */
51
+ localOnly(): this;
50
52
  /** Appends `rules` to the field's validation rules. */
51
53
  rules(rules: Rule<V | null | undefined>[]): this;
52
54
  /** Marks the field as computed. */
@@ -63,6 +63,11 @@ class ValueFieldConfigBuilder {
63
63
  this.config.readOnly = true;
64
64
  return this;
65
65
  }
66
+ /** Marks the field as local only. */
67
+ localOnly() {
68
+ this.config.isLocalOnly = true;
69
+ return this;
70
+ }
66
71
  /** Appends `rules` to the field's validation rules. */
67
72
  rules(rules) {
68
73
  var _a;
@@ -16,6 +16,7 @@ function newFragmentField(parentInstance, key) {
16
16
  _isIdKey: false,
17
17
  _isDeleteKey: false,
18
18
  _isReadOnlyKey: false,
19
+ _isLocalOnly: false,
19
20
  touched: false,
20
21
  valid: true,
21
22
  readOnly: true,
@@ -50,7 +50,7 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
50
50
  // Default the id key to "id" unless some other field has isIdKey set
51
51
  (key === "id" &&
52
52
  !("isIdKey" in config) && // Allow setting `isIdKey=false`
53
- !Object.entries(objectConfig).some(([other, c]) => other !== key && c.isIdKey)), config.isDeleteKey || false, config.isReadOnlyKey || false, (_a = config.computed) !== null && _a !== void 0 ? _a :
53
+ !Object.entries(objectConfig).some(([other, c]) => other !== key && c.isIdKey)), config.isDeleteKey || false, config.isReadOnlyKey || false, config.isLocalOnly || false, (_a = config.computed) !== null && _a !== void 0 ? _a :
54
54
  // If instance is a mobx class, we can detect computeds as they won't be enumerable
55
55
  ((0, mobx_1.isObservable)(instance) ? !(key in originalCopy) : false), (_b = config.readOnly) !== null && _b !== void 0 ? _b : false, (_c = config.strictOrder) !== null && _c !== void 0 ? _c : true, maybeAutoSave);
56
56
  }
@@ -138,7 +138,7 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
138
138
  return getFields(this).flatMap((f) => f.errors.map((e) => `${f.key}: ${e}`));
139
139
  },
140
140
  get dirty() {
141
- return (getFields(this).some((f) => f.dirty) ||
141
+ return (getFields(this).some((f) => f.dirty && !f._isLocalOnly) ||
142
142
  // `isUnset` checks if our `parent[key] === undefined`, which can mean "surely we're dirty",
143
143
  // but as long as we've got some keys actually set
144
144
  (this.isUnset() && !(0, utils_1.areEqual)(this.value, {})));
@@ -212,6 +212,9 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
212
212
  // References only include the id key below
213
213
  if (config.reference)
214
214
  return;
215
+ // Ignore local-only fields
216
+ if (f._isLocalOnly)
217
+ return;
215
218
  if (f.dirty ||
216
219
  // If the caller used useFormState.ifUndefined to provide some default values, then those keys may not
217
220
  // look dirty, but if we're new we should include them anyway.
@@ -70,5 +70,6 @@ export interface FieldStateInternal<T, V> extends FieldState<V> {
70
70
  _isIdKey: boolean;
71
71
  _isDeleteKey: boolean;
72
72
  _isReadOnlyKey: boolean;
73
+ _isLocalOnly: boolean;
73
74
  }
74
- export declare function newValueFieldState<T, K extends keyof T>(parentCopy: T, parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<T[K] | null | undefined>[], isIdKey: boolean, isDeleteKey: boolean, isReadOnlyKey: boolean, computed: boolean, readOnly: boolean, strictOrder: boolean, maybeAutoSave: () => void): FieldState<T[K] | null | undefined>;
75
+ export declare function newValueFieldState<T, K extends keyof T>(parentCopy: T, parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<T[K] | null | undefined>[], isIdKey: boolean, isDeleteKey: boolean, isReadOnlyKey: boolean, isLocalOnly: boolean, computed: boolean, readOnly: boolean, strictOrder: boolean, maybeAutoSave: () => void): FieldState<T[K] | null | undefined>;
@@ -6,7 +6,7 @@ const mobx_1 = require("mobx");
6
6
  const proxies_1 = require("../proxies");
7
7
  const rules_1 = require("../rules");
8
8
  const utils_1 = require("../utils");
9
- function newValueFieldState(parentCopy, parentInstance, parentState, key, rules, isIdKey, isDeleteKey, isReadOnlyKey, computed, readOnly, strictOrder, maybeAutoSave) {
9
+ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules, isIdKey, isDeleteKey, isReadOnlyKey, isLocalOnly, computed, readOnly, strictOrder, maybeAutoSave) {
10
10
  // Because we read/write the value directly back into parentInstance[key],
11
11
  // which itself is not a proxy, we use this as our "value changed" trigger.
12
12
  const _tick = (0, mobx_1.observable)({ value: 1 });
@@ -31,6 +31,7 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
31
31
  _isIdKey: isIdKey,
32
32
  _isDeleteKey: isDeleteKey,
33
33
  _isReadOnlyKey: isReadOnlyKey,
34
+ _isLocalOnly: isLocalOnly,
34
35
  rules,
35
36
  get value() {
36
37
  // If we're wrapping a mobx store, then we'll get reactivity from parentInstance[key]
@@ -640,6 +640,33 @@ describe("formState", () => {
640
640
  a1.books.remove(0);
641
641
  expect(a1.dirty).toBeFalsy();
642
642
  });
643
+ it("can ignore isLocalOnly fields as not dirty", () => {
644
+ // Given we have an `isPublished` field that is marked as local only
645
+ const config = {
646
+ id: { type: "value" },
647
+ books: {
648
+ type: "list",
649
+ config: {
650
+ id: { type: "value" },
651
+ isPublished: { type: "value", isLocalOnly: true },
652
+ },
653
+ },
654
+ };
655
+ // And it starts out true
656
+ const a1 = (0, objectField_1.createObjectState)(config, {
657
+ id: "a:1",
658
+ books: [{ id: "b:1", isPublished: true }],
659
+ });
660
+ // When we change the value to false
661
+ a1.books.rows[0].isPublished.value = false;
662
+ // Then the field itself is dirty
663
+ expect(a1.books.rows[0].isPublished.dirty).toBe(true);
664
+ // But object is not
665
+ expect(a1.dirty).toBe(false);
666
+ expect(a1.books.dirty).toBe(false);
667
+ // And we don't include it in changedValue
668
+ expect(a1.changedValue).toEqual({ id: "a:1" });
669
+ });
643
670
  it("handles list fields with duplicate ids", () => {
644
671
  // Given an author that incorrectly has duplicate books
645
672
  const a1 = createAuthorInputState({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/form-state",
3
- "version": "2.27.3",
3
+ "version": "2.28.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",