react-f0rm 0.11.1 → 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
@@ -63,6 +63,16 @@
63
63
  const result = [];
64
64
  let identifier = "";
65
65
  const flushIdentifier = () => {
66
+ if (isIndex(identifier)) {
67
+ const dotted = [...result.map(String), identifier].join(".");
68
+ const bracket = result.reduce(
69
+ (acc, seg) => acc + (typeof seg === "number" ? `[${seg}]` : `${acc ? "." : ""}${seg}`),
70
+ ""
71
+ );
72
+ throw new TypeError(
73
+ `Numeric path segment must use bracket notation: "${dotted}" \u2192 "${bracket}[${identifier}]" (path: ${path})`
74
+ );
75
+ }
66
76
  result.push(identifier);
67
77
  identifier = "";
68
78
  };
@@ -239,7 +249,23 @@
239
249
  isSubmitSuccessful: void 0
240
250
  };
241
251
  }
252
+ const valuesCaches = /* @__PURE__ */ new WeakMap();
253
+ function bumpValuesVersion(form) {
254
+ const cache = valuesCaches.get(form);
255
+ if (cache) cache.version++;
256
+ }
242
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) {
243
269
  const { initialValues, parsedValues, values, deleted } = form;
244
270
  const owned = /* @__PURE__ */ new Set();
245
271
  let merged = parsedValues ?? initialValues;
@@ -277,10 +303,23 @@
277
303
  pruneDirtyBaselines(form, path);
278
304
  if (options?.shouldDirty === false) setDirtyBaseline(form, path, value);
279
305
  bumpDirtyVersion(form);
306
+ bumpValuesVersion(form);
280
307
  if (options?.shouldTouch) setTouchedByPath(form, path);
281
308
  if (options?.shouldValidate) form.validators.get(path.key)?.();
282
309
  emit(emitter, "change", path);
283
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
+ }
284
323
  function changeValue(form, name, value, options) {
285
324
  changeValueByPath(form, create$1(name), value, options);
286
325
  }
@@ -482,7 +521,8 @@
482
521
  function removeField(form, name) {
483
522
  removeFieldByPath(form, create$1(name));
484
523
  }
485
- function removeFieldByPath(form, { key, value: segments }) {
524
+ function removeFieldByPath(form, path) {
525
+ const { key, value: segments } = path;
486
526
  const { emitter, values, touched, errors, validating, deleted } = form;
487
527
  values.delete(key);
488
528
  touched.delete(key);
@@ -491,10 +531,11 @@
491
531
  clearDirtyBaselines(form, key);
492
532
  if (!hasLiveBranch(values, segments)) deleted.add(key);
493
533
  bumpDirtyVersion(form);
494
- emit(emitter, "change");
495
- emit(emitter, "touched");
496
- emit(emitter, "errors");
497
- 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);
498
539
  }
499
540
  function hasLiveBranch(values, segments) {
500
541
  for (let i = 1; i < segments.length; i++) {
@@ -531,13 +572,19 @@
531
572
  form.deleted.clear();
532
573
  clearDirtyBaselines(form);
533
574
  bumpDirtyVersion(form);
575
+ bumpValuesVersion(form);
534
576
  emit(form.emitter, "change");
535
577
  }
536
578
  function reset(form, initialValues, options) {
537
- const dirtyValues = options?.keepDirtyValues ? Object.keys(getDirtyFields(form)).map((key) => ({
538
- key,
539
- value: getValue(form, key)
540
- })) : [];
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
+ }
541
588
  form.initialValues = initialValues;
542
589
  form.parsedValues = void 0;
543
590
  if (!options?.keepErrors) clearErrors(form);
@@ -551,8 +598,9 @@
551
598
  if (!options?.keepSubmitCount) form.submitCount = 0;
552
599
  if (!options?.keepIsSubmitted) form.isSubmitSuccessful = void 0;
553
600
  bumpDirtyVersion(form);
554
- for (const { key, value } of dirtyValues) {
555
- setValueByPath(form, create$1(key), value);
601
+ bumpValuesVersion(form);
602
+ for (const { segments, value } of dirtyValues) {
603
+ setValueByPath(form, create$1(segments), value);
556
604
  }
557
605
  emit(emitter, "change");
558
606
  emit(emitter, "touched");
@@ -584,15 +632,16 @@
584
632
  emit(emitter, "errors", path);
585
633
  }
586
634
  bumpDirtyVersion(form);
635
+ bumpValuesVersion(form);
587
636
  }
588
637
  function hasErrors({ errors }) {
589
638
  return errors.size > 0;
590
639
  }
