mobx-formkit 1.1.2 → 1.1.4
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/AGENTS.md +2 -2
- package/lib/ArrayMap.d.ts +2 -0
- package/lib/ArrayMap.d.ts.map +1 -1
- package/lib/ArrayMap.js +3 -0
- package/lib/ArrayMap.js.map +1 -1
- package/lib/Base.d.ts +25 -1
- package/lib/Base.d.ts.map +1 -1
- package/lib/Base.js +167 -22
- package/lib/Base.js.map +1 -1
- package/lib/Field.d.ts.map +1 -1
- package/lib/Field.js +50 -17
- package/lib/Field.js.map +1 -1
- package/lib/esm/ArrayMap.js +3 -0
- package/lib/esm/ArrayMap.js.map +1 -1
- package/lib/esm/Base.js +167 -22
- package/lib/esm/Base.js.map +1 -1
- package/lib/esm/Field.js +51 -18
- package/lib/esm/Field.js.map +1 -1
- package/package.json +1 -1
- package/src/ArrayMap.ts +4 -0
- package/src/Base.ts +212 -18
- package/src/Field.ts +55 -16
- package/umd/MobxFormkit.umd.js +220 -39
- package/umd/MobxFormkit.umd.js.map +1 -1
- package/umd/MobxFormkit.umd.min.js +1 -1
- package/umd/MobxFormkit.umd.min.js.map +1 -1
package/src/Base.ts
CHANGED
|
@@ -110,7 +110,15 @@ export default abstract class Base<
|
|
|
110
110
|
set: action,
|
|
111
111
|
add: action,
|
|
112
112
|
del: action,
|
|
113
|
+
move: action,
|
|
113
114
|
});
|
|
115
|
+
|
|
116
|
+
// Any reorder of the fields (also when `fields.move()` is called
|
|
117
|
+
// directly) flags the field and the form as changed. (#657)
|
|
118
|
+
this.fields.onMove = () => {
|
|
119
|
+
this.$changed++;
|
|
120
|
+
this.state.form.$changed++;
|
|
121
|
+
};
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
execHook = (name: string, fallback: any = {}, ...args: any[]): any =>
|
|
@@ -294,6 +302,7 @@ export default abstract class Base<
|
|
|
294
302
|
path: string,
|
|
295
303
|
data: any,
|
|
296
304
|
update: boolean = false,
|
|
305
|
+
value?: any,
|
|
297
306
|
): any {
|
|
298
307
|
const initial = this.state.get("current", "props");
|
|
299
308
|
const struct = pathToStruct(path);
|
|
@@ -316,7 +325,10 @@ export default abstract class Base<
|
|
|
316
325
|
};
|
|
317
326
|
|
|
318
327
|
const props = {
|
|
319
|
-
$value:
|
|
328
|
+
$value:
|
|
329
|
+
value !== void 0
|
|
330
|
+
? value
|
|
331
|
+
: get(initial[SeparatedPropsMode.values], path),
|
|
320
332
|
$computed: _try(SeparatedPropsMode.computed),
|
|
321
333
|
$label: _try(SeparatedPropsMode.labels),
|
|
322
334
|
$placeholder: _try(SeparatedPropsMode.placeholders),
|
|
@@ -455,11 +467,24 @@ export default abstract class Base<
|
|
|
455
467
|
return transform(
|
|
456
468
|
$fields,
|
|
457
469
|
(check: any, field: any) => {
|
|
470
|
+
// Array containers are skipped because most of their props are
|
|
471
|
+
// computed recursively on their nested fields. Some props are not
|
|
472
|
+
// recursive (isDirty/isPristine compare the container value with
|
|
473
|
+
// its initial value) or must aggregate the container itself
|
|
474
|
+
// (isValid/hasError), so they are taken into account for array
|
|
475
|
+
// containers too. (#656, #657)
|
|
476
|
+
const isContainerProp = (
|
|
477
|
+
[
|
|
478
|
+
FieldPropsEnum.isValid,
|
|
479
|
+
FieldPropsEnum.hasError,
|
|
480
|
+
FieldPropsEnum.isDirty,
|
|
481
|
+
FieldPropsEnum.isPristine,
|
|
482
|
+
] as string[]
|
|
483
|
+
).includes(prop);
|
|
458
484
|
if (
|
|
485
|
+
isContainerProp ||
|
|
459
486
|
!field.fields.size ||
|
|
460
|
-
!Array.isArray(field.initial)
|
|
461
|
-
prop === FieldPropsEnum.isValid ||
|
|
462
|
-
prop === FieldPropsEnum.hasError
|
|
487
|
+
!Array.isArray(field.initial)
|
|
463
488
|
) {
|
|
464
489
|
check.push(field[prop]);
|
|
465
490
|
}
|
|
@@ -531,6 +556,24 @@ export default abstract class Base<
|
|
|
531
556
|
);
|
|
532
557
|
|
|
533
558
|
if ($field != null && field !== void 0) {
|
|
559
|
+
// an array provided for a field which is not an array container
|
|
560
|
+
// is the field value itself (not a set of nested fields)
|
|
561
|
+
const $rawValue = get(raw, $path);
|
|
562
|
+
const $absPath = [this.path ?? "", $path].join(".").replace(/^\.+/, "");
|
|
563
|
+
if (Array.isArray($rawValue) && !this.isArrayContainer($absPath)) {
|
|
564
|
+
$field.value = parseInput(
|
|
565
|
+
applyInputConverterOnUpdate ? $field.$input : (val) => val,
|
|
566
|
+
{
|
|
567
|
+
fallbackValueOption: this.state.options.get(
|
|
568
|
+
OptionsEnum.fallbackValue,
|
|
569
|
+
this,
|
|
570
|
+
),
|
|
571
|
+
separated: $rawValue,
|
|
572
|
+
},
|
|
573
|
+
);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
534
577
|
if (Array.isArray($field.values())) {
|
|
535
578
|
const n: number = Math.max(
|
|
536
579
|
-1,
|
|
@@ -586,15 +629,22 @@ export default abstract class Base<
|
|
|
586
629
|
// init field into the container field
|
|
587
630
|
$container.$changed++;
|
|
588
631
|
$container.state.form.$changed++;
|
|
589
|
-
|
|
632
|
+
// the value provided by the update wins over the declared values
|
|
633
|
+
$container.initField(
|
|
634
|
+
$key,
|
|
635
|
+
$newFieldPath,
|
|
636
|
+
field,
|
|
637
|
+
true,
|
|
638
|
+
get(raw, $path),
|
|
639
|
+
);
|
|
590
640
|
} else if (recursion) {
|
|
591
641
|
if (has(field, FieldPropsEnum.fields) && field.fields != null) {
|
|
592
642
|
// handle nested fields if defined
|
|
593
|
-
this.deepUpdate(field.fields, $path);
|
|
643
|
+
this.deepUpdate(field.fields, $path, true, raw);
|
|
594
644
|
} else {
|
|
595
645
|
// handle nested fields if undefined or null
|
|
596
646
|
const $fields = pathToFieldsTree(this.state.struct(), $path);
|
|
597
|
-
this.deepUpdate($fields, $path, false);
|
|
647
|
+
this.deepUpdate($fields, $path, false, raw);
|
|
598
648
|
}
|
|
599
649
|
}
|
|
600
650
|
});
|
|
@@ -868,24 +918,99 @@ export default abstract class Base<
|
|
|
868
918
|
return this;
|
|
869
919
|
}
|
|
870
920
|
|
|
871
|
-
|
|
872
|
-
if (
|
|
873
|
-
|
|
874
|
-
|
|
921
|
+
// a plain field holding an array value gets the new item appended
|
|
922
|
+
if (
|
|
923
|
+
this.isPlainArrayField() &&
|
|
924
|
+
!has(obj, FieldPropsEnum.key) &&
|
|
925
|
+
!has(obj, FieldPropsEnum.name)
|
|
926
|
+
) {
|
|
927
|
+
const $values = Array.isArray((this as any).value)
|
|
928
|
+
? [...((this as any).value as any[])]
|
|
929
|
+
: [];
|
|
930
|
+
$values.push(
|
|
931
|
+
has(obj, FieldPropsEnum.value)
|
|
932
|
+
? obj[FieldPropsEnum.value]
|
|
933
|
+
: defaultValue({
|
|
934
|
+
fallbackValueOption: this.state.options.get(
|
|
935
|
+
OptionsEnum.fallbackValue,
|
|
936
|
+
this,
|
|
937
|
+
),
|
|
938
|
+
nullable: (this as any).$nullable,
|
|
939
|
+
type: (this as any).type,
|
|
940
|
+
}),
|
|
941
|
+
);
|
|
942
|
+
this.set(FieldPropsEnum.value, $values);
|
|
943
|
+
this.$changed++;
|
|
944
|
+
this.state.form.$changed++;
|
|
945
|
+
execEvent && this.execHook(FieldPropsEnum.onAdd);
|
|
946
|
+
return this;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const field = this.createField(obj);
|
|
950
|
+
|
|
951
|
+
this.$changed++;
|
|
952
|
+
this.state.form.$changed++;
|
|
953
|
+
execEvent && this.execHook(FieldPropsEnum.onAdd);
|
|
954
|
+
return field;
|
|
955
|
+
}
|
|
875
956
|
|
|
876
|
-
|
|
877
|
-
|
|
957
|
+
/**
|
|
958
|
+
Create a new field into this container (no change counters, no hooks)
|
|
959
|
+
*/
|
|
960
|
+
createField(obj?: any, key?: string): any {
|
|
878
961
|
const tree = pathToFieldsTree(
|
|
879
962
|
this.state.struct(),
|
|
880
963
|
this.path ?? "",
|
|
881
964
|
0,
|
|
882
965
|
true,
|
|
883
966
|
);
|
|
884
|
-
const
|
|
967
|
+
const nestedFields = tree[0]?.fields ?? null;
|
|
968
|
+
|
|
969
|
+
// An object without field-definition props, whose keys match the
|
|
970
|
+
// nested fields of the container, holds the values of the new item
|
|
971
|
+
// (e.g. add({ name: 'John', qty: 1 })). The 'name' prop is ambiguous:
|
|
972
|
+
// it is treated as a value only when the container defines a nested
|
|
973
|
+
// field called 'name'. (#657)
|
|
974
|
+
const payloadKeys = isPlainObject(obj)
|
|
975
|
+
? Object.keys(obj).filter((k) => k !== FieldPropsEnum.key)
|
|
976
|
+
: [];
|
|
977
|
+
const isValuesPayload =
|
|
978
|
+
!!nestedFields &&
|
|
979
|
+
isPlainObject(obj) &&
|
|
980
|
+
!has(obj, FieldPropsEnum.value) &&
|
|
981
|
+
!has(obj, FieldPropsEnum.fields) &&
|
|
982
|
+
payloadKeys.length > 0 &&
|
|
983
|
+
(!has(obj, FieldPropsEnum.name) ||
|
|
984
|
+
Object.prototype.hasOwnProperty.call(nestedFields, FieldPropsEnum.name));
|
|
985
|
+
|
|
986
|
+
const $key = key ?? obj?.[FieldPropsEnum.key];
|
|
987
|
+
const $fieldKey: any =
|
|
988
|
+
$key !== void 0
|
|
989
|
+
? $key
|
|
990
|
+
: !isValuesPayload && obj?.[FieldPropsEnum.name] !== void 0
|
|
991
|
+
? obj[FieldPropsEnum.name]
|
|
992
|
+
: maxKey(this.fields);
|
|
993
|
+
|
|
994
|
+
const $path = ($k: any) =>
|
|
995
|
+
[this.path, $k].join(".").replace(/^\.+/, "");
|
|
996
|
+
const field = this.initField(
|
|
997
|
+
$fieldKey,
|
|
998
|
+
$path($fieldKey),
|
|
999
|
+
isValuesPayload ? tree[0] : merge(tree[0], obj),
|
|
1000
|
+
false,
|
|
1001
|
+
has(obj, FieldPropsEnum.value) ? obj[FieldPropsEnum.value] : void 0,
|
|
1002
|
+
);
|
|
885
1003
|
const hasValues =
|
|
886
1004
|
has(obj, FieldPropsEnum.value) || has(obj, FieldPropsEnum.fields);
|
|
887
1005
|
|
|
888
|
-
if (
|
|
1006
|
+
if (isValuesPayload) {
|
|
1007
|
+
each(payloadKeys, (fieldName) => {
|
|
1008
|
+
const nestedField = field.select(fieldName);
|
|
1009
|
+
if (nestedField && !nestedField.hasNestedFields) {
|
|
1010
|
+
nestedField.set(FieldPropsEnum.value, (obj as any)[fieldName]);
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
} else if (!hasValues) {
|
|
889
1014
|
// When the struct defines nested fields and obj is a plain object
|
|
890
1015
|
// without field-definition props (key, name), treat obj properties
|
|
891
1016
|
// directly as nested field values (e.g. add({ firstname: 'John' }))
|
|
@@ -925,12 +1050,81 @@ export default abstract class Base<
|
|
|
925
1050
|
}
|
|
926
1051
|
}
|
|
927
1052
|
|
|
928
|
-
this.$changed++;
|
|
929
|
-
this.state.form.$changed++;
|
|
930
|
-
execEvent && this.execHook(FieldPropsEnum.onAdd);
|
|
931
1053
|
return field;
|
|
932
1054
|
}
|
|
933
1055
|
|
|
1056
|
+
/**
|
|
1057
|
+
Checks if the given path refers to an array container
|
|
1058
|
+
(a struct entry like `path[]` or `path[].subfield`)
|
|
1059
|
+
*/
|
|
1060
|
+
isArrayContainer(path: string): boolean {
|
|
1061
|
+
const struct = this.state.struct();
|
|
1062
|
+
if (!Array.isArray(struct)) return false;
|
|
1063
|
+
const structPath = (path ?? "").replace(/\.\d+\./g, "[].");
|
|
1064
|
+
return struct.some(
|
|
1065
|
+
(s: any) => typeof s === "string" && s.startsWith(`${structPath}[]`),
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
Checks if the field is a plain field holding an array value
|
|
1071
|
+
(not an array container with nested fields)
|
|
1072
|
+
*/
|
|
1073
|
+
isPlainArrayField(): boolean {
|
|
1074
|
+
return (
|
|
1075
|
+
!this.hasNestedFields &&
|
|
1076
|
+
!this.isArrayContainer(this.path ?? "") &&
|
|
1077
|
+
Array.isArray((this as any).value)
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
Applies a value to a field: for fields with nested fields the value is
|
|
1083
|
+
applied to the nested fields, otherwise the field value is set
|
|
1084
|
+
*/
|
|
1085
|
+
applyValue($field: any, value: any): void {
|
|
1086
|
+
if (value === void 0) return;
|
|
1087
|
+
if (isPlainObject(value) && $field.fields.size) {
|
|
1088
|
+
each(value, (val, key) => {
|
|
1089
|
+
const $nested = $field.select(key);
|
|
1090
|
+
if ($nested) {
|
|
1091
|
+
$nested.fields.size
|
|
1092
|
+
? this.applyValue($nested, val)
|
|
1093
|
+
: $nested.set(FieldPropsEnum.value, val);
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
$field.set(FieldPropsEnum.value, value);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
Writes an array of values into an array container: the nested fields are
|
|
1103
|
+
created, updated and removed to match the given array
|
|
1104
|
+
*/
|
|
1105
|
+
applyArrayValue($field: any, values: any[]): void {
|
|
1106
|
+
// remove the nested fields which are not in the new values
|
|
1107
|
+
getObservableMapValues($field.fields).forEach(($f: any) => {
|
|
1108
|
+
if (Number($f.name) >= values.length) {
|
|
1109
|
+
$field.fields.delete($f.name);
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
// create or update the nested fields
|
|
1114
|
+
values.forEach((value, index) => {
|
|
1115
|
+
const key = String(index);
|
|
1116
|
+
const $nested = $field.fields.get(key);
|
|
1117
|
+
if ($nested) {
|
|
1118
|
+
this.applyValue($nested, value);
|
|
1119
|
+
} else {
|
|
1120
|
+
$field.createField(
|
|
1121
|
+
isPlainObject(value) ? value : { [FieldPropsEnum.value]: value },
|
|
1122
|
+
key,
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
934
1128
|
/**
|
|
935
1129
|
Del Field
|
|
936
1130
|
*/
|
package/src/Field.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
isEvent,
|
|
21
21
|
pathToStruct,
|
|
22
22
|
isArrayFromStruct,
|
|
23
|
+
getObservableMapValues,
|
|
23
24
|
} from "./utils";
|
|
24
25
|
|
|
25
26
|
import { parseInput, parseCheckOutput, defaultValue } from "./parser";
|
|
@@ -503,12 +504,13 @@ export default class Field<T = any> extends Base<Record<string, any>> implements
|
|
|
503
504
|
|
|
504
505
|
get isDirty(): boolean {
|
|
505
506
|
const value = this.changed ? this.value : this.initial;
|
|
506
|
-
|
|
507
|
+
// a soft deleted field differs from its initial state
|
|
508
|
+
return !isEqual(this.initial, value) || this.deleted;
|
|
507
509
|
}
|
|
508
510
|
|
|
509
511
|
get isPristine(): boolean {
|
|
510
512
|
const value = this.changed ? this.value : this.initial;
|
|
511
|
-
return isEqual(this.initial, value);
|
|
513
|
+
return isEqual(this.initial, value) && !this.deleted;
|
|
512
514
|
}
|
|
513
515
|
|
|
514
516
|
get isEmpty(): boolean {
|
|
@@ -539,9 +541,9 @@ export default class Field<T = any> extends Base<Record<string, any>> implements
|
|
|
539
541
|
}
|
|
540
542
|
|
|
541
543
|
get deleted(): boolean {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
544
|
+
// an explicit flag on the field itself always wins
|
|
545
|
+
if (this.$deleted) return true;
|
|
546
|
+
return this.hasNestedFields ? this.check(FieldPropsEnum.deleted, true) : false;
|
|
545
547
|
}
|
|
546
548
|
|
|
547
549
|
/* ------------------------------------------------------------------ */
|
|
@@ -551,22 +553,25 @@ export default class Field<T = any> extends Base<Record<string, any>> implements
|
|
|
551
553
|
const $get = ($: any) =>
|
|
552
554
|
isBool($, this.value) ? $.target.checked : $.target.value;
|
|
553
555
|
|
|
554
|
-
//
|
|
556
|
+
// resolve the value to be set
|
|
557
|
+
let $value: any;
|
|
555
558
|
if (isNil(e) || isNil(e.target)) {
|
|
556
559
|
if (!isNil(v) && !isNil(v.target)) {
|
|
557
560
|
v = $get(v); // eslint-disable-line
|
|
558
561
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
+
$value = $try(e, v);
|
|
563
|
+
} else {
|
|
564
|
+
$value = $get(e);
|
|
562
565
|
}
|
|
563
566
|
|
|
564
|
-
|
|
565
|
-
|
|
567
|
+
// an array set on a field with nested fields is applied to the nested
|
|
568
|
+
// fields, otherwise the value would be silently dropped
|
|
569
|
+
if (this.hasNestedFields && Array.isArray($value)) {
|
|
570
|
+
(this.state.form as any).applyArrayValue(this, $value);
|
|
566
571
|
return;
|
|
567
572
|
}
|
|
568
573
|
|
|
569
|
-
this.value =
|
|
574
|
+
this.value = $value;
|
|
570
575
|
});
|
|
571
576
|
|
|
572
577
|
onSync = (...args: any) =>
|
|
@@ -876,7 +881,14 @@ export default class Field<T = any> extends Base<Record<string, any>> implements
|
|
|
876
881
|
type: this.type,
|
|
877
882
|
});
|
|
878
883
|
|
|
879
|
-
|
|
884
|
+
if (this.isArrayContainer(this.path ?? "") && Array.isArray(this.value)) {
|
|
885
|
+
// an array container is emptied: the nested fields are removed
|
|
886
|
+
getObservableMapValues(this.fields).forEach(($f: any) =>
|
|
887
|
+
this.fields.delete($f.name),
|
|
888
|
+
);
|
|
889
|
+
} else {
|
|
890
|
+
deep && this.each((field: FieldInterface) => field.clear(deep));
|
|
891
|
+
}
|
|
880
892
|
|
|
881
893
|
this.state.options.get(OptionsEnum.validateOnClear, this)
|
|
882
894
|
? this.validate({
|
|
@@ -897,10 +909,37 @@ export default class Field<T = any> extends Base<Record<string, any>> implements
|
|
|
897
909
|
this.files = undefined;
|
|
898
910
|
|
|
899
911
|
const useDefaultValue = this.$default !== this.$initial;
|
|
900
|
-
|
|
901
|
-
|
|
912
|
+
const $target = useDefaultValue ? this.$default : this.$initial;
|
|
913
|
+
|
|
914
|
+
if (
|
|
915
|
+
this.isArrayContainer(this.path ?? "") &&
|
|
916
|
+
!useDefaultValue &&
|
|
917
|
+
Array.isArray($target)
|
|
918
|
+
) {
|
|
919
|
+
// an array container is rebuilt from its initial (or default) array:
|
|
920
|
+
// the removed nested fields are restored and the added ones dropped
|
|
921
|
+
const $values = $target as any[];
|
|
922
|
+
const $fields = getObservableMapValues(this.fields);
|
|
923
|
+
const $match =
|
|
924
|
+
$fields.length === $values.length &&
|
|
925
|
+
$fields.every(($f: any, i: number) => isEqual($f.value, $values[i]));
|
|
926
|
+
if (!$match) {
|
|
927
|
+
$fields.forEach(($f: any) => this.fields.delete($f.name));
|
|
928
|
+
$values.forEach((value, index) =>
|
|
929
|
+
this.createField(
|
|
930
|
+
isPlainObject(value) ? value : { [FieldPropsEnum.value]: value },
|
|
931
|
+
String(index),
|
|
932
|
+
),
|
|
933
|
+
);
|
|
934
|
+
} else {
|
|
935
|
+
deep && this.each((field: FieldInterface) => field.reset(deep));
|
|
936
|
+
}
|
|
937
|
+
} else {
|
|
938
|
+
if (useDefaultValue) this.value = this.$default;
|
|
939
|
+
if (!useDefaultValue) this.value = this.$initial;
|
|
902
940
|
|
|
903
|
-
|
|
941
|
+
deep && this.each((field: FieldInterface) => field.reset(deep));
|
|
942
|
+
}
|
|
904
943
|
|
|
905
944
|
this.state.options.get(OptionsEnum.validateOnReset, this)
|
|
906
945
|
? this.validate({
|