@juantroconisf/lib 11.6.0 → 11.8.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.js CHANGED
@@ -44,11 +44,13 @@ function handleNestedChange({
44
44
  value,
45
45
  hasNestedValues = false
46
46
  }) {
47
- if (!hasNestedValues) return { ...state, [id]: value };
48
- const propertyDepth = String(id).split("."), newValues = { ...state };
49
- let current = newValues;
50
- for (let i = 0; i < propertyDepth.length - 1; i++) {
51
- const key = propertyDepth[i];
47
+ const isNested = hasNestedValues || String(id).includes(".");
48
+ if (!isNested) return { ...state, [id]: value };
49
+ const keys = String(id).split(".");
50
+ const newState = { ...state };
51
+ let current = newState;
52
+ for (let i = 0; i < keys.length - 1; i++) {
53
+ const key = keys[i];
52
54
  if (Array.isArray(current[key])) {
53
55
  current[key] = [...current[key]];
54
56
  } else {
@@ -56,8 +58,8 @@ function handleNestedChange({
56
58
  }
57
59
  current = current[key];
58
60
  }
59
- current[propertyDepth[propertyDepth.length - 1]] = value;
60
- return { ...newValues };
61
+ current[keys[keys.length - 1]] = value;
62
+ return newState;
61
63
  }
62
64
  function handleArrayItemChange({
63
65
  state,
@@ -98,21 +100,6 @@ function removeCompositeKeysByPrefix(map, prefix) {
98
100
  }
99
101
  return result;
100
102
  }
101
- function setNestedValue(state, dotPath, value) {
102
- const keys = dotPath.split(".");
103
- const copy = { ...state };
104
- let current = copy;
105
- for (let i = 0; i < keys.length - 1; i++) {
106
- if (Array.isArray(current[keys[i]])) {
107
- current[keys[i]] = [...current[keys[i]]];
108
- } else {
109
- current[keys[i]] = { ...current[keys[i]] };
110
- }
111
- current = current[keys[i]];
112
- }
113
- current[keys[keys.length - 1]] = value;
114
- return copy;
115
- }
116
103
 
117
104
  // src/hooks/useForm.tsx
118
105
  var import_react3 = require("@heroui/react");
@@ -407,7 +394,7 @@ function useForm(schema, {
407
394
  }, [schema]);
408
395
  const [state, setState] = (0, import_react2.useState)(initialState), [metadata, setMetadata] = (0, import_react2.useState)(/* @__PURE__ */ new Map());
409
396
  useComponentLanguage();
410
- const stateRef = (0, import_react2.useRef)(state), metadataRef = (0, import_react2.useRef)(metadata);
397
+ const stateRef = (0, import_react2.useRef)(state), metadataRef = (0, import_react2.useRef)(metadata), propsCache = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
411
398
  stateRef.current = state;
412
399
  metadataRef.current = metadata;
413
400
  const indexMap = (0, import_react2.useMemo)(() => {
@@ -441,7 +428,17 @@ function useForm(schema, {
441
428
  const indexMapRef = (0, import_react2.useRef)(indexMap);
442
429
  indexMapRef.current = indexMap;
443
430
  const getIndex = (0, import_react2.useCallback)((arrayKey, itemId) => {
444
- return indexMapRef.current.get(arrayKey)?.get(itemId);
431
+ const map = indexMapRef.current.get(arrayKey);
432
+ if (!map) return void 0;
433
+ const index = map.get(itemId);
434
+ if (index !== void 0) return index;
435
+ if (typeof itemId === "string") {
436
+ const num = Number(itemId);
437
+ if (!isNaN(num)) return map.get(num);
438
+ } else if (typeof itemId === "number") {
439
+ return map.get(String(itemId));
440
+ }
441
+ return void 0;
445
442
  }, []);
446
443
  const ruleCache = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
447
444
  (0, import_react2.useMemo)(() => {
@@ -468,14 +465,7 @@ function useForm(schema, {
468
465
  const runValidation = (0, import_react2.useCallback)(
469
466
  (ruleDef, value, compositeKey, realPath, fullState) => {
470
467
  if ((0, import_yup2.isSchema)(ruleDef)) {
471
- try {
472
- if (validationSchema && realPath && fullState !== void 0) {
473
- validationSchema.validateSyncAt(realPath, fullState);
474
- } else {
475
- ruleDef.validateSync(value);
476
- }
477
- return false;
478
- } catch (err) {
468
+ const updateMetadata = (err) => {
479
469
  const error = {
480
470
  isInvalid: true,
481
471
  errorMessage: err.message || "Invalid"
@@ -490,7 +480,32 @@ function useForm(schema, {
490
480
  newMap.set(compositeKey, { ...currentMeta, ...error });
491
481
  return newMap;
492
482
  });
493
- return true;
483
+ };
484
+ try {
485
+ if (validationSchema && realPath && fullState !== void 0) {
486
+ validationSchema.validateSyncAt(realPath, fullState);
487
+ } else {
488
+ ruleDef.validateSync(value);
489
+ }
490
+ return false;
491
+ } catch (err) {
492
+ if (err.name === "ValidationError") {
493
+ updateMetadata(err);
494
+ return true;
495
+ }
496
+ return (async () => {
497
+ try {
498
+ if (validationSchema && realPath && fullState !== void 0) {
499
+ await validationSchema.validateAt(realPath, fullState);
500
+ } else {
501
+ await ruleDef.validate(value);
502
+ }
503
+ return false;
504
+ } catch (asyncErr) {
505
+ updateMetadata(asyncErr);
506
+ return true;
507
+ }
508
+ })();
494
509
  }
495
510
  }
496
511
  return false;
@@ -501,35 +516,52 @@ function useForm(schema, {
501
516
  (compositeKey, fieldPath, realPath, value, fullState) => {
502
517
  let schemaRule = getRule(fieldPath, validationSchema);
503
518
  if (schemaRule) {
504
- if (runValidation(schemaRule, value, compositeKey, realPath, fullState))
505
- return true;
519
+ const result = runValidation(
520
+ schemaRule,
521
+ value,
522
+ compositeKey,
523
+ realPath,
524
+ fullState
525
+ );
526
+ if (result === true) return true;
527
+ if (result instanceof Promise) {
528
+ return result.then((res) => {
529
+ if (!res) clearError();
530
+ return res;
531
+ });
532
+ }
506
533
  }
507
- setMetadata((prev) => {
508
- const newMap = new Map(prev);
509
- const currentMeta = newMap.get(compositeKey) || {
510
- isTouched: false,
511
- isInvalid: false,
512
- errorMessage: ""
513
- };
514
- newMap.set(compositeKey, {
515
- ...currentMeta,
516
- isInvalid: false,
517
- errorMessage: ""
534
+ function clearError() {
535
+ setMetadata((prev) => {
536
+ const newMap = new Map(prev);
537
+ const currentMeta = newMap.get(compositeKey) || {
538
+ isTouched: false,
539
+ isInvalid: false,
540
+ errorMessage: ""
541
+ };
542
+ newMap.set(compositeKey, {
543
+ ...currentMeta,
544
+ isInvalid: false,
545
+ errorMessage: ""
546
+ });
547
+ return newMap;
518
548
  });
519
- return newMap;
520
- });
549
+ }
550
+ clearError();
521
551
  return false;
522
552
  },
523
553
  [getRule, runValidation, validationSchema]
524
554
  );
525
555
  const validateAll = (0, import_react2.useCallback)(() => {
526
556
  if (!validationSchema) return false;
527
- let hasError = false;
528
- const newMetadata = new Map(metadataRef.current);
529
- try {
530
- validationSchema.validateSync(state, { abortEarly: false });
531
- } catch (err) {
532
- hasError = true;
557
+ const cleanMetadata = new Map(metadataRef.current);
558
+ cleanMetadata.forEach((value, key) => {
559
+ if (value.isInvalid) {
560
+ cleanMetadata.set(key, { ...value, isInvalid: false, errorMessage: "" });
561
+ }
562
+ });
563
+ const handleErrors = (err) => {
564
+ const newMetadata = new Map(cleanMetadata);
533
565
  if (err.inner) {
534
566
  err.inner.forEach((validationError) => {
535
567
  const yupPath = validationError.path;
@@ -570,9 +602,27 @@ function useForm(schema, {
570
602
  });
571
603
  });
572
604
  }
605
+ setMetadata(newMetadata);
606
+ return true;
607
+ };
608
+ try {
609
+ validationSchema.validateSync(state, { abortEarly: false });
610
+ } catch (err) {
611
+ if (err.name === "ValidationError") {
612
+ return handleErrors(err);
613
+ }
614
+ return (async () => {
615
+ try {
616
+ await validationSchema.validate(state, { abortEarly: false });
617
+ setMetadata(cleanMetadata);
618
+ return false;
619
+ } catch (asyncErr) {
620
+ return handleErrors(asyncErr);
621
+ }
622
+ })();
573
623
  }
574
- setMetadata(newMetadata);
575
- return hasError;
624
+ setMetadata(cleanMetadata);
625
+ return false;
576
626
  }, [validationSchema, state, arrayIdentifiers]);
577
627
  const handleFieldChange = (0, import_react2.useCallback)(
578
628
  (resolution, newValue) => {
@@ -657,7 +707,12 @@ function useForm(schema, {
657
707
  nextState = { ...nextState, [parentKey]: parentArr };
658
708
  }
659
709
  } else if (type === "deep" /* Deep */) {
660
- nextState = setNestedValue(nextState, resolution.realPath, finalValue);
710
+ nextState = handleNestedChange({
711
+ state: nextState,
712
+ id: resolution.realPath,
713
+ value: finalValue,
714
+ hasNestedValues: true
715
+ });
661
716
  }
662
717
  setState(nextState);
663
718
  validateField(
@@ -713,8 +768,8 @@ function useForm(schema, {
713
768
  [validateField, getRule, validationSchema]
714
769
  );
715
770
  const on = (0, import_react2.useMemo)(
716
- () => ({
717
- input: (...args) => {
771
+ () => {
772
+ const getCachedProps = (method, args, factory) => {
718
773
  const data = resolveFieldData(
719
774
  args,
720
775
  stateRef.current,
@@ -724,117 +779,78 @@ function useForm(schema, {
724
779
  validationSchema
725
780
  );
726
781
  if (!data) return {};
727
- return {
782
+ const meta = metadataRef.current.get(data.compositeKey);
783
+ const deps = JSON.stringify([
784
+ data.value,
785
+ meta?.isInvalid,
786
+ meta?.errorMessage,
787
+ meta?.isTouched
788
+ ]);
789
+ const cacheKey = `${method}:${JSON.stringify(args)}`;
790
+ const cached = propsCache.current.get(cacheKey);
791
+ if (cached && cached.deps === deps) {
792
+ return cached.props;
793
+ }
794
+ const props = factory(data);
795
+ propsCache.current.set(cacheKey, { props, deps });
796
+ return props;
797
+ };
798
+ return {
799
+ input: (...args) => getCachedProps("input", args, (data) => ({
728
800
  ...createHandlers(data),
729
801
  value: data.value === null || data.value === void 0 ? "" : typeof data.value === "boolean" ? data.value : String(data.value),
730
802
  onValueChange: (v) => handleFieldChange(data, v)
731
- };
732
- },
733
- select: (...args) => {
734
- const data = resolveFieldData(
735
- args,
736
- stateRef.current,
737
- getIndex,
738
- getNestedValue,
739
- getRule,
740
- validationSchema
741
- );
742
- if (!data) return {};
743
- const isArray = Array.isArray(data.value);
744
- return {
745
- ...createHandlers(data),
746
- selectedKeys: data.value === null || data.value === void 0 ? [] : isArray ? data.value.map(String) : [String(data.value)],
747
- onSelectionChange: (v) => {
748
- const fixed = typeof v === "string" || v === null ? v : isArray ? Array.from(v) : Array.from(v)[0] ?? null;
749
- handleFieldChange(data, fixed);
750
- }
751
- };
752
- },
753
- autocomplete: (...args) => {
754
- const data = resolveFieldData(
755
- args,
756
- stateRef.current,
757
- getIndex,
758
- getNestedValue,
759
- getRule,
760
- validationSchema
761
- );
762
- if (!data) return {};
763
- return {
803
+ })),
804
+ select: (...args) => getCachedProps("select", args, (data) => {
805
+ const isArray = Array.isArray(data.value);
806
+ return {
807
+ ...createHandlers(data),
808
+ selectedKeys: data.value === null || data.value === void 0 ? [] : isArray ? data.value.map(String) : [String(data.value)],
809
+ onSelectionChange: (v) => {
810
+ const fixed = typeof v === "string" || v === null ? v : isArray ? Array.from(v) : Array.from(v)[0] ?? null;
811
+ handleFieldChange(data, fixed);
812
+ }
813
+ };
814
+ }),
815
+ autocomplete: (...args) => getCachedProps("autocomplete", args, (data) => ({
764
816
  ...createHandlers(data),
765
817
  selectedKey: data.value === null || data.value === void 0 ? null : String(data.value),
766
818
  onSelectionChange: (v) => {
767
819
  const fixed = typeof v === "string" || v === null || v === void 0 ? v : String(v);
768
820
  handleFieldChange(data, fixed);
769
821
  }
770
- };
771
- },
772
- numberInput: (...args) => {
773
- const data = resolveFieldData(
774
- args,
775
- stateRef.current,
776
- getIndex,
777
- getNestedValue,
778
- getRule,
779
- validationSchema
780
- );
781
- if (!data) return {};
782
- return {
822
+ })),
823
+ numberInput: (...args) => getCachedProps("numberInput", args, (data) => ({
783
824
  ...createHandlers(data),
784
825
  value: data.value === null || data.value === void 0 ? "" : String(data.value),
785
826
  onValueChange: (v) => handleFieldChange(data, v)
786
- };
787
- },
788
- checkbox: (...args) => {
789
- const data = resolveFieldData(
790
- args,
791
- stateRef.current,
792
- getIndex,
793
- getNestedValue,
794
- getRule,
795
- validationSchema
796
- );
797
- if (!data) return {};
798
- return {
827
+ })),
828
+ checkbox: (...args) => getCachedProps("checkbox", args, (data) => ({
799
829
  ...createHandlers(data),
800
830
  isSelected: Boolean(data.value),
801
831
  onValueChange: (v) => handleFieldChange(data, v)
802
- };
803
- },
804
- switch: (...args) => {
805
- const data = resolveFieldData(
806
- args,
807
- stateRef.current,
808
- getIndex,
809
- getNestedValue,
810
- getRule,
811
- validationSchema
812
- );
813
- if (!data) return {};
814
- return {
832
+ })),
833
+ switch: (...args) => getCachedProps("switch", args, (data) => ({
815
834
  ...createHandlers(data),
816
835
  isSelected: Boolean(data.value),
817
836
  onValueChange: (v) => handleFieldChange(data, v)
818
- };
819
- },
820
- radio: (...args) => {
821
- const data = resolveFieldData(
822
- args,
823
- stateRef.current,
824
- getIndex,
825
- getNestedValue,
826
- getRule,
827
- validationSchema
828
- );
829
- if (!data) return {};
830
- return {
837
+ })),
838
+ radio: (...args) => getCachedProps("radio", args, (data) => ({
831
839
  ...createHandlers(data),
832
840
  value: data.value === null || data.value === void 0 ? "" : String(data.value),
833
841
  onValueChange: (v) => handleFieldChange(data, v)
834
- };
835
- }
836
- }),
837
- [createHandlers, getIndex, handleFieldChange, getRule, validationSchema]
842
+ }))
843
+ };
844
+ },
845
+ [
846
+ createHandlers,
847
+ getIndex,
848
+ handleFieldChange,
849
+ getRule,
850
+ validationSchema,
851
+ state,
852
+ metadata
853
+ ]
838
854
  );
839
855
  const helpers = (0, import_react2.useMemo)(
840
856
  () => ({
@@ -949,9 +965,86 @@ function useForm(schema, {
949
965
  const index = getIndex(arrayKey, itemId);
950
966
  if (index === void 0) return void 0;
951
967
  return getNestedValue(stateRef.current, arrayKey)[index];
968
+ },
969
+ validateItem: async (arrayKey, itemId) => {
970
+ if (!validationSchema) return false;
971
+ const index = getIndex(arrayKey, itemId);
972
+ if (index === void 0) return false;
973
+ const yupPath = `${String(arrayKey)}[${index}]`;
974
+ const prefix = `${String(arrayKey)}.${itemId}.`;
975
+ const cleanMetadata = new Map(metadataRef.current);
976
+ for (const key of cleanMetadata.keys()) {
977
+ if (key.startsWith(prefix) || key === `${String(arrayKey)}.${itemId}`) {
978
+ const meta = cleanMetadata.get(key);
979
+ cleanMetadata.set(key, {
980
+ ...meta,
981
+ isInvalid: false,
982
+ errorMessage: ""
983
+ });
984
+ }
985
+ }
986
+ const handleErrors = (err) => {
987
+ const newMetadata = new Map(cleanMetadata);
988
+ if (err.inner) {
989
+ err.inner.forEach((validationError) => {
990
+ const localDotPath = validationError.path.replace(
991
+ /\[(\d+)\]/g,
992
+ ".$1"
993
+ );
994
+ const parts = localDotPath.split(".");
995
+ let current = stateRef.current;
996
+ const compositeParts = [];
997
+ for (let i = 0; i < parts.length; i++) {
998
+ const part = parts[i];
999
+ if (Array.isArray(current)) {
1000
+ const idx = parseInt(part, 10);
1001
+ const item = current[idx];
1002
+ if (item && typeof item === "object") {
1003
+ const genericPath = compositeParts.join(".").replace(/\.\d+/g, "");
1004
+ const idKey = arrayIdentifiers?.[genericPath] || "id";
1005
+ const id = item[idKey];
1006
+ compositeParts.push(String(id !== void 0 ? id : idx));
1007
+ } else {
1008
+ compositeParts.push(part);
1009
+ }
1010
+ current = item;
1011
+ } else {
1012
+ compositeParts.push(part);
1013
+ current = current?.[part];
1014
+ }
1015
+ }
1016
+ const compositeKey = compositeParts.join(".");
1017
+ const currentMeta = newMetadata.get(compositeKey) || {
1018
+ isTouched: false,
1019
+ isInvalid: false,
1020
+ errorMessage: ""
1021
+ };
1022
+ newMetadata.set(compositeKey, {
1023
+ ...currentMeta,
1024
+ isTouched: true,
1025
+ isInvalid: true,
1026
+ errorMessage: validationError.message
1027
+ });
1028
+ });
1029
+ }
1030
+ setMetadata(newMetadata);
1031
+ return true;
1032
+ };
1033
+ try {
1034
+ await validationSchema.validateAt(yupPath, stateRef.current, {
1035
+ abortEarly: false
1036
+ });
1037
+ setMetadata(cleanMetadata);
1038
+ return false;
1039
+ } catch (err) {
1040
+ if (err.name === "ValidationError") {
1041
+ return handleErrors(err);
1042
+ }
1043
+ return false;
1044
+ }
952
1045
  }
953
1046
  }),
954
- [getIndex, arrayIdentifiers]
1047
+ [getIndex, arrayIdentifiers, state, metadata]
955
1048
  );
956
1049
  const onBlur = (0, import_react2.useCallback)(
957
1050
  (id) => {
@@ -1035,9 +1128,9 @@ function useForm(schema, {
1035
1128
  [getIndex, handleFieldChange, getRule, validationSchema]
1036
1129
  );
1037
1130
  const onFormSubmit = (0, import_react2.useCallback)(
1038
- (fn) => (e) => {
1131
+ (fn) => async (e) => {
1039
1132
  e.preventDefault();
1040
- if (validateAll()) return;
1133
+ if (await validateAll()) return;
1041
1134
  fn(stateRef.current, e);
1042
1135
  },
1043
1136
  [validateAll]
@@ -1070,9 +1163,9 @@ function useForm(schema, {
1070
1163
  const ControlledForm = (0, import_react2.useMemo)(() => {
1071
1164
  return (props) => {
1072
1165
  const { onSubmit, ...rest } = props;
1073
- const handleSubmit = (e) => {
1166
+ const handleSubmit = async (e) => {
1074
1167
  e.preventDefault();
1075
- if (validateAllRef.current()) {
1168
+ if (await validateAllRef.current()) {
1076
1169
  return;
1077
1170
  }
1078
1171
  onFormSubmitPropRef.current?.(stateRef.current, e);