react-f0rm 1.0.0 → 1.1.1

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
@@ -49,12 +49,17 @@
49
49
  };
50
50
  }
51
51
 
52
+ const PATH_CACHE_LIMIT = 1e4;
52
53
  const pathCache = /* @__PURE__ */ new Map();
53
54
  function normalizePath(path) {
54
55
  if (Array.isArray(path)) return path;
55
56
  const cached = pathCache.get(path);
56
57
  if (cached) return cached;
57
58
  const value = parsePath(path);
59
+ if (pathCache.size >= PATH_CACHE_LIMIT) {
60
+ const oldest = pathCache.keys().next();
61
+ if (!oldest.done) pathCache.delete(oldest.value);
62
+ }
58
63
  pathCache.set(path, value);
59
64
  return value;
60
65
  }
@@ -249,7 +254,23 @@
249
254
  isSubmitSuccessful: void 0
250
255
  };
251
256
  }
257
+ const valuesCaches = /* @__PURE__ */ new WeakMap();
258
+ function bumpValuesVersion(form) {
259
+ const cache = valuesCaches.get(form);
260
+ if (cache) cache.version++;
261
+ }
252
262
  function getValues(form) {
263
+ let cache = valuesCaches.get(form);
264
+ if (!cache) {
265
+ cache = { version: 0, result: computeValues(form) };
266
+ valuesCaches.set(form, cache);
267
+ } else if (cache.version > 0) {
268
+ cache.result = computeValues(form);
269
+ cache.version = 0;
270
+ }
271
+ return cache.result;
272
+ }
273
+ function computeValues(form) {
253
274
  const { initialValues, parsedValues, values, deleted } = form;
254
275
  const owned = /* @__PURE__ */ new Set();
255
276
  let merged = parsedValues ?? initialValues;
@@ -287,10 +308,23 @@
287
308
  pruneDirtyBaselines(form, path);
288
309
  if (options?.shouldDirty === false) setDirtyBaseline(form, path, value);
289
310
  bumpDirtyVersion(form);
311
+ bumpValuesVersion(form);
290
312
  if (options?.shouldTouch) setTouchedByPath(form, path);
291
313
  if (options?.shouldValidate) form.validators.get(path.key)?.();
292
314
  emit(emitter, "change", path);
293
315
  }
316
+ function seedValueByPath(form, path, value) {
317
+ const { values, deleted } = form;
318
+ values.set(path.key, value);
319
+ pruneDescendantKeys(values, path);
320
+ reviveBranch(deleted, path);
321
+ pruneDirtyBaselines(form, path);
322
+ bumpDirtyVersion(form);
323
+ bumpValuesVersion(form);
324
+ }
325
+ function emitChangeByPath({ emitter }, path) {
326
+ emit(emitter, "change", path);
327
+ }
294
328
  function changeValue(form, name, value, options) {
295
329
  changeValueByPath(form, create$1(name), value, options);
296
330
  }
@@ -492,7 +526,8 @@
492
526
  function removeField(form, name) {
493
527
  removeFieldByPath(form, create$1(name));
494
528
  }
