@strictly/react-form 0.0.1 → 0.0.2

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 (173) hide show
  1. package/.out/core/mobx/field_adapter.d.ts +7 -6
  2. package/.out/core/mobx/field_adapter_builder.d.ts +12 -13
  3. package/.out/core/mobx/field_adapter_builder.js +8 -12
  4. package/.out/core/mobx/field_adapters_of_values.d.ts +4 -0
  5. package/.out/core/mobx/flattened_adapters_of_fields.d.ts +2 -2
  6. package/.out/core/mobx/flattened_list_types_of_type.d.ts +8 -0
  7. package/.out/core/mobx/form_fields_of_field_adapters.d.ts +8 -0
  8. package/.out/core/mobx/form_presenter.d.ts +21 -24
  9. package/.out/core/mobx/form_presenter.js +64 -69
  10. package/.out/core/mobx/merge_field_adapters_with_two_way_converter.d.ts +13 -0
  11. package/.out/core/mobx/merge_field_adapters_with_two_way_converter.js +11 -0
  12. package/.out/core/mobx/merge_field_adapters_with_validators.d.ts +11 -0
  13. package/.out/core/mobx/merge_field_adapters_with_validators.js +45 -0
  14. package/.out/core/mobx/specs/fixtures.d.ts +7 -0
  15. package/.out/core/mobx/specs/fixtures.js +20 -0
  16. package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.js +5 -2
  17. package/.out/core/mobx/specs/{flattened_list_type_defs_of.tests.js → flattened_list_types_of_types.tests.js} +7 -7
  18. package/.out/core/mobx/specs/form_presenter.tests.js +162 -60
  19. package/.out/core/mobx/specs/merge_field_adapters_with_two_way_converter.js +89 -0
  20. package/.out/core/mobx/specs/merge_field_adapters_with_validators.tests.js +172 -0
  21. package/.out/core/mobx/types.d.ts +2 -2
  22. package/.out/field_converters/chain_field_converter.d.ts +3 -3
  23. package/.out/field_converters/chain_field_converter.js +17 -12
  24. package/.out/field_converters/identity_converter.d.ts +3 -3
  25. package/.out/field_converters/identity_converter.js +10 -6
  26. package/.out/field_converters/integer_to_string_converter.d.ts +5 -4
  27. package/.out/field_converters/integer_to_string_converter.js +13 -6
  28. package/.out/field_converters/list_converter.d.ts +2 -2
  29. package/.out/field_converters/list_converter.js +6 -1
  30. package/.out/field_converters/maybe_identity_converter.d.ts +3 -3
  31. package/.out/field_converters/maybe_identity_converter.js +3 -1
  32. package/.out/field_converters/nullable_to_boolean_converter.d.ts +9 -8
  33. package/.out/field_converters/nullable_to_boolean_converter.js +13 -7
  34. package/.out/field_converters/select_value_type_converter.d.ts +20 -15
  35. package/.out/field_converters/select_value_type_converter.js +29 -14
  36. package/.out/field_converters/specs/chain_field_converter.tests.d.ts +1 -0
  37. package/.out/field_converters/specs/chain_field_converter.tests.js +251 -0
  38. package/.out/field_converters/trimming_string_converter.d.ts +3 -3
  39. package/.out/field_converters/trimming_string_converter.js +7 -3
  40. package/.out/field_converters/validating_converter.d.ts +3 -3
  41. package/.out/field_converters/validating_converter.js +7 -5
  42. package/.out/index.d.ts +9 -2
  43. package/.out/index.js +9 -2
  44. package/.out/mantine/create_checkbox.d.ts +2 -3
  45. package/.out/mantine/create_checkbox.js +6 -5
  46. package/.out/mantine/create_pill.js +2 -2
  47. package/.out/mantine/create_radio.js +1 -1
  48. package/.out/mantine/create_radio_group.d.ts +2 -3
  49. package/.out/mantine/create_radio_group.js +4 -3
  50. package/.out/mantine/create_text_input.d.ts +2 -3
  51. package/.out/mantine/create_text_input.js +6 -5
  52. package/.out/mantine/create_value_input.d.ts +2 -3
  53. package/.out/mantine/create_value_input.js +6 -5
  54. package/.out/mantine/error_renderer.d.ts +6 -0
  55. package/.out/mantine/error_renderer.js +5 -0
  56. package/.out/mantine/hooks.d.ts +9 -13
  57. package/.out/mantine/hooks.js +10 -15
  58. package/.out/mantine/specs/checkbox_hooks.stories.d.ts +7 -2
  59. package/.out/mantine/specs/checkbox_hooks.stories.js +33 -6
  60. package/.out/mantine/specs/list_hooks.stories.js +2 -2
  61. package/.out/mantine/specs/radio_group_hooks.stories.d.ts +7 -2
  62. package/.out/mantine/specs/radio_group_hooks.stories.js +33 -6
  63. package/.out/mantine/specs/select_hooks.stories.d.ts +8 -2
  64. package/.out/mantine/specs/select_hooks.stories.js +45 -8
  65. package/.out/mantine/specs/text_input_hooks.stories.d.ts +5 -1
  66. package/.out/mantine/specs/text_input_hooks.stories.js +23 -8
  67. package/.out/mantine/specs/value_input_hooks.stories.d.ts +7 -2
  68. package/.out/mantine/specs/value_input_hooks.stories.js +49 -15
  69. package/.out/mantine/types.d.ts +4 -1
  70. package/.out/tsconfig.tsbuildinfo +1 -1
  71. package/.out/types/error_of_field.d.ts +2 -0
  72. package/.out/types/error_of_field.js +1 -0
  73. package/.out/types/field.d.ts +1 -1
  74. package/.out/types/field_converters.d.ts +17 -10
  75. package/.out/types/field_converters.js +5 -5
  76. package/.out/types/flattened_validators_of_fields.d.ts +8 -0
  77. package/.out/types/flattened_validators_of_fields.js +1 -0
  78. package/.out/types/merge_validators.d.ts +7 -0
  79. package/.out/types/merge_validators.js +38 -0
  80. package/.out/types/specs/flattened_validators_of_fields.tests.d.ts +1 -0
  81. package/.out/types/specs/flattened_validators_of_fields.tests.js +16 -0
  82. package/.out/types/specs/merge_validators.tests.d.ts +1 -0
  83. package/.out/types/specs/merge_validators.tests.js +192 -0
  84. package/.out/util/partial.d.ts +11 -5
  85. package/.out/util/partial.js +55 -15
  86. package/.turbo/turbo-build.log +9 -9
  87. package/.turbo/turbo-check-types.log +1 -1
  88. package/.turbo/turbo-release$colon$exports.log +1 -1
  89. package/README.md +5 -1
  90. package/core/mobx/field_adapter.ts +15 -7
  91. package/core/mobx/field_adapter_builder.ts +39 -75
  92. package/core/mobx/field_adapters_of_values.ts +17 -0
  93. package/core/mobx/flattened_adapters_of_fields.ts +3 -3
  94. package/core/mobx/flattened_list_types_of_type.ts +17 -0
  95. package/core/mobx/form_fields_of_field_adapters.ts +16 -0
  96. package/core/mobx/form_presenter.ts +117 -104
  97. package/core/mobx/merge_field_adapters_with_two_way_converter.ts +68 -0
  98. package/core/mobx/merge_field_adapters_with_validators.ts +99 -0
  99. package/core/mobx/specs/fixtures.ts +73 -0
  100. package/core/mobx/specs/flattened_adapters_of_fields.tests.ts +23 -2
  101. package/core/mobx/specs/flattened_list_types_of_types.tests.ts +35 -0
  102. package/core/mobx/specs/form_presenter.tests.ts +248 -124
  103. package/core/mobx/specs/merge_field_adapters_with_two_way_converter.ts +140 -0
  104. package/core/mobx/specs/merge_field_adapters_with_validators.tests.ts +259 -0
  105. package/core/mobx/types.ts +3 -3
  106. package/dist/index.cjs +459 -211
  107. package/dist/index.d.cts +153 -111
  108. package/dist/index.d.ts +153 -111
  109. package/dist/index.js +453 -200
  110. package/field_converters/chain_field_converter.ts +37 -23
  111. package/field_converters/identity_converter.ts +14 -10
  112. package/field_converters/integer_to_string_converter.ts +15 -9
  113. package/field_converters/list_converter.ts +8 -3
  114. package/field_converters/maybe_identity_converter.ts +7 -4
  115. package/field_converters/nullable_to_boolean_converter.ts +23 -16
  116. package/field_converters/select_value_type_converter.ts +86 -26
  117. package/field_converters/specs/chain_field_converter.tests.ts +302 -0
  118. package/field_converters/trimming_string_converter.ts +11 -6
  119. package/field_converters/validating_converter.ts +21 -11
  120. package/index.ts +9 -2
  121. package/mantine/create_checkbox.tsx +15 -8
  122. package/mantine/create_list.tsx +1 -4
  123. package/mantine/create_pill.tsx +2 -2
  124. package/mantine/create_radio.tsx +1 -1
  125. package/mantine/create_radio_group.tsx +8 -6
  126. package/mantine/create_text_input.tsx +20 -8
  127. package/mantine/create_value_input.tsx +17 -8
  128. package/mantine/error_renderer.ts +15 -0
  129. package/mantine/hooks.tsx +25 -51
  130. package/mantine/specs/__snapshots__/checkbox_hooks.tests.tsx.snap +126 -0
  131. package/mantine/specs/__snapshots__/radio_group_hooks.tests.tsx.snap +356 -0
  132. package/mantine/specs/__snapshots__/select_hooks.tests.tsx.snap +208 -12
  133. package/mantine/specs/__snapshots__/text_input_hooks.tests.tsx.snap +45 -0
  134. package/mantine/specs/__snapshots__/value_input_hooks.tests.tsx.snap +194 -8
  135. package/mantine/specs/checkbox_hooks.stories.tsx +47 -7
  136. package/mantine/specs/list_hooks.stories.tsx +2 -2
  137. package/mantine/specs/radio_group_hooks.stories.tsx +47 -7
  138. package/mantine/specs/select_hooks.stories.tsx +55 -8
  139. package/mantine/specs/text_input_hooks.stories.tsx +32 -7
  140. package/mantine/specs/value_input_hooks.stories.tsx +57 -16
  141. package/mantine/types.ts +5 -1
  142. package/package.json +16 -4
  143. package/tsconfig.json +1 -0
  144. package/types/error_of_field.ts +3 -0
  145. package/types/field.ts +1 -1
  146. package/types/field_converters.ts +21 -10
  147. package/types/flattened_validators_of_fields.ts +34 -0
  148. package/types/merge_validators.ts +80 -0
  149. package/types/specs/error_type_of_field.tests.ts +2 -2
  150. package/types/specs/flattened_validators_of_fields.tests.ts +93 -0
  151. package/types/specs/merge_validators.tests.ts +267 -0
  152. package/util/partial.tsx +200 -16
  153. package/.out/core/mobx/flattened_list_type_defs_of.d.ts +0 -8
  154. package/.out/field_validators/minimum_string_length_field_validator.d.ts +0 -2
  155. package/.out/field_validators/minimum_string_length_field_validator.js +0 -8
  156. package/.out/types/error_type_of_field.d.ts +0 -2
  157. package/.out/types/field_validator.d.ts +0 -3
  158. package/.out/types/flattened_form_fields_of.d.ts +0 -9
  159. package/.out/types/specs/flattened_form_fields_of.tests.js +0 -13
  160. package/core/mobx/flattened_list_type_defs_of.ts +0 -17
  161. package/core/mobx/specs/flattened_list_type_defs_of.tests.ts +0 -35
  162. package/field_validators/minimum_string_length_field_validator.ts +0 -13
  163. package/mantine/specs/__snapshots__/check_box_hooks.tests.tsx.snap +0 -227
  164. package/types/error_type_of_field.ts +0 -3
  165. package/types/field_validator.ts +0 -7
  166. package/types/flattened_form_fields_of.ts +0 -16
  167. package/types/specs/flattened_form_fields_of.tests.ts +0 -43
  168. /package/.out/core/mobx/{flattened_list_type_defs_of.js → field_adapters_of_values.js} +0 -0
  169. /package/.out/core/mobx/{specs/flattened_list_type_defs_of.tests.d.ts → flattened_list_types_of_type.js} +0 -0
  170. /package/.out/{types/error_type_of_field.js → core/mobx/form_fields_of_field_adapters.js} +0 -0
  171. /package/.out/{types/field_validator.js → core/mobx/specs/flattened_list_types_of_types.tests.d.ts} +0 -0
  172. /package/.out/{types/flattened_form_fields_of.js → core/mobx/specs/merge_field_adapters_with_two_way_converter.d.ts} +0 -0
  173. /package/.out/{types/specs/flattened_form_fields_of.tests.d.ts → core/mobx/specs/merge_field_adapters_with_validators.tests.d.ts} +0 -0
