@strictly/react-form 0.0.13 → 0.0.14

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 (35) hide show
  1. package/.out/core/mobx/field_adapter.d.ts +1 -0
  2. package/.out/core/mobx/form_model.d.ts +3 -1
  3. package/.out/core/mobx/form_model.js +23 -15
  4. package/.out/core/mobx/hooks.d.ts +2 -2
  5. package/.out/core/mobx/merge_field_adapters_with_two_way_converter.d.ts +2 -2
  6. package/.out/core/mobx/specs/form_model.tests.js +26 -21
  7. package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +14 -21
  8. package/.out/core/mobx/sub_form_field_adapters.d.ts +5 -6
  9. package/.out/core/mobx/sub_form_field_adapters.js +6 -11
  10. package/.out/core/mobx/types.d.ts +3 -3
  11. package/.out/index.d.ts +2 -0
  12. package/.out/index.js +2 -0
  13. package/.out/mantine/field_view.d.ts +18 -0
  14. package/.out/mantine/field_view.js +16 -0
  15. package/.out/tsconfig.tsbuildinfo +1 -1
  16. package/.out/util/empty.d.ts +1 -0
  17. package/.out/util/empty.js +3 -0
  18. package/.turbo/turbo-build.log +8 -8
  19. package/.turbo/turbo-check-types.log +1 -1
  20. package/core/mobx/field_adapter.ts +9 -0
  21. package/core/mobx/form_model.ts +26 -16
  22. package/core/mobx/hooks.tsx +2 -2
  23. package/core/mobx/merge_field_adapters_with_two_way_converter.ts +2 -1
  24. package/core/mobx/specs/form_model.tests.ts +35 -20
  25. package/core/mobx/specs/sub_form_field_adapters.tests.ts +14 -34
  26. package/core/mobx/sub_form_field_adapters.ts +11 -26
  27. package/core/mobx/types.ts +10 -7
  28. package/dist/index.cjs +99 -67
  29. package/dist/index.d.cts +32 -12
  30. package/dist/index.d.ts +32 -12
  31. package/dist/index.js +77 -49
  32. package/index.ts +2 -0
  33. package/mantine/field_view.tsx +39 -0
  34. package/package.json +1 -1
  35. package/util/empty.tsx +3 -0
@@ -8,3 +8,4 @@ export type FromOfFieldAdapter<C extends FieldAdapter> = C extends FieldAdapter<
8
8
  export type ToOfFieldAdapter<C extends FieldAdapter> = C extends FieldAdapter<infer _F, infer To> ? To : never;
9
9
  export type ErrorOfFieldAdapter<C extends FieldAdapter> = C extends FieldAdapter<infer _From, infer _To, infer E> ? NonNullable<E> : never;
10
10
  export type ValuePathOfFieldAdapter<C extends FieldAdapter> = C extends FieldAdapter<infer _From, infer _To, infer _E, infer ValuePath> ? ValuePath : never;
11
+ export type ContextOfFieldAdapter<F extends FieldAdapter> = F extends FieldAdapter<infer _From, infer _To, infer _E, infer _P, infer Context> ? Context : never;
@@ -20,7 +20,7 @@ type FlattenedErrors<ValuePathsToAdapters extends Readonly<Record<string, FieldA
20
20
  export type ValuePathsToAdaptersOf<TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>, ValuePathsToTypePaths extends Readonly<Record<string, string>>> = keyof TypePathsToAdapters extends ValueOf<ValuePathsToTypePaths> ? {
21
21
  readonly [K in keyof ValuePathsToTypePaths as unknown extends TypePathsToAdapters[ValuePathsToTypePaths[K]] ? never : K]: NonNullable<TypePathsToAdapters[ValuePathsToTypePaths[K]]>;
22
22
  } : never;
23
- export declare class FormModel<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ValueOfType<ReadonlyTypeOfType<T>>>, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
23
+ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ContextType>, ContextType = {}, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
24
24
  readonly type: T;
25
25
  protected readonly adapters: TypePathsToAdapters;
26
26
  accessor value: MobxValueOfType<T>;
@@ -28,6 +28,8 @@ export declare class FormModel<T extends Type, ValueToTypePaths extends Readonly
28
28
  accessor errors: FlattenedErrors<ValuePathsToAdapters>;
29
29
  private readonly flattenedTypeDefs;
30
30
  constructor(type: T, value: ValueOfType<ReadonlyTypeOfType<T>>, adapters: TypePathsToAdapters);