591
640
  async function trigger(form, name) {
592
- const settle = () => waitUntil(
641
+ const settle = (keys2) => waitUntil(
593
642
  form.emitter,
594
643
  "validating",
595
- () => fieldsSettled(form),
644
+ () => keys2 === void 0 ? fieldsSettled(form) : keys2.every((key) => !form.validating.has(key)),
596
645
  () => false
597
646
  );
598
647
  if (name === void 0) {
@@ -603,7 +652,7 @@
603
652
  }
604
653
  const keys = typeof name === "string" || isSegmentsPath(name) ? [create$1(name).key] : name.map((one) => create$1(one).key);
605
654
  keys.forEach((key) => form.validators.get(key)?.());
606
- await settle();
655
+ await settle(keys);
607
656
  return keys.every((key) => !form.errors.has(key));
608
657
  }
609
658
  function isSegmentsPath(name) {
@@ -614,7 +663,10 @@
614
663
  }
615
664
  function setFormErrors(form, result, segments = [], footprint) {
616
665
  Object.entries(result).forEach(([key, value]) => {
617
- const path = [...segments, ...normalizePath(key)];
666
+ const path = [
667
+ ...segments,
668
+ ...isIndex(key) ? [key] : normalizePath(key)
669
+ ];
618
670
  if (typeof value === "string") {
619
671
  if (value) {
620
672
  setError(form, path, value);
@@ -640,6 +692,7 @@
640
692
  function setParsedValues(form, values) {
641
693
  if (values === void 0 || values === form.parsedValues) return;
642
694
  form.parsedValues = values;
695
+ bumpValuesVersion(form);
643
696
  emit(form.emitter, "change");
644
697
  }
645
698
  function applyValidateResult(form, result) {
@@ -781,6 +834,46 @@
781
834
  });
782
835
  }
783
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
+ }
784
877
  async function ensureValidate(form) {
785
878
  form.validators.forEach((validator) => validator());
786
879
  await waitUntil(
@@ -789,11 +882,11 @@
789
882
  () => fieldsSettled(form),
790
883
  () => hasErrors(form)
791
884
  ).catch(() => {
792
- throw new Error(getFirstError(form));
885
+ throw validationError(form);
793
886
  });
794
887
  if (form.validate) {
795
888
  await runFormValidate(form);
796
- if (hasErrors(form)) throw new Error(getFirstError(form));
889
+ if (hasErrors(form)) throw validationError(form);
797
890
  }
798
891
  }
799
892
  async function validate(form) {
@@ -1203,6 +1296,7 @@
1203
1296
  if (seededRef.current === null)
1204
1297
  seededRef.current = { done: false, source: void 0 };
1205
1298
  React.useEffect(() => {
1299
+ if (initialValues === void 0) return;
1206
1300
  const seeded = seededRef.current;
1207
1301
  if (seeded.done && (seeded.source === initialValues || isEqual(seeded.source, initialValues))) {
1208
1302
  return;
@@ -1499,6 +1593,7 @@
1499
1593
  validate,
1500
1594
  rules,
1501
1595
  validateDebounce,
1596
+ validateDeps,
1502
1597
  delayError,
1503
1598
  disabled,
1504
1599
  mode: modeOption
@@ -1507,6 +1602,17 @@
1507
1602
  const form = f1 || contextForm;
1508
1603
  if (!form) throw new Error("no form provided");
1509
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
+ });
1510
1616
  const validator = useValidate(
1511
1617
  combineRulesAndValidate(rules, validate),
1512
1618
  path,
@@ -1522,17 +1628,12 @@
1522
1628
  const value = useValueByPath(form, path);
1523
1629
  const formDisabled = useWatch(form.emitter, "disabled", () => form.disabled);
1524
1630
  const mode = modeOption ?? form.mode;
1525
- React.useEffect(() => {
1526
- if (initialValue === void 0) return;
1527
- if (getValueByPath(form, path) === void 0) {
1528
- setValueByPath(form, path, initialValue);
1529
- }
1530
- }, [form, path, initialValue]);
1531
1631
  const onChange = useStageFn((v) => {
1532
1632
  setValueByPath(form, path, v);
1533
1633
  if (mode === "onChange" || mode === "all" || mode === "onTouched" && hasTouchedByPath(form, path) || liveErrors.length > 0 && form.reValidateMode === "onChange")
1534
1634
  validator();
1535
1635
  revalidateFormOnChange(form, path, mode);
1636
+ revalidateDependentsOnChange(form, path, mode);
1536
1637
  });
1537
1638
  React.useEffect(() => {
1538
1639
  form.changeHandlers.set(path.key, onChange);
@@ -1541,6 +1642,13 @@
1541
1642
  form.changeHandlers.delete(path.key);
1542
1643
  };
1543
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]);
1544
1652
  const onBlur = useStageFn(() => {
1545
1653
  setTouchedByPath(form, path);
1546
1654
  if (mode === "onBlur" || mode === "onTouched" || mode === "all" || liveErrors.length > 0 && form.reValidateMode === "onBlur")
@@ -1570,9 +1678,11 @@
1570
1678
  return useFieldCore(options, FormContext);
1571
1679
  }
1572
1680
 
1573
- let idCounter = 0;
1574
- function generateId() {
1575
- 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}`;
1576
1686
  }
1577
1687
  const arrayIdsRegistry = /* @__PURE__ */ new WeakMap();
1578
1688
  function getArrayIds(form, key) {
@@ -1608,13 +1718,13 @@
1608
1718
  const computeFields = React.useCallback(() => {
1609
1719
  const arr = getArray();
1610
1720
  while (idsRef.current.length < arr.length) {
1611
- idsRef.current.push(generateId());
1721
+ idsRef.current.push(generateId(form));
1612
1722
  }
1613
1723
  while (idsRef.current.length > arr.length) {
1614
1724
  idsRef.current.pop();
1615
1725
  }
1616
1726
  return idsRef.current.map((id, index) => ({ id, index }));
1617
- }, [getArray]);
1727
+ }, [getArray, form]);
1618
1728
  const [fields, syncFields] = React.useReducer(
1619
1729
  computeFields,
1620
1730
  void 0,
@@ -1634,17 +1744,17 @@
1634
1744
  }, [form, path.key]);
1635
1745
  const append = useStageFn((value) => {
1636
1746
  const arr = getArray();
1637
- idsRef.current.push(generateId());
1747
+ idsRef.current.push(generateId(form));
1638
1748
  setArray([...arr, value]);
1639
1749
  });
1640
1750
  const prepend = useStageFn((value) => {
1641
1751
  const arr = getArray();
1642
- idsRef.current.unshift(generateId());
1752
+ idsRef.current.unshift(generateId(form));
1643
1753
  setArray([value, ...arr]);
1644
1754
  });
1645
1755
  const insert = useStageFn((index, value) => {
1646
1756
  const arr = getArray();
1647
- idsRef.current.splice(index, 0, generateId());
1757
+ idsRef.current.splice(index, 0, generateId(form));
1648
1758
  const newArr = [...arr.slice(0, index), value, ...arr.slice(index)];
1649
1759
  setArray(newArr);
1650
1760
  });
@@ -1674,7 +1784,7 @@
1674
1784
  setArray(newArr);
1675
1785
  });
1676
1786
  const replace = useStageFn((values) => {
1677
- idsRef.current = values.map(() => generateId());
1787
+ idsRef.current = values.map(() => generateId(form));
1678
1788
  setArray([...values]);
1679
1789
  });
1680
1790
  const update = useStageFn((index, value) => {
@@ -2061,6 +2171,7 @@
2061
2171
  exports.clearErrors = clearErrors;
2062
2172
  exports.createForm = create;
2063
2173
  exports.createFormContext = createFormContext;
2174
+ exports.emitChangeByPath = emitChangeByPath;
2064
2175
  exports.ensureValidate = ensureValidate;
2065
2176
  exports.fieldErrorId = fieldErrorId;
2066
2177
  exports.getDirtyFields = getDirtyFields;
@@ -2082,11 +2193,14 @@
2082
2193
  exports.incrementSubmitCount = incrementSubmitCount;
2083
2194
  exports.isDirty = isDirty;
2084
2195
  exports.isTouched = isTouched;
2196
+ exports.registerFieldValidateDeps = registerFieldValidateDeps;
2085
2197
  exports.removeField = removeField;
2086
2198
  exports.removeFieldByPath = removeFieldByPath;
2087
2199
  exports.reset = reset;
2088
2200
  exports.resetField = resetField;
2201
+ exports.revalidateDependentsOnChange = revalidateDependentsOnChange;
2089
2202
  exports.revalidateFormOnChange = revalidateFormOnChange;
2203
+ exports.seedValueByPath = seedValueByPath;
2090
2204
  exports.setDisabled = setDisabled;
2091
2205
  exports.setError = setError;
2092
2206
  exports.setErrorByPath = setErrorByPath;
@@ -2102,6 +2216,7 @@
2102
2216
  exports.setValueByPath = setValueByPath;
2103
2217
  exports.subscribe = subscribe;
2104
2218
  exports.trigger = trigger;
2219
+ exports.unregisterFieldValidateDeps = unregisterFieldValidateDeps;
2105
2220
  exports.unsetValidatingByPath = unsetValidatingByPath;
2106
2221
  exports.useCanSubmit = useCanSubmit;
2107
2222
  exports.useCheckboxGroupContext = useCheckboxGroupContext;