@@ -4,6 +4,7 @@ import {
4
4
  assertState,
5
5
  checkValidNumber,
6
6
  type ElementOfArray,
7
+ map,
7
8
  type Maybe,
8
9
  toArray,
9
10
  UnreachableError,
@@ -11,18 +12,19 @@ import {
11
12
  import {
12
13
  type Accessor,
13
14
  type AnyValueType,
14
- flattenAccessorsOf,
15
- type FlattenedValueTypesOf,
16
- flattenTypeDefsOf,
17
- flattenValueTypeTo,
15
+ flattenAccessorsOfType,
16
+ type FlattenedValuesOfType,
17
+ flattenTypesOfType,
18
+ flattenValuesOfType,
19
+ flattenValueTo,
18
20
  jsonPathPop,
19
21
  mobxCopy,
20
- type MobxValueTypeOf,
21
- type ReadonlyTypeDefOf,
22
+ type MobxValueOfType,
23
+ type ReadonlyTypeOfType,
22
24
  type StrictTypeDef,
23
25
  type Type,
26
+ type ValueOfType,
24
27
  valuePathToTypePath,
25
- type ValueTypeOf,
26
28
  } from '@strictly/define'
27
29
  import {
28
30
  computed,
@@ -39,23 +41,24 @@ import {
39
41
  type Field,
40
42
  } from 'types/field'
41
43
  import {
42
- FieldConversionResult,
44
+ type AnnotatedFieldConversion,
45
+ UnreliableFieldConversionType,
43
46
  } from 'types/field_converters'
44
47
  import {
45
- type ErrorTypeOfFieldAdapter,
48
+ type ErrorOfFieldAdapter,
46
49
  type FieldAdapter,
47
- type ToTypeOfFieldAdapter,
50
+ type ToOfFieldAdapter,
48
51
  } from './field_adapter'
49
52
  import {
50
- type FlattenedListTypeDefsOf,
51
- } from './flattened_list_type_defs_of'
53
+ type FlattenedListTypesOfType,
54
+ } from './flattened_list_types_of_type'
52
55
 
53
56
  export type FlattenedConvertedFieldsOf<
54
57
  ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
55
58
  > = {
56
59
  readonly [K in keyof ValuePathsToAdapters]: Field<
57
- ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>,
58
- ErrorTypeOfFieldAdapter<ValuePathsToAdapters[K]>
60
+ ToOfFieldAdapter<ValuePathsToAdapters[K]>,
61
+ ErrorOfFieldAdapter<ValuePathsToAdapters[K]>
59
62
  >
60
63
  }
61
64
 
@@ -73,22 +76,20 @@ export type FlattenedTypePathsToAdaptersOf<
73
76
  }
74
77
 
75
78
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
- type FieldOverride<V = any> = {
77
- value: V,
78
- }
79
+ type FieldOverride<V = any> = Maybe<V>
79
80
 
80
81
  type FlattenedFieldOverrides<
81
82
  ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
82
83
  > = {
83
84
  -readonly [K in keyof ValuePathsToAdapters]?: FieldOverride<
84
- ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>
85
+ ToOfFieldAdapter<ValuePathsToAdapters[K]>
85
86
  >
86
87
  }
87
88
 
88
89
  type FlattenedErrors<
89
90
  ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
90
91
  > = {
91
- -readonly [K in keyof ValuePathsToAdapters]?: ErrorTypeOfFieldAdapter<ValuePathsToAdapters[K]>
92
+ -readonly [K in keyof ValuePathsToAdapters]?: ErrorOfFieldAdapter<ValuePathsToAdapters[K]>
92
93
  }
93
94
 
94
95
  export type ValuePathsToAdaptersOf<
@@ -105,8 +106,8 @@ export class FormPresenter<
105
106
  T extends Type,
106
107
  ValueToTypePaths extends Readonly<Record<string, string>>,
107
108
  TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
108
- FlattenedValueTypesOf<T, '*'>,
109
- ValueTypeOf<ReadonlyTypeDefOf<T>>
109
+ FlattenedValuesOfType<T, '*'>,
110
+ ValueOfType<ReadonlyTypeOfType<T>>
110
111
  >,
111
112
  ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<
112
113
  TypePathsToAdapters,
@@ -114,14 +115,14 @@ export class FormPresenter<
114
115
  >,
115
116
  > {
116
117
  constructor(
117
- readonly typeDef: T,
118
+ readonly type: T,
118
119
  private readonly adapters: TypePathsToAdapters,
119
120
  ) {
120
121
  }
121
122
 
122
123
  private maybeGetAdapterForValuePath(valuePath: keyof ValuePathsToAdapters) {
123
124
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
124
- const typePath = valuePathToTypePath(this.typeDef, valuePath as string, true)
125
+ const typePath = valuePathToTypePath(this.type, valuePath as string, true)
125
126
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
126
127
  return this.adapters[typePath as keyof TypePathsToAdapters]
127
128
  }
@@ -135,13 +136,13 @@ export class FormPresenter<
135
136
  }
136
137
 
137
138
  typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K] {
138
- return valuePathToTypePath<ValueToTypePaths, K>(this.typeDef, valuePath, true)
139
+ return valuePathToTypePath<ValueToTypePaths, K>(this.type, valuePath, true)
139
140
  }