495
- function removeFieldByPath(form, { key, value: segments }) {
529
+ function removeFieldByPath(form, path) {
530
+ const { key, value: segments } = path;
496
531
  const { emitter, values, touched, errors, validating, deleted } = form;
497
532
  values.delete(key);
498
533
  touched.delete(key);
@@ -501,10 +536,11 @@
501
536
  clearDirtyBaselines(form, key);
502
537
  if (!hasLiveBranch(values, segments)) deleted.add(key);
503
538
  bumpDirtyVersion(form);
504
- emit(emitter, "change");
505
- emit(emitter, "touched");
506
- emit(emitter, "errors");
507
- emit(emitter, "validating");
539
+ bumpValuesVersion(form);
540
+ emit(emitter, "change", path);
541
+ emit(emitter, "touched", path);
542
+ emit(emitter, "errors", path);
543
+ emit(emitter, "validating", path);
508
544
  }
509
545
  function hasLiveBranch(values, segments) {
510
546
  for (let i = 1; i < segments.length; i++) {
@@ -541,13 +577,19 @@
541
577
  form.deleted.clear();
542
578
  clearDirtyBaselines(form);
543
579
  bumpDirtyVersion(form);
580
+ bumpValuesVersion(form);
544
581
  emit(form.emitter, "change");
545
582
  }
546
583
  function reset(form, initialValues, options) {
547
- const dirtyValues = options?.keepDirtyValues ? Object.keys(getDirtyFields(form)).map((key) => ({
548
- key,
549
- value: getValue(form, key)
550
- })) : [];
584
+ const dirtyValues = [];
585
+ if (options?.keepDirtyValues) {
586
+ for (const [key, value] of form.values) {
587
+ const segments = JSON.parse(key);
588
+ if (getDirtyBaseline(form, key, segments) !== value) {
589
+ dirtyValues.push({ segments, value });
590
+ }
591
+ }
592
+ }
551
593
  form.initialValues = initialValues;
552
594
  form.parsedValues = void 0;
553
595
  if (!options?.keepErrors) clearErrors(form);
@@ -561,8 +603,9 @@
561
603
  if (!options?.keepSubmitCount) form.submitCount = 0;
562
604
  if (!options?.keepIsSubmitted) form.isSubmitSuccessful = void 0;
563
605
  bumpDirtyVersion(form);
564
- for (const { key, value } of dirtyValues) {
565
- setValueByPath(form, create$1(key), value);
606
+ bumpValuesVersion(form);
607
+ for (const { segments, value } of dirtyValues) {
608
+ setValueByPath(form, create$1(segments), value);
566
609
  }
567
610
  emit(emitter, "change");
568
611
  emit(emitter, "touched");
@@ -594,15 +637,16 @@
594
637
  emit(emitter, "errors", path);
595
638
  }
596
639
  bumpDirtyVersion(form);
640
+ bumpValuesVersion(form);
597
641
  }
598
642
  function hasErrors({ errors }) {
599
643
  return errors.size > 0;
600
644
  }
601
645
  async function trigger(form, name) {
602
- const settle = () => waitUntil(
646
+ const settle = (keys2) => waitUntil(
603
647
  form.emitter,
604
648
  "validating",
605
- () => fieldsSettled(form),
649
+ () => keys2 === void 0 ? fieldsSettled(form) : keys2.every((key) => !form.validating.has(key)),
606
650
  () => false
607
651
  );
608
652
  if (name === void 0) {
@@ -613,7 +657,7 @@
613
657
  }
614
658
  const keys = typeof name === "string" || isSegmentsPath(name) ? [create$1(name).key] : name.map((one) => create$1(one).key);
615
659
  keys.forEach((key) => form.validators.get(key)?.());
616
- await settle();
660
+ await settle(keys);
617
661
  return keys.every((key) => !form.errors.has(key));
618
662
  }
619
663
  function isSegmentsPath(name) {
@@ -653,6 +697,7 @@
653
697
  function setParsedValues(form, values) {
654
698
  if (values === void 0 || values === form.parsedValues) return;
655
699
  form.parsedValues = values;
700
+ bumpValuesVersion(form);
656
701
  emit(form.emitter, "change");
657
702
  }
658
703
  function applyValidateResult(form, result) {
@@ -794,6 +839,46 @@
794
839
  });
795
840
  }
796
841
  }
842
+ const fieldValidateDeps = /* @__PURE__ */ new WeakMap();
843
+ function registerFieldValidateDeps(form, key, depKeys) {
844
+ let deps = fieldValidateDeps.get(form);
845
+ if (!deps) {
846
+ deps = /* @__PURE__ */ new Map();
847
+ fieldValidateDeps.set(form, deps);
848
+ }
849
+ for (const depKey of depKeys) {
850
+ let dependents = deps.get(depKey);
851
+ if (!dependents) {
852
+ dependents = /* @__PURE__ */ new Set();
853
+ deps.set(depKey, dependents);
854
+ }
855
+ dependents.add(key);
856
+ }
857
+ }
858
+ function unregisterFieldValidateDeps(form, key, depKeys) {
859
+ const deps = fieldValidateDeps.get(form);
860
+ if (!deps) return;
861
+ for (const depKey of depKeys) {
862
+ const dependents = deps.get(depKey);
863
+ if (!dependents?.delete(key)) continue;
864
+ if (!dependents.size) deps.delete(depKey);
865
+ }
866
+ }
867
+ function revalidateDependentsOnChange(form, path, mode) {
868
+ const dependents = fieldValidateDeps.get(form)?.get(path.key);
869
+ if (!dependents?.size) return;
870
+ for (const dependent of dependents) {
871
+ if (dependent === path.key) continue;
872
+ if (mode === "onChange" || mode === "all" || mode === "onTouched" && hasTouchedByPath(form, path) || form.reValidateMode === "onChange" && form.errors.has(dependent)) {
873
+ form.validators.get(dependent)?.();
874
+ }
875
+ }
876
+ }
877
+ function validationError(form) {
878
+ const error = new Error(getFirstError(form));
879
+ error.errors = getErrors(form);
880
+ return error;
881
+ }
797
882
  async function ensureValidate(form) {
798
883
  form.validators.forEach((validator) => validator());
799
884
  await waitUntil(
@@ -802,11 +887,11 @@
802
887
  () => fieldsSettled(form),
803
888
  () => hasErrors(form)
804
889
  ).catch(() => {
805
- throw new Error(getFirstError(form));
890
+ throw validationError(form);
806
891
  });
807
892
  if (form.validate) {
808
893
  await runFormValidate(form);
809
- if (hasErrors(form)) throw new Error(getFirstError(form));
894
+ if (hasErrors(form)) throw validationError(form);
810
895
  }
811
896
  }
812
897
  async function validate(form) {
@@ -1216,6 +1301,7 @@
1216
1301
  if (seededRef.current === null)
1217
1302
  seededRef.current = { done: false, source: void 0 };
1218
1303
  React.useEffect(() => {
1304
+ if (initialValues === void 0) return;
1219
1305
  const seeded = seededRef.current;
1220
1306
  if (seeded.done && (seeded.source === initialValues || isEqual(seeded.source, initialValues))) {
1221
1307
  return;
@@ -1512,6 +1598,7 @@
1512
1598
  validate,
1513
1599
  rules,
1514
1600
  validateDebounce,
1601
+ validateDeps,
1515
1602
  delayError,
1516
1603
  disabled,
1517
1604
  mode: modeOption
@@ -1520,6 +1607,17 @@
1520
1607
  const form = f1 || contextForm;
1521
1608
  if (!form) throw new Error("no form provided");
1522
1609
  const path = usePath(name);
1610
+ const seededRef = React.useRef(false);
1611
+ const seeded = initialValue !== void 0 && getValueByPath(form, path) === void 0;
1612
+ if (seeded) {
1613
+ seedValueByPath(form, path, initialValue);
1614
+ seededRef.current = true;
1615
+ }
1616
+ React.useEffect(() => {
1617
+ if (!seededRef.current) return;
1618
+ seededRef.current = false;
1619
+ emitChangeByPath(form, path);
1620
+ });
1523
1621
  const validator = useValidate(
1524
1622
  combineRulesAndValidate(rules, validate),
1525
1623
  path,
@@ -1535,17 +1633,12 @@
1535
1633
  const value = useValueByPath(form, path);
1536
1634
  const formDisabled = useWatch(form.emitter, "disabled", () => form.disabled);
1537
1635
  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
1636
  const onChange = useStageFn((v) => {
1545
1637
  setValueByPath(form, path, v);
1546
1638
  if (mode === "onChange" || mode === "all" || mode === "onTouched" && hasTouchedByPath(form, path) || liveErrors.length > 0 && form.reValidateMode === "onChange")
1547
1639
  validator();
1548
1640
  revalidateFormOnChange(form, path, mode);
1641
+ revalidateDependentsOnChange(form, path, mode);
1549
1642
  });
1550
1643
  React.useEffect(() => {
1551
1644
  form.changeHandlers.set(path.key, onChange);
@@ -1554,6 +1647,13 @@
1554
1647
  form.changeHandlers.delete(path.key);
1555
1648
  };
1556
1649
  }, [form, path.key, onChange]);
1650
+ const depsKey = validateDeps?.length ? validateDeps.join("\n") : void 0;
1651
+ React.useEffect(() => {
1652
+ if (!depsKey) return;
1653
+ const depKeys = validateDeps.map((dep) => create$1(dep).key);
1654
+ registerFieldValidateDeps(form, path.key, depKeys);
1655
+ return () => unregisterFieldValidateDeps(form, path.key, depKeys);
1656
+ }, [form, path.key, depsKey]);
1557
1657
  const onBlur = useStageFn(() => {
1558
1658
  setTouchedByPath(form, path);
1559
1659
  if (mode === "onBlur" || mode === "onTouched" || mode === "all" || liveErrors.length > 0 && form.reValidateMode === "onBlur")
@@ -1583,9 +1683,11 @@
1583
1683
  return useFieldCore(options, FormContext);
1584
1684
  }
1585
1685
 
1586
- let idCounter = 0;
1587
- function generateId() {
1588
- return `_${++idCounter}`;
1686
+ const idCounters = /* @__PURE__ */ new WeakMap();
1687
+ function generateId(form) {
1688
+ const next = (idCounters.get(form) ?? 0) + 1;
1689
+ idCounters.set(form, next);
1690
+ return `_${next}`;
1589
1691
  }
1590
1692
  const arrayIdsRegistry = /* @__PURE__ */ new WeakMap();
1591
1693
  function getArrayIds(form, key) {
@@ -1621,13 +1723,13 @@
1621
1723
  const computeFields = React.useCallback(() => {
1622
1724
  const arr = getArray();
1623
1725
  while (idsRef.current.length < arr.length) {
1624
- idsRef.current.push(generateId());
1726
+ idsRef.current.push(generateId(form));
1625
1727
  }
1626
1728
  while (idsRef.current.length > arr.length) {
1627
1729
  idsRef.current.pop();
1628
1730
  }
1629
1731
  return idsRef.current.map((id, index) => ({ id, index }));
1630
- }, [getArray]);
1732
+ }, [getArray, form]);
1631
1733
  const [fields, syncFields] = React.useReducer(
1632
1734
  computeFields,
1633
1735
  void 0,
@@ -1647,17 +1749,17 @@
1647
1749
  }, [form, path.key]);
1648
1750
  const append = useStageFn((value) => {
1649
1751
  const arr = getArray();
1650
- idsRef.current.push(generateId());
1752
+ idsRef.current.push(generateId(form));
1651
1753
  setArray([...arr, value]);
1652
1754
  });
1653
1755
  const prepend = useStageFn((value) => {
1654
1756
  const arr = getArray();
1655
- idsRef.current.unshift(generateId());
1757
+ idsRef.current.unshift(generateId(form));
1656
1758
  setArray([value, ...arr]);
1657
1759
  });
1658
1760
  const insert = useStageFn((index, value) => {
1659
1761
  const arr = getArray();
1660
- idsRef.current.splice(index, 0, generateId());
1762
+ idsRef.current.splice(index, 0, generateId(form));
1661
1763
  const newArr = [...arr.slice(0, index), value, ...arr.slice(index)];
1662
1764
  setArray(newArr);
1663
1765
  });
@@ -1687,7 +1789,7 @@
1687
1789
  setArray(newArr);
1688
1790
  });
1689
1791
  const replace = useStageFn((values) => {
1690
- idsRef.current = values.map(() => generateId());
1792
+ idsRef.current = values.map(() => generateId(form));
1691
1793
  setArray([...values]);
1692
1794
  });
1693
1795
  const update = useStageFn((index, value) => {
@@ -2074,6 +2176,7 @@
2074
2176
  exports.clearErrors = clearErrors;
2075
2177
  exports.createForm = create;
2076
2178
  exports.createFormContext = createFormContext;
2179
+ exports.emitChangeByPath = emitChangeByPath;
2077
2180
  exports.ensureValidate = ensureValidate;
2078
2181
  exports.fieldErrorId = fieldErrorId;
2079
2182
  exports.getDirtyFields = getDirtyFields;
@@ -2095,11 +2198,14 @@
2095
2198
  exports.incrementSubmitCount = incrementSubmitCount;
2096
2199
  exports.isDirty = isDirty;
2097
2200
  exports.isTouched = isTouched;
2201
+ exports.registerFieldValidateDeps = registerFieldValidateDeps;
2098
2202
  exports.removeField = removeField;
2099
2203
  exports.removeFieldByPath = removeFieldByPath;
2100
2204
  exports.reset = reset;
2101
2205
  exports.resetField = resetField;
2206
+ exports.revalidateDependentsOnChange = revalidateDependentsOnChange;
2102
2207
  exports.revalidateFormOnChange = revalidateFormOnChange;
2208
+ exports.seedValueByPath = seedValueByPath;
2103
2209
  exports.setDisabled = setDisabled;
2104
2210
  exports.setError = setError;
2105
2211
  exports.setErrorByPath = setErrorByPath;
@@ -2115,6 +2221,7 @@
2115
2221
  exports.setValueByPath = setValueByPath;
2116
2222
  exports.subscribe = subscribe;
2117
2223
  exports.trigger = trigger;
2224
+ exports.unregisterFieldValidateDeps = unregisterFieldValidateDeps;
2118
2225
  exports.unsetValidatingByPath = unsetValidatingByPath;
2119
2226
  exports.useCanSubmit = useCanSubmit;
2120
2227
  exports.useCheckboxGroupContext = useCheckboxGroupContext;