@strictly/react-form 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.out/core/mobx/field_adapter_builder.d.ts +1 -1
  2. package/.out/core/mobx/form_model.d.ts +9 -6
  3. package/.out/core/mobx/form_model.js +77 -42
  4. package/.out/core/mobx/specs/form_model.tests.js +80 -20
  5. package/.out/core/mobx/types.d.ts +4 -4
  6. package/.out/core/props.d.ts +2 -0
  7. package/.out/index.d.ts +0 -1
  8. package/.out/index.js +0 -1
  9. package/.out/mantine/create_field_view.d.ts +20 -0
  10. package/.out/mantine/create_field_view.js +54 -0
  11. package/.out/mantine/create_list.js +3 -2
  12. package/.out/mantine/hooks.d.ts +4 -1
  13. package/.out/mantine/hooks.js +14 -2
  14. package/.out/mantine/specs/field_view_hooks.stories.d.ts +12 -0
  15. package/.out/mantine/specs/field_view_hooks.stories.js +61 -0
  16. package/.out/mantine/specs/field_view_hooks.tests.d.ts +1 -0
  17. package/.out/mantine/specs/field_view_hooks.tests.js +12 -0
  18. package/.out/tsconfig.tsbuildinfo +1 -1
  19. package/.turbo/turbo-build.log +8 -8
  20. package/.turbo/turbo-check-types.log +1 -1
  21. package/core/mobx/field_adapter_builder.ts +2 -2
  22. package/core/mobx/form_model.ts +89 -47
  23. package/core/mobx/specs/form_model.tests.ts +131 -11
  24. package/core/mobx/types.ts +4 -4
  25. package/core/props.ts +4 -0
  26. package/dist/index.cjs +165 -89
  27. package/dist/index.d.cts +45 -40
  28. package/dist/index.d.ts +45 -40
  29. package/dist/index.js +162 -81
  30. package/index.ts +0 -1
  31. package/mantine/create_field_view.tsx +94 -0
  32. package/mantine/create_list.tsx +9 -2
  33. package/mantine/hooks.tsx +19 -2
  34. package/mantine/specs/__snapshots__/field_view_hooks.tests.tsx.snap +153 -0
  35. package/mantine/specs/field_view_hooks.stories.tsx +112 -0
  36. package/mantine/specs/field_view_hooks.tests.tsx +15 -0
  37. package/package.json +1 -1
  38. package/.out/mantine/field_view.d.ts +0 -18
  39. package/.out/mantine/field_view.js +0 -16
  40. package/mantine/field_view.tsx +0 -39
@@ -21,5 +21,5 @@ export declare function adapterFromPrototype<From, To, ValuePath extends string,
21
21
  export declare function adapterFromPrototype<From, To, E, ValuePath extends string, Context>(converter: TwoWayFieldConverter<From, To, E, ValuePath, Context>, prototype: From): FieldAdapterBuilder<From, To, E, ValuePath, Context>;
22
22
  export declare function identityAdapter<V, ValuePath extends string, Context>(prototype: V, required?: boolean): FieldAdapterBuilder<V, V, never, ValuePath, Context>;
23
23
  export declare function trimmingStringAdapter<ValuePath extends string, Context>(): FieldAdapterBuilder<string, string, never, ValuePath, Context>;
24
- export declare function listAdapter<E, ValuePath extends string, Context>(): FieldAdapterBuilder<readonly E[], readonly E[], never, ValuePath, Context>;
24
+ export declare function listAdapter<E, ValuePath extends string = string, Context = unknown>(): FieldAdapterBuilder<readonly E[], readonly E[], never, ValuePath, Context>;
25
25
  export {};
@@ -1,5 +1,6 @@
1
1
  import { type ElementOfArray, type Maybe } from '@strictly/base';
2
2
  import { type Accessor, type FlattenedValuesOfType, type MobxValueOfType, type ReadonlyTypeOfType, type Type, type ValueOfType } from '@strictly/define';
3
+ import { type FormMode } from 'core/props';
3
4
  import { type ReadonlyDeep, type SimplifyDeep, type StringKeyOf, type UnionToIntersection, type ValueOf } from 'type-fest';
4
5
  import { type Field } from 'types/field';
5
6
  import { type ContextOfFieldAdapter, type ErrorOfFieldAdapter, type FieldAdapter, type ToOfFieldAdapter } from './field_adapter';