140
141
 
141
142
  setFieldValueAndValidate<K extends keyof ValuePathsToAdapters>(
142
143
  model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
143
144
  valuePath: K,
144
- value: ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>,
145
+ value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
145
146
  ): boolean {
146
147
  return this.internalSetFieldValue(model, valuePath, value, true)
147
148
  }
@@ -149,15 +150,16 @@ export class FormPresenter<
149
150
  setFieldValue<K extends keyof ValuePathsToAdapters>(
150
151
  model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
151
152
  valuePath: K,
152
- value: ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>,
153
+ value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
153
154
  ): boolean {
154
155
  return this.internalSetFieldValue(model, valuePath, value, false)
155
156
  }
156
157
 
157
- addListItem<K extends keyof FlattenedListTypeDefsOf<T>>(
158
+ addListItem<K extends keyof FlattenedListTypesOfType<T>>(
158
159
  model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
159
160
  valuePath: K,
160
- elementValue: Maybe<ElementOfArray<FlattenedValueTypesOf<T>[K]>>,
161
+ // TODO can this type be simplified?
162
+ elementValue: Maybe<ElementOfArray<FlattenedValuesOfType<T>[K]>> = null,
161
163
  index?: number,
162
164
  ) {
163
165
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -236,7 +238,7 @@ export class FormPresenter<
236
238
  })
