@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.
- package/.out/core/mobx/field_adapter_builder.d.ts +1 -1
- package/.out/core/mobx/form_model.d.ts +9 -6
- package/.out/core/mobx/form_model.js +77 -42
- package/.out/core/mobx/specs/form_model.tests.js +80 -20
- package/.out/core/mobx/types.d.ts +4 -4
- package/.out/core/props.d.ts +2 -0
- package/.out/index.d.ts +0 -1
- package/.out/index.js +0 -1
- package/.out/mantine/create_field_view.d.ts +20 -0
- package/.out/mantine/create_field_view.js +54 -0
- package/.out/mantine/create_list.js +3 -2
- package/.out/mantine/hooks.d.ts +4 -1
- package/.out/mantine/hooks.js +14 -2
- package/.out/mantine/specs/field_view_hooks.stories.d.ts +12 -0
- package/.out/mantine/specs/field_view_hooks.stories.js +61 -0
- package/.out/mantine/specs/field_view_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/field_view_hooks.tests.js +12 -0
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/core/mobx/field_adapter_builder.ts +2 -2
- package/core/mobx/form_model.ts +89 -47
- package/core/mobx/specs/form_model.tests.ts +131 -11
- package/core/mobx/types.ts +4 -4
- package/core/props.ts +4 -0
- package/dist/index.cjs +165 -89
- package/dist/index.d.cts +45 -40
- package/dist/index.d.ts +45 -40
- package/dist/index.js +162 -81
- package/index.ts +0 -1
- package/mantine/create_field_view.tsx +94 -0
- package/mantine/create_list.tsx +9 -2
- package/mantine/hooks.tsx +19 -2
- package/mantine/specs/__snapshots__/field_view_hooks.tests.tsx.snap +153 -0
- package/mantine/specs/field_view_hooks.stories.tsx +112 -0
- package/mantine/specs/field_view_hooks.tests.tsx +15 -0
- package/package.json +1 -1
- package/.out/mantine/field_view.d.ts +0 -18
- package/.out/mantine/field_view.js +0 -16
- package/mantine/field_view.tsx +0 -39
package/.turbo/turbo-build.log
CHANGED
|
@@ -7,12 +7,12 @@ $ tsup
|
|
|
7
7
|
[34mCLI[39m Target: es6
|
|
8
8
|
[34mCJS[39m Build start
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
10
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m62.28 KB[39m
|
|
11
|
+
[32mCJS[39m ⚡️ Build success in 117ms
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m58.27 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 140ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
18
|
-
Done in 10.
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 9749ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m38.08 KB[39m
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m38.08 KB[39m
|
|
18
|
+
Done in 10.88s.
|
|
@@ -336,8 +336,8 @@ export function trimmingStringAdapter<
|
|
|
336
336
|
|
|
337
337
|
export function listAdapter<
|
|
338
338
|
E,
|
|
339
|
-
ValuePath extends string,
|
|
340
|
-
Context,
|
|
339
|
+
ValuePath extends string = string,
|
|
340
|
+
Context = unknown,
|
|
341
341
|
>() {
|
|
342
342
|
return new FieldAdapterBuilder<readonly E[], readonly E[], never, ValuePath, Context>(
|
|
343
343
|
annotatedIdentityConverter<readonly E[], ValuePath, Context>(false),
|
package/core/mobx/form_model.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type ValueOfType,
|
|
27
27
|
valuePathToTypePath,
|
|
28
28
|
} from '@strictly/define'
|
|
29
|
+
import { type FormMode } from 'core/props'
|
|
29
30
|
import {
|
|
30
31
|
computed,
|
|
31
32
|
observable,
|
|
@@ -105,11 +106,18 @@ export type ValuePathsToAdaptersOf<
|
|
|
105
106
|
: never
|
|
106
107
|
|
|
107
108
|
export type ContextOf<TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>> =
|
|
108
|
-
UnionToIntersection<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
UnionToIntersection<
|
|
110
|
+
| {
|
|
111
|
+
readonly [
|
|
112
|
+
K in keyof TypePathsToAdapters
|
|
113
|
+
]: TypePathsToAdapters[K] extends undefined ? undefined
|
|
114
|
+
// ignore unspecified values
|
|
115
|
+
: unknown extends ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>> ? never
|
|
116
|
+
: ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>>
|
|
117
|
+
}[keyof TypePathsToAdapters]
|
|
118
|
+
// ensure we have at least one thing to intersect (can end up with a `never` context otherwise)
|
|
119
|
+
| {}
|
|
120
|
+
>
|
|
113
121
|
|
|
114
122
|
export abstract class FormModel<
|
|
115
123
|
T extends Type,
|
|
@@ -135,12 +143,12 @@ export abstract class FormModel<
|
|
|
135
143
|
|
|
136
144
|
constructor(
|
|
137
145
|
readonly type: T,
|
|
138
|
-
|
|
146
|
+
private readonly originalValue: ValueOfType<ReadonlyTypeOfType<T>>,
|
|
139
147
|
protected readonly adapters: TypePathsToAdapters,
|
|
148
|
+
protected readonly mode: FormMode,
|
|
140
149
|
) {
|
|
141
|
-
this.value = mobxCopy(type,
|
|
150
|
+
this.value = mobxCopy(type, originalValue)
|
|
142
151
|
this.flattenedTypeDefs = flattenTypesOfType(type)
|
|
143
|
-
const contextValue = this.toContext(value)
|
|
144
152
|
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
145
153
|
// then returned to
|
|
146
154
|
const conversions = flattenValueTo(
|
|
@@ -149,11 +157,14 @@ export abstract class FormModel<
|
|
|
149
157
|
() => {},
|
|
150
158
|
(
|
|
151
159
|
_t: StrictTypeDef,
|
|
152
|
-
|
|
160
|
+
fieldValue: AnyValueType,
|
|
153
161
|
_setter,
|
|
154
162
|
typePath,
|
|
155
163
|
valuePath,
|
|
156
164
|
): AnnotatedFieldConversion<FieldOverride> | undefined => {
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
166
|
+
const contextValue = this.toContext(originalValue, valuePath as keyof ValuePathsToAdapters)
|
|
167
|
+
|
|
157
168
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
158
169
|
const adapter = this.adapters[typePath as keyof TypePathsToAdapters]
|
|
159
170
|
if (adapter == null) {
|
|
@@ -168,7 +179,7 @@ export abstract class FormModel<
|
|
|
168
179
|
return
|
|
169
180
|
}
|
|
170
181
|
// cannot call this.context yet as the "this" pointer has not been fully created
|
|
171
|
-
return convert(
|
|
182
|
+
return convert(fieldValue, valuePath, contextValue)
|
|
172
183
|
},
|
|
173
184
|
)
|
|
174
185
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -177,12 +188,21 @@ export abstract class FormModel<
|
|
|
177
188
|
}) as FlattenedFieldOverrides<ValuePathsToAdapters>
|
|
178
189
|
}
|
|
179
190
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
protected abstract toContext(
|
|
192
|
+
value: ValueOfType<ReadonlyTypeOfType<T>>,
|
|
193
|
+
valuePath: keyof ValuePathsToAdapters,
|
|
194
|
+
): ContextType
|
|
184
195
|
|
|
185
|
-
|
|
196
|
+
get forceMutableFields() {
|
|
197
|
+
switch (this.mode) {
|
|
198
|
+
case 'create':
|
|
199
|
+
return true
|
|
200
|
+
case 'edit':
|
|
201
|
+
return false
|
|
202
|
+
default:
|
|
203
|
+
return this.mode satisfies never
|
|
204
|
+
}
|
|
205
|
+
}
|
|
186
206
|
|
|
187
207
|
@computed
|
|
188
208
|
get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
|
|
@@ -256,6 +276,7 @@ export abstract class FormModel<
|
|
|
256
276
|
const accessor = this.getAccessorForValuePath(valuePath)
|
|
257
277
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
258
278
|
const fieldTypeDef = this.flattenedTypeDefs[typePath as string]
|
|
279
|
+
const context = this.toContext(this.value, valuePath)
|
|
259
280
|
const {
|
|
260
281
|
value,
|
|
261
282
|
required,
|
|
@@ -267,20 +288,20 @@ export abstract class FormModel<
|
|
|
267
288
|
? mobxCopy(
|
|
268
289
|
fieldTypeDef,
|
|
269
290
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
270
|
-
create(valuePath as string,
|
|
291
|
+
create(valuePath as string, context),
|
|
271
292
|
)
|
|
272
293
|
// fake values can't be copied
|
|
273
294
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
274
|
-
: create(valuePath as string,
|
|
295
|
+
: create(valuePath as string, context),
|
|
275
296
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
276
297
|
valuePath as string,
|
|
277
|
-
|
|
298
|
+
context,
|
|
278
299
|
)
|
|
279
300
|
const error = this.errors[valuePath]
|
|
280
301
|
return {
|
|
281
302
|
value: fieldOverride != null ? fieldOverride[0] : value,
|
|
282
303
|
error,
|
|
283
|
-
readonly,
|
|
304
|
+
readonly: readonly && !this.forceMutableFields,
|
|
284
305
|
required,
|
|
285
306
|
}
|
|
286
307
|
}
|
|
@@ -360,7 +381,9 @@ export abstract class FormModel<
|
|
|
360
381
|
: elementAdapter.create(
|
|
361
382
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
362
383
|
elementTypePath as string,
|
|
363
|
-
|
|
384
|
+
// TODO what can we use for the value path here?
|
|
385
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
386
|
+
this.toContext(this.value, valuePath as unknown as keyof ValuePathsToAdapters),
|
|
364
387
|
)
|
|
365
388
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
366
389
|
const originalList: any[] = accessor.value
|
|
@@ -505,7 +528,7 @@ export abstract class FormModel<
|
|
|
505
528
|
assertExists(revert, 'setting value not supported {}', valuePath)
|
|
506
529
|
|
|
507
530
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
508
|
-
const conversion = revert(value, valuePath as any, this.
|
|
531
|
+
const conversion = revert(value, valuePath as any, this.toContext(this.value, valuePath))
|
|
509
532
|
const accessor = this.getAccessorForValuePath(valuePath)
|
|
510
533
|
return runInAction(() => {
|
|
511
534
|
this.fieldOverrides[valuePath] = [value]
|
|
@@ -548,10 +571,12 @@ export abstract class FormModel<
|
|
|
548
571
|
convert,
|
|
549
572
|
create,
|
|
550
573
|
} = adapter
|
|
551
|
-
|
|
574
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
575
|
+
const context = this.toContext(this.value, valuePath as unknown as keyof ValuePathsToAdapters)
|
|
576
|
+
const value = create(valuePath, context)
|
|
552
577
|
const {
|
|
553
578
|
value: displayValue,
|
|
554
|
-
} = convert(value, valuePath,
|
|
579
|
+
} = convert(value, valuePath, context)
|
|
555
580
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
556
581
|
const key = valuePath as unknown as keyof ValuePathsToAdapters
|
|
557
582
|
runInAction(() => {
|
|
@@ -586,16 +611,18 @@ export abstract class FormModel<
|
|
|
586
611
|
} = this.getAdapterForValuePath(valuePath)
|
|
587
612
|
const fieldOverride = this.fieldOverrides[valuePath]
|
|
588
613
|
const accessor = this.getAccessorForValuePath(valuePath)
|
|
614
|
+
const context = this.toContext(this.value, valuePath)
|
|
615
|
+
|
|
589
616
|
const {
|
|
590
617
|
value: storedValue,
|
|
591
618
|
} = convert(
|
|
592
619
|
accessor != null
|
|
593
620
|
? accessor.value
|
|
594
621
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
595
|
-
: create(valuePath as string,
|
|
622
|
+
: create(valuePath as string, context),
|
|
596
623
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
597
624
|
valuePath as string,
|
|
598
|
-
|
|
625
|
+
context,
|
|
599
626
|
)
|
|
600
627
|
const value = fieldOverride != null
|
|
601
628
|
? fieldOverride[0]
|
|
@@ -605,13 +632,13 @@ export abstract class FormModel<
|
|
|
605
632
|
if (ignoreDefaultValue) {
|
|
606
633
|
const {
|
|
607
634
|
value: defaultDisplayValue,
|
|
608
|
-
} = convert(create(valuePath,
|
|
635
|
+
} = convert(create(valuePath, context), valuePath, context)
|
|
609
636
|
if (defaultDisplayValue === value) {
|
|
610
637
|
return true
|
|
611
638
|
}
|
|
612
639
|
}
|
|
613
640
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
614
|
-
const conversion = revert(value, valuePath as string,
|
|
641
|
+
const conversion = revert(value, valuePath as string, context)
|
|
615
642
|
return runInAction(() => {
|
|
616
643
|
switch (conversion.type) {
|
|
617
644
|
case UnreliableFieldConversionType.Failure:
|
|
@@ -632,11 +659,14 @@ export abstract class FormModel<
|
|
|
632
659
|
})
|
|
633
660
|
}
|
|
634
661
|
|
|
635
|
-
validateAll(): boolean {
|
|
662
|
+
validateAll(force: boolean = this.mode === 'create'): boolean {
|
|
636
663
|
// sort keys shortest to longest so parent changes don't overwrite child changes
|
|
637
664
|
const accessors = toArray(this.accessors).toSorted(function ([a], [b]) {
|
|
638
665
|
return a.length - b.length
|
|
639
666
|
})
|
|
667
|
+
|
|
668
|
+
const flattenedOriginalValues = flattenValuesOfType(this.type, this.originalValue)
|
|
669
|
+
|
|
640
670
|
return runInAction(() => {
|
|
641
671
|
return accessors.reduce(
|
|
642
672
|
(
|
|
@@ -662,32 +692,44 @@ export abstract class FormModel<
|
|
|
662
692
|
return success
|
|
663
693
|
}
|
|
664
694
|
const fieldOverride = this.fieldOverrides[adapterPath]
|
|
695
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
696
|
+
const context = this.toContext(this.value, valuePath as keyof ValuePathsToAdapters)
|
|
665
697
|
const {
|
|
666
698
|
value: storedValue,
|
|
667
|
-
} = convert(accessor.value, valuePath,
|
|
699
|
+
} = convert(accessor.value, valuePath, context)
|
|
668
700
|
const value = fieldOverride != null
|
|
669
701
|
? fieldOverride[0]
|
|
670
702
|
: storedValue
|
|
671
|
-
// TODO
|
|
703
|
+
// TODO customizable comparisons
|
|
672
704
|
const dirty = fieldOverride != null && fieldOverride[0] !== storedValue
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
705
|
+
const needsValidation = force
|
|
706
|
+
|| !(valuePath in flattenedOriginalValues)
|
|
707
|
+
|| storedValue !== convert(
|
|
708
|
+
flattenedOriginalValues[valuePath],
|
|
709
|
+
valuePath,
|
|
710
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
711
|
+
this.toContext(this.originalValue, valuePath as keyof ValuePathsToAdapters),
|
|
712
|
+
).value
|
|
713
|
+
if (needsValidation) {
|
|
714
|
+
const conversion = revert(value, valuePath, context)
|
|
715
|
+
switch (conversion.type) {
|
|
716
|
+
case UnreliableFieldConversionType.Failure:
|
|
717
|
+
this.errors[adapterPath] = conversion.error
|
|
718
|
+
if (conversion.value != null && dirty) {
|
|
719
|
+
accessor.set(conversion.value[0])
|
|
720
|
+
}
|
|
721
|
+
return false
|
|
722
|
+
case UnreliableFieldConversionType.Success:
|
|
723
|
+
if (dirty) {
|
|
724
|
+
accessor.set(conversion.value)
|
|
725
|
+
}
|
|
726
|
+
delete this.errors[adapterPath]
|
|
727
|
+
return success
|
|
728
|
+
default:
|
|
729
|
+
throw new UnreachableError(conversion)
|
|
730
|
+
}
|
|
690
731
|
}
|
|
732
|
+
return success
|
|
691
733
|
},
|
|
692
734
|
true,
|
|
693
735
|
)
|
|
@@ -2,6 +2,7 @@ import { expectDefinedAndReturn } from '@strictly/base'
|
|
|
2
2
|
import {
|
|
3
3
|
booleanType,
|
|
4
4
|
type FlattenedValuesOfType,
|
|
5
|
+
flattenValidatorsOfValidatingType,
|
|
5
6
|
list,
|
|
6
7
|
nullType,
|
|
7
8
|
numberType,
|
|
@@ -13,7 +14,9 @@ import {
|
|
|
13
14
|
type ValueOfType,
|
|
14
15
|
type ValueToTypePathsOfType,
|
|
15
16
|
} from '@strictly/define'
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
type FieldAdapter,
|
|
19
|
+
} from 'core/mobx/field_adapter'
|
|
17
20
|
import {
|
|
18
21
|
adapterFromTwoWayConverter,
|
|
19
22
|
identityAdapter,
|
|
@@ -23,11 +26,14 @@ import {
|
|
|
23
26
|
FormModel,
|
|
24
27
|
type ValuePathsToAdaptersOf,
|
|
25
28
|
} from 'core/mobx/form_model'
|
|
29
|
+
import { mergeAdaptersWithValidators } from 'core/mobx/merge_field_adapters_with_validators'
|
|
26
30
|
import { IntegerToStringConverter } from 'field_converters/integer_to_string_converter'
|
|
27
31
|
import { NullableToBooleanConverter } from 'field_converters/nullable_to_boolean_converter'
|
|
28
32
|
import { SelectDiscriminatedUnionConverter } from 'field_converters/select_value_type_converter'
|
|
29
33
|
import { prototypingFieldValueFactory } from 'field_value_factories/prototyping_field_value_factory'
|
|
30
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
type Simplify,
|
|
36
|
+
} from 'type-fest'
|
|
31
37
|
import { type Field } from 'types/field'
|
|
32
38
|
import {
|
|
33
39
|
UnreliableFieldConversionType,
|
|
@@ -54,9 +60,13 @@ class TestFormModel<
|
|
|
54
60
|
{}
|
|
55
61
|
>,
|
|
56
62
|
> extends FormModel<T, ValueToTypePaths, TypePathsToAdapters, {}> {
|
|
57
|
-
override toContext(
|
|
63
|
+
override toContext(
|
|
64
|
+
value: ValueOfType<T>,
|
|
65
|
+
valuePath: keyof ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>,
|
|
66
|
+
) {
|
|
58
67
|
return {
|
|
59
|
-
|
|
68
|
+
value,
|
|
69
|
+
valuePath,
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
}
|
|
@@ -195,6 +205,7 @@ describe('all', function () {
|
|
|
195
205
|
typeDef,
|
|
196
206
|
originalValue,
|
|
197
207
|
adapters,
|
|
208
|
+
'create',
|
|
198
209
|
)
|
|
199
210
|
})
|
|
200
211
|
|
|
@@ -255,6 +266,7 @@ describe('all', function () {
|
|
|
255
266
|
typeDef,
|
|
256
267
|
originalValue,
|
|
257
268
|
adapters,
|
|
269
|
+
'create',
|
|
258
270
|
)
|
|
259
271
|
})
|
|
260
272
|
|
|
@@ -296,6 +308,7 @@ describe('all', function () {
|
|
|
296
308
|
typeDef,
|
|
297
309
|
value,
|
|
298
310
|
adapters,
|
|
311
|
+
'create',
|
|
299
312
|
)
|
|
300
313
|
})
|
|
301
314
|
|
|
@@ -374,6 +387,7 @@ describe('all', function () {
|
|
|
374
387
|
typeDef,
|
|
375
388
|
value,
|
|
376
389
|
converters,
|
|
390
|
+
'create',
|
|
377
391
|
)
|
|
378
392
|
})
|
|
379
393
|
|
|
@@ -444,6 +458,7 @@ describe('all', function () {
|
|
|
444
458
|
typeDef,
|
|
445
459
|
value,
|
|
446
460
|
converters,
|
|
461
|
+
'create',
|
|
447
462
|
)
|
|
448
463
|
})
|
|
449
464
|
|
|
@@ -509,6 +524,7 @@ describe('all', function () {
|
|
|
509
524
|
typeDef,
|
|
510
525
|
originalValue,
|
|
511
526
|
adapters,
|
|
527
|
+
'create',
|
|
512
528
|
)
|
|
513
529
|
})
|
|
514
530
|
|
|
@@ -635,6 +651,7 @@ describe('all', function () {
|
|
|
635
651
|
typeDef,
|
|
636
652
|
originalValue,
|
|
637
653
|
converters,
|
|
654
|
+
'create',
|
|
638
655
|
)
|
|
639
656
|
})
|
|
640
657
|
|
|
@@ -740,10 +757,10 @@ describe('all', function () {
|
|
|
740
757
|
|
|
741
758
|
// no longer passes context, but will pass context eventually again
|
|
742
759
|
describe('passes context', function () {
|
|
743
|
-
let contextCopy:
|
|
760
|
+
let contextCopy: string
|
|
744
761
|
beforeEach(function () {
|
|
745
762
|
integerToStringAdapter.revert.mockImplementationOnce(function (_value, _path, context) {
|
|
746
|
-
contextCopy =
|
|
763
|
+
contextCopy = JSON.stringify(context)
|
|
747
764
|
return {
|
|
748
765
|
type: UnreliableFieldConversionType.Success,
|
|
749
766
|
value: 1,
|
|
@@ -751,7 +768,7 @@ describe('all', function () {
|
|
|
751
768
|
})
|
|
752
769
|
})
|
|
753
770
|
|
|
754
|
-
it('supplies the
|
|
771
|
+
it('supplies the context when converting', function () {
|
|
755
772
|
model.setFieldValueAndValidate('$.2', '4')
|
|
756
773
|
|
|
757
774
|
expect(integerToStringAdapter.revert).toHaveBeenCalledOnce()
|
|
@@ -759,14 +776,23 @@ describe('all', function () {
|
|
|
759
776
|
'4',
|
|
760
777
|
'$.2',
|
|
761
778
|
{
|
|
762
|
-
|
|
779
|
+
// the supplied value isn't a copy, so it will be the model value, even
|
|
780
|
+
// if the value has since changed
|
|
781
|
+
value: model.value,
|
|
782
|
+
valuePath: '$.2',
|
|
763
783
|
},
|
|
764
784
|
)
|
|
765
785
|
})
|
|
766
786
|
|
|
767
|
-
it('supplies the context', function () {
|
|
768
|
-
|
|
769
|
-
|
|
787
|
+
it('supplies the correct context value at the time it is being checked', function () {
|
|
788
|
+
// the copy will show the supplied value however
|
|
789
|
+
expect(JSON.parse(contextCopy)).toEqual({
|
|
790
|
+
value: [
|
|
791
|
+
1,
|
|
792
|
+
3,
|
|
793
|
+
7,
|
|
794
|
+
],
|
|
795
|
+
valuePath: '$.2',
|
|
770
796
|
})
|
|
771
797
|
})
|
|
772
798
|
})
|
|
@@ -976,6 +1002,7 @@ describe('all', function () {
|
|
|
976
1002
|
type,
|
|
977
1003
|
originalValue,
|
|
978
1004
|
adapters,
|
|
1005
|
+
'create',
|
|
979
1006
|
)
|
|
980
1007
|
})
|
|
981
1008
|
|
|
@@ -1044,6 +1071,7 @@ describe('all', function () {
|
|
|
1044
1071
|
a: 1,
|
|
1045
1072
|
},
|
|
1046
1073
|
adapters,
|
|
1074
|
+
'create',
|
|
1047
1075
|
)
|
|
1048
1076
|
it.each([
|
|
1049
1077
|
[
|
|
@@ -1076,6 +1104,7 @@ describe('all', function () {
|
|
|
1076
1104
|
b: false,
|
|
1077
1105
|
},
|
|
1078
1106
|
adapters,
|
|
1107
|
+
'create',
|
|
1079
1108
|
)
|
|
1080
1109
|
it.each([
|
|
1081
1110
|
[
|
|
@@ -1125,6 +1154,7 @@ describe('all', function () {
|
|
|
1125
1154
|
typeDef,
|
|
1126
1155
|
originalValue,
|
|
1127
1156
|
converters,
|
|
1157
|
+
'create',
|
|
1128
1158
|
)
|
|
1129
1159
|
})
|
|
1130
1160
|
|
|
@@ -1150,5 +1180,95 @@ describe('all', function () {
|
|
|
1150
1180
|
})
|
|
1151
1181
|
})
|
|
1152
1182
|
})
|
|
1183
|
+
|
|
1184
|
+
describe('interaction with create and edit modes', () => {
|
|
1185
|
+
const typeDef = object().readonlyField('n', numberType.enforce(n => n < 10 ? 'err' : null))
|
|
1186
|
+
const adapters = mergeAdaptersWithValidators(
|
|
1187
|
+
{
|
|
1188
|
+
$: identityAdapter({ n: 0 }),
|
|
1189
|
+
'$.n': integerToStringAdapter,
|
|
1190
|
+
} as const,
|
|
1191
|
+
flattenValidatorsOfValidatingType(typeDef),
|
|
1192
|
+
)
|
|
1193
|
+
type JsonPaths = {
|
|
1194
|
+
$: '$',
|
|
1195
|
+
'$.n': '$.n',
|
|
1196
|
+
}
|
|
1197
|
+
let originalValue: ValueOfType<typeof typeDef>
|
|
1198
|
+
beforeEach(() => {
|
|
1199
|
+
originalValue = {
|
|
1200
|
+
n: 1,
|
|
1201
|
+
}
|
|
1202
|
+
})
|
|
1203
|
+
describe('create mode', () => {
|
|
1204
|
+
let model: FormModel<
|
|
1205
|
+
typeof typeDef,
|
|
1206
|
+
JsonPaths,
|
|
1207
|
+
typeof adapters
|
|
1208
|
+
>
|
|
1209
|
+
beforeEach(() => {
|
|
1210
|
+
model = new TestFormModel<
|
|
1211
|
+
typeof typeDef,
|
|
1212
|
+
JsonPaths,
|
|
1213
|
+
typeof adapters
|
|
1214
|
+
>(
|
|
1215
|
+
typeDef,
|
|
1216
|
+
originalValue,
|
|
1217
|
+
adapters,
|
|
1218
|
+
'create',
|
|
1219
|
+
)
|
|
1220
|
+
})
|
|
1221
|
+
|
|
1222
|
+
it('makes the field editable', () => {
|
|
1223
|
+
expect(model.fields['$.n'].readonly).toBeFalsy()
|
|
1224
|
+
})
|
|
1225
|
+
|
|
1226
|
+
it('fails validation', () => {
|
|
1227
|
+
expect(model.validateAll()).toBeFalsy()
|
|
1228
|
+
})
|
|
1229
|
+
|
|
1230
|
+
it('passes validation with valid data', () => {
|
|
1231
|
+
model.setFieldValue('$.n', '10')
|
|
1232
|
+
expect(model.validateAll()).toBeTruthy()
|
|
1233
|
+
})
|
|
1234
|
+
})
|
|
1235
|
+
describe('edit model', () => {
|
|
1236
|
+
let model: FormModel<
|
|
1237
|
+
typeof typeDef,
|
|
1238
|
+
JsonPaths,
|
|
1239
|
+
typeof adapters
|
|
1240
|
+
>
|
|
1241
|
+
beforeEach(function () {
|
|
1242
|
+
model = new TestFormModel<
|
|
1243
|
+
typeof typeDef,
|
|
1244
|
+
JsonPaths,
|
|
1245
|
+
typeof adapters
|
|
1246
|
+
>(
|
|
1247
|
+
typeDef,
|
|
1248
|
+
originalValue,
|
|
1249
|
+
adapters,
|
|
1250
|
+
'edit',
|
|
1251
|
+
)
|
|
1252
|
+
})
|
|
1253
|
+
|
|
1254
|
+
it('respects the field being readonly', () => {
|
|
1255
|
+
expect(model.fields['$.n'].readonly).toBeTruthy()
|
|
1256
|
+
})
|
|
1257
|
+
|
|
1258
|
+
it('validates successfully with clean, but invalid data', () => {
|
|
1259
|
+
expect(model.validateAll()).toBeTruthy()
|
|
1260
|
+
})
|
|
1261
|
+
|
|
1262
|
+
it('fails validation with invalid, dirty data', () => {
|
|
1263
|
+
model.setFieldValue('$.n', '2')
|
|
1264
|
+
expect(model.validateAll()).toBeFalsy()
|
|
1265
|
+
})
|
|
1266
|
+
|
|
1267
|
+
it('passes validation with valid, dirty data', () => {
|
|
1268
|
+
model.setFieldValue('$.n', '10')
|
|
1269
|
+
expect(model.validateAll()).toBeTruthy()
|
|
1270
|
+
})
|
|
1271
|
+
})
|
|
1272
|
+
})
|
|
1153
1273
|
})
|
|
1154
1274
|
})
|
package/core/mobx/types.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from './form_model'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Used to extract the supported value paths from a
|
|
9
|
+
* Used to extract the supported value paths from a model
|
|
10
10
|
*/
|
|
11
11
|
export type ValuePathsOfModel<
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -22,7 +22,7 @@ export type ValuePathsOfModel<
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Used to extract the render type (so the value that is passed to the view) of a given value path
|
|
25
|
-
* from a
|
|
25
|
+
* from a model
|
|
26
26
|
*/
|
|
27
27
|
export type ToValueOfModelValuePath<
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -38,9 +38,9 @@ export type ToValueOfModelValuePath<
|
|
|
38
38
|
: never
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Extracts the form fields from
|
|
41
|
+
* Extracts the form fields from a form model. The recommended way is to
|
|
42
42
|
* define the form fields explicitly and use that type to enforce the types
|
|
43
|
-
* of your converters, but generating the FormFields from your
|
|
43
|
+
* of your converters, but generating the FormFields from your model
|
|
44
44
|
* is less typing, albeit at the cost of potentially getting type errors
|
|
45
45
|
* reported a long way away from the source
|
|
46
46
|
*/
|
package/core/props.ts
CHANGED
|
@@ -14,8 +14,12 @@ export type FieldsViewProps<F extends Fields> = {
|
|
|
14
14
|
onFieldSubmit?(this: void, key: keyof F): boolean | void,
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export type FormMode = 'edit' | 'create'
|
|
18
|
+
|
|
17
19
|
export type FormProps<O> = {
|
|
18
20
|
value: O,
|
|
19
21
|
|
|
20
22
|
onValueChange: (value: O) => void,
|
|
23
|
+
|
|
24
|
+
mode: FormMode,
|
|
21
25
|
}
|