@@ -21,18 +22,20 @@ export type ValuePathsToAdaptersOf<TypePathsToAdapters extends Partial<Readonly<
21
22
  readonly [K in keyof ValuePathsToTypePaths as unknown extends TypePathsToAdapters[ValuePathsToTypePaths[K]] ? never : K]: NonNullable<TypePathsToAdapters[ValuePathsToTypePaths[K]]>;
22
23
  } : never;
23
24
  export type ContextOf<TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>> = UnionToIntersection<{
24
- readonly [K in keyof TypePathsToAdapters]: TypePathsToAdapters[K] extends undefined ? undefined : ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>>;
25
- }[keyof TypePathsToAdapters]>;
25
+ readonly [K in keyof TypePathsToAdapters]: TypePathsToAdapters[K] extends undefined ? undefined : unknown extends ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>> ? never : ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>>;
26
+ }[keyof TypePathsToAdapters] | {}>;
26
27
  export declare abstract class FormModel<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ContextType>, ContextType = ContextOf<TypePathsToAdapters>, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
27
28
  readonly type: T;
29
+ private readonly originalValue;
28
30
  protected readonly adapters: TypePathsToAdapters;
31
+ protected readonly mode: FormMode;
29
32
  accessor value: MobxValueOfType<T>;
30
33
  accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>;
31
34
  accessor errors: FlattenedErrors<ValuePathsToAdapters>;
32
35
  private readonly flattenedTypeDefs;
33
- constructor(type: T, value: ValueOfType<ReadonlyTypeOfType<T>>, adapters: TypePathsToAdapters);
34
- get context(): ContextType;
35
- protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>): ContextType;
36
+ constructor(type: T, originalValue: ValueOfType<ReadonlyTypeOfType<T>>, adapters: TypePathsToAdapters, mode: FormMode);
37
+ protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>, valuePath: keyof ValuePathsToAdapters): ContextType;
38
+ get forceMutableFields(): boolean;
36
39
  get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>;
37
40
  private get knownFields();
38
41
  private maybeSynthesizeFieldByValuePath;
@@ -52,6 +55,6 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
52
55
  clearAll(value: ValueOfType<T>): void;
53
56
  isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
54
57
  validateField<K extends keyof ValuePathsToAdapters>(valuePath: K, ignoreDefaultValue?: boolean): boolean;
55
- validateAll(): boolean;
58
+ validateAll(force?: boolean): boolean;
56
59
  }
57
60
  export {};
@@ -49,7 +49,7 @@ import { computed, observable, runInAction, } from 'mobx';
49
49
  import { UnreliableFieldConversionType, } from 'types/field_converters';
