mobx-formkit 1.1.3 → 1.2.0

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.
@@ -1095,7 +1095,7 @@
1095
1095
  }
1096
1096
  });
1097
1097
  }
1098
- initField(key, path, data, update = false) {
1098
+ initField(key, path, data, update = false, value) {
1099
1099
  const initial = this.state.get("current", "props");
1100
1100
  const struct = pathToStruct(path);
1101
1101
  // try to get props from separated objects
@@ -1111,7 +1111,9 @@
1111
1111
  return t;
1112
1112
  };
1113
1113
  const props = {
1114
- $value: lodash.get(initial[SeparatedPropsMode.values], path),
1114
+ $value: value !== void 0
1115
+ ? value
1116
+ : lodash.get(initial[SeparatedPropsMode.values], path),
1115
1117
  $computed: _try(SeparatedPropsMode.computed),
1116
1118
  $label: _try(SeparatedPropsMode.labels),
1117
1119
  $placeholder: _try(SeparatedPropsMode.placeholders),
@@ -1272,6 +1274,17 @@
1272
1274
  this.state.form.select(this.path ?? "", null, false);
1273
1275
  const applyInputConverterOnUpdate = this.state.options.get(OptionsEnum.applyInputConverterOnUpdate, this);
1274
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
+ }
1275
1288
  if (Array.isArray($field.values())) {
1276
1289
  const n = Math.max(-1, ...Object.keys(field.fields || {}).map(Number));
1277
1290
  getObservableMapValues($field.fields).forEach(($f) => {
@@ -1309,17 +1322,18 @@
1309
1322
  // init field into the container field
1310
1323
  $container.$changed++;
1311
1324
  $container.state.form.$changed++;
1312
- $container.initField($key, $newFieldPath, field, true);
1325
+ // the value provided by the update wins over the declared values
1326
+ $container.initField($key, $newFieldPath, field, true, lodash.get(raw, $path));
1313
1327
  }
1314
1328
  else if (recursion) {
1315
1329
  if (lodash.has(field, FieldPropsEnum.fields) && field.fields != null) {
1316
1330
  // handle nested fields if defined
1317
- this.deepUpdate(field.fields, $path);
1331
+ this.deepUpdate(field.fields, $path, true, raw);
1318
1332
  }
1319
1333
  else {
1320
1334
  // handle nested fields if undefined or null
1321
1335
  const $fields = pathToFieldsTree(this.state.struct(), $path);
1322
- this.deepUpdate($fields, $path, false);
1336
+ this.deepUpdate($fields, $path, false, raw);
1323
1337
  }
1324
1338
  }
1325
1339
  });
@@ -1494,7 +1508,36 @@
1494
1508
  execEvent && this.execHook(FieldPropsEnum.onAdd);
1495
1509
  return this;
1496
1510
  }
1497
- let key;
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) {
1498
1541
  const tree = pathToFieldsTree(this.state.struct(), this.path ?? "", 0, true);
1499
1542
  const nestedFields = tree[0]?.fields ?? null;
1500
1543
  // An object without field-definition props, whose keys match the
@@ -1512,14 +1555,14 @@
1512
1555
  payloadKeys.length > 0 &&
1513
1556
  (!lodash.has(obj, FieldPropsEnum.name) ||
1514
1557
  Object.prototype.hasOwnProperty.call(nestedFields, FieldPropsEnum.name));
1515
- if (lodash.has(obj, FieldPropsEnum.key))
1516
- key = obj.key;
1517
- if (!isValuesPayload && lodash.has(obj, FieldPropsEnum.name))
1518
- key = obj.name;
1519
- if (!key)
1520
- key = maxKey(this.fields);
1521
- const $path = ($key) => [this.path, $key].join(".").replace(/^\.+/, "");
1522
- const field = this.initField(key, $path(key), isValuesPayload ? tree[0] : lodash.merge(tree[0], obj));
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);
1523
1566
  const hasValues = lodash.has(obj, FieldPropsEnum.value) || lodash.has(obj, FieldPropsEnum.fields);
1524
1567
  if (isValuesPayload) {
1525
1568
  lodash.each(payloadKeys, (fieldName) => {
@@ -1559,11 +1602,71 @@
1559
1602
  })));
1560
1603
  }
1561
1604
  }
1562
- this.$changed++;
1563
- this.state.form.$changed++;
1564
- execEvent && this.execHook(FieldPropsEnum.onAdd);
1565
1605
  return field;
1566
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 ?? this.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
+ }
1567
1670
  /**
1568
1671
  Del Field
1569
1672
  */
@@ -2616,19 +2719,24 @@
2616
2719
  /* EVENTS HANDLERS */
2617
2720
  sync = mobx.action((e, v = null) => {
2618
2721
  const $get = ($) => isBool($, this.value) ? $.target.checked : $.target.value;
2619
- // assume "v" or "e" are the values
2722
+ // resolve the value to be set
2723
+ let $value;
2620
2724
  if (lodash.isNil(e) || lodash.isNil(e.target)) {
2621
2725
  if (!lodash.isNil(v) && !lodash.isNil(v.target)) {
2622
2726
  v = $get(v); // eslint-disable-line
2623
2727
  }
2624
- this.value = $try(e, v);
2625
- return;
2728
+ $value = $try(e, v);
2729
+ }
2730
+ else {
2731
+ $value = $get(e);
2626
2732
  }
2627
- if (!lodash.isNil(e.target)) {
2628
- this.value = $get(e);
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);
2629
2737
  return;
2630
2738
  }
2631
- this.value = e;
2739
+ this.value = $value;
2632
2740
  });
2633
2741
  onSync = (...args) => this.type === "file"
2634
2742
  ? this.onDrop(...args)
@@ -2832,7 +2940,13 @@
2832
2940
  nullable: this.$nullable,
2833
2941
  type: this.type,
2834
2942
  });
2835
- deep && this.each((field) => field.clear(deep));
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
+ }
2836
2950
  this.state.options.get(OptionsEnum.validateOnClear, this)
2837
2951
  ? this.validate({
2838
2952
  showErrors: this.state.options.get(OptionsEnum.showErrorsOnClear, this),
@@ -2847,11 +2961,31 @@
2847
2961
  this.$changed = 0;
2848
2962
  this.files = undefined;
2849
2963
  const useDefaultValue = this.$default !== this.$initial;
2850
- if (useDefaultValue)
2851
- this.value = this.$default;
2852
- if (!useDefaultValue)
2853
- this.value = this.$initial;
2854
- deep && this.each((field) => field.reset(deep));
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
+ }
2855
2989
  this.state.options.get(OptionsEnum.validateOnReset, this)
2856
2990
  ? this.validate({
2857
2991
  showErrors: this.state.options.get(OptionsEnum.showErrorsOnReset, this),