@juantroconisf/lib 11.7.0 → 11.9.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.d.mts CHANGED
@@ -238,6 +238,11 @@ interface HelpersFunc<O extends StateType> {
238
238
  moveById: <K extends ArrayPaths<O>>(arrayKey: K, fromId: string | number, toId: string | number) => void;
239
239
  /** Gets an item from an array by its unique identifier (O(1) via indexMap). */
240
240
  getItemById: <K extends ArrayPaths<O>>(arrayKey: K, itemId: string | number) => (NestedFieldValue<O, K> extends (infer E)[] ? E : never) | undefined;
241
+ /** Validates a single item in an array by its unique identifier. */
242
+ validateItem: <K extends ArrayPaths<O>>(arrayKey: K, itemId: string | number) => Promise<{
243
+ isValid: boolean;
244
+ errors: string[];
245
+ }>;
241
246
  }
242
247
  /**
243
248
  * The response object from the useForm hook.
@@ -285,7 +290,7 @@ interface UseFormResponse<O extends StateType> {
285
290
  * If validation fails, identifying errors and touching fields.
286
291
  * If validation succeeds, calls the provided handler with the current state.
287
292
  */
288
- onFormSubmit: (fn: FormSubmitHandler<O>) => (e: React.FormEvent) => void;
293
+ onFormSubmit: (fn: FormSubmitHandler<O>) => (e: React.FormEvent) => Promise<void>;
289
294
  /**
290
295
  * A controlled form component that handles submission and validation.
291
296
  */
package/dist/index.d.ts CHANGED
@@ -238,6 +238,11 @@ interface HelpersFunc<O extends StateType> {
238
238
  moveById: <K extends ArrayPaths<O>>(arrayKey: K, fromId: string | number, toId: string | number) => void;
239
239
  /** Gets an item from an array by its unique identifier (O(1) via indexMap). */
240
240
  getItemById: <K extends ArrayPaths<O>>(arrayKey: K, itemId: string | number) => (NestedFieldValue<O, K> extends (infer E)[] ? E : never) | undefined;
241
+ /** Validates a single item in an array by its unique identifier. */
242
+ validateItem: <K extends ArrayPaths<O>>(arrayKey: K, itemId: string | number) => Promise<{
243
+ isValid: boolean;
244
+ errors: string[];
245
+ }>;
241
246
  }
242
247
  /**
243
248
  * The response object from the useForm hook.
@@ -285,7 +290,7 @@ interface UseFormResponse<O extends StateType> {
285
290
  * If validation fails, identifying errors and touching fields.
286
291
  * If validation succeeds, calls the provided handler with the current state.
287
292
  */
288
- onFormSubmit: (fn: FormSubmitHandler<O>) => (e: React.FormEvent) => void;
293
+ onFormSubmit: (fn: FormSubmitHandler<O>) => (e: React.FormEvent) => Promise<void>;
289
294
  /**
290
295
  * A controlled form component that handles submission and validation.
291
296
  */