31
+ get context(): ContextType;
32
+ protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>): ContextType;
31
33
  get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>;
32
34
  private get knownFields();
33
35
  private maybeSynthesizeFieldByValuePath;
@@ -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;
52
+ var _b, _c, _d, _e;
53
53
  let _instanceExtraInitializers = [];
54
54
  let _value_decorators;
55
55
  let _value_initializers = [];
@@ -60,6 +60,7 @@ let FormModel = (() => {
60
60
  let _errors_decorators;
61
61
  let _errors_initializers = [];
62
62
  let _errors_extraInitializers = [];
63
+ let _get_context_decorators;
63
64
  let _get_fields_decorators;
64
65
  let _get_knownFields_decorators;
65
66
  let _get_accessors_decorators;
@@ -94,6 +95,7 @@ let FormModel = (() => {
94
95
  });
95
96
  this.value = mobxCopy(type, value);
96
97
  this.flattenedTypeDefs = flattenTypesOfType(type);
98
+ const contextValue = this.toContext(value);
97
99
  // pre-populate field overrides for consistent behavior when default information is overwritten
98
100
  // then returned to
99
101
  const conversions = flattenValueTo(type, this.value, () => { }, (_t, value, _setter, typePath, valuePath) => {
@@ -107,13 +109,17 @@ let FormModel = (() => {
107
109
  // no need to store a temporary value if the value cannot be written back
108
110
  return;
109
111
  }
110
- return convert(value, valuePath, this.value);
112
+ // cannot call this.context yet as the "this" pointer has not been fully created
113
+ return convert(value, valuePath, contextValue);
111
114
  });
112
115
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
113
116
  this.fieldOverrides = map(conversions, function (_k, v) {
114
117
  return v && [v.value];
115
118
  });
116
119
  }
120
+ get context() {
121
+ return this.toContext(this.value);
122
+ }
117
123
  get fields() {
118
124
  return new Proxy(this.knownFields, {
119
125
  get: (target, prop) => {
@@ -172,12 +178,12 @@ let FormModel = (() => {
172
178
  : fieldTypeDef != null
173
179
  ? mobxCopy(fieldTypeDef,
174
180
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
175
- create(valuePath, this.value))
181
+ create(valuePath, this.context))
176
182
  // fake values can't be copied
177
183
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
178
- : create(valuePath, this.value),
184
+ : create(valuePath, this.context),
179
185
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
180
- valuePath, this.value);
186
+ valuePath, this.context);
181
187
  const error = this.errors[valuePath];
182
188
  return {
183
189
  value: fieldOverride != null ? fieldOverride[0] : value,
@@ -229,7 +235,7 @@ let FormModel = (() => {
229
235
  ? elementValue[0]
230
236
  : elementAdapter.create(
231
237
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
232
- elementTypePath, this.value);
238
+ elementTypePath, this.context);
233
239
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
240
  const originalList = accessor.value;
235
241
  const newList = [
@@ -340,7 +346,7 @@ let FormModel = (() => {
340
346
  const { revert } = this.getAdapterForValuePath(valuePath);
341
347
  assertExists(revert, 'setting value not supported {}', valuePath);
342
348
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
343
- const conversion = revert(value, valuePath, this.value);
349
+ const conversion = revert(value, valuePath, this.context);
344
350
  const accessor = this.getAccessorForValuePath(valuePath);
345
351
  return runInAction(() => {
346
352
  this.fieldOverrides[valuePath] = [value];
@@ -378,8 +384,8 @@ let FormModel = (() => {
378
384
  return;
379
385
  }
380
386
  const { convert, create, } = adapter;
381
- const value = create(valuePath, this.value);
382
- const { value: displayValue, } = convert(value, valuePath, this.value);
387
+ const value = create(valuePath, this.context);
388
+ const { value: displayValue, } = convert(value, valuePath, this.context);
383
389
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
384
390
  const key = valuePath;
385
391
  runInAction(() => {
@@ -407,22 +413,22 @@ let FormModel = (() => {
407
413
  const { value: storedValue, } = convert(accessor != null
408
414
  ? accessor.value
409
415
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
410
- : create(valuePath, this.value),
416
+ : create(valuePath, this.context),
411
417
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
412
- valuePath, this.value);
418
+ valuePath, this.context);
413
419
  const value = fieldOverride != null
414
420
  ? fieldOverride[0]
415
421
  : storedValue;
416
422
  const dirty = storedValue !== value;
417
423
  assertExists(revert, 'changing field directly not supported {}', valuePath);
418
424
  if (ignoreDefaultValue) {
419
- const { value: defaultDisplayValue, } = convert(create(valuePath, this.value), valuePath, this.value);
425
+ const { value: defaultDisplayValue, } = convert(create(valuePath, this.context), valuePath, this.context);
420
426
  if (defaultDisplayValue === value) {
421
427
  return true;
422
428
  }
423
429
  }
424
430
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
425
- const conversion = revert(value, valuePath, this.value);
431
+ const conversion = revert(value, valuePath, this.context);
426
432
  return runInAction(() => {
427
433
  switch (conversion.type) {
428
434
  case UnreliableFieldConversionType.Failure:
@@ -462,13 +468,13 @@ let FormModel = (() => {
462
468
  return success;
463
469
  }
464
470
  const fieldOverride = this.fieldOverrides[adapterPath];
465
- const { value: storedValue, } = convert(accessor.value, valuePath, this.value);
471
+ const { value: storedValue, } = convert(accessor.value, valuePath, this.context);
466
472
  const value = fieldOverride != null
467
473
  ? fieldOverride[0]
468
474
  : storedValue;
469
475
  // TODO more nuanced comparison
470
476
  const dirty = fieldOverride != null && fieldOverride[0] !== storedValue;
471
- const conversion = revert(value, valuePath, this.value);
477
+ const conversion = revert(value, valuePath, this.context);
472
478
  switch (conversion.type) {
473
479
  case UnreliableFieldConversionType.Failure:
474
480
  this.errors[adapterPath] = conversion.error;
@@ -497,12 +503,14 @@ let FormModel = (() => {
497
503
  _value_decorators = [(_b = observable).ref.bind(_b)];
498
504
  _fieldOverrides_decorators = [(_c = observable).shallow.bind(_c)];
499
505
  _errors_decorators = [(_d = observable).shallow.bind(_d)];
506
+ _get_context_decorators = [(_e = computed).struct.bind(_e)];
500
507
  _get_fields_decorators = [computed];
501
508
  _get_knownFields_decorators = [computed];
502
509
  _get_accessors_decorators = [computed];
503
510
  __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);
504
511
  __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);
505
512
  __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);
506
514
  __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);
507
515
  __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);
508
516
  __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,8 +1,8 @@
1
1
  import { type ReadonlyTypeOfType, type ValueOfType } from '@strictly/define';
2
2
  import { type FormModel } from './form_model';
3
3
  import { type FormFieldsOfModel, type ValuePathsOfModel } from './types';
4
- type ValueOfModel<M extends FormModel<any, any, any, any>> = M extends FormModel<infer T, any, any, any> ? ValueOfType<ReadonlyTypeOfType<T>> : never;
5
- export declare function useDefaultMobxFormHooks<M extends FormModel<any, any, any, any>, F extends FormFieldsOfModel<M> = FormFieldsOfModel<M>>(model: M, { onValidFieldSubmit, onValidFormSubmit, }?: {
4
+ type ValueOfModel<M extends FormModel<any, any, any, any, any>> = M extends FormModel<infer T, any, any, any, any> ? ValueOfType<ReadonlyTypeOfType<T>> : never;
5
+ export declare function useDefaultMobxFormHooks<M extends FormModel<any, any, any, any, any>, F extends FormFieldsOfModel<M> = FormFieldsOfModel<M>>(model: M, { onValidFieldSubmit, onValidFormSubmit, }?: {
6
6
  onValidFieldSubmit?: <Path extends ValuePathsOfModel<M>>(valuePath: Path) => void;
7
7
  onValidFormSubmit?: (value: ValueOfModel<M>) => void;
8
8
  }): {
@@ -1,7 +1,7 @@
1
1
  import { type TwoWayFieldConverter } from 'types/field_converters';
2
- import { type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter, type ValuePathOfFieldAdapter } from './field_adapter';
2
+ import { type ContextOfFieldAdapter, type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter, type ValuePathOfFieldAdapter } from './field_adapter';
3
3
  export type MergedOfFieldAdaptersWithTwoWayConverter<FieldAdapters extends Readonly<Record<string, FieldAdapter>>, E, Context> = {
4
- [K in keyof FieldAdapters]: FieldAdapter<FromOfFieldAdapter<FieldAdapters[K]>, ToOfFieldAdapter<FieldAdapters[K]>, ErrorOfFieldAdapter<FieldAdapters[K]> | E, ValuePathOfFieldAdapter<FieldAdapters[K]>, Context>;
4
+ [K in keyof FieldAdapters]: FieldAdapter<FromOfFieldAdapter<FieldAdapters[K]>, ToOfFieldAdapter<FieldAdapters[K]>, ErrorOfFieldAdapter<FieldAdapters[K]> | E, ValuePathOfFieldAdapter<FieldAdapters[K]>, ContextOfFieldAdapter<FieldAdapters[K]> & Context>;
5
5
  };
6
6
  type ValuePathsOfFieldAdapters<FieldAdapters extends Readonly<Record<string, FieldAdapter>>> = {
7
7
  [K in keyof FieldAdapters]: ValuePathOfFieldAdapter<FieldAdapters[K]>;
@@ -11,6 +11,13 @@ import { createMockedAdapter, resetMockAdapter, } from './fixtures';
11
11
  const IS_NAN_ERROR = 1;
12
12
  const originalIntegerToStringAdapter = adapterFromTwoWayConverter(new IntegerToStringConverter(IS_NAN_ERROR), prototypingFieldValueFactory(0));
13
13
  const originalBooleanToBooleanAdapter = identityAdapter(false);
14
+ class TestFormModel extends FormModel {
15
+ toContext() {
16
+ return {
17
+ ctx: true,
18
+ };
19
+ }
20
+ }
14
21
  describe('all', function () {
15
22
  const integerToStringAdapter = createMockedAdapter(originalIntegerToStringAdapter);
16
23
  const booleanToBooleanAdapter = createMockedAdapter(originalBooleanToBooleanAdapter);
@@ -64,7 +71,7 @@ describe('all', function () {
64
71
  let model;
65
72
  beforeEach(function () {
66
73
  originalValue = 5;
67
- model = new FormModel(typeDef, originalValue, adapters);
74
+ model = new TestFormModel(typeDef, originalValue, adapters);
68
75
  });
69
76
  describe('accessors', function () {
70
77
  it('gets the expected value', function () {
@@ -105,7 +112,7 @@ describe('all', function () {
105
112
  readonly: false,
106
113
  });
107
114
  originalValue = 5;
108
- model = new FormModel(typeDef, originalValue, adapters);
115
+ model = new TestFormModel(typeDef, originalValue, adapters);
109
116
  });
110
117
  it('reports required status', function () {
111
118
  expect(model.fields).toEqual(expect.objectContaining({
@@ -130,7 +137,7 @@ describe('all', function () {
130
137
  4,
131
138
  17,
132
139
  ];
133
- model = new FormModel(typeDef, value, adapters);
140
+ model = new TestFormModel(typeDef, value, adapters);
134
141
  });
135
142
  describe('accessors', function () {
136
143
  it.each([
@@ -192,7 +199,7 @@ describe('all', function () {
192
199
  a: 1,
193
200
  b: 2,
194
201
  };
195
- model = new FormModel(typeDef, value, converters);
202
+ model = new TestFormModel(typeDef, value, converters);
196
203
  });
197
204
  describe('accessors', function () {
198
205
  it.each([
@@ -243,7 +250,7 @@ describe('all', function () {
243
250
  a: 1,
244
251
  b: true,
245
252
  };
246
- model = new FormModel(typeDef, value, converters);
253
+ model = new TestFormModel(typeDef, value, converters);
247
254
  });
248
255
  describe('accessors', function () {
249
256
  it.each([
@@ -289,7 +296,7 @@ describe('all', function () {
289
296
  const originalValue = 2;
290
297
  let model;
291
298
  beforeEach(function () {
292
- model = new FormModel(typeDef, originalValue, adapters);
299
+ model = new TestFormModel(typeDef, originalValue, adapters);
293
300
  });
294
301
  describe('setFieldValueAndValidate', function () {
295
302
  describe('success', function () {
@@ -391,7 +398,7 @@ describe('all', function () {
391
398
  3,
392
399
  7,
393
400
  ];
394
- model = new FormModel(typeDef, originalValue, converters);
401
+ model = new TestFormModel(typeDef, originalValue, converters);
395
402
  });
396
403
  describe('setFieldValueAndValidate', function () {
397
404
  describe('success', function () {
@@ -486,7 +493,7 @@ describe('all', function () {
486
493
  let contextCopy;
487
494
  beforeEach(function () {
488
495
  integerToStringAdapter.revert.mockImplementationOnce(function (_value, _path, context) {
489
- contextCopy = [...context];
496
+ contextCopy = Object.assign({}, context);
490
497
  return {
491
498
  type: UnreliableFieldConversionType.Success,
492
499
  value: 1,
@@ -496,16 +503,14 @@ describe('all', function () {
496
503
  it('supplies the full, previous context when converting', function () {
497
504
  model.setFieldValueAndValidate('$.2', '4');
498
505
  expect(integerToStringAdapter.revert).toHaveBeenCalledOnce();
499
- expect(integerToStringAdapter.revert).toHaveBeenCalledWith('4', '$.2',
500
- // uses the same pointer
501
- model.value);
506
+ expect(integerToStringAdapter.revert).toHaveBeenCalledWith('4', '$.2', {
507
+ ctx: true,
508
+ });
502
509
  });
503
- it('supplies the context as it is at the time call', function () {
504
- expect(contextCopy).toEqual([
505
- 1,
506
- 3,
507
- 7,
508
- ]);
510
+ it('supplies the context', function () {
511
+ expect(contextCopy).toEqual({
512
+ ctx: true,
513
+ });
509
514
  });
510
515
  });
511
516
  describe('addListItem', function () {
@@ -682,7 +687,7 @@ describe('all', function () {
682
687
  let model;
683
688
  beforeEach(function () {
684
689
  originalValue = null;
685
- model = new FormModel(type, originalValue, adapters);
690
+ model = new TestFormModel(type, originalValue, adapters);
686
691
  });
687
692
  it('has the expected fields', function () {
688
693
  expect(model.fields).toEqual({
@@ -727,7 +732,7 @@ describe('all', function () {
727
732
  };
728
733
  describe('isValuePathActive', function () {
729
734
  describe('discriminator x', function () {
730
- const model = new FormModel(type, {
735
+ const model = new TestFormModel(type, {
731
736
  d: 'x',
732
737
  a: 1,
733
738
  }, adapters);
@@ -750,7 +755,7 @@ describe('all', function () {
750
755
  });
751
756
  });
752
757
  describe('discriminator y', function () {
753
- const model = new FormModel(type, {
758
+ const model = new TestFormModel(type, {
754
759
  d: 'y',
755
760
  b: false,
756
761
  }, adapters);
@@ -785,7 +790,7 @@ describe('all', function () {
785
790
  let model;
786
791
  beforeEach(function () {
787
792
  originalValue = 1;
788
- model = new FormModel(typeDef, originalValue, converters);
793
+ model = new TestFormModel(typeDef, originalValue, converters);
789
794
  });
790
795
  it('returns the default value for the fake field', function () {
791
796
  expect(model.fields['$.fake']).toEqual(expect.objectContaining({
@@ -1,10 +1,9 @@
1
- import { list, numberType, object, stringType, } from '@strictly/define';
2
1
  import { subFormFieldAdapters, } from 'core/mobx/sub_form_field_adapters';
3
2
  import { UnreliableFieldConversionType } from 'types/field_converters';
4
3
  import { mockDeep, mockReset, } from 'vitest-mock-extended';
5
4
  describe('subFormFieldAdapters', () => {
6
5
  describe('empty value', () => {
7
- const adapters = subFormFieldAdapters({}, '$.a', stringType);
6
+ const adapters = subFormFieldAdapters({}, '$.a');
8
7
  it('equals expected type', () => {
9
8
  expectTypeOf(adapters).toEqualTypeOf();
10
9
  });
@@ -15,11 +14,10 @@ describe('subFormFieldAdapters', () => {
15
14
  describe('single adapter', () => {
16
15
  const mockedFieldAdapter1 = mockDeep();
17
16
  const fieldAdapter1 = mockedFieldAdapter1;
18
- const type = object().field('a', stringType);
19
17
  const subAdapters = {
20
18
  $: fieldAdapter1,
21
19
  };
22
- const adapters = subFormFieldAdapters(subAdapters, '$.a', type);
20
+ const adapters = subFormFieldAdapters(subAdapters, '$.a');
23
21
  beforeEach(() => {
24
22
  mockReset(mockedFieldAdapter1);
25
23
  });
@@ -38,7 +36,7 @@ describe('subFormFieldAdapters', () => {
38
36
  readonly: false,
39
37
  };
40
38
  mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
41
- const returnedValue = adapters['$.a'].convert('x', '$.a', { a: 'y' });
39
+ const returnedValue = adapters['$.a'].convert('x', '$.a', 'y');
42
40
  expect(fieldAdapter1.convert).toHaveBeenCalledWith('x', '$', 'y');
43
41
  expect(returnedValue).toEqual(mockedReturnedValue);
44
42
  });
@@ -49,14 +47,14 @@ describe('subFormFieldAdapters', () => {
49
47
  value: 'ok',
50
48
  };
51
49
  mockedFieldAdapter1.revert.mockReturnValue(mockedReturnedValue);
52
- const returnedValue = (_b = (_a = adapters['$.a']).revert) === null || _b === void 0 ? void 0 : _b.call(_a, true, '$.a', { a: 'y' });
50
+ const returnedValue = (_b = (_a = adapters['$.a']).revert) === null || _b === void 0 ? void 0 : _b.call(_a, true, '$.a', 'y');
53
51
  expect(fieldAdapter1.revert).toHaveBeenCalledWith(true, '$', 'y');
54
52
  expect(returnedValue).toEqual(mockedReturnedValue);
55
53
  });
56
54
  it('calls create with the correct paths and values', () => {
57
55
  const mockedReturnedValue = 'x';
58
56
  mockedFieldAdapter1.create.mockReturnValue(mockedReturnedValue);
59
- const returnedValue = adapters['$.a'].create('$.a', { a: 'y' });
57
+ const returnedValue = adapters['$.a'].create('$.a', 'y');
60
58
  expect(fieldAdapter1.create).toHaveBeenCalledWith('$', 'y');
61
59
  expect(returnedValue).toEqual(mockedReturnedValue);
62
60
  });
@@ -66,12 +64,10 @@ describe('subFormFieldAdapters', () => {
66
64
  const fieldAdapter1 = mockedFieldAdapter1;
67
65
  const mockedFieldAdapter2 = mockDeep();
68
66
  const fieldAdapter2 = mockedFieldAdapter2;
69
- const type = object()
70
- .field('a', object().field('x', stringType).field('y', numberType));
71
67
  const adapters = subFormFieldAdapters({
72
68
  '$.x': fieldAdapter1,
73
69
  '$.y': fieldAdapter2,
74
- }, '$.a', type);
70
+ }, '$.a');
75
71
  beforeEach(() => {
76
72
  mockReset(mockedFieldAdapter1);
77
73
  mockReset(mockedFieldAdapter2);
@@ -87,12 +83,11 @@ describe('subFormFieldAdapters', () => {
87
83
  });
88
84
  });
89
85
  describe('calls convert with correct paths and values', () => {
90
- const subContext = {
91
- x: 'a',
92
- y: 1,
93
- };
94
86
  const context = {
95
- a: subContext,
87
+ a: {
88
+ x: 'a',
89
+ y: 1,
90
+ },
96
91
  };
97
92
  it('calls $.a.x', () => {
98
93
  const mockedReturnedValue = {
@@ -102,7 +97,7 @@ describe('subFormFieldAdapters', () => {
102
97
  };
103
98
  mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
104
99
  const returnedValue = adapters['$.a.x'].convert('b', '$.a.x', context);
105
- expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$.x', subContext);
100
+ expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$.x', context);
106
101
  expect(returnedValue).toEqual(mockedReturnedValue);
107
102
  });
108
103
  it('calls $.a.y', () => {
@@ -113,7 +108,7 @@ describe('subFormFieldAdapters', () => {
113
108
  };
114
109
  mockedFieldAdapter2.convert.mockReturnValue(mockedReturnedValue);
115
110
  const returnedValue = adapters['$.a.y'].convert(2, '$.a.y', context);
116
- expect(fieldAdapter2.convert).toHaveBeenCalledWith(2, '$.y', subContext);
111
+ expect(fieldAdapter2.convert).toHaveBeenCalledWith(2, '$.y', context);
117
112
  expect(returnedValue).toEqual(mockedReturnedValue);
118
113
  });
119
114
  });
@@ -121,11 +116,10 @@ describe('subFormFieldAdapters', () => {
121
116
  describe('list adapter', () => {
122
117
  const mockedFieldAdapter1 = mockDeep();
123
118
  const fieldAdapter1 = mockedFieldAdapter1;
124
- const type = list(stringType);
125
119
  const subAdapters = {
126
120
  $: fieldAdapter1,
127
121
  };
128
- const adapters = subFormFieldAdapters(subAdapters, '$.*', type);
122
+ const adapters = subFormFieldAdapters(subAdapters, '$.*');
129
123
  beforeEach(() => {
130
124
  mockReset(mockedFieldAdapter1);
131
125
  });
@@ -140,7 +134,6 @@ describe('subFormFieldAdapters', () => {
140
134
  });
141
135
  describe('calls convert with correct paths and values', () => {
142
136
  const subContext = 'a';
143
- const context = [subContext];
144
137
  it('calls $.*', () => {
145
138
  const mockedReturnedValue = {
146
139
  value: false,
@@ -148,7 +141,7 @@ describe('subFormFieldAdapters', () => {
148
141
  required: false,
149
142
  };
150
143
  mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
151
- const returnedValue = adapters['$.*'].convert('b', '$.0', context);
144
+ const returnedValue = adapters['$.*'].convert('b', '$.0', subContext);
152
145
  expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$', subContext);
153
146
  expect(returnedValue).toEqual(mockedReturnedValue);
154
147
  });
@@ -1,9 +1,8 @@
1
1
  import { type StringConcatOf } from '@strictly/base';
2
- import { type ReadonlyTypeOfType, type Type, type ValueOfType } from '@strictly/define';
3
- import { type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter, type ValuePathOfFieldAdapter } from './field_adapter';
4
- type SubFormFieldAdapter<F extends FieldAdapter, ValuePath extends string, Context> = FieldAdapter<FromOfFieldAdapter<F>, ToOfFieldAdapter<F>, ErrorOfFieldAdapter<F>, ValuePathOfFieldAdapter<F> extends StringConcatOf<'$', infer ValuePathSuffix> ? `${ValuePath}${ValuePathSuffix}` : string, Context>;
5
- type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, ValuePath extends string, Context> = {
6
- [K in keyof SubAdapters as K extends StringConcatOf<'$', infer TypePathSuffix> ? `${TypePath}${TypePathSuffix}` : never]: SubFormFieldAdapter<SubAdapters[K], ValuePath, Context>;
2
+ import { type ContextOfFieldAdapter, type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter, type ValuePathOfFieldAdapter } from './field_adapter';
3
+ type SubFormFieldAdapter<F extends FieldAdapter, ValuePath extends string> = FieldAdapter<FromOfFieldAdapter<F>, ToOfFieldAdapter<F>, ErrorOfFieldAdapter<F>, ValuePathOfFieldAdapter<F> extends StringConcatOf<'$', infer ValuePathSuffix> ? `${ValuePath}${ValuePathSuffix}` : string, ContextOfFieldAdapter<F>>;
4
+ type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, ValuePath extends string> = {
5
+ [K in keyof SubAdapters as K extends StringConcatOf<'$', infer TypePathSuffix> ? `${TypePath}${TypePathSuffix}` : never]: SubFormFieldAdapter<SubAdapters[K], ValuePath>;
7
6
  };
8
- export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, TypePathsToValuePaths extends Record<TypePath, string>, ContextType extends Type>(subAdapters: SubAdapters, parentTypePath: TypePath, contextType: ContextType): SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath], ValueOfType<ReadonlyTypeOfType<ContextType>>>;
7
+ export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, TypePathsToValuePaths extends Record<TypePath, string>>(subAdapters: SubAdapters, parentTypePath: TypePath): SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath]>;
9
8
  export {};
@@ -1,15 +1,10 @@
1
- import { flattenValuesOfType, } from '@strictly/define';
2
- export function subFormFieldAdapters(subAdapters, parentTypePath, contextType) {
1
+ export function subFormFieldAdapters(subAdapters, parentTypePath) {
3
2
  // assume the number of '.' in the type path will correspond to the number of '.' in the value path
4
3
  const dotCount = parentTypePath.split('.').length;
5
- function getSubValuePathAndContext(valuePath, context) {
4
+ function getSubValuePath(valuePath) {
6
5
  const parentValuePath = valuePath.split('.').slice(0, dotCount).join('.');
7
6
  const subValuePath = valuePath.replace(parentValuePath, '$');
8
- const subContext = flattenValuesOfType(contextType, context)[parentValuePath];
9
- return [
10
- subValuePath,
11
- subContext,
12
- ];
7
+ return subValuePath;
13
8
  }
14
9
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
15
10
  return Object.entries(subAdapters).reduce((acc, [subTypePath, subAdapter,]) => {
@@ -17,13 +12,13 @@ export function subFormFieldAdapters(subAdapters, parentTypePath, contextType) {
17
12
  // adapt field adapter with new path and context
18
13
  const adaptedAdapter = {
19
14
  convert: (from, valuePath, context) => {
20
- return subAdapter.convert(from, ...getSubValuePathAndContext(valuePath, context));
15
+ return subAdapter.convert(from, getSubValuePath(valuePath), context);
21
16
  },
22
17
  create: (valuePath, context) => {
23
- return subAdapter.create(...getSubValuePathAndContext(valuePath, context));
18
+ return subAdapter.create(getSubValuePath(valuePath), context);
24
19
  },
25
20
  revert: subAdapter.revert && ((from, valuePath, context) => {
26
- return subAdapter.revert(from, ...getSubValuePathAndContext(valuePath, context));
21
+ return subAdapter.revert(from, getSubValuePath(valuePath), context);
27
22
  }),
28
23
  };
29
24
  acc[typePath] = adaptedAdapter;
@@ -3,12 +3,12 @@ import { type FlattenedConvertedFieldsOf, type FormModel } from './form_model';
3
3
  /**
4
4
  * Used to extract the supported value paths from a presenter
5
5
  */
6
- export type ValuePathsOfModel<Presenter extends FormModel<any, any, any, any>> = Presenter extends FormModel<infer _1, infer _2, infer _3, infer ValuePathsToAdapters> ? keyof ValuePathsToAdapters : never;
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
9
  * from a presenter
10
10
  */
11
- export type ToValueOfModelValuePath<Presenter extends FormModel<any, any, any, any>, K extends ValuePathsOfModel<Presenter>> = Presenter extends FormModel<infer _1, infer _2, infer _3, infer ValuePathsToAdapters> ? ToOfFieldAdapter<ValuePathsToAdapters[K]> : never;
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
13
  * Extracts the form fields from the presenter. The recommended way is to
14
14
  * define the form fields explicitly and use that type to enforce the types
@@ -16,4 +16,4 @@ export type ToValueOfModelValuePath<Presenter extends FormModel<any, any, any, a
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
  */
19
- export type FormFieldsOfModel<Presenter extends FormModel<any, any, any, any>> = Presenter extends FormModel<infer _1, infer _2, infer _3, infer ValuePathsToAdapters> ? FlattenedConvertedFieldsOf<ValuePathsToAdapters> : never;
19
+ export type FormFieldsOfModel<Model extends FormModel<any, any, any, any, any>> = Model extends FormModel<infer _1, infer _2, infer _3, infer _4, infer ValuePathsToAdapters> ? FlattenedConvertedFieldsOf<ValuePathsToAdapters> : never;
package/.out/index.d.ts CHANGED
@@ -17,9 +17,11 @@ 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';
20
21
  export * from './mantine/hooks';
21
22
  export * from './types/error_of_field';
22
23
  export * from './types/field';
23
24
  export * from './types/field_converters';
24
25
  export * from './types/merge_validators';
26
+ export * from './util/empty';
25
27
  export * from './util/partial';
package/.out/index.js CHANGED
@@ -17,9 +17,11 @@ 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';
20
21
  export * from './mantine/hooks';
21
22
  export * from './types/error_of_field';
22
23
  export * from './types/field';
23
24
  export * from './types/field_converters';
24
25
  export * from './types/merge_validators';
26
+ export * from './util/empty';
25
27
  export * from './util/partial';
@@ -0,0 +1,18 @@
1
+ import { type ComponentType } from 'react';
2
+ import { type ErrorOfField } from 'types/error_of_field';
3
+ import { type Fields } from 'types/field';
4
+ import { type ValueTypeOfField } from 'types/value_type_of_field';
5
+ /**
6
+ * Displays current value and error of a field
7
+ */
8
+ export declare function FieldView<F extends Fields, K extends keyof F>({ fields, valuePath, children, }: {
9
+ fields: F;
10
+ valuePath: K;
11
+ children: (props: {
12
+ value: ValueTypeOfField<F[K]>;
13
+ error: ErrorOfField<F[K]> | undefined;
14
+ ErrorSink: ComponentType<{
15
+ error: ErrorOfField<F[K]>;
16
+ }>;
17
+ }) => JSX.Element;
18
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Observer } from 'mobx-react';
3
+ import { Empty } from 'util/empty';
4
+ /**
5
+ * Displays current value and error of a field
6
+ */
7
+ export function FieldView({ fields, valuePath, children, }) {
8
+ return (_jsx(Observer, { children: () => {
9
+ const { value, error, } = fields[valuePath];
10
+ return children({
11
+ value,
12
+ error,
13
+ ErrorSink: Empty,
14
+ });
15
+ } }));
16
+ }