50
50
  let FormModel = (() => {
51
51
  var _a, _FormModel_value_accessor_storage, _FormModel_fieldOverrides_accessor_storage, _FormModel_errors_accessor_storage;
52
- var _b, _c, _d, _e;
52
+ var _b, _c, _d;
53
53
  let _instanceExtraInitializers = [];
54
54
  let _value_decorators;
55
55
  let _value_initializers = [];
@@ -60,7 +60,6 @@ let FormModel = (() => {
60
60
  let _errors_decorators;
61
61
  let _errors_initializers = [];
62
62
  let _errors_extraInitializers = [];
63
- let _get_context_decorators;
64
63
  let _get_fields_decorators;
65
64
  let _get_knownFields_decorators;
66
65
  let _get_accessors_decorators;
@@ -71,19 +70,31 @@ let FormModel = (() => {
71
70
  set fieldOverrides(value) { __classPrivateFieldSet(this, _FormModel_fieldOverrides_accessor_storage, value, "f"); }
72
71
  get errors() { return __classPrivateFieldGet(this, _FormModel_errors_accessor_storage, "f"); }
73
72
  set errors(value) { __classPrivateFieldSet(this, _FormModel_errors_accessor_storage, value, "f"); }
74
- constructor(type, value, adapters) {
73
+ constructor(type, originalValue, adapters, mode) {
75
74
  Object.defineProperty(this, "type", {
76
75
  enumerable: true,
77
76
  configurable: true,
78
77
  writable: true,
79
78
  value: (__runInitializers(this, _instanceExtraInitializers), type)
80
79
  });
80
+ Object.defineProperty(this, "originalValue", {
81
+ enumerable: true,
82
+ configurable: true,
83
+ writable: true,
84
+ value: originalValue
85
+ });
81
86
  Object.defineProperty(this, "adapters", {
82
87
  enumerable: true,
83
88
  configurable: true,
84
89
  writable: true,
85
90
  value: adapters
86
91
  });
92
+ Object.defineProperty(this, "mode", {
93
+ enumerable: true,
94
+ configurable: true,
95
+ writable: true,
96
+ value: mode
97
+ });
87
98
  _FormModel_value_accessor_storage.set(this, __runInitializers(this, _value_initializers, void 0));
88
99
  _FormModel_fieldOverrides_accessor_storage.set(this, (__runInitializers(this, _value_extraInitializers), __runInitializers(this, _fieldOverrides_initializers, void 0)));
89
100
  _FormModel_errors_accessor_storage.set(this, (__runInitializers(this, _fieldOverrides_extraInitializers), __runInitializers(this, _errors_initializers, {})));
@@ -93,12 +104,13 @@ let FormModel = (() => {
93
104
  writable: true,
94
105
  value: __runInitializers(this, _errors_extraInitializers)
95
106
  });
96
- this.value = mobxCopy(type, value);
107
+ this.value = mobxCopy(type, originalValue);
97
108
  this.flattenedTypeDefs = flattenTypesOfType(type);
98
- const contextValue = this.toContext(value);
99
109
  // pre-populate field overrides for consistent behavior when default information is overwritten
100
110
  // then returned to
101
- const conversions = flattenValueTo(type, this.value, () => { }, (_t, value, _setter, typePath, valuePath) => {
111
+ const conversions = flattenValueTo(type, this.value, () => { }, (_t, fieldValue, _setter, typePath, valuePath) => {
112
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
113
+ const contextValue = this.toContext(originalValue, valuePath);
102
114
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
103
115
  const adapter = this.adapters[typePath];
104
116
  if (adapter == null) {
@@ -110,15 +122,22 @@ let FormModel = (() => {
110
122
  return;
111
123
  }
112
124
  // cannot call this.context yet as the "this" pointer has not been fully created
113
- return convert(value, valuePath, contextValue);
125
+ return convert(fieldValue, valuePath, contextValue);
114
126
  });
115
127
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
116
128
  this.fieldOverrides = map(conversions, function (_k, v) {
117
129
  return v && [v.value];
118
130
  });
119
131
  }
120
- get context() {
121
- return this.toContext(this.value);
132
+ get forceMutableFields() {
133
+ switch (this.mode) {
134
+ case 'create':
135
+ return true;
136
+ case 'edit':
137
+ return false;
138
+ default:
139
+ return this.mode;
140
+ }
122
141
  }
123
142
  get fields() {
124
143
  return new Proxy(this.knownFields, {
@@ -173,22 +192,23 @@ let FormModel = (() => {
173
192
  const accessor = this.getAccessorForValuePath(valuePath);
174
193
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
175
194
  const fieldTypeDef = this.flattenedTypeDefs[typePath];
195
+ const context = this.toContext(this.value, valuePath);
176
196
  const { value, required, readonly, } = convert(accessor != null
177
197
  ? accessor.value
178
198
  : fieldTypeDef != null
179
199
  ? mobxCopy(fieldTypeDef,
180
200
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
181
- create(valuePath, this.context))
201
+ create(valuePath, context))
182
202
  // fake values can't be copied
183
203
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
184
- : create(valuePath, this.context),
204
+ : create(valuePath, context),
185
205
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
186
- valuePath, this.context);
206
+ valuePath, context);
187
207
  const error = this.errors[valuePath];
188
208
  return {
189
209
  value: fieldOverride != null ? fieldOverride[0] : value,
190
210
  error,
191
- readonly,
211
+ readonly: readonly && !this.forceMutableFields,
192
212
  required,
193
213
  };
194
214
  }
@@ -235,7 +255,10 @@ let FormModel = (() => {
235
255
  ? elementValue[0]
236
256
  : elementAdapter.create(
237
257
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
238
- elementTypePath, this.context);
258
+ elementTypePath,
259
+ // TODO what can we use for the value path here?
260
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
261
+ this.toContext(this.value, valuePath));
239
262
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
263
  const originalList = accessor.value;
241
264
  const newList = [
@@ -346,7 +369,7 @@ let FormModel = (() => {
346
369
  const { revert } = this.getAdapterForValuePath(valuePath);
347
370
  assertExists(revert, 'setting value not supported {}', valuePath);
348
371
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
349
- const conversion = revert(value, valuePath, this.context);
372
+ const conversion = revert(value, valuePath, this.toContext(this.value, valuePath));
350
373
  const accessor = this.getAccessorForValuePath(valuePath);
351
374
  return runInAction(() => {
352
375
  this.fieldOverrides[valuePath] = [value];
@@ -384,8 +407,10 @@ let FormModel = (() => {
384
407
  return;
385
408
  }
386
409
  const { convert, create, } = adapter;
387
- const value = create(valuePath, this.context);
388
- const { value: displayValue, } = convert(value, valuePath, this.context);
410
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
411
+ const context = this.toContext(this.value, valuePath);
412
+ const value = create(valuePath, context);
413
+ const { value: displayValue, } = convert(value, valuePath, context);
389
414
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
390
415
  const key = valuePath;
391
416
  runInAction(() => {
@@ -410,25 +435,26 @@ let FormModel = (() => {
410
435
  const { convert, revert, create, } = this.getAdapterForValuePath(valuePath);
411
436
  const fieldOverride = this.fieldOverrides[valuePath];
412
437
  const accessor = this.getAccessorForValuePath(valuePath);
438
+ const context = this.toContext(this.value, valuePath);
413
439
  const { value: storedValue, } = convert(accessor != null
414
440
  ? accessor.value
415
441
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
416
- : create(valuePath, this.context),
442
+ : create(valuePath, context),
417
443
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
418
- valuePath, this.context);
444
+ valuePath, context);
419
445
  const value = fieldOverride != null
420
446
  ? fieldOverride[0]
421
447
  : storedValue;
422
448
  const dirty = storedValue !== value;
423
449
  assertExists(revert, 'changing field directly not supported {}', valuePath);
424
450
  if (ignoreDefaultValue) {
425
- const { value: defaultDisplayValue, } = convert(create(valuePath, this.context), valuePath, this.context);
451
+ const { value: defaultDisplayValue, } = convert(create(valuePath, context), valuePath, context);
426
452
  if (defaultDisplayValue === value) {
427
453
  return true;
428
454
  }
429
455
  }
430
456
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
431
- const conversion = revert(value, valuePath, this.context);
457
+ const conversion = revert(value, valuePath, context);
432
458
  return runInAction(() => {
433
459
  switch (conversion.type) {
434
460
  case UnreliableFieldConversionType.Failure:
@@ -448,11 +474,12 @@ let FormModel = (() => {
448
474
  }
449
475
  });
450
476
  }
451
- validateAll() {
477
+ validateAll(force = this.mode === 'create') {
452
478
  // sort keys shortest to longest so parent changes don't overwrite child changes
453
479
  const accessors = toArray(this.accessors).toSorted(function ([a], [b]) {
454
480
  return a.length - b.length;
455
481
  });
482
+ const flattenedOriginalValues = flattenValuesOfType(this.type, this.originalValue);
456
483
  return runInAction(() => {
457
484
  return accessors.reduce((success, [valuePath, accessor,]) => {
458
485
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -468,29 +495,39 @@ let FormModel = (() => {
468
495
  return success;
469
496
  }
470
497
  const fieldOverride = this.fieldOverrides[adapterPath];
471
- const { value: storedValue, } = convert(accessor.value, valuePath, this.context);
498
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
499
+ const context = this.toContext(this.value, valuePath);
500
+ const { value: storedValue, } = convert(accessor.value, valuePath, context);
472
501
  const value = fieldOverride != null
473
502
  ? fieldOverride[0]
474
503
  : storedValue;
475
- // TODO more nuanced comparison
504
+ // TODO customizable comparisons
476
505
  const dirty = fieldOverride != null && fieldOverride[0] !== storedValue;
477
- const conversion = revert(value, valuePath, this.context);
478
- switch (conversion.type) {
479
- case UnreliableFieldConversionType.Failure:
480
- this.errors[adapterPath] = conversion.error;
481
- if (conversion.value != null && dirty) {
482
- accessor.set(conversion.value[0]);
483
- }
484
- return false;
485
- case UnreliableFieldConversionType.Success:
486
- if (dirty) {
487
- accessor.set(conversion.value);
488
- }
489
- delete this.errors[adapterPath];
490
- return success;
491
- default:
492
- throw new UnreachableError(conversion);
506
+ const needsValidation = force
507
+ || !(valuePath in flattenedOriginalValues)
508
+ || storedValue !== convert(flattenedOriginalValues[valuePath], valuePath,
509
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
510
+ this.toContext(this.originalValue, valuePath)).value;
511
+ if (needsValidation) {
512
+ const conversion = revert(value, valuePath, context);
513
+ switch (conversion.type) {
514
+ case UnreliableFieldConversionType.Failure:
515
+ this.errors[adapterPath] = conversion.error;
516
+ if (conversion.value != null && dirty) {
517
+ accessor.set(conversion.value[0]);
518
+ }
519
+ return false;
520
+ case UnreliableFieldConversionType.Success:
521
+ if (dirty) {
522
+ accessor.set(conversion.value);
523
+ }
524
+ delete this.errors[adapterPath];
525
+ return success;
526
+ default:
527
+ throw new UnreachableError(conversion);
528
+ }
493
529
  }
530
+ return success;
494
531
  }, true);
495
532
  });
496
533
  }
@@ -503,14 +540,12 @@ let FormModel = (() => {
503
540
  _value_decorators = [(_b = observable).ref.bind(_b)];
504
541
  _fieldOverrides_decorators = [(_c = observable).shallow.bind(_c)];
505
542
  _errors_decorators = [(_d = observable).shallow.bind(_d)];
506
- _get_context_decorators = [(_e = computed).struct.bind(_e)];
507
543
  _get_fields_decorators = [computed];
508
544
  _get_knownFields_decorators = [computed];
509
545
  _get_accessors_decorators = [computed];
510
546
  __esDecorate(_a, null, _value_decorators, { kind: "accessor", name: "value", static: false, private: false, access: { has: obj => "value" in obj, get: obj => obj.value, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, _value_initializers, _value_extraInitializers);
511
547
  __esDecorate(_a, null, _fieldOverrides_decorators, { kind: "accessor", name: "fieldOverrides", static: false, private: false, access: { has: obj => "fieldOverrides" in obj, get: obj => obj.fieldOverrides, set: (obj, value) => { obj.fieldOverrides = value; } }, metadata: _metadata }, _fieldOverrides_initializers, _fieldOverrides_extraInitializers);
512
548
  __esDecorate(_a, null, _errors_decorators, { kind: "accessor", name: "errors", static: false, private: false, access: { has: obj => "errors" in obj, get: obj => obj.errors, set: (obj, value) => { obj.errors = value; } }, metadata: _metadata }, _errors_initializers, _errors_extraInitializers);
513
- __esDecorate(_a, null, _get_context_decorators, { kind: "getter", name: "context", static: false, private: false, access: { has: obj => "context" in obj, get: obj => obj.context }, metadata: _metadata }, null, _instanceExtraInitializers);
514
549
  __esDecorate(_a, null, _get_fields_decorators, { kind: "getter", name: "fields", static: false, private: false, access: { has: obj => "fields" in obj, get: obj => obj.fields }, metadata: _metadata }, null, _instanceExtraInitializers);
515
550
  __esDecorate(_a, null, _get_knownFields_decorators, { kind: "getter", name: "knownFields", static: false, private: false, access: { has: obj => "knownFields" in obj, get: obj => obj.knownFields }, metadata: _metadata }, null, _instanceExtraInitializers);
516
551
  __esDecorate(_a, null, _get_accessors_decorators, { kind: "getter", name: "accessors", static: false, private: false, access: { has: obj => "accessors" in obj, get: obj => obj.accessors }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -1,7 +1,8 @@
1
1
  import { expectDefinedAndReturn } from '@strictly/base';
2
- import { booleanType, list, nullType, numberType, object, record, stringType, union, } from '@strictly/define';
2
+ import { booleanType, flattenValidatorsOfValidatingType, list, nullType, numberType, object, record, stringType, union, } from '@strictly/define';
3
3
  import { adapterFromTwoWayConverter, identityAdapter, } from 'core/mobx/field_adapter_builder';
4
4
  import { FormModel, } from 'core/mobx/form_model';
5
+ import { mergeAdaptersWithValidators } from 'core/mobx/merge_field_adapters_with_validators';
5
6
  import { IntegerToStringConverter } from 'field_converters/integer_to_string_converter';
6
7
  import { NullableToBooleanConverter } from 'field_converters/nullable_to_boolean_converter';
7
8
  import { SelectDiscriminatedUnionConverter } from 'field_converters/select_value_type_converter';
@@ -12,9 +13,10 @@ const IS_NAN_ERROR = 1;
12
13
  const originalIntegerToStringAdapter = adapterFromTwoWayConverter(new IntegerToStringConverter(IS_NAN_ERROR), prototypingFieldValueFactory(0));
13
14
  const originalBooleanToBooleanAdapter = identityAdapter(false);
14
15
  class TestFormModel extends FormModel {
15
- toContext() {
16
+ toContext(value, valuePath) {
16
17
  return {
17
- ctx: true,
18
+ value,
19
+ valuePath,
18
20
  };
19
21
  }
20
22
  }
@@ -71,7 +73,7 @@ describe('all', function () {
71
73
  let model;
72
74
  beforeEach(function () {
73
75
  originalValue = 5;
74
- model = new TestFormModel(typeDef, originalValue, adapters);
76
+ model = new TestFormModel(typeDef, originalValue, adapters, 'create');
75
77
  });
76
78
  describe('accessors', function () {
77
79
  it('gets the expected value', function () {
@@ -112,7 +114,7 @@ describe('all', function () {
112
114
  readonly: false,
113
115
  });
114
116
  originalValue = 5;
115
- model = new TestFormModel(typeDef, originalValue, adapters);
117
+ model = new TestFormModel(typeDef, originalValue, adapters, 'create');
116
118
  });
117
119
  it('reports required status', function () {
118
120
  expect(model.fields).toEqual(expect.objectContaining({
@@ -137,7 +139,7 @@ describe('all', function () {
137
139
  4,
138
140
  17,
139
141
  ];
140
- model = new TestFormModel(typeDef, value, adapters);
142
+ model = new TestFormModel(typeDef, value, adapters, 'create');
141
143
  });
142
144
  describe('accessors', function () {
143
145
  it.each([
@@ -199,7 +201,7 @@ describe('all', function () {
199
201
  a: 1,
200
202
  b: 2,
201
203
  };
202
- model = new TestFormModel(typeDef, value, converters);
204
+ model = new TestFormModel(typeDef, value, converters, 'create');
203
205
  });
204
206
  describe('accessors', function () {
205
207
  it.each([
@@ -250,7 +252,7 @@ describe('all', function () {
250
252
  a: 1,
251
253
  b: true,
252
254
  };
253
- model = new TestFormModel(typeDef, value, converters);
255
+ model = new TestFormModel(typeDef, value, converters, 'create');
254
256
  });
255
257
  describe('accessors', function () {
256
258
  it.each([
@@ -296,7 +298,7 @@ describe('all', function () {
296
298
  const originalValue = 2;
297
299
  let model;
298
300
  beforeEach(function () {
299
- model = new TestFormModel(typeDef, originalValue, adapters);
301
+ model = new TestFormModel(typeDef, originalValue, adapters, 'create');
300
302
  });
301
303
  describe('setFieldValueAndValidate', function () {
302
304
  describe('success', function () {
@@ -398,7 +400,7 @@ describe('all', function () {
398
400
  3,
399
401
  7,
400
402
  ];
401
- model = new TestFormModel(typeDef, originalValue, converters);
403
+ model = new TestFormModel(typeDef, originalValue, converters, 'create');
402
404
  });
403
405
  describe('setFieldValueAndValidate', function () {
404
406
  describe('success', function () {
@@ -493,23 +495,32 @@ describe('all', function () {
493
495
  let contextCopy;
494
496
  beforeEach(function () {
495
497
  integerToStringAdapter.revert.mockImplementationOnce(function (_value, _path, context) {
496
- contextCopy = Object.assign({}, context);
498
+ contextCopy = JSON.stringify(context);
497
499
  return {
498
500
  type: UnreliableFieldConversionType.Success,
499
501
  value: 1,
500
502
  };
501
503
  });
502
504
  });
503
- it('supplies the full, previous context when converting', function () {
505
+ it('supplies the context when converting', function () {
504
506
  model.setFieldValueAndValidate('$.2', '4');
505
507
  expect(integerToStringAdapter.revert).toHaveBeenCalledOnce();
506
508
  expect(integerToStringAdapter.revert).toHaveBeenCalledWith('4', '$.2', {
507
- ctx: true,
509
+ // the supplied value isn't a copy, so it will be the model value, even
510
+ // if the value has since changed
511
+ value: model.value,
512
+ valuePath: '$.2',
508
513
  });
509
514
  });
510
- it('supplies the context', function () {
511
- expect(contextCopy).toEqual({
512
- ctx: true,
515
+ it('supplies the correct context value at the time it is being checked', function () {
516
+ // the copy will show the supplied value however
517
+ expect(JSON.parse(contextCopy)).toEqual({
518
+ value: [
519
+ 1,
520
+ 3,
521
+ 7,
522
+ ],
523
+ valuePath: '$.2',
513
524
  });
514
525
  });
515
526
  });
@@ -687,7 +698,7 @@ describe('all', function () {
687
698
  let model;
688
699
  beforeEach(function () {
689
700
  originalValue = null;
690
- model = new TestFormModel(type, originalValue, adapters);
701
+ model = new TestFormModel(type, originalValue, adapters, 'create');
691
702
  });
692
703
  it('has the expected fields', function () {
693
704
  expect(model.fields).toEqual({
@@ -735,7 +746,7 @@ describe('all', function () {
735
746
  const model = new TestFormModel(type, {
736
747
  d: 'x',
737
748
  a: 1,
738
- }, adapters);
749
+ }, adapters, 'create');
739
750
  it.each([
740
751
  [
741
752
  '$',
@@ -758,7 +769,7 @@ describe('all', function () {
758
769
  const model = new TestFormModel(type, {
759
770
  d: 'y',
760
771
  b: false,
761
- }, adapters);
772
+ }, adapters, 'create');
762
773
  it.each([
763
774
  [
764
775
  '$',
@@ -790,7 +801,7 @@ describe('all', function () {
790
801
  let model;
791
802
  beforeEach(function () {
792
803
  originalValue = 1;
793
- model = new TestFormModel(typeDef, originalValue, converters);
804
+ model = new TestFormModel(typeDef, originalValue, converters, 'create');
794
805
  });
795
806
  it('returns the default value for the fake field', function () {
796
807
  expect(model.fields['$.fake']).toEqual(expect.objectContaining({
@@ -811,5 +822,54 @@ describe('all', function () {
811
822
  });
812
823
  });
813
824
  });
825
+ describe('interaction with create and edit modes', () => {
826
+ const typeDef = object().readonlyField('n', numberType.enforce(n => n < 10 ? 'err' : null));
827
+ const adapters = mergeAdaptersWithValidators({
828
+ $: identityAdapter({ n: 0 }),
829
+ '$.n': integerToStringAdapter,
830
+ }, flattenValidatorsOfValidatingType(typeDef));
831
+ let originalValue;
832
+ beforeEach(() => {
833
+ originalValue = {
834
+ n: 1,
835
+ };
836
+ });
837
+ describe('create mode', () => {
838
+ let model;
839
+ beforeEach(() => {
840
+ model = new TestFormModel(typeDef, originalValue, adapters, 'create');
841
+ });
842
+ it('makes the field editable', () => {
843
+ expect(model.fields['$.n'].readonly).toBeFalsy();
844
+ });
845
+ it('fails validation', () => {
846
+ expect(model.validateAll()).toBeFalsy();
847
+ });
848
+ it('passes validation with valid data', () => {
849
+ model.setFieldValue('$.n', '10');
850
+ expect(model.validateAll()).toBeTruthy();
851
+ });
852
+ });
853
+ describe('edit model', () => {
854
+ let model;
855
+ beforeEach(function () {
856
+ model = new TestFormModel(typeDef, originalValue, adapters, 'edit');
857
+ });
858
+ it('respects the field being readonly', () => {
859
+ expect(model.fields['$.n'].readonly).toBeTruthy();
860
+ });
861
+ it('validates successfully with clean, but invalid data', () => {
862
+ expect(model.validateAll()).toBeTruthy();
863
+ });
864
+ it('fails validation with invalid, dirty data', () => {
865
+ model.setFieldValue('$.n', '2');
866
+ expect(model.validateAll()).toBeFalsy();
867
+ });
868
+ it('passes validation with valid, dirty data', () => {
869
+ model.setFieldValue('$.n', '10');
870
+ expect(model.validateAll()).toBeTruthy();
871
+ });
872
+ });
873
+ });
814
874
  });
815
875
  });
@@ -1,18 +1,18 @@
1
1
  import { type ToOfFieldAdapter } from './field_adapter';
2
2
  import { type FlattenedConvertedFieldsOf, type FormModel } from './form_model';
3
3
  /**
4
- * Used to extract the supported value paths from a presenter
4
+ * Used to extract the supported value paths from a model
5
5
  */
6
6
  export type ValuePathsOfModel<Model extends FormModel<any, any, any, any, any>> = Model extends FormModel<infer _1, infer _2, infer _3, infer _4, infer ValuePathsToAdapters> ? keyof ValuePathsToAdapters : never;
7
7
  /**
8
8
  * Used to extract the render type (so the value that is passed to the view) of a given value path
9
- * from a presenter
9
+ * from a model
10
10
  */
11
11
  export type ToValueOfModelValuePath<Model extends FormModel<any, any, any, any, any>, K extends ValuePathsOfModel<Model>> = Model extends FormModel<infer _1, infer _2, infer _3, infer _4, infer ValuePathsToAdapters> ? ToOfFieldAdapter<ValuePathsToAdapters[K]> : never;
12
12
  /**
13
- * Extracts the form fields from the presenter. The recommended way is to
13
+ * Extracts the form fields from a form model. The recommended way is to
14
14
  * define the form fields explicitly and use that type to enforce the types
15
- * of your converters, but generating the FormFields from your presenter
15
+ * of your converters, but generating the FormFields from your model
16
16
  * is less typing, albeit at the cost of potentially getting type errors
17
17
  * reported a long way away from the source
18
18
  */
@@ -6,7 +6,9 @@ export type FieldsViewProps<F extends Fields> = {
6
6
  onFieldBlur?(this: void, key: keyof F): void;
7
7
  onFieldSubmit?(this: void, key: keyof F): boolean | void;
8
8
  };
9
+ export type FormMode = 'edit' | 'create';
9
10
  export type FormProps<O> = {
10
11
  value: O;
11
12
  onValueChange: (value: O) => void;
13
+ mode: FormMode;
12
14
  };
package/.out/index.d.ts CHANGED
@@ -17,7 +17,6 @@ export * from './field_converters/trimming_string_converter';
17
17
  export * from './field_converters/validating_converter';
18
18
  export * from './field_value_factories/prototyping_field_value_factory';
19
19
  export * from './mantine/error_renderer';
20
- export * from './mantine/field_view';
21
20
  export * from './mantine/hooks';
22
21
  export * from './types/error_of_field';
23
22
  export * from './types/field';
package/.out/index.js CHANGED
@@ -17,7 +17,6 @@ export * from './field_converters/trimming_string_converter';
17
17
  export * from './field_converters/validating_converter';
18
18
  export * from './field_value_factories/prototyping_field_value_factory';
19
19
  export * from './mantine/error_renderer';
20
- export * from './mantine/field_view';
21
20
  export * from './mantine/hooks';
22
21
  export * from './types/error_of_field';
23
22
  export * from './types/field';
@@ -0,0 +1,20 @@
1
+ import { type ComponentType } from 'react';
2
+ import { type AllFieldsOfFields } from 'types/all_fields_of_fields';
3
+ import { type ErrorOfField } from 'types/error_of_field';
4
+ import { type Fields } from 'types/field';
5
+ import { type ValueTypeOfField } from 'types/value_type_of_field';
6
+ import { type MantineForm } from './types';
7
+ export type FieldViewProps<F extends Fields, K extends keyof F> = {
8
+ children: (props: {
9
+ value: ValueTypeOfField<F[K]>;
10
+ error: ErrorOfField<F[K]> | undefined;
11
+ ErrorSink: ComponentType<{
12
+ error: ErrorOfField<F[K]>;
13
+ }>;
14
+ onFocus: () => void;
15
+ onBlur: () => void;
16
+ onValueChange: (v: ValueTypeOfField<F[K]>) => void;
17
+ onSubmit: () => void;
18
+ }) => JSX.Element;
19
+ };
20
+ export declare function createFieldView<F extends Fields, K extends keyof AllFieldsOfFields<F>>(this: MantineForm<F>, valuePath: K): ComponentType<FieldViewProps<F, K>>;