237
239
  }
238
240
 
239
- removeListItem<K extends keyof FlattenedListTypeDefsOf<T>>(
241
+ removeListItem<K extends keyof FlattenedListTypesOfType<T>>(
240
242
  model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
241
243
  elementValuePath: `${K}.${number}`,
242
244
  ) {
@@ -316,7 +318,7 @@ export class FormPresenter<
316
318
  private internalSetFieldValue<K extends keyof ValuePathsToAdapters>(
317
319
  model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
318
320
  valuePath: K,
319
- value: ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>,
321
+ value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
320
322
  displayValidation: boolean,
321
323
  ): boolean {
322
324
  const { revert } = this.getAdapterForValuePath(valuePath)
@@ -327,11 +329,9 @@ export class FormPresenter<
327
329
  const conversion = revert(value, valuePath as any, model.value)
328
330
  const accessor = model.getAccessorForValuePath(valuePath)
329
331
  return runInAction(() => {
330
- model.fieldOverrides[valuePath] = {
331
- value,
332
- }
332
+ model.fieldOverrides[valuePath] = [value]
333
333
  switch (conversion.type) {
334
- case FieldConversionResult.Failure:
334
+ case UnreliableFieldConversionType.Failure:
335
335
  if (displayValidation) {
336
336
  model.errors[valuePath] = conversion.error
337
337
  }
@@ -339,7 +339,7 @@ export class FormPresenter<
339
339
  accessor.set(conversion.value[0])
340
340
  }
341
341
  return false
342
- case FieldConversionResult.Success:
342
+ case UnreliableFieldConversionType.Success:
343
343
  delete model.errors[valuePath]
344
344
  accessor?.set(conversion.value)
345
345
  return true
@@ -377,27 +377,38 @@ export class FormPresenter<
377
377
  } = adapter
378
378
  const accessor = model.accessors[valuePath]
379
379
  const value = accessor == null ? create(valuePath, model.value) : accessor.value
380
- const displayValue = convert(value, valuePath, model.value)
380
+ const {
381
+ value: displayValue,
382
+ } = convert(value, valuePath, model.value)
383
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
384
+ const key = valuePath as unknown as keyof ValuePathsToAdapters
381
385
  runInAction(function () {
382
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
383
- model.fieldOverrides[valuePath as unknown as keyof ValuePathsToAdapters] = {
384
- value: displayValue,
385
- }
386
+ model.fieldOverrides[key] = [displayValue]
386
387
  })
387
388
  }
388
389
 
389
390
  clearAll(
390
391
  model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
391
- value: ValueTypeOf<T>,
392
+ value: ValueOfType<T>,
392
393
  ): void {
393
394
  runInAction(() => {
394
395
  model.errors = {}
395
396
  // TODO this isn't correct, should reload from value
396
397
  model.fieldOverrides = {}
397
- model.value = mobxCopy(this.typeDef, value)
398
+ model.value = mobxCopy(this.type, value)
398
399
  })
399
400
  }
400
401
 
402
+ isValuePathActive<K extends keyof ValuePathsToAdapters>(
403
+ model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
404
+ valuePath: K,
405
+ ): boolean {
406
+ const values = flattenValuesOfType(this.type, model.value)
407
+ const keys = new Set(Object.keys(values))
408
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
409
+ return keys.has(valuePath as string)
410
+ }
411
+
401
412
  validateField<K extends keyof ValuePathsToAdapters>(
402
413
  model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
403
414
  valuePath: K,
@@ -409,7 +420,9 @@ export class FormPresenter<
409
420
  } = this.getAdapterForValuePath(valuePath)
410
421
  const fieldOverride = model.fieldOverrides[valuePath]
411
422
  const accessor = model.getAccessorForValuePath(valuePath)
412
- const storedValue = convert(
423
+ const {
424
+ value: storedValue,
425
+ } = convert(
413
426
  accessor != null
414
427
  ? accessor.value
415
428
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -419,7 +432,7 @@ export class FormPresenter<
419
432
  model.value,
420
433
  )
421
434
  const value = fieldOverride != null
422
- ? fieldOverride.value
435
+ ? fieldOverride[0]
423
436
  : storedValue
424
437
  const dirty = storedValue !== value
425
438
  assertExists(revert, 'changing field directly not supported {}', valuePath)
@@ -428,13 +441,13 @@ export class FormPresenter<
428
441
  const conversion = revert(value, valuePath as string, model.value)
429
442
  return runInAction(function () {
430
443
  switch (conversion.type) {
431
- case FieldConversionResult.Failure:
444
+ case UnreliableFieldConversionType.Failure:
432
445
  model.errors[valuePath] = conversion.error
433
446
  if (conversion.value != null && accessor != null && dirty) {
434
447
  accessor.set(conversion.value[0])
435
448
  }
436
449
  return false
437
- case FieldConversionResult.Success:
450
+ case UnreliableFieldConversionType.Success:
438
451
  delete model.errors[valuePath]
439
452
  if (accessor != null && dirty) {
440
453
  accessor.set(conversion.value)
@@ -476,22 +489,24 @@ export class FormPresenter<
476
489
  return success
477
490
  }
478
491
  const fieldOverride = model.fieldOverrides[adapterPath]
479
- const storedValue = convert(accessor.value, valuePath, model.value)
492
+ const {
493
+ value: storedValue,
494
+ } = convert(accessor.value, valuePath, model.value)
480
495
  const value = fieldOverride != null
481
- ? fieldOverride.value
496
+ ? fieldOverride[0]
482
497
  : storedValue
483
498
  // TODO more nuanced comparison
484
- const dirty = fieldOverride != null && fieldOverride.value !== storedValue
499
+ const dirty = fieldOverride != null && fieldOverride[0] !== storedValue
485
500
 
486
501
  const conversion = revert(value, valuePath, model.value)
487
502
  switch (conversion.type) {
488
- case FieldConversionResult.Failure:
503
+ case UnreliableFieldConversionType.Failure:
489
504
  model.errors[adapterPath] = conversion.error
490
505
  if (conversion.value != null && dirty) {
491
506
  accessor.set(conversion.value[0])
492
507
  }
493
508
  return false
494
- case FieldConversionResult.Success:
509
+ case UnreliableFieldConversionType.Success:
495
510
  if (dirty) {
496
511
  accessor.set(conversion.value)
497
512
  }
@@ -506,14 +521,14 @@ export class FormPresenter<
506
521
  })
507
522
  }
508
523
 
509
- createModel(value: ValueTypeOf<ReadonlyTypeDefOf<T>>): FormModel<
524
+ createModel(value: ValueOfType<ReadonlyTypeOfType<T>>): FormModel<
510
525
  T,
511
526
  ValueToTypePaths,
512
527
  TypePathsToAdapters,
513
528
  ValuePathsToAdapters
514
529
  > {
515
530
  return new FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>(
516
- this.typeDef,
531
+ this.type,
517
532
  value,
518
533
  this.adapters,
519
534
  )
@@ -524,8 +539,8 @@ export class FormModel<
524
539
  T extends Type,
525
540
  ValueToTypePaths extends Readonly<Record<string, string>>,
526
541
  TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
527
- FlattenedValueTypesOf<T, '*'>,
528
- ValueTypeOf<ReadonlyTypeDefOf<T>>
542
+ FlattenedValuesOfType<T, '*'>,
543
+ ValueOfType<ReadonlyTypeOfType<T>>
529
544
  >,
530
545
  ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<
531
546
  TypePathsToAdapters,
@@ -533,7 +548,7 @@ export class FormModel<
533
548
  >,
534
549
  > {
535
550
  @observable.ref
536
- accessor value: MobxValueTypeOf<T>
551
+ accessor value: MobxValueOfType<T>
537
552
  @observable.shallow
538
553
  accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>
539
554
  @observable.shallow
@@ -542,19 +557,25 @@ export class FormModel<
542
557
  private readonly flattenedTypeDefs: Readonly<Record<string, Type>>
543
558
 
544
559
  constructor(
545
- private readonly typeDef: T,
546
- value: ValueTypeOf<ReadonlyTypeDefOf<T>>,
560
+ private readonly type: T,
561
+ value: ValueOfType<ReadonlyTypeOfType<T>>,
547
562
  private readonly adapters: TypePathsToAdapters,
548
563
  ) {
549
- this.value = mobxCopy(typeDef, value)
550
- this.flattenedTypeDefs = flattenTypeDefsOf(typeDef)
564
+ this.value = mobxCopy(type, value)
565
+ this.flattenedTypeDefs = flattenTypesOfType(type)
551
566
  // pre-populate field overrides for consistent behavior when default information is overwritten
552
567
  // then returned to
553
- this.fieldOverrides = flattenValueTypeTo(
554
- typeDef,
568
+ const conversions = flattenValueTo(
569
+ type,
555
570
  this.value,
556
571
  () => {},
557
- (_t: StrictTypeDef, value: AnyValueType, _setter, typePath, valuePath): FieldOverride | undefined => {
572
+ (
573
+ _t: StrictTypeDef,
574
+ value: AnyValueType,
575
+ _setter,
576
+ typePath,
577
+ valuePath,
578
+ ): AnnotatedFieldConversion<FieldOverride> | undefined => {
558
579
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
559
580
  const adapter = this.adapters[typePath as keyof TypePathsToAdapters]
560
581
  if (adapter == null) {
@@ -568,12 +589,13 @@ export class FormModel<
568
589
  // no need to store a temporary value if the value cannot be written back
569
590
  return
570
591
  }
571
- const displayValue = convert(value, valuePath, this.value)
572
- return {
573
- value: displayValue,
574
- }
592
+ return convert(value, valuePath, this.value)
575
593
  },
576
594
  )
595
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
596
+ this.fieldOverrides = map(conversions, function (_k, v) {
597
+ return v && [v.value]
598
+ }) as FlattenedFieldOverrides<ValuePathsToAdapters>
577
599
  }
578
600
 
579
601
  @computed
@@ -598,8 +620,8 @@ export class FormModel<
598
620
 
599
621
  @computed
600
622
  private get knownFields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
601
- return flattenValueTypeTo(
602
- this.typeDef,
623
+ return flattenValueTo(
624
+ this.type,
603
625
  this.value,
604
626
  () => {},
605
627
  // TODO swap these to valuePath, typePath in flatten
@@ -619,7 +641,7 @@ export class FormModel<
619
641
  try {
620
642
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
621
643
  typePath = valuePathToTypePath<ValueToTypePaths, keyof ValueToTypePaths>(
622
- this.typeDef,
644
+ this.type,
623
645
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
624
646
  valuePath as keyof ValueToTypePaths,
625
647
  true,
@@ -648,31 +670,32 @@ export class FormModel<
648
670
  const accessor = this.getAccessorForValuePath(valuePath)
649
671
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
650
672
  const fieldTypeDef = this.flattenedTypeDefs[typePath as string]
651
- const value = fieldOverride
652
- ? fieldOverride.value
653
- : convert(
654
- accessor != null
655
- ? accessor.value
656
- : fieldTypeDef != null
657
- ? mobxCopy(
658
- fieldTypeDef,
659
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
660
- create(valuePath as string, this.value),
661
- )
662
- // fake values can't be copied
673
+ const {
674
+ value,
675
+ required,
676
+ readonly,
677
+ } = convert(
678
+ accessor != null
679
+ ? accessor.value
680
+ : fieldTypeDef != null
681
+ ? mobxCopy(
682
+ fieldTypeDef,
663
683
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
664
- : create(valuePath as string, this.value),
684
+ create(valuePath as string, this.value),
685
+ )
686
+ // fake values can't be copied
665
687
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
666
- valuePath as string,
667
- this.value,
668
- )
688
+ : create(valuePath as string, this.value),
689
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
690
+ valuePath as string,
691
+ this.value,
692
+ )
669
693
  const error = this.errors[valuePath]
670
694
  return {
671
- value,
695
+ value: fieldOverride != null ? fieldOverride[0] : value,
672
696
  error,
673
- // if we can't write it back, then we have to disable it
674
- disabled: this.isDisabled(valuePath),
675
- required: this.isRequired(valuePath),
697
+ readonly,
698
+ required,
676
699
  }
677
700
  }
678
701
 
@@ -684,22 +707,12 @@ export class FormModel<
684
707
  @computed
685
708
  // should only be referenced internally, so loosely typed
686
709
  get accessors(): Readonly<Record<string, Accessor>> {
687
- return flattenAccessorsOf<T, Readonly<Record<string, Accessor>>>(
688
- this.typeDef,
710
+ return flattenAccessorsOfType<T, Readonly<Record<string, Accessor>>>(
711
+ this.type,
689
712
  this.value,
690
- (value: ValueTypeOf<T>): void => {
691
- this.value = mobxCopy(this.typeDef, value)
713
+ (value: ValueOfType<T>): void => {
714
+ this.value = mobxCopy(this.type, value)
692
715
  },
693
716
  )
694
717
  }
695
-
696
- protected isDisabled(_valuePath: keyof ValuePathsToAdapters): boolean {
697
- // TODO infer from types
698
- return false
699
- }
700
-
701
- protected isRequired(_valuePath: keyof ValuePathsToAdapters): boolean {
702
- // TODO infer from types
703
- return false
704
- }
705
718
  }
@@ -0,0 +1,68 @@
1
+ import { map } from '@strictly/base'
2
+ import {
3
+ chainAnnotatedFieldConverter,
4
+ chainUnreliableFieldConverter,
5
+ } from 'field_converters/chain_field_converter'
6
+ import { type TwoWayFieldConverter } from 'types/field_converters'
7
+ import {
8
+ type ErrorOfFieldAdapter,
9
+ type FieldAdapter,
10
+ type FromOfFieldAdapter,
11
+ type ToOfFieldAdapter,
12
+ type ValuePathOfFieldAdapter,
13
+ } from './field_adapter'
14
+
15
+ export type MergedOfFieldAdaptersWithTwoWayConverter<
16
+ FieldAdapters extends Readonly<Record<string, FieldAdapter>>,
17
+ E,
18
+ Context,
19
+ > = {
20
+ [K in keyof FieldAdapters]: FieldAdapter<
21
+ FromOfFieldAdapter<FieldAdapters[K]>,
22
+ ToOfFieldAdapter<FieldAdapters[K]>,
23
+ ErrorOfFieldAdapter<FieldAdapters[K]> | E,
24
+ ValuePathOfFieldAdapter<FieldAdapters[K]>,
25
+ Context
26
+ >
27
+ }
28
+
29
+ type ValuePathsOfFieldAdapters<FieldAdapters extends Readonly<Record<string, FieldAdapter>>> = {
30
+ [K in keyof FieldAdapters]: ValuePathOfFieldAdapter<FieldAdapters[K]>
31
+ }[keyof FieldAdapters]
32
+
33
+ type TosOfFieldAdapters<FieldAdapters extends Readonly<Record<string, FieldAdapter>>> = {
34
+ [K in keyof FieldAdapters]: ToOfFieldAdapter<FieldAdapters[K]>
35
+ }[keyof FieldAdapters]
36
+
37
+ export function mergeFieldAdaptersWithTwoWayConverter<
38
+ // must have a field adapter for every validator
39
+ FieldAdapters extends Readonly<Record<string, FieldAdapter>>,
40
+ E,
41
+ Context,
42
+ >(
43
+ fieldAdapters: FieldAdapters,
44
+ converter: TwoWayFieldConverter<
45
+ TosOfFieldAdapters<FieldAdapters>,
46
+ TosOfFieldAdapters<FieldAdapters>,
47
+ E,
48
+ ValuePathsOfFieldAdapters<FieldAdapters>,
49
+ Context
50
+ >,
51
+ ): MergedOfFieldAdaptersWithTwoWayConverter<FieldAdapters, E, Context> {
52
+ return map<keyof FieldAdapters, FieldAdapter>(
53
+ fieldAdapters,
54
+ function (_key, adapter) {
55
+ return {
56
+ convert: chainAnnotatedFieldConverter(
57
+ adapter.convert.bind(adapter),
58
+ converter.convert.bind(converter),
59
+ ),
60
+ revert: adapter.revert && chainUnreliableFieldConverter(
61
+ converter.revert.bind(converter),
62
+ adapter.revert.bind(adapter),
63
+ ),
64
+ create: adapter.create.bind(adapter),
65
+ }
66
+ },
67
+ )
68
+ }
@@ -0,0 +1,99 @@
1
+ import { reduce } from '@strictly/base'
2
+ import {
3
+ annotations,
4
+ validate,
5
+ type Validator,
6
+ } from '@strictly/define'
7
+ import { type Simplify } from 'type-fest'
8
+ import {
9
+ type AnnotatedFieldConversion,
10
+ type UnreliableFieldConversion,
11
+ UnreliableFieldConversionType,
12
+ } from 'types/field_converters'
13
+ import { type FieldAdapter } from './field_adapter'
14
+
15
+ export type MergedOfFieldAdaptersWithValidators<
16
+ // must have a field adapter for every validator
17
+ FieldAdapters extends Readonly<Record<Key, FieldAdapter>>,
18
+ Validators extends Partial<Readonly<Record<string, Validator>>>,
19
+ Key extends keyof Validators = keyof Validators,
20
+ > = Simplify<{
21
+ readonly [K in Key]: MergedOfFieldAdapterWithValidator<FieldAdapters[K], Validators[K]>
22
+ } & {
23
+ readonly [K in Exclude<keyof FieldAdapters, Key>]: FieldAdapters[K]
24
+ }>
25
+
26
+ type MergedOfFieldAdapterWithValidator<
27
+ A extends FieldAdapter,
28
+ V extends Validator | undefined,
29
+ > = undefined extends V ? A
30
+ : A extends FieldAdapter<infer From, infer To, infer E1, infer P1, infer C1>
31
+ ? V extends Validator<From, infer E2, infer P2, infer C2> ? FieldAdapter<From, To, E1 | E2, P1 | P2, C1 | C2>
32
+ : never
33
+ : never
34
+
35
+ export function mergeAdaptersWithValidators<
36
+ // must have a field adapter for every validator
37
+ FieldAdapters extends Readonly<Record<Key, FieldAdapter>>,
38
+ Validators extends Readonly<Record<string, Validator>>,
39
+ Key extends keyof Validators = keyof Validators,
40
+ >(
41
+ adapters: FieldAdapters,
42
+ validators: Validators,
43
+ ): MergedOfFieldAdaptersWithValidators<FieldAdapters, Validators, Key> {
44
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
45
+ return reduce<
46
+ Key,
47
+ FieldAdapter,
48
+ Partial<Record<Key, FieldAdapter>>
49
+ >(
50
+ adapters,
51
+ function (acc, key, adapter) {
52
+ const validator = validators[key]
53
+ if (validator == null) {
54
+ acc[key] = adapter
55
+ return acc
56
+ }
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ function revert(to: any, ...params: [any, any]): UnreliableFieldConversion {
59
+ const result = adapter.revert!(to, ...params)
60
+ if (result.type === UnreliableFieldConversionType.Failure) {
61
+ return result
62
+ }
63
+ const validationError = validate(validator, result.value, ...params)
64
+ if (validationError == null) {
65
+ return result
66
+ }
67
+ return {
68
+ type: UnreliableFieldConversionType.Failure,
69
+ value: [result.value] as const,
70
+ error: validationError,
71
+ }
72
+ }
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ function convert(from: any, ...params: [any, any]): AnnotatedFieldConversion {
75
+ const {
76
+ required: required1,
77
+ readonly: readonly1,
78
+ value,
79
+ } = adapter.convert(from, ...params)
80
+ const {
81
+ required: required2,
82
+ readonly: readonly2,
83
+ } = annotations(validator, ...params)
84
+ return {
85
+ value,
86
+ required: required1 || required2,
87
+ readonly: readonly1 || readonly2,
88
+ }
89
+ }
90
+ acc[key] = {
91
+ ...adapter,
92
+ convert,
93
+ revert: adapter.revert && revert,
94
+ }
95
+ return acc
96
+ },
97
+ {},
98
+ ) as MergedOfFieldAdaptersWithValidators<FieldAdapters, Validators, Key>
99
+ }