package/dist/index.js CHANGED
@@ -428,7 +428,17 @@ function useForm(schema, {
428
428
  const indexMapRef = (0, import_react2.useRef)(indexMap);
429
429
  indexMapRef.current = indexMap;
430
430
  const getIndex = (0, import_react2.useCallback)((arrayKey, itemId) => {
431
- 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;
432
442
  }, []);
433
443
  const ruleCache = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
434
444
  (0, import_react2.useMemo)(() => {
@@ -543,9 +553,21 @@ function useForm(schema, {
543
553
  [getRule, runValidation, validationSchema]
544
554
  );
545
555
  const validateAll = (0, import_react2.useCallback)(() => {
546
- if (!validationSchema) return false;
547
- const newMetadata = new Map(metadataRef.current);
556
+ if (!validationSchema)
557
+ return Promise.resolve({ isValid: true, errors: [] });
558
+ const cleanMetadata = new Map(metadataRef.current);
559
+ cleanMetadata.forEach((value, key) => {
560
+ if (value.isInvalid) {
561
+ cleanMetadata.set(key, {
562
+ ...value,
563
+ isInvalid: false,
564
+ errorMessage: ""
565
+ });
566
+ }
567
+ });
548
568
  const handleErrors = (err) => {
569
+ const newMetadata = new Map(cleanMetadata);
570
+ const errors = [];
549
571
  if (err.inner) {
550
572
  err.inner.forEach((validationError) => {
551
573
  const yupPath = validationError.path;
@@ -573,6 +595,7 @@ function useForm(schema, {
573
595
  }
574
596
  }
575
597
  const compositeKey = compositeParts.join(".");
598
+ errors.push(compositeKey);
576
599
  const currentMeta = newMetadata.get(compositeKey) || {
577
600
  isTouched: false,
578
601
  isInvalid: false,
@@ -587,25 +610,26 @@ function useForm(schema, {
587
610
  });
588
611
  }
589
612
  setMetadata(newMetadata);
590
- return true;
613
+ return { isValid: false, errors };
591
614
  };
592
615
  try {
593
616
  validationSchema.validateSync(state, { abortEarly: false });
594
617
  } catch (err) {
595
618
  if (err.name === "ValidationError") {
596
- return handleErrors(err);
619
+ return Promise.resolve(handleErrors(err));
597
620
  }
598
621
  return (async () => {
599
622
  try {
600
623
  await validationSchema.validate(state, { abortEarly: false });
601
- return false;
624
+ setMetadata(cleanMetadata);
625
+ return { isValid: true, errors: [] };
602
626
  } catch (asyncErr) {
603
627
  return handleErrors(asyncErr);
604
628
  }
605
629
  })();
606
630
  }
607
- setMetadata(newMetadata);
608
- return false;
631
+ setMetadata(cleanMetadata);
632
+ return Promise.resolve({ isValid: true, errors: [] });
609
633
  }, [validationSchema, state, arrayIdentifiers]);
610
634
  const handleFieldChange = (0, import_react2.useCallback)(
611
635
  (resolution, newValue) => {
@@ -750,91 +774,88 @@ function useForm(schema, {
750
774
  },
751
775
  [validateField, getRule, validationSchema]
752
776
  );
753
- const on = (0, import_react2.useMemo)(
754
- () => {
755
- const getCachedProps = (method, args, factory) => {
756
- const data = resolveFieldData(
757
- args,
758
- stateRef.current,
759
- getIndex,
760
- getNestedValue,
761
- getRule,
762
- validationSchema
763
- );
764
- if (!data) return {};
765
- const meta = metadataRef.current.get(data.compositeKey);
766
- const deps = JSON.stringify([
767
- data.value,
768
- meta?.isInvalid,
769
- meta?.errorMessage,
770
- meta?.isTouched
771
- ]);
772
- const cacheKey = `${method}:${JSON.stringify(args)}`;
773
- const cached = propsCache.current.get(cacheKey);
774
- if (cached && cached.deps === deps) {
775
- return cached.props;
776
- }
777
- const props = factory(data);
778
- propsCache.current.set(cacheKey, { props, deps });
779
- return props;
780
- };
781
- return {
782
- input: (...args) => getCachedProps("input", args, (data) => ({
783
- ...createHandlers(data),
784
- value: data.value === null || data.value === void 0 ? "" : typeof data.value === "boolean" ? data.value : String(data.value),
785
- onValueChange: (v) => handleFieldChange(data, v)
786
- })),
787
- select: (...args) => getCachedProps("select", args, (data) => {
788
- const isArray = Array.isArray(data.value);
789
- return {
790
- ...createHandlers(data),
791
- selectedKeys: data.value === null || data.value === void 0 ? [] : isArray ? data.value.map(String) : [String(data.value)],
792
- onSelectionChange: (v) => {
793
- const fixed = typeof v === "string" || v === null ? v : isArray ? Array.from(v) : Array.from(v)[0] ?? null;
794
- handleFieldChange(data, fixed);
795
- }
796
- };
797
- }),
798
- autocomplete: (...args) => getCachedProps("autocomplete", args, (data) => ({
777
+ const on = (0, import_react2.useMemo)(() => {
778
+ const getCachedProps = (method, args, factory) => {
779
+ const data = resolveFieldData(
780
+ args,
781
+ stateRef.current,
782
+ getIndex,
783
+ getNestedValue,
784
+ getRule,
785
+ validationSchema
786
+ );
787
+ if (!data) return {};
788
+ const meta = metadataRef.current.get(data.compositeKey);
789
+ const deps = JSON.stringify([
790
+ data.value,
791
+ meta?.isInvalid,
792
+ meta?.errorMessage,
793
+ meta?.isTouched
794
+ ]);
795
+ const cacheKey = `${method}:${JSON.stringify(args)}`;
796
+ const cached = propsCache.current.get(cacheKey);
797
+ if (cached && cached.deps === deps) {
798
+ return cached.props;
799
+ }
800
+ const props = factory(data);
801
+ propsCache.current.set(cacheKey, { props, deps });
802
+ return props;
803
+ };
804
+ return {
805
+ input: (...args) => getCachedProps("input", args, (data) => ({
806
+ ...createHandlers(data),
807
+ value: data.value === null || data.value === void 0 ? "" : typeof data.value === "boolean" ? data.value : String(data.value),
808
+ onValueChange: (v) => handleFieldChange(data, v)
809
+ })),
810
+ select: (...args) => getCachedProps("select", args, (data) => {
811
+ const isArray = Array.isArray(data.value);
812
+ return {
799
813
  ...createHandlers(data),
800
- selectedKey: data.value === null || data.value === void 0 ? null : String(data.value),
814
+ selectedKeys: data.value === null || data.value === void 0 ? [] : isArray ? data.value.map(String) : [String(data.value)],
801
815
  onSelectionChange: (v) => {
802
- const fixed = typeof v === "string" || v === null || v === void 0 ? v : String(v);
816
+ const fixed = typeof v === "string" || v === null ? v : isArray ? Array.from(v) : Array.from(v)[0] ?? null;
803
817
  handleFieldChange(data, fixed);
804
818
  }
805
- })),
806
- numberInput: (...args) => getCachedProps("numberInput", args, (data) => ({
807
- ...createHandlers(data),
808
- value: data.value === null || data.value === void 0 ? "" : String(data.value),
809
- onValueChange: (v) => handleFieldChange(data, v)
810
- })),
811
- checkbox: (...args) => getCachedProps("checkbox", args, (data) => ({
812
- ...createHandlers(data),
813
- isSelected: Boolean(data.value),
814
- onValueChange: (v) => handleFieldChange(data, v)
815
- })),
816
- switch: (...args) => getCachedProps("switch", args, (data) => ({
817
- ...createHandlers(data),
818
- isSelected: Boolean(data.value),
819
- onValueChange: (v) => handleFieldChange(data, v)
820
- })),
821
- radio: (...args) => getCachedProps("radio", args, (data) => ({
822
- ...createHandlers(data),
823
- value: data.value === null || data.value === void 0 ? "" : String(data.value),
824
- onValueChange: (v) => handleFieldChange(data, v)
825
- }))
826
- };
827
- },
828
- [
829
- createHandlers,
830
- getIndex,
831
- handleFieldChange,
832
- getRule,
833
- validationSchema,
834
- state,
835
- metadata
836
- ]
837
- );
819
+ };
820
+ }),
821
+ autocomplete: (...args) => getCachedProps("autocomplete", args, (data) => ({
822
+ ...createHandlers(data),
823
+ selectedKey: data.value === null || data.value === void 0 ? null : String(data.value),
824
+ onSelectionChange: (v) => {
825
+ const fixed = typeof v === "string" || v === null || v === void 0 ? v : String(v);
826
+ handleFieldChange(data, fixed);
827
+ }
828
+ })),
829
+ numberInput: (...args) => getCachedProps("numberInput", args, (data) => ({
830
+ ...createHandlers(data),
831
+ value: data.value === null || data.value === void 0 ? "" : String(data.value),
832
+ onValueChange: (v) => handleFieldChange(data, v)
833
+ })),
834
+ checkbox: (...args) => getCachedProps("checkbox", args, (data) => ({
835
+ ...createHandlers(data),
836
+ isSelected: Boolean(data.value),
837
+ onValueChange: (v) => handleFieldChange(data, v)
838
+ })),
839
+ switch: (...args) => getCachedProps("switch", args, (data) => ({
840
+ ...createHandlers(data),
841
+ isSelected: Boolean(data.value),
842
+ onValueChange: (v) => handleFieldChange(data, v)
843
+ })),
844
+ radio: (...args) => getCachedProps("radio", args, (data) => ({
845
+ ...createHandlers(data),
846
+ value: data.value === null || data.value === void 0 ? "" : String(data.value),
847
+ onValueChange: (v) => handleFieldChange(data, v)
848
+ }))
849
+ };
850
+ }, [
851
+ createHandlers,
852
+ getIndex,
853
+ handleFieldChange,
854
+ getRule,
855
+ validationSchema,
856
+ state,
857
+ metadata
858
+ ]);
838
859
  const helpers = (0, import_react2.useMemo)(
839
860
  () => ({
840
861
  addItem: (arrayKey, item, index) => {
@@ -948,6 +969,85 @@ function useForm(schema, {
948
969
  const index = getIndex(arrayKey, itemId);
949
970
  if (index === void 0) return void 0;
950
971
  return getNestedValue(stateRef.current, arrayKey)[index];
972
+ },
973
+ validateItem: async (arrayKey, itemId) => {
974
+ if (!validationSchema) return { isValid: true, errors: [] };
975
+ const index = getIndex(arrayKey, itemId);
976
+ if (index === void 0) return { isValid: true, errors: [] };
977
+ const yupPath = `${String(arrayKey)}[${index}]`;
978
+ const prefix = `${String(arrayKey)}.${itemId}.`;
979
+ const cleanMetadata = new Map(metadataRef.current);
980
+ for (const key of cleanMetadata.keys()) {
981
+ if (key.startsWith(prefix) || key === `${String(arrayKey)}.${itemId}`) {
982
+ const meta = cleanMetadata.get(key);
983
+ cleanMetadata.set(key, {
984
+ ...meta,
985
+ isInvalid: false,
986
+ errorMessage: ""
987
+ });
988
+ }
989
+ }
990
+ const handleErrors = (err) => {
991
+ const newMetadata = new Map(cleanMetadata);
992
+ const errors = [];
993
+ if (err.inner) {
994
+ err.inner.forEach((validationError) => {
995
+ const localDotPath = validationError.path.replace(
996
+ /\[(\d+)\]/g,
997
+ ".$1"
998
+ );
999
+ const parts = localDotPath.split(".");
1000
+ let current = stateRef.current;
1001
+ const compositeParts = [];
1002
+ for (let i = 0; i < parts.length; i++) {
1003
+ const part = parts[i];
1004
+ if (Array.isArray(current)) {
1005
+ const idx = parseInt(part, 10);
1006
+ const item = current[idx];
1007
+ if (item && typeof item === "object") {
1008
+ const genericPath = compositeParts.join(".").replace(/\.\d+/g, "");
1009
+ const idKey = arrayIdentifiers?.[genericPath] || "id";
1010
+ const id = item[idKey];
1011
+ compositeParts.push(String(id !== void 0 ? id : idx));
1012
+ } else {
1013
+ compositeParts.push(part);
1014
+ }
1015
+ current = item;
1016
+ } else {
1017
+ compositeParts.push(part);
1018
+ current = current?.[part];
1019
+ }
1020
+ }
1021
+ const compositeKey = compositeParts.join(".");
1022
+ errors.push(compositeKey);
1023
+ const currentMeta = newMetadata.get(compositeKey) || {
1024
+ isTouched: false,
1025
+ isInvalid: false,
1026
+ errorMessage: ""
1027
+ };
1028
+ newMetadata.set(compositeKey, {
1029
+ ...currentMeta,
1030
+ isTouched: true,
1031
+ isInvalid: true,
1032
+ errorMessage: validationError.message
1033
+ });
1034
+ });
1035
+ }
1036
+ setMetadata(newMetadata);
1037
+ return { isValid: false, errors };
1038
+ };
1039
+ try {
1040
+ await validationSchema.validateAt(yupPath, stateRef.current, {
1041
+ abortEarly: false
1042
+ });
1043
+ setMetadata(cleanMetadata);
1044
+ return { isValid: true, errors: [] };
1045
+ } catch (err) {
1046
+ if (err.name === "ValidationError") {
1047
+ return handleErrors(err);
1048
+ }
1049
+ return { isValid: true, errors: [] };
1050
+ }
951
1051
  }
952
1052
  }),
953
1053
  [getIndex, arrayIdentifiers, state, metadata]
@@ -1036,7 +1136,8 @@ function useForm(schema, {
1036
1136
  const onFormSubmit = (0, import_react2.useCallback)(
1037
1137
  (fn) => async (e) => {
1038
1138
  e.preventDefault();
1039
- if (await validateAll()) return;
1139
+ const { isValid } = await validateAll();
1140
+ if (!isValid) return;
1040
1141
  fn(stateRef.current, e);
1041
1142
  },
1042
1143
  [validateAll]
@@ -1071,9 +1172,8 @@ function useForm(schema, {
1071
1172
  const { onSubmit, ...rest } = props;
1072
1173
  const handleSubmit = async (e) => {
1073
1174
  e.preventDefault();
1074
- if (await validateAllRef.current()) {
1075
- return;
1076
- }
1175
+ const { isValid } = await validateAllRef.current();
1176
+ if (!isValid) return;
1077
1177
  onFormSubmitPropRef.current?.(stateRef.current, e);
1078
1178
  if (resetOnSubmitRef.current) {
1079
1179
  handleReset({ keepValues: keepValuesPropRef.current });
package/dist/index.mjs CHANGED
@@ -402,7 +402,17 @@ function useForm(schema, {
402
402
  const indexMapRef = useRef(indexMap);
403
403
  indexMapRef.current = indexMap;
404
404
  const getIndex = useCallback((arrayKey, itemId) => {
405
- return indexMapRef.current.get(arrayKey)?.get(itemId);
405
+ const map = indexMapRef.current.get(arrayKey);
406
+ if (!map) return void 0;
407
+ const index = map.get(itemId);
408
+ if (index !== void 0) return index;
409
+ if (typeof itemId === "string") {
410
+ const num = Number(itemId);
411
+ if (!isNaN(num)) return map.get(num);
412
+ } else if (typeof itemId === "number") {
413
+ return map.get(String(itemId));
414
+ }
415
+ return void 0;
406
416
  }, []);
407
417
  const ruleCache = useRef(/* @__PURE__ */ new Map());
408
418
  useMemo(() => {
@@ -517,9 +527,21 @@ function useForm(schema, {
517
527
  [getRule, runValidation, validationSchema]
518
528
  );
519
529
  const validateAll = useCallback(() => {
520
- if (!validationSchema) return false;
521
- const newMetadata = new Map(metadataRef.current);
530
+ if (!validationSchema)
531
+ return Promise.resolve({ isValid: true, errors: [] });
532
+ const cleanMetadata = new Map(metadataRef.current);
533
+ cleanMetadata.forEach((value, key) => {
534
+ if (value.isInvalid) {
535
+ cleanMetadata.set(key, {
536
+ ...value,
537
+ isInvalid: false,
538
+ errorMessage: ""
539
+ });
540
+ }
541
+ });
522
542
  const handleErrors = (err) => {
543
+ const newMetadata = new Map(cleanMetadata);
544
+ const errors = [];
523
545
  if (err.inner) {
524
546
  err.inner.forEach((validationError) => {
525
547
  const yupPath = validationError.path;
@@ -547,6 +569,7 @@ function useForm(schema, {
547
569
  }
548
570
  }
549
571
  const compositeKey = compositeParts.join(".");
572
+ errors.push(compositeKey);
550
573
  const currentMeta = newMetadata.get(compositeKey) || {
551
574
  isTouched: false,
552
575
  isInvalid: false,
@@ -561,25 +584,26 @@ function useForm(schema, {
561
584
  });
562
585
  }
563
586
  setMetadata(newMetadata);
564
- return true;
587
+ return { isValid: false, errors };
565
588
  };
566
589
  try {
567
590
  validationSchema.validateSync(state, { abortEarly: false });
568
591
  } catch (err) {
569
592
  if (err.name === "ValidationError") {
570
- return handleErrors(err);
593
+ return Promise.resolve(handleErrors(err));
571
594
  }
572
595
  return (async () => {
573
596
  try {
574
597
  await validationSchema.validate(state, { abortEarly: false });
575
- return false;
598
+ setMetadata(cleanMetadata);
599
+ return { isValid: true, errors: [] };
576
600
  } catch (asyncErr) {
577
601
  return handleErrors(asyncErr);
578
602
  }
579
603
  })();
580
604
  }
581
- setMetadata(newMetadata);
582
- return false;
605
+ setMetadata(cleanMetadata);
606
+ return Promise.resolve({ isValid: true, errors: [] });
583
607
  }, [validationSchema, state, arrayIdentifiers]);
584
608
  const handleFieldChange = useCallback(
585
609
  (resolution, newValue) => {
@@ -724,91 +748,88 @@ function useForm(schema, {
724
748
  },
725
749
  [validateField, getRule, validationSchema]
726
750
  );
727
- const on = useMemo(
728
- () => {
729
- const getCachedProps = (method, args, factory) => {
730
- const data = resolveFieldData(
731
- args,
732
- stateRef.current,
733
- getIndex,
734
- getNestedValue,
735
- getRule,
736
- validationSchema
737
- );
738
- if (!data) return {};
739
- const meta = metadataRef.current.get(data.compositeKey);
740
- const deps = JSON.stringify([
741
- data.value,
742
- meta?.isInvalid,
743
- meta?.errorMessage,
744
- meta?.isTouched
745
- ]);
746
- const cacheKey = `${method}:${JSON.stringify(args)}`;
747
- const cached = propsCache.current.get(cacheKey);
748
- if (cached && cached.deps === deps) {
749
- return cached.props;
750
- }
751
- const props = factory(data);
752
- propsCache.current.set(cacheKey, { props, deps });
753
- return props;
754
- };
755
- return {
756
- input: (...args) => getCachedProps("input", args, (data) => ({
757
- ...createHandlers(data),
758
- value: data.value === null || data.value === void 0 ? "" : typeof data.value === "boolean" ? data.value : String(data.value),
759
- onValueChange: (v) => handleFieldChange(data, v)
760
- })),
761
- select: (...args) => getCachedProps("select", args, (data) => {
762
- const isArray = Array.isArray(data.value);
763
- return {
764
- ...createHandlers(data),
765
- selectedKeys: data.value === null || data.value === void 0 ? [] : isArray ? data.value.map(String) : [String(data.value)],
766
- onSelectionChange: (v) => {
767
- const fixed = typeof v === "string" || v === null ? v : isArray ? Array.from(v) : Array.from(v)[0] ?? null;
768
- handleFieldChange(data, fixed);
769
- }
770
- };
771
- }),
772
- autocomplete: (...args) => getCachedProps("autocomplete", args, (data) => ({
751
+ const on = useMemo(() => {
752
+ const getCachedProps = (method, args, factory) => {
753
+ const data = resolveFieldData(
754
+ args,
755
+ stateRef.current,
756
+ getIndex,
757
+ getNestedValue,
758
+ getRule,
759
+ validationSchema
760
+ );
761
+ if (!data) return {};
762
+ const meta = metadataRef.current.get(data.compositeKey);
763
+ const deps = JSON.stringify([
764
+ data.value,
765
+ meta?.isInvalid,
766
+ meta?.errorMessage,
767
+ meta?.isTouched
768
+ ]);
769
+ const cacheKey = `${method}:${JSON.stringify(args)}`;
770
+ const cached = propsCache.current.get(cacheKey);
771
+ if (cached && cached.deps === deps) {
772
+ return cached.props;
773
+ }
774
+ const props = factory(data);
775
+ propsCache.current.set(cacheKey, { props, deps });
776
+ return props;
777
+ };
778
+ return {
779
+ input: (...args) => getCachedProps("input", args, (data) => ({
780
+ ...createHandlers(data),
781
+ value: data.value === null || data.value === void 0 ? "" : typeof data.value === "boolean" ? data.value : String(data.value),
782
+ onValueChange: (v) => handleFieldChange(data, v)
783
+ })),
784
+ select: (...args) => getCachedProps("select", args, (data) => {
785
+ const isArray = Array.isArray(data.value);
786
+ return {
773
787
  ...createHandlers(data),
774
- selectedKey: data.value === null || data.value === void 0 ? null : String(data.value),
788
+ selectedKeys: data.value === null || data.value === void 0 ? [] : isArray ? data.value.map(String) : [String(data.value)],
775
789
  onSelectionChange: (v) => {
776
- const fixed = typeof v === "string" || v === null || v === void 0 ? v : String(v);
790
+ const fixed = typeof v === "string" || v === null ? v : isArray ? Array.from(v) : Array.from(v)[0] ?? null;
777
791
  handleFieldChange(data, fixed);
778
792
  }
779
- })),
780
- numberInput: (...args) => getCachedProps("numberInput", args, (data) => ({
781
- ...createHandlers(data),
782
- value: data.value === null || data.value === void 0 ? "" : String(data.value),
783
- onValueChange: (v) => handleFieldChange(data, v)
784
- })),
785
- checkbox: (...args) => getCachedProps("checkbox", args, (data) => ({
786
- ...createHandlers(data),
787
- isSelected: Boolean(data.value),
788
- onValueChange: (v) => handleFieldChange(data, v)
789
- })),
790
- switch: (...args) => getCachedProps("switch", args, (data) => ({
791
- ...createHandlers(data),
792
- isSelected: Boolean(data.value),
793
- onValueChange: (v) => handleFieldChange(data, v)
794
- })),
795
- radio: (...args) => getCachedProps("radio", args, (data) => ({
796
- ...createHandlers(data),
797
- value: data.value === null || data.value === void 0 ? "" : String(data.value),
798
- onValueChange: (v) => handleFieldChange(data, v)
799
- }))
800
- };
801
- },
802
- [
803
- createHandlers,
804
- getIndex,
805
- handleFieldChange,
806
- getRule,
807
- validationSchema,
808
- state,
809
- metadata
810
- ]
811
- );
793
+ };
794
+ }),
795
+ autocomplete: (...args) => getCachedProps("autocomplete", args, (data) => ({
796
+ ...createHandlers(data),
797
+ selectedKey: data.value === null || data.value === void 0 ? null : String(data.value),
798
+ onSelectionChange: (v) => {
799
+ const fixed = typeof v === "string" || v === null || v === void 0 ? v : String(v);
800
+ handleFieldChange(data, fixed);
801
+ }
802
+ })),
803
+ numberInput: (...args) => getCachedProps("numberInput", args, (data) => ({
804
+ ...createHandlers(data),
805
+ value: data.value === null || data.value === void 0 ? "" : String(data.value),
806
+ onValueChange: (v) => handleFieldChange(data, v)
807
+ })),
808
+ checkbox: (...args) => getCachedProps("checkbox", args, (data) => ({
809
+ ...createHandlers(data),
810
+ isSelected: Boolean(data.value),
811
+ onValueChange: (v) => handleFieldChange(data, v)
812
+ })),
813
+ switch: (...args) => getCachedProps("switch", args, (data) => ({
814
+ ...createHandlers(data),
815
+ isSelected: Boolean(data.value),
816
+ onValueChange: (v) => handleFieldChange(data, v)
817
+ })),
818
+ radio: (...args) => getCachedProps("radio", args, (data) => ({
819
+ ...createHandlers(data),
820
+ value: data.value === null || data.value === void 0 ? "" : String(data.value),
821
+ onValueChange: (v) => handleFieldChange(data, v)
822
+ }))
823
+ };
824
+ }, [
825
+ createHandlers,
826
+ getIndex,
827
+ handleFieldChange,
828
+ getRule,
829
+ validationSchema,
830
+ state,
831
+ metadata
832
+ ]);
812
833
  const helpers = useMemo(
813
834
  () => ({
814
835
  addItem: (arrayKey, item, index) => {
@@ -922,6 +943,85 @@ function useForm(schema, {
922
943
  const index = getIndex(arrayKey, itemId);
923
944
  if (index === void 0) return void 0;
924
945
  return getNestedValue(stateRef.current, arrayKey)[index];
946
+ },
947
+ validateItem: async (arrayKey, itemId) => {
948
+ if (!validationSchema) return { isValid: true, errors: [] };
949
+ const index = getIndex(arrayKey, itemId);
950
+ if (index === void 0) return { isValid: true, errors: [] };
951
+ const yupPath = `${String(arrayKey)}[${index}]`;
952
+ const prefix = `${String(arrayKey)}.${itemId}.`;
953
+ const cleanMetadata = new Map(metadataRef.current);
954
+ for (const key of cleanMetadata.keys()) {
955
+ if (key.startsWith(prefix) || key === `${String(arrayKey)}.${itemId}`) {
956
+ const meta = cleanMetadata.get(key);
957
+ cleanMetadata.set(key, {
958
+ ...meta,
959
+ isInvalid: false,
960
+ errorMessage: ""
961
+ });
962
+ }
963
+ }
964
+ const handleErrors = (err) => {
965
+ const newMetadata = new Map(cleanMetadata);
966
+ const errors = [];
967
+ if (err.inner) {
968
+ err.inner.forEach((validationError) => {
969
+ const localDotPath = validationError.path.replace(
970
+ /\[(\d+)\]/g,
971
+ ".$1"
972
+ );
973
+ const parts = localDotPath.split(".");
974
+ let current = stateRef.current;
975
+ const compositeParts = [];
976
+ for (let i = 0; i < parts.length; i++) {
977
+ const part = parts[i];
978
+ if (Array.isArray(current)) {
979
+ const idx = parseInt(part, 10);
980
+ const item = current[idx];
981
+ if (item && typeof item === "object") {
982
+ const genericPath = compositeParts.join(".").replace(/\.\d+/g, "");
983
+ const idKey = arrayIdentifiers?.[genericPath] || "id";
984
+ const id = item[idKey];
985
+ compositeParts.push(String(id !== void 0 ? id : idx));
986
+ } else {
987
+ compositeParts.push(part);
988
+ }
989
+ current = item;
990
+ } else {
991
+ compositeParts.push(part);
992
+ current = current?.[part];
993
+ }
994
+ }
995
+ const compositeKey = compositeParts.join(".");
996
+ errors.push(compositeKey);
997
+ const currentMeta = newMetadata.get(compositeKey) || {
998
+ isTouched: false,
999
+ isInvalid: false,
1000
+ errorMessage: ""
1001
+ };
1002
+ newMetadata.set(compositeKey, {
1003
+ ...currentMeta,
1004
+ isTouched: true,
1005
+ isInvalid: true,
1006
+ errorMessage: validationError.message
1007
+ });
1008
+ });
1009
+ }
1010
+ setMetadata(newMetadata);
1011
+ return { isValid: false, errors };
1012
+ };
1013
+ try {
1014
+ await validationSchema.validateAt(yupPath, stateRef.current, {
1015
+ abortEarly: false
1016
+ });
1017
+ setMetadata(cleanMetadata);
1018
+ return { isValid: true, errors: [] };
1019
+ } catch (err) {
1020
+ if (err.name === "ValidationError") {
1021
+ return handleErrors(err);
1022
+ }
1023
+ return { isValid: true, errors: [] };
1024
+ }
925
1025
  }
926
1026
  }),
927
1027
  [getIndex, arrayIdentifiers, state, metadata]
@@ -1010,7 +1110,8 @@ function useForm(schema, {
1010
1110
  const onFormSubmit = useCallback(
1011
1111
  (fn) => async (e) => {
1012
1112
  e.preventDefault();
1013
- if (await validateAll()) return;
1113
+ const { isValid } = await validateAll();
1114
+ if (!isValid) return;
1014
1115
  fn(stateRef.current, e);
1015
1116
  },
1016
1117
  [validateAll]
@@ -1045,9 +1146,8 @@ function useForm(schema, {
1045
1146
  const { onSubmit, ...rest } = props;
1046
1147
  const handleSubmit = async (e) => {
1047
1148
  e.preventDefault();
1048
- if (await validateAllRef.current()) {
1049
- return;
1050
- }
1149
+ const { isValid } = await validateAllRef.current();
1150
+ if (!isValid) return;
1051
1151
  onFormSubmitPropRef.current?.(stateRef.current, e);
1052
1152
  if (resetOnSubmitRef.current) {
1053
1153
  handleReset({ keepValues: keepValuesPropRef.current });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juantroconisf/lib",
3
- "version": "11.7.0",
3
+ "version": "11.9.0",
4
4
  "description": "A form validation library for HeroUI.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",