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/umd/MobxFormkit.umd.js
CHANGED
|
@@ -129,6 +129,8 @@
|
|
|
129
129
|
class ArrayMap {
|
|
130
130
|
/** Duck-typing marker for utility code */
|
|
131
131
|
_isArrayMap = true;
|
|
132
|
+
/** Called after a successful `move()` — used to flag the change as dirty */
|
|
133
|
+
onMove;
|
|
132
134
|
/** Internal observable array: entries stored as [key, value] pairs */
|
|
133
135
|
_entries = mobx.observable.array([]);
|
|
134
136
|
constructor(entries) {
|
|
@@ -246,6 +248,7 @@
|
|
|
246
248
|
return;
|
|
247
249
|
const [entry] = this._entries.splice(fromIndex, 1);
|
|
248
250
|
this._entries.splice(toIndex, 0, entry);
|
|
251
|
+
this.onMove?.();
|
|
249
252
|
});
|
|
250
253
|
}
|
|
251
254
|
/**
|
|
@@ -963,7 +966,14 @@
|
|
|
963
966
|
set: mobx.action,
|
|
964
967
|
add: mobx.action,
|
|
965
968
|
del: mobx.action,
|
|
969
|
+
move: mobx.action,
|
|
966
970
|
});
|
|
971
|
+
// Any reorder of the fields (also when `fields.move()` is called
|
|
972
|
+
// directly) flags the field and the form as changed. (#657)
|
|
973
|
+
this.fields.onMove = () => {
|
|
974
|
+
this.$changed++;
|
|
975
|
+
this.state.form.$changed++;
|
|
976
|
+
};
|
|
967
977
|
}
|
|
968
978
|
execHook = (name, fallback = {}, ...args) => $try(fallback[name], this.$hooks[name], this.noop).apply(this, [this, ...args]);
|
|
969
979
|
execHandler = (name, args, fallback = undefined, hook = null, execHook = true) => [
|
|
@@ -1085,7 +1095,7 @@
|
|
|
1085
1095
|
}
|
|
1086
1096
|
});
|
|
1087
1097
|
}
|
|
1088
|
-
initField(key, path, data, update = false) {
|
|
1098
|
+
initField(key, path, data, update = false, value) {
|
|
1089
1099
|
const initial = this.state.get("current", "props");
|
|
1090
1100
|
const struct = pathToStruct(path);
|
|
1091
1101
|
// try to get props from separated objects
|
|
@@ -1101,7 +1111,9 @@
|
|
|
1101
1111
|
return t;
|
|
1102
1112
|
};
|
|
1103
1113
|
const props = {
|
|
1104
|
-
$value:
|
|
1114
|
+
$value: value !== void 0
|
|
1115
|
+
? value
|
|
1116
|
+
: lodash.get(initial[SeparatedPropsMode.values], path),
|
|
1105
1117
|
$computed: _try(SeparatedPropsMode.computed),
|
|
1106
1118
|
$label: _try(SeparatedPropsMode.labels),
|
|
1107
1119
|
$placeholder: _try(SeparatedPropsMode.placeholders),
|
|
@@ -1204,10 +1216,21 @@
|
|
|
1204
1216
|
deepCheck(type, prop, fields) {
|
|
1205
1217
|
const $fields = getObservableMapValues(fields);
|
|
1206
1218
|
return lodash.transform($fields, (check, field) => {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1219
|
+
// Array containers are skipped because most of their props are
|
|
1220
|
+
// computed recursively on their nested fields. Some props are not
|
|
1221
|
+
// recursive (isDirty/isPristine compare the container value with
|
|
1222
|
+
// its initial value) or must aggregate the container itself
|
|
1223
|
+
// (isValid/hasError), so they are taken into account for array
|
|
1224
|
+
// containers too. (#656, #657)
|
|
1225
|
+
const isContainerProp = [
|
|
1226
|
+
FieldPropsEnum.isValid,
|
|
1227
|
+
FieldPropsEnum.hasError,
|
|
1228
|
+
FieldPropsEnum.isDirty,
|
|
1229
|
+
FieldPropsEnum.isPristine,
|
|
1230
|
+
].includes(prop);
|
|
1231
|
+
if (isContainerProp ||
|
|
1232
|
+
!field.fields.size ||
|
|
1233
|
+
!Array.isArray(field.initial)) {
|
|
1211
1234
|
check.push(field[prop]);
|
|
1212
1235
|
}
|
|
1213
1236
|
check.push(checkPropOccurrence({
|
|
@@ -1251,6 +1274,17 @@
|
|
|
1251
1274
|
this.state.form.select(this.path ?? "", null, false);
|
|
1252
1275
|
const applyInputConverterOnUpdate = this.state.options.get(OptionsEnum.applyInputConverterOnUpdate, this);
|
|
1253
1276
|
if ($field != null && field !== void 0) {
|
|
1277
|
+
// an array provided for a field which is not an array container
|
|
1278
|
+
// is the field value itself (not a set of nested fields)
|
|
1279
|
+
const $rawValue = lodash.get(raw, $path);
|
|
1280
|
+
const $absPath = [this.path ?? "", $path].join(".").replace(/^\.+/, "");
|
|
1281
|
+
if (Array.isArray($rawValue) && !this.isArrayContainer($absPath)) {
|
|
1282
|
+
$field.value = parseInput(applyInputConverterOnUpdate ? $field.$input : (val) => val, {
|
|
1283
|
+
fallbackValueOption: this.state.options.get(OptionsEnum.fallbackValue, this),
|
|
1284
|
+
separated: $rawValue,
|
|
1285
|
+
});
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1254
1288
|
if (Array.isArray($field.values())) {
|
|
1255
1289
|
const n = Math.max(-1, ...Object.keys(field.fields || {}).map(Number));
|
|
1256
1290
|
getObservableMapValues($field.fields).forEach(($f) => {
|
|
@@ -1288,17 +1322,18 @@
|
|
|
1288
1322
|
// init field into the container field
|
|
1289
1323
|
$container.$changed++;
|
|
1290
1324
|
$container.state.form.$changed++;
|
|
1291
|
-
|
|
1325
|
+
// the value provided by the update wins over the declared values
|
|
1326
|
+
$container.initField($key, $newFieldPath, field, true, lodash.get(raw, $path));
|
|
1292
1327
|
}
|
|
1293
1328
|
else if (recursion) {
|
|
1294
1329
|
if (lodash.has(field, FieldPropsEnum.fields) && field.fields != null) {
|
|
1295
1330
|
// handle nested fields if defined
|
|
1296
|
-
this.deepUpdate(field.fields, $path);
|
|
1331
|
+
this.deepUpdate(field.fields, $path, true, raw);
|
|
1297
1332
|
}
|
|
1298
1333
|
else {
|
|
1299
1334
|
// handle nested fields if undefined or null
|
|
1300
1335
|
const $fields = pathToFieldsTree(this.state.struct(), $path);
|
|
1301
|
-
this.deepUpdate($fields, $path, false);
|
|
1336
|
+
this.deepUpdate($fields, $path, false, raw);
|
|
1302
1337
|
}
|
|
1303
1338
|
}
|
|
1304
1339
|
});
|
|
@@ -1473,18 +1508,71 @@
|
|
|
1473
1508
|
execEvent && this.execHook(FieldPropsEnum.onAdd);
|
|
1474
1509
|
return this;
|
|
1475
1510
|
}
|
|
1476
|
-
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1511
|
+
// a plain field holding an array value gets the new item appended
|
|
1512
|
+
if (this.isPlainArrayField() &&
|
|
1513
|
+
!lodash.has(obj, FieldPropsEnum.key) &&
|
|
1514
|
+
!lodash.has(obj, FieldPropsEnum.name)) {
|
|
1515
|
+
const $values = Array.isArray(this.value)
|
|
1516
|
+
? [...this.value]
|
|
1517
|
+
: [];
|
|
1518
|
+
$values.push(lodash.has(obj, FieldPropsEnum.value)
|
|
1519
|
+
? obj[FieldPropsEnum.value]
|
|
1520
|
+
: defaultValue({
|
|
1521
|
+
fallbackValueOption: this.state.options.get(OptionsEnum.fallbackValue, this),
|
|
1522
|
+
nullable: this.$nullable,
|
|
1523
|
+
type: this.type,
|
|
1524
|
+
}));
|
|
1525
|
+
this.set(FieldPropsEnum.value, $values);
|
|
1526
|
+
this.$changed++;
|
|
1527
|
+
this.state.form.$changed++;
|
|
1528
|
+
execEvent && this.execHook(FieldPropsEnum.onAdd);
|
|
1529
|
+
return this;
|
|
1530
|
+
}
|
|
1531
|
+
const field = this.createField(obj);
|
|
1532
|
+
this.$changed++;
|
|
1533
|
+
this.state.form.$changed++;
|
|
1534
|
+
execEvent && this.execHook(FieldPropsEnum.onAdd);
|
|
1535
|
+
return field;
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
Create a new field into this container (no change counters, no hooks)
|
|
1539
|
+
*/
|
|
1540
|
+
createField(obj, key) {
|
|
1484
1541
|
const tree = pathToFieldsTree(this.state.struct(), this.path ?? "", 0, true);
|
|
1485
|
-
const
|
|
1542
|
+
const nestedFields = tree[0]?.fields ?? null;
|
|
1543
|
+
// An object without field-definition props, whose keys match the
|
|
1544
|
+
// nested fields of the container, holds the values of the new item
|
|
1545
|
+
// (e.g. add({ name: 'John', qty: 1 })). The 'name' prop is ambiguous:
|
|
1546
|
+
// it is treated as a value only when the container defines a nested
|
|
1547
|
+
// field called 'name'. (#657)
|
|
1548
|
+
const payloadKeys = lodash.isPlainObject(obj)
|
|
1549
|
+
? Object.keys(obj).filter((k) => k !== FieldPropsEnum.key)
|
|
1550
|
+
: [];
|
|
1551
|
+
const isValuesPayload = !!nestedFields &&
|
|
1552
|
+
lodash.isPlainObject(obj) &&
|
|
1553
|
+
!lodash.has(obj, FieldPropsEnum.value) &&
|
|
1554
|
+
!lodash.has(obj, FieldPropsEnum.fields) &&
|
|
1555
|
+
payloadKeys.length > 0 &&
|
|
1556
|
+
(!lodash.has(obj, FieldPropsEnum.name) ||
|
|
1557
|
+
Object.prototype.hasOwnProperty.call(nestedFields, FieldPropsEnum.name));
|
|
1558
|
+
const $key = key ?? obj?.[FieldPropsEnum.key];
|
|
1559
|
+
const $fieldKey = $key !== void 0
|
|
1560
|
+
? $key
|
|
1561
|
+
: !isValuesPayload && obj?.[FieldPropsEnum.name] !== void 0
|
|
1562
|
+
? obj[FieldPropsEnum.name]
|
|
1563
|
+
: maxKey(this.fields);
|
|
1564
|
+
const $path = ($k) => [this.path, $k].join(".").replace(/^\.+/, "");
|
|
1565
|
+
const field = this.initField($fieldKey, $path($fieldKey), isValuesPayload ? tree[0] : lodash.merge(tree[0], obj), false, lodash.has(obj, FieldPropsEnum.value) ? obj[FieldPropsEnum.value] : void 0);
|
|
1486
1566
|
const hasValues = lodash.has(obj, FieldPropsEnum.value) || lodash.has(obj, FieldPropsEnum.fields);
|
|
1487
|
-
if (
|
|
1567
|
+
if (isValuesPayload) {
|
|
1568
|
+
lodash.each(payloadKeys, (fieldName) => {
|
|
1569
|
+
const nestedField = field.select(fieldName);
|
|
1570
|
+
if (nestedField && !nestedField.hasNestedFields) {
|
|
1571
|
+
nestedField.set(FieldPropsEnum.value, obj[fieldName]);
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
else if (!hasValues) {
|
|
1488
1576
|
// When the struct defines nested fields and obj is a plain object
|
|
1489
1577
|
// without field-definition props (key, name), treat obj properties
|
|
1490
1578
|
// directly as nested field values (e.g. add({ firstname: 'John' }))
|
|
@@ -1514,11 +1602,71 @@
|
|
|
1514
1602
|
})));
|
|
1515
1603
|
}
|
|
1516
1604
|
}
|
|
1517
|
-
this.$changed++;
|
|
1518
|
-
this.state.form.$changed++;
|
|
1519
|
-
execEvent && this.execHook(FieldPropsEnum.onAdd);
|
|
1520
1605
|
return field;
|
|
1521
1606
|
}
|
|
1607
|
+
/**
|
|
1608
|
+
Checks if the given path refers to an array container
|
|
1609
|
+
(a struct entry like `path[]` or `path[].subfield`)
|
|
1610
|
+
*/
|
|
1611
|
+
isArrayContainer(path) {
|
|
1612
|
+
const struct = this.state.struct();
|
|
1613
|
+
if (!Array.isArray(struct))
|
|
1614
|
+
return false;
|
|
1615
|
+
const structPath = (path ?? "").replace(/\.\d+\./g, "[].");
|
|
1616
|
+
return struct.some((s) => typeof s === "string" && s.startsWith(`${structPath}[]`));
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
Checks if the field is a plain field holding an array value
|
|
1620
|
+
(not an array container with nested fields)
|
|
1621
|
+
*/
|
|
1622
|
+
isPlainArrayField() {
|
|
1623
|
+
return (!this.hasNestedFields &&
|
|
1624
|
+
!this.isArrayContainer(this.path ?? "") &&
|
|
1625
|
+
Array.isArray(this.value));
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
Applies a value to a field: for fields with nested fields the value is
|
|
1629
|
+
applied to the nested fields, otherwise the field value is set
|
|
1630
|
+
*/
|
|
1631
|
+
applyValue($field, value) {
|
|
1632
|
+
if (value === void 0)
|
|
1633
|
+
return;
|
|
1634
|
+
if (lodash.isPlainObject(value) && $field.fields.size) {
|
|
1635
|
+
lodash.each(value, (val, key) => {
|
|
1636
|
+
const $nested = $field.select(key);
|
|
1637
|
+
if ($nested) {
|
|
1638
|
+
$nested.fields.size
|
|
1639
|
+
? this.applyValue($nested, val)
|
|
1640
|
+
: $nested.set(FieldPropsEnum.value, val);
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
$field.set(FieldPropsEnum.value, value);
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
Writes an array of values into an array container: the nested fields are
|
|
1649
|
+
created, updated and removed to match the given array
|
|
1650
|
+
*/
|
|
1651
|
+
applyArrayValue($field, values) {
|
|
1652
|
+
// remove the nested fields which are not in the new values
|
|
1653
|
+
getObservableMapValues($field.fields).forEach(($f) => {
|
|
1654
|
+
if (Number($f.name) >= values.length) {
|
|
1655
|
+
$field.fields.delete($f.name);
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
// create or update the nested fields
|
|
1659
|
+
values.forEach((value, index) => {
|
|
1660
|
+
const key = String(index);
|
|
1661
|
+
const $nested = $field.fields.get(key);
|
|
1662
|
+
if ($nested) {
|
|
1663
|
+
this.applyValue($nested, value);
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
$field.createField(lodash.isPlainObject(value) ? value : { [FieldPropsEnum.value]: value }, key);
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1522
1670
|
/**
|
|
1523
1671
|
Del Field
|
|
1524
1672
|
*/
|
|
@@ -2526,11 +2674,12 @@
|
|
|
2526
2674
|
}
|
|
2527
2675
|
get isDirty() {
|
|
2528
2676
|
const value = this.changed ? this.value : this.initial;
|
|
2529
|
-
|
|
2677
|
+
// a soft deleted field differs from its initial state
|
|
2678
|
+
return !lodash.isEqual(this.initial, value) || this.deleted;
|
|
2530
2679
|
}
|
|
2531
2680
|
get isPristine() {
|
|
2532
2681
|
const value = this.changed ? this.value : this.initial;
|
|
2533
|
-
return lodash.isEqual(this.initial, value);
|
|
2682
|
+
return lodash.isEqual(this.initial, value) && !this.deleted;
|
|
2534
2683
|
}
|
|
2535
2684
|
get isEmpty() {
|
|
2536
2685
|
if (this.hasNestedFields)
|
|
@@ -2561,27 +2710,33 @@
|
|
|
2561
2710
|
: this.$touched;
|
|
2562
2711
|
}
|
|
2563
2712
|
get deleted() {
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2713
|
+
// an explicit flag on the field itself always wins
|
|
2714
|
+
if (this.$deleted)
|
|
2715
|
+
return true;
|
|
2716
|
+
return this.hasNestedFields ? this.check(FieldPropsEnum.deleted, true) : false;
|
|
2567
2717
|
}
|
|
2568
2718
|
/* ------------------------------------------------------------------ */
|
|
2569
2719
|
/* EVENTS HANDLERS */
|
|
2570
2720
|
sync = mobx.action((e, v = null) => {
|
|
2571
2721
|
const $get = ($) => isBool($, this.value) ? $.target.checked : $.target.value;
|
|
2572
|
-
//
|
|
2722
|
+
// resolve the value to be set
|
|
2723
|
+
let $value;
|
|
2573
2724
|
if (lodash.isNil(e) || lodash.isNil(e.target)) {
|
|
2574
2725
|
if (!lodash.isNil(v) && !lodash.isNil(v.target)) {
|
|
2575
2726
|
v = $get(v); // eslint-disable-line
|
|
2576
2727
|
}
|
|
2577
|
-
|
|
2578
|
-
return;
|
|
2728
|
+
$value = $try(e, v);
|
|
2579
2729
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2730
|
+
else {
|
|
2731
|
+
$value = $get(e);
|
|
2732
|
+
}
|
|
2733
|
+
// an array set on a field with nested fields is applied to the nested
|
|
2734
|
+
// fields, otherwise the value would be silently dropped
|
|
2735
|
+
if (this.hasNestedFields && Array.isArray($value)) {
|
|
2736
|
+
this.state.form.applyArrayValue(this, $value);
|
|
2582
2737
|
return;
|
|
2583
2738
|
}
|
|
2584
|
-
this.value =
|
|
2739
|
+
this.value = $value;
|
|
2585
2740
|
});
|
|
2586
2741
|
onSync = (...args) => this.type === "file"
|
|
2587
2742
|
? this.onDrop(...args)
|
|
@@ -2785,7 +2940,13 @@
|
|
|
2785
2940
|
nullable: this.$nullable,
|
|
2786
2941
|
type: this.type,
|
|
2787
2942
|
});
|
|
2788
|
-
|
|
2943
|
+
if (this.isArrayContainer(this.path ?? "") && Array.isArray(this.value)) {
|
|
2944
|
+
// an array container is emptied: the nested fields are removed
|
|
2945
|
+
getObservableMapValues(this.fields).forEach(($f) => this.fields.delete($f.name));
|
|
2946
|
+
}
|
|
2947
|
+
else {
|
|
2948
|
+
deep && this.each((field) => field.clear(deep));
|
|
2949
|
+
}
|
|
2789
2950
|
this.state.options.get(OptionsEnum.validateOnClear, this)
|
|
2790
2951
|
? this.validate({
|
|
2791
2952
|
showErrors: this.state.options.get(OptionsEnum.showErrorsOnClear, this),
|
|
@@ -2800,11 +2961,31 @@
|
|
|
2800
2961
|
this.$changed = 0;
|
|
2801
2962
|
this.files = undefined;
|
|
2802
2963
|
const useDefaultValue = this.$default !== this.$initial;
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2964
|
+
const $target = useDefaultValue ? this.$default : this.$initial;
|
|
2965
|
+
if (this.isArrayContainer(this.path ?? "") &&
|
|
2966
|
+
!useDefaultValue &&
|
|
2967
|
+
Array.isArray($target)) {
|
|
2968
|
+
// an array container is rebuilt from its initial (or default) array:
|
|
2969
|
+
// the removed nested fields are restored and the added ones dropped
|
|
2970
|
+
const $values = $target;
|
|
2971
|
+
const $fields = getObservableMapValues(this.fields);
|
|
2972
|
+
const $match = $fields.length === $values.length &&
|
|
2973
|
+
$fields.every(($f, i) => lodash.isEqual($f.value, $values[i]));
|
|
2974
|
+
if (!$match) {
|
|
2975
|
+
$fields.forEach(($f) => this.fields.delete($f.name));
|
|
2976
|
+
$values.forEach((value, index) => this.createField(lodash.isPlainObject(value) ? value : { [FieldPropsEnum.value]: value }, String(index)));
|
|
2977
|
+
}
|
|
2978
|
+
else {
|
|
2979
|
+
deep && this.each((field) => field.reset(deep));
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
else {
|
|
2983
|
+
if (useDefaultValue)
|
|
2984
|
+
this.value = this.$default;
|
|
2985
|
+
if (!useDefaultValue)
|
|
2986
|
+
this.value = this.$initial;
|
|
2987
|
+
deep && this.each((field) => field.reset(deep));
|
|
2988
|
+
}
|
|
2808
2989
|
this.state.options.get(OptionsEnum.validateOnReset, this)
|
|
2809
2990
|
? this.validate({
|
|
2810
2991
|
showErrors: this.state.options.get(OptionsEnum.showErrorsOnReset, this),
|