react-f0rm 1.0.0 → 1.1.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.
package/dist/index.umd.js CHANGED
@@ -249,7 +249,23 @@
249
249
  isSubmitSuccessful: void 0
250
250
  };
251
251
  }
252
+ const valuesCaches = /* @__PURE__ */ new WeakMap();
253
+ function bumpValuesVersion(form) {
254
+ const cache = valuesCaches.get(form);
255
+ if (cache) cache.version++;
256
+ }
252
257
  function getValues(form) {
258
+ let cache = valuesCaches.get(form);
259
+ if (!cache) {
260
+ cache = { version: 0, result: computeValues(form) };
261
+ valuesCaches.set(form, cache);
262
+ } else if (cache.version > 0) {
263
+ cache.result = computeValues(form);
264
+ cache.version = 0;
265
+ }
266
+ return cache.result;
267
+ }
268
+ function computeValues(form) {
253
269
  const { initialValues, parsedValues, values, deleted } = form;
254
270
  const owned = /* @__PURE__ */ new Set();
255
271
  let merged = parsedValues ?? initialValues;
@@ -287,10 +303,23 @@
287
303
  pruneDirtyBaselines(form, path);
288
304
  if (options?.shouldDirty === false) setDirtyBaseline(form, path, value);
289
305
  bumpDirtyVersion(form);
306
+ bumpValuesVersion(form);
290
307
  if (options?.shouldTouch) setTouchedByPath(form, path);
291
308
  if (options?.shouldValidate) form.validators.get(path.key)?.();
292
309
  emit(emitter, "change", path);
293
310
  }
311
+ function seedValueByPath(form, path, value) {
312
+ const { values, deleted } = form;
313
+ values.set(path.key, value);
314
+ pruneDescendantKeys(values, path);
315
+ reviveBranch(deleted, path);
316
+ pruneDirtyBaselines(form, path);
317
+ bumpDirtyVersion(form);
318
+ bumpValuesVersion(form);
319
+ }
320
+ function emitChangeByPath({ emitter }, path) {
321
+ emit(emitter, "change", path);
322
+ }
294
323
  function changeValue(form, name, value, options) {
295
324
  changeValueByPath(form, create$1(name), value, options);
296
325
  }
@@ -492,7 +521,8 @@
492
521
  function removeField(form, name) {
493
522
  removeFieldByPath(form, create$1(name));
494
523
  }
495
- function removeFieldByPath(form, { key, value: segments }) {
524
+ function removeFieldByPath(form, path) {
525
+ const { key, value: segments } = path;
496
526
  const { emitter, values, touched, errors, validating, deleted } = form;
497
527
  values.delete(key);
498
528
  touched.delete(key);
@@ -501,10 +531,11 @@
501
531
  clearDirtyBaselines(form, key);
502
532
  if (!hasLiveBranch(values, segments)) deleted.add(key);
503
533
  bumpDirtyVersion(form);
504
- emit(emitter, "change");
505
- emit(emitter, "touched");
506
- emit(emitter, "errors");
507
- emit(emitter, "validating");
534
+ bumpValuesVersion(form);
535
+ emit(emitter, "change", path);
536
+ emit(emitter, "touched", path);
537
+ emit(emitter, "errors", path);
538
+ emit(emitter, "validating", path);
508
539
  }
509
540
  function hasLiveBranch(values, segments) {
510
541
  for (let i = 1; i < segments.length; i++) {
@@ -541,13 +572,19 @@
541
572
  form.deleted.clear();
542
573
  clearDirtyBaselines(form);
543
574
  bumpDirtyVersion(form);
575
+ bumpValuesVersion(form);
544
576
  emit(form.emitter, "change");
545
577
  }
546
578
  function reset(form, initialValues, options) {
547
- const dirtyValues = options?.keepDirtyValues ? Object.keys(getDirtyFields(form)).map((key) => ({
548
- key,
549
- value: getValue(form, key)
550
- })) : [];
579
+ const dirtyValues = [];
580
+ if (options?.keepDirtyValues) {
581
+ for (const [key, value] of form.values) {
582
+ const segments = JSON.parse(key);
583
+ if (getDirtyBaseline(form, key, segments) !== value) {
584
+ dirtyValues.push({ segments, value });
585
+ }
586
+ }
587
+ }
551
588
  form.initialValues = initialValues;
552
589
  form.parsedValues = void 0;
553
590
  if (!options?.keepErrors) clearErrors(form);
@@ -561,8 +598,9 @@
561
598
  if (!options?.keepSubmitCount) form.submitCount = 0;
562
599
  if (!options?.keepIsSubmitted) form.isSubmitSuccessful = void 0;
563
600
  bumpDirtyVersion(form);
564
- for (const { key, value } of dirtyValues) {
565
- setValueByPath(form, create$1(key), value);
601
+ bumpValuesVersion(form);
602
+ for (const { segments, value } of dirtyValues) {
603
+ setValueByPath(form, create$1(segments), value);
566
604
  }
567
605
  emit(emitter, "change");
568
606
  emit(emitter, "touched");
@@ -594,15 +632,16 @@
594
632
  emit(emitter, "errors", path);
595
633
  }
596
634
  bumpDirtyVersion(form);
635
+ bumpValuesVersion(form);
597
636
  }
598
637
  function hasErrors({ errors }) {
599
638
  return errors.size > 0;
600
639
  }
601
640
  async function trigger(form, name) {
602
- const settle = () => waitUntil(
641
+ const settle = (keys2) => waitUntil(
603
642
  form.emitter,
604
643
  "validating",
605
- () => fieldsSettled(form),
644
+ () => keys2 === void 0 ? fieldsSettled(form) : keys2.every((key) => !form.validating.has(key)),
606
645
  () => false
607
646
  );
608
647
  if (name === void 0) {
@@ -613,7 +652,7 @@
613
652
  }
614
653
  const keys = typeof name === "string" || isSegmentsPath(name) ? [create$1(name).key] : name.map((one) => create$1(one).key);
615
654
  keys.forEach((key) => form.validators.get(key)?.());
616
- await settle();
655
+ await settle(keys);
617
656
  return keys.every((key) => !form.errors.has(key));
618
657
  }
619
658
  function isSegmentsPath(name) {
@@ -653,6 +692,7 @@
653
692
  function setParsedValues(form, values) {
654
693
  if (values === void 0 || values === form.parsedValues) return;
655
694
  form.parsedValues = values;
695
+ bumpValuesVersion(form);
656
696
  emit(form.emitter, "change");
657
697
  }
658
698
  function applyValidateResult(form, result) {
@@ -794,6 +834,46 @@
794
834
  });
795
835
  }
796
836
  }
837
+ const fieldValidateDeps = /* @__PURE__ */ new WeakMap();
838
+ function registerFieldValidateDeps(form, key, depKeys) {
839
+ let deps = fieldValidateDeps.get(form);
840
+ if (!deps) {
841
+ deps = /* @__PURE__ */ new Map();
842
+ fieldValidateDeps.set(form, deps);
843
+ }
844
+ for (const depKey of depKeys) {
845
+ let dependents = deps.get(depKey);
846
+ if (!dependents) {
847
+ dependents = /* @__PURE__ */ new Set();
848
+ deps.set(depKey, dependents);
849
+ }
850
+ dependents.add(key);
851
+ }
852
+ }
853
+ function unregisterFieldValidateDeps(form, key, depKeys) {
854
+ const deps = fieldValidateDeps.get(form);
855
+ if (!deps) return;
856
+ for (const depKey of depKeys) {
857
+ const dependents = deps.get(depKey);
858
+ if (!dependents?.delete(key)) continue;
859
+ if (!dependents.size) deps.delete(depKey);
860
+ }
861
+ }
862
+ function revalidateDependentsOnChange(form, path, mode) {
863
+ const dependents = fieldValidateDeps.get(form)?.get(path.key);
864
+ if (!dependents?.size) return;
865
+ for (const dependent of dependents) {
866
+ if (dependent === path.key) continue;
867
+ if (mode === "onChange" || mode === "all" || mode === "onTouched" && hasTouchedByPath(form, path) || form.reValidateMode === "onChange" && form.errors.has(dependent)) {
868
+ form.validators.get(dependent)?.();
869
+ }
870
+ }
871
+ }
872
+ function validationError(form) {
873
+ const error = new Error(getFirstError(form));
874
+ error.errors = getErrors(form);
875
+ return error;
876
+ }
797
877
  async function ensureValidate(form) {
798
878
  form.validators.forEach((validator) => validator());
799
879
  await waitUntil(
@@ -802,11 +882,11 @@
802
882
  () => fieldsSettled(form),
803
883
  () => hasErrors(form)
804
884
  ).catch(() => {
805
- throw new Error(getFirstError(form));
885
+ throw validationError(form);
806
886
  });
807
887
  if (form.validate) {
808
888
  await runFormValidate(form);
809
- if (hasErrors(form)) throw new Error(getFirstError(form));
889
+ if (hasErrors(form)) throw validationError(form);
810
890
  }
811
891
  }
812
892
  async function validate(form) {
@@ -1216,6 +1296,7 @@
1216
1296
  if (seededRef.current === null)
1217
1297
  seededRef.current = { done: false, source: void 0 };
1218
1298
  React.useEffect(() => {
1299
+ if (initialValues === void 0) return;
1219
1300
  const seeded = seededRef.current;
1220
1301
  if (seeded.done && (seeded.source === initialValues || isEqual(seeded.source, initialValues))) {
1221
1302
  return;
@@ -1512,6 +1593,7 @@
1512
1593
  validate,
1513
1594
  rules,
1514
1595
  validateDebounce,
1596
+ validateDeps,
1515
1597
  delayError,
1516
1598
  disabled,
1517
1599
  mode: modeOption
@@ -1520,6 +1602,17 @@
1520
1602
  const form = f1 || contextForm;
1521
1603
  if (!form) throw new Error("no form provided");
1522
1604
  const path = usePath(name);
1605
+ const seededRef = React.useRef(false);
1606
+ const seeded = initialValue !== void 0 && getValueByPath(form, path) === void 0;
1607
+ if (seeded) {
1608
+ seedValueByPath(form, path, initialValue);
1609
+ seededRef.current = true;
1610
+ }
1611
+ React.useEffect(() => {
1612
+ if (!seededRef.current) return;
1613
+ seededRef.current = false;
1614
+ emitChangeByPath(form, path);
1615
+ });
1523
1616
  const validator = useValidate(
1524
1617
  combineRulesAndValidate(rules, validate),
1525
1618
  path,
@@ -1535,17 +1628,12 @@
1535
1628
  const value = useValueByPath(form, path);
1536
1629
  const formDisabled = useWatch(form.emitter, "disabled", () => form.disabled);
1537
1630
  const mode = modeOption ?? form.mode;
1538
- React.useEffect(() => {
1539
- if (initialValue === void 0) return;
1540
- if (getValueByPath(form, path) === void 0) {
1541
- setValueByPath(form, path, initialValue);
1542
- }
1543
- }, [form, path, initialValue]);
1544
1631
  const onChange = useStageFn((v) => {
1545
1632
  setValueByPath(form, path, v);
1546
1633
  if (mode === "onChange" || mode === "all" || mode === "onTouched" && hasTouchedByPath(form, path) || liveErrors.length > 0 && form.reValidateMode === "onChange")
1547
1634
  validator();
1548
1635
  revalidateFormOnChange(form, path, mode);
1636
+ revalidateDependentsOnChange(form, path, mode);
1549
1637
  });
1550
1638
  React.useEffect(() => {
1551
1639
  form.changeHandlers.set(path.key, onChange);
@@ -1554,6 +1642,13 @@
1554
1642
  form.changeHandlers.delete(path.key);
1555
1643
  };
1556
1644
  }, [form, path.key, onChange]);
1645
+ const depsKey = validateDeps?.length ? validateDeps.join("\n") : void 0;
1646
+ React.useEffect(() => {
1647
+ if (!depsKey) return;
1648
+ const depKeys = validateDeps.map((dep) => create$1(dep).key);
1649
+ registerFieldValidateDeps(form, path.key, depKeys);
1650
+ return () => unregisterFieldValidateDeps(form, path.key, depKeys);
1651
+ }, [form, path.key, depsKey]);
1557
1652
  const onBlur = useStageFn(() => {
1558
1653
  setTouchedByPath(form, path);
1559
1654
  if (mode === "onBlur" || mode === "onTouched" || mode === "all" || liveErrors.length > 0 && form.reValidateMode === "onBlur")
@@ -1583,9 +1678,11 @@
1583
1678
  return useFieldCore(options, FormContext);
1584
1679
  }
1585
1680
 
1586
- let idCounter = 0;
1587
- function generateId() {
1588
- return `_${++idCounter}`;
1681
+ const idCounters = /* @__PURE__ */ new WeakMap();
1682
+ function generateId(form) {
1683
+ const next = (idCounters.get(form) ?? 0) + 1;
1684
+ idCounters.set(form, next);
1685
+ return `_${next}`;
1589
1686
  }
1590
1687
  const arrayIdsRegistry = /* @__PURE__ */ new WeakMap();
1591
1688
  function getArrayIds(form, key) {
@@ -1621,13 +1718,13 @@
1621
1718
  const computeFields = React.useCallback(() => {
1622
1719
  const arr = getArray();
1623
1720
  while (idsRef.current.length < arr.length) {
1624
- idsRef.current.push(generateId());
1721
+ idsRef.current.push(generateId(form));
1625
1722
  }
1626
1723
  while (idsRef.current.length > arr.length) {
1627
1724
  idsRef.current.pop();
1628
1725
  }
1629
1726
  return idsRef.current.map((id, index) => ({ id, index }));
1630
- }, [getArray]);
1727
+ }, [getArray, form]);
1631
1728
  const [fields, syncFields] = React.useReducer(
1632
1729
  computeFields,
1633
1730
  void 0,
@@ -1647,17 +1744,17 @@
1647
1744
  }, [form, path.key]);
1648
1745
  const append = useStageFn((value) => {
1649
1746
  const arr = getArray();
1650
- idsRef.current.push(generateId());
1747
+ idsRef.current.push(generateId(form));
1651
1748
  setArray([...arr, value]);
1652
1749
  });
1653
1750
  const prepend = useStageFn((value) => {
1654
1751
  const arr = getArray();
1655
- idsRef.current.unshift(generateId());
1752
+ idsRef.current.unshift(generateId(form));
1656
1753
  setArray([value, ...arr]);
1657
1754
  });
1658
1755
  const insert = useStageFn((index, value) => {
1659
1756
  const arr = getArray();
1660
- idsRef.current.splice(index, 0, generateId());
1757
+ idsRef.current.splice(index, 0, generateId(form));
1661
1758
  const newArr = [...arr.slice(0, index), value, ...arr.slice(index)];
1662
1759
  setArray(newArr);
1663
1760
  });
@@ -1687,7 +1784,7 @@
1687
1784
  setArray(newArr);
1688
1785
  });
1689
1786
  const replace = useStageFn((values) => {
1690
- idsRef.current = values.map(() => generateId());
1787
+ idsRef.current = values.map(() => generateId(form));
1691
1788
  setArray([...values]);
1692
1789
  });
1693
1790
  const update = useStageFn((index, value) => {
@@ -2074,6 +2171,7 @@
2074
2171
  exports.clearErrors = clearErrors;
2075
2172
  exports.createForm = create;
2076
2173
  exports.createFormContext = createFormContext;
2174
+ exports.emitChangeByPath = emitChangeByPath;
2077
2175
  exports.ensureValidate = ensureValidate;
2078
2176
  exports.fieldErrorId = fieldErrorId;
2079
2177
  exports.getDirtyFields = getDirtyFields;
@@ -2095,11 +2193,14 @@
2095
2193
  exports.incrementSubmitCount = incrementSubmitCount;
2096
2194
  exports.isDirty = isDirty;
2097
2195
  exports.isTouched = isTouched;
2196
+ exports.registerFieldValidateDeps = registerFieldValidateDeps;
2098
2197
  exports.removeField = removeField;
2099
2198
  exports.removeFieldByPath = removeFieldByPath;
2100
2199
  exports.reset = reset;
2101
2200
  exports.resetField = resetField;
2201
+ exports.revalidateDependentsOnChange = revalidateDependentsOnChange;
2102
2202
  exports.revalidateFormOnChange = revalidateFormOnChange;
2203
+ exports.seedValueByPath = seedValueByPath;
2103
2204
  exports.setDisabled = setDisabled;
2104
2205
  exports.setError = setError;
2105
2206
  exports.setErrorByPath = setErrorByPath;
@@ -2115,6 +2216,7 @@
2115
2216
  exports.setValueByPath = setValueByPath;
2116
2217
  exports.subscribe = subscribe;
2117
2218
  exports.trigger = trigger;
2219
+ exports.unregisterFieldValidateDeps = unregisterFieldValidateDeps;
2118
2220
  exports.unsetValidatingByPath = unsetValidatingByPath;
2119
2221
  exports.useCanSubmit = useCanSubmit;
2120
2222
  exports.useCheckboxGroupContext = useCheckboxGroupContext;