@hy_ong/zod-kit 0.2.4 → 0.2.6

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.
@@ -0,0 +1,77 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+ var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
5
+
6
+ // src/validators/common/one-of.ts
7
+ var _zod = require('zod');
8
+ function oneOf(required, options) {
9
+ const { values = [], defaultValue = null, caseSensitive = true, transform, i18n } = _nullishCoalesce(options, () => ( {}));
10
+ const isRequired = _nullishCoalesce(required, () => ( false));
11
+ const valuesArr = values;
12
+ const isAllStrings = valuesArr.every((v) => typeof v === "string");
13
+ const getMessage = (key, params) => {
14
+ if (i18n) {
15
+ const currentLocale = _chunkQ7TUNJD4cjs.getLocale.call(void 0, );
16
+ const customMessages = i18n[currentLocale];
17
+ if (customMessages && customMessages[key]) {
18
+ const template = customMessages[key];
19
+ return template.replace(/\$\{(\w+)}/g, (_, k) => _nullishCoalesce(_optionalChain([params, 'optionalAccess', _2 => _2[k]]), () => ( "")));
20
+ }
21
+ }
22
+ return _chunkQ7TUNJD4cjs.t.call(void 0, `common.oneOf.${key}`, params);
23
+ };
24
+ const isEmpty = (v) => v === "" || v === null || v === void 0;
25
+ let valueSchema;
26
+ if (isAllStrings && valuesArr.length > 0) {
27
+ valueSchema = _zod.z.enum(valuesArr, {
28
+ error: (issue) => {
29
+ if (isEmpty(issue.input)) return getMessage("required");
30
+ return getMessage("invalid", { values: valuesArr.join(", ") });
31
+ }
32
+ });
33
+ } else if (valuesArr.length >= 2) {
34
+ const literals = valuesArr.map((v) => _zod.z.literal(v));
35
+ valueSchema = _zod.z.union(literals, {
36
+ error: () => getMessage("invalid", { values: valuesArr.join(", ") })
37
+ });
38
+ } else if (valuesArr.length === 1) {
39
+ valueSchema = _zod.z.literal(valuesArr[0]);
40
+ } else {
41
+ valueSchema = _zod.z.never();
42
+ }
43
+ if (!caseSensitive) {
44
+ valueSchema = _zod.z.string().transform((v) => {
45
+ const match = valuesArr.find((val) => typeof val === "string" && val.toLowerCase() === v.toLowerCase());
46
+ return _nullishCoalesce(match, () => ( v));
47
+ }).pipe(valueSchema);
48
+ }
49
+ if (transform) {
50
+ valueSchema = valueSchema.transform((v) => transform(v));
51
+ }
52
+ const fallback = defaultValue;
53
+ const emptySchema = _zod.z.union([_zod.z.literal(""), _zod.z.null(), _zod.z.undefined()]).transform(() => fallback);
54
+ if (isRequired && fallback === null) {
55
+ const emptyRejectSchema = _zod.z.union([_zod.z.literal(""), _zod.z.null(), _zod.z.undefined()]).refine(() => false, { message: getMessage("required") });
56
+ if (!isAllStrings) {
57
+ const numCoerceSchema = _zod.z.string().transform((v) => {
58
+ const n = Number(v);
59
+ return isNaN(n) ? v : n;
60
+ }).pipe(valueSchema);
61
+ return _zod.z.union([valueSchema, numCoerceSchema, emptyRejectSchema]);
62
+ }
63
+ return _zod.z.union([valueSchema, emptyRejectSchema]);
64
+ }
65
+ if (!isAllStrings) {
66
+ const numCoerceSchema = _zod.z.string().transform((v) => {
67
+ const n = Number(v);
68
+ return isNaN(n) ? v : n;
69
+ }).pipe(valueSchema);
70
+ return _zod.z.union([valueSchema, numCoerceSchema, emptySchema]);
71
+ }
72
+ return _zod.z.union([valueSchema, emptySchema]);
73
+ }
74
+
75
+
76
+
77
+ exports.oneOf = oneOf;
@@ -8,6 +8,7 @@ var _zod = require('zod');
8
8
  function manyOf(required, options) {
9
9
  const { values = [], defaultValue = null, min, max, allowDuplicates = false, caseSensitive = true, transform, i18n } = _nullishCoalesce(options, () => ( {}));
10
10
  const isRequired = _nullishCoalesce(required, () => ( false));
11
+ const valuesArr = values;
11
12
  const getMessage = (key, params) => {
12
13
  if (i18n) {
13
14
  const currentLocale = _chunkQ7TUNJD4cjs.getLocale.call(void 0, );
@@ -20,44 +21,24 @@ function manyOf(required, options) {
20
21
  return _chunkQ7TUNJD4cjs.t.call(void 0, `common.manyOf.${key}`, params);
21
22
  };
22
23
  const normalizeItem = (item) => {
23
- const hasNumbers = values.some((v) => typeof v === "number");
24
+ const hasNumbers = valuesArr.some((v) => typeof v === "number");
24
25
  if (hasNumbers && typeof item === "string" && !isNaN(Number(item)) && item.trim() !== "") {
25
26
  const numVal = Number(item);
26
- if (values.includes(numVal)) return numVal;
27
+ if (valuesArr.includes(numVal)) return numVal;
27
28
  }
28
29
  if (!caseSensitive && typeof item === "string") {
29
- const match = values.find((v) => typeof v === "string" && v.toLowerCase() === item.toLowerCase());
30
+ const match = valuesArr.find((v) => typeof v === "string" && v.toLowerCase() === item.toLowerCase());
30
31
  if (match !== void 0) return match;
31
32
  return item;
32
33
  }
33
34
  return item;
34
35
  };
35
- const preprocessFn = (val) => {
36
- if (val === null || val === void 0 || val === "") {
37
- return defaultValue;
38
- }
39
- if (!Array.isArray(val)) {
40
- return [normalizeItem(val)];
41
- }
42
- return val.map(normalizeItem);
43
- };
44
- const baseSchema = _zod.z.preprocess(preprocessFn, _zod.z.any());
45
- const schema = baseSchema.superRefine((val, ctx) => {
46
- if (val === null) {
47
- if (isRequired) {
48
- ctx.addIssue({ code: "custom", message: getMessage("required") });
49
- }
50
- return;
51
- }
52
- if (!Array.isArray(val)) {
53
- ctx.addIssue({ code: "custom", message: getMessage("invalid", { values: values.join(", ") }) });
54
- return;
55
- }
36
+ const arraySchema = _zod.z.array(_zod.z.any()).transform((arr) => arr.map(normalizeItem)).superRefine((val, ctx) => {
56
37
  for (const item of val) {
57
- if (!values.includes(item)) {
38
+ if (!valuesArr.includes(item)) {
58
39
  ctx.addIssue({
59
40
  code: "custom",
60
- message: getMessage("invalid", { values: values.join(", ") })
41
+ message: getMessage("invalid", { values: valuesArr.join(", ") })
61
42
  });
62
43
  return;
63
44
  }
@@ -81,10 +62,17 @@ function manyOf(required, options) {
81
62
  return;
82
63
  }
83
64
  }).transform((val) => {
84
- if (val === null || !Array.isArray(val) || !transform) return val;
65
+ if (!transform) return val;
85
66
  return transform(val);
86
67
  });
87
- return schema;
68
+ const singleToArray = _zod.z.union([_zod.z.string(), _zod.z.number()]).transform((v) => [normalizeItem(v)]).pipe(arraySchema);
69
+ const fallback = defaultValue;
70
+ if (isRequired && fallback === null) {
71
+ const emptyRejectSchema = _zod.z.union([_zod.z.literal(""), _zod.z.null(), _zod.z.undefined()]).refine(() => false, { message: getMessage("required") });
72
+ return _zod.z.union([arraySchema, singleToArray, emptyRejectSchema]);
73
+ }
74
+ const emptySchema = _zod.z.union([_zod.z.literal(""), _zod.z.null(), _zod.z.undefined()]).transform(() => fallback);
75
+ return _zod.z.union([arraySchema, singleToArray, emptySchema]);
88
76
  }
89
77
 
90
78
 
@@ -8,6 +8,7 @@ import { z } from "zod";
8
8
  function manyOf(required, options) {
9
9
  const { values = [], defaultValue = null, min, max, allowDuplicates = false, caseSensitive = true, transform, i18n } = options ?? {};
10
10
  const isRequired = required ?? false;
11
+ const valuesArr = values;
11
12
  const getMessage = (key, params) => {
12
13
  if (i18n) {
13
14
  const currentLocale = getLocale();
@@ -20,44 +21,24 @@ function manyOf(required, options) {
20
21
  return t(`common.manyOf.${key}`, params);
21
22
  };
22
23
  const normalizeItem = (item) => {
23
- const hasNumbers = values.some((v) => typeof v === "number");
24
+ const hasNumbers = valuesArr.some((v) => typeof v === "number");
24
25
  if (hasNumbers && typeof item === "string" && !isNaN(Number(item)) && item.trim() !== "") {
25
26
  const numVal = Number(item);
26
- if (values.includes(numVal)) return numVal;
27
+ if (valuesArr.includes(numVal)) return numVal;
27
28
  }
28
29
  if (!caseSensitive && typeof item === "string") {
29
- const match = values.find((v) => typeof v === "string" && v.toLowerCase() === item.toLowerCase());
30
+ const match = valuesArr.find((v) => typeof v === "string" && v.toLowerCase() === item.toLowerCase());
30
31
  if (match !== void 0) return match;
31
32
  return item;
32
33
  }
33
34
  return item;
34
35
  };
35
- const preprocessFn = (val) => {
36
- if (val === null || val === void 0 || val === "") {
37
- return defaultValue;
38
- }
39
- if (!Array.isArray(val)) {
40
- return [normalizeItem(val)];
41
- }
42
- return val.map(normalizeItem);
43
- };
44
- const baseSchema = z.preprocess(preprocessFn, z.any());
45
- const schema = baseSchema.superRefine((val, ctx) => {
46
- if (val === null) {
47
- if (isRequired) {
48
- ctx.addIssue({ code: "custom", message: getMessage("required") });
49
- }
50
- return;
51
- }
52
- if (!Array.isArray(val)) {
53
- ctx.addIssue({ code: "custom", message: getMessage("invalid", { values: values.join(", ") }) });
54
- return;
55
- }
36
+ const arraySchema = z.array(z.any()).transform((arr) => arr.map(normalizeItem)).superRefine((val, ctx) => {
56
37
  for (const item of val) {
57
- if (!values.includes(item)) {
38
+ if (!valuesArr.includes(item)) {
58
39
  ctx.addIssue({
59
40
  code: "custom",
60
- message: getMessage("invalid", { values: values.join(", ") })
41
+ message: getMessage("invalid", { values: valuesArr.join(", ") })
61
42
  });
62
43
  return;
63
44
  }
@@ -81,10 +62,17 @@ function manyOf(required, options) {
81
62
  return;
82
63
  }
83
64
  }).transform((val) => {
84
- if (val === null || !Array.isArray(val) || !transform) return val;
65
+ if (!transform) return val;
85
66
  return transform(val);
86
67
  });
87
- return schema;
68
+ const singleToArray = z.union([z.string(), z.number()]).transform((v) => [normalizeItem(v)]).pipe(arraySchema);
69
+ const fallback = defaultValue;
70
+ if (isRequired && fallback === null) {
71
+ const emptyRejectSchema = z.union([z.literal(""), z.null(), z.undefined()]).refine(() => false, { message: getMessage("required") });
72
+ return z.union([arraySchema, singleToArray, emptyRejectSchema]);
73
+ }
74
+ const emptySchema = z.union([z.literal(""), z.null(), z.undefined()]).transform(() => fallback);
75
+ return z.union([arraySchema, singleToArray, emptySchema]);
88
76
  }
89
77
 
90
78
  export {
@@ -0,0 +1,77 @@
1
+ import {
2
+ getLocale,
3
+ t
4
+ } from "./chunk-POIDES2L.js";
5
+
6
+ // src/validators/common/one-of.ts
7
+ import { z } from "zod";
8
+ function oneOf(required, options) {
9
+ const { values = [], defaultValue = null, caseSensitive = true, transform, i18n } = options ?? {};
10
+ const isRequired = required ?? false;
11
+ const valuesArr = values;
12
+ const isAllStrings = valuesArr.every((v) => typeof v === "string");
13
+ const getMessage = (key, params) => {
14
+ if (i18n) {
15
+ const currentLocale = getLocale();
16
+ const customMessages = i18n[currentLocale];
17
+ if (customMessages && customMessages[key]) {
18
+ const template = customMessages[key];
19
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
20
+ }
21
+ }
22
+ return t(`common.oneOf.${key}`, params);
23
+ };
24
+ const isEmpty = (v) => v === "" || v === null || v === void 0;
25
+ let valueSchema;
26
+ if (isAllStrings && valuesArr.length > 0) {
27
+ valueSchema = z.enum(valuesArr, {
28
+ error: (issue) => {
29
+ if (isEmpty(issue.input)) return getMessage("required");
30
+ return getMessage("invalid", { values: valuesArr.join(", ") });
31
+ }
32
+ });
33
+ } else if (valuesArr.length >= 2) {
34
+ const literals = valuesArr.map((v) => z.literal(v));
35
+ valueSchema = z.union(literals, {
36
+ error: () => getMessage("invalid", { values: valuesArr.join(", ") })
37
+ });
38
+ } else if (valuesArr.length === 1) {
39
+ valueSchema = z.literal(valuesArr[0]);
40
+ } else {
41
+ valueSchema = z.never();
42
+ }
43
+ if (!caseSensitive) {
44
+ valueSchema = z.string().transform((v) => {
45
+ const match = valuesArr.find((val) => typeof val === "string" && val.toLowerCase() === v.toLowerCase());
46
+ return match ?? v;
47
+ }).pipe(valueSchema);
48
+ }
49
+ if (transform) {
50
+ valueSchema = valueSchema.transform((v) => transform(v));
51
+ }
52
+ const fallback = defaultValue;
53
+ const emptySchema = z.union([z.literal(""), z.null(), z.undefined()]).transform(() => fallback);
54
+ if (isRequired && fallback === null) {
55
+ const emptyRejectSchema = z.union([z.literal(""), z.null(), z.undefined()]).refine(() => false, { message: getMessage("required") });
56
+ if (!isAllStrings) {
57
+ const numCoerceSchema = z.string().transform((v) => {
58
+ const n = Number(v);
59
+ return isNaN(n) ? v : n;
60
+ }).pipe(valueSchema);
61
+ return z.union([valueSchema, numCoerceSchema, emptyRejectSchema]);
62
+ }
63
+ return z.union([valueSchema, emptyRejectSchema]);
64
+ }
65
+ if (!isAllStrings) {
66
+ const numCoerceSchema = z.string().transform((v) => {
67
+ const n = Number(v);
68
+ return isNaN(n) ? v : n;
69
+ }).pipe(valueSchema);
70
+ return z.union([valueSchema, numCoerceSchema, emptySchema]);
71
+ }
72
+ return z.union([valueSchema, emptySchema]);
73
+ }
74
+
75
+ export {
76
+ oneOf
77
+ };
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkBZWQPSHOcjs = require('../chunk-BZWQPSHO.cjs');
3
+ var _chunk5U5ERV2Pcjs = require('../chunk-5U5ERV2P.cjs');
4
4
  require('../chunk-Q7TUNJD4.cjs');
5
5
 
6
6
 
7
- exports.manyOf = _chunkBZWQPSHOcjs.manyOf;
7
+ exports.manyOf = _chunk5U5ERV2Pcjs.manyOf;
@@ -7,8 +7,10 @@ import { L as Locale } from '../config-CABSSvAp.cjs';
7
7
  * Provides multi-select validation that restricts input to an array of values
8
8
  * from a predefined set, with min/max selection, duplicate control, and transformation.
9
9
  *
10
+ * Avoids z.preprocess() to preserve z.input types for React Hook Form compatibility.
11
+ *
10
12
  * @author Ong Hoe Yuan
11
- * @version 0.2.2
13
+ * @version 0.2.6
12
14
  */
13
15
 
14
16
  /**
@@ -32,11 +34,11 @@ type ManyOfMessages = {
32
34
  * Configuration options for manyOf validation
33
35
  *
34
36
  * @template IsRequired - Whether the field is required (affects return type)
35
- * @template T - The type of allowed values
37
+ * @template V - The tuple type of allowed values, preserving literal types
36
38
  *
37
39
  * @interface ManyOfOptions
38
- * @property {T[]} values - Array of allowed values
39
- * @property {T[] | null} [defaultValue] - Default value when input is empty
40
+ * @property {V} values - Array of allowed values
41
+ * @property {V[number][] | null} [defaultValue] - Default value when input is empty
40
42
  * @property {number} [min] - Minimum number of selections
41
43
  * @property {number} [max] - Maximum number of selections
42
44
  * @property {boolean} [allowDuplicates=false] - Whether to allow duplicate selections
@@ -58,17 +60,19 @@ type ManyOfOptions<IsRequired extends boolean = true, V extends readonly (string
58
60
  * Type alias for manyOf validation schema based on required flag
59
61
  *
60
62
  * @template IsRequired - Whether the field is required
61
- * @template T - The type of allowed values
63
+ * @template V - The tuple type of allowed values
62
64
  */
63
- type ManyOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number][]> : ZodType<V[number][] | null>;
65
+ type ManyOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number][], V[number][]> : ZodType<V[number][] | null, V[number][] | "" | null | undefined>;
64
66
  /**
65
67
  * Creates a Zod schema for multi-select validation that restricts values to a predefined set
66
68
  *
69
+ * Avoids z.preprocess() to preserve z.input types for React Hook Form compatibility.
70
+ *
67
71
  * @template IsRequired - Whether the field is required (affects return type)
68
- * @template T - The type of allowed values (string | number)
72
+ * @template V - The tuple type of allowed values (inferred via const type parameter)
69
73
  * @param {IsRequired} [required=false] - Whether the field is required
70
- * @param {ManyOfOptions<IsRequired, T>} options - Configuration options (values is required)
71
- * @returns {ManyOfSchema<IsRequired, T>} Zod schema for manyOf validation
74
+ * @param {ManyOfOptions<IsRequired, V>} options - Configuration options (values is required)
75
+ * @returns {ManyOfSchema<IsRequired, V>} Zod schema for manyOf validation
72
76
  *
73
77
  * @example
74
78
  * ```typescript
@@ -7,8 +7,10 @@ import { L as Locale } from '../config-CABSSvAp.js';
7
7
  * Provides multi-select validation that restricts input to an array of values
8
8
  * from a predefined set, with min/max selection, duplicate control, and transformation.
9
9
  *
10
+ * Avoids z.preprocess() to preserve z.input types for React Hook Form compatibility.
11
+ *
10
12
  * @author Ong Hoe Yuan
11
- * @version 0.2.2
13
+ * @version 0.2.6
12
14
  */
13
15
 
14
16
  /**
@@ -32,11 +34,11 @@ type ManyOfMessages = {
32
34
  * Configuration options for manyOf validation
33
35
  *
34
36
  * @template IsRequired - Whether the field is required (affects return type)
35
- * @template T - The type of allowed values
37
+ * @template V - The tuple type of allowed values, preserving literal types
36
38
  *
37
39
  * @interface ManyOfOptions
38
- * @property {T[]} values - Array of allowed values
39
- * @property {T[] | null} [defaultValue] - Default value when input is empty
40
+ * @property {V} values - Array of allowed values
41
+ * @property {V[number][] | null} [defaultValue] - Default value when input is empty
40
42
  * @property {number} [min] - Minimum number of selections
41
43
  * @property {number} [max] - Maximum number of selections
42
44
  * @property {boolean} [allowDuplicates=false] - Whether to allow duplicate selections
@@ -58,17 +60,19 @@ type ManyOfOptions<IsRequired extends boolean = true, V extends readonly (string
58
60
  * Type alias for manyOf validation schema based on required flag
59
61
  *
60
62
  * @template IsRequired - Whether the field is required
61
- * @template T - The type of allowed values
63
+ * @template V - The tuple type of allowed values
62
64
  */
63
- type ManyOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number][]> : ZodType<V[number][] | null>;
65
+ type ManyOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number][], V[number][]> : ZodType<V[number][] | null, V[number][] | "" | null | undefined>;
64
66
  /**
65
67
  * Creates a Zod schema for multi-select validation that restricts values to a predefined set
66
68
  *
69
+ * Avoids z.preprocess() to preserve z.input types for React Hook Form compatibility.
70
+ *
67
71
  * @template IsRequired - Whether the field is required (affects return type)
68
- * @template T - The type of allowed values (string | number)
72
+ * @template V - The tuple type of allowed values (inferred via const type parameter)
69
73
  * @param {IsRequired} [required=false] - Whether the field is required
70
- * @param {ManyOfOptions<IsRequired, T>} options - Configuration options (values is required)
71
- * @returns {ManyOfSchema<IsRequired, T>} Zod schema for manyOf validation
74
+ * @param {ManyOfOptions<IsRequired, V>} options - Configuration options (values is required)
75
+ * @returns {ManyOfSchema<IsRequired, V>} Zod schema for manyOf validation
72
76
  *
73
77
  * @example
74
78
  * ```typescript
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  manyOf
3
- } from "../chunk-7X3XPK6A.js";
3
+ } from "../chunk-S4OJGXWC.js";
4
4
  import "../chunk-POIDES2L.js";
5
5
  export {
6
6
  manyOf
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkFMPHW7IDcjs = require('../chunk-FMPHW7ID.cjs');
3
+ var _chunk5NBWPAHCcjs = require('../chunk-5NBWPAHC.cjs');
4
4
  require('../chunk-Q7TUNJD4.cjs');
5
5
 
6
6
 
7
- exports.oneOf = _chunkFMPHW7IDcjs.oneOf;
7
+ exports.oneOf = _chunk5NBWPAHCcjs.oneOf;
@@ -7,8 +7,11 @@ import { L as Locale } from '../config-CABSSvAp.cjs';
7
7
  * Provides single-select validation that restricts input to a predefined set of allowed values,
8
8
  * with support for case-insensitive matching, default values, and transformation.
9
9
  *
10
+ * When required=true, z.input is narrowed to V[number] (no empty/null/undefined),
11
+ * making it fully compatible with React Hook Form resolvers and type-aware form libraries.
12
+ *
10
13
  * @author Ong Hoe Yuan
11
- * @version 0.2.2
14
+ * @version 0.2.6
12
15
  */
13
16
 
14
17
  /**
@@ -26,11 +29,11 @@ type OneOfMessages = {
26
29
  * Configuration options for oneOf validation
27
30
  *
28
31
  * @template IsRequired - Whether the field is required (affects return type)
29
- * @template T - The type of allowed values
32
+ * @template V - The tuple type of allowed values, preserving literal types
30
33
  *
31
34
  * @interface OneOfOptions
32
- * @property {T[]} values - Array of allowed values
33
- * @property {T | null} [defaultValue] - Default value when input is empty
35
+ * @property {V} values - Array of allowed values
36
+ * @property {V[number] | null} [defaultValue] - Default value when input is empty
34
37
  * @property {boolean} [caseSensitive=true] - Whether string matching is case-sensitive
35
38
  * @property {Function} [transform] - Custom transformation function applied after validation
36
39
  * @property {Record<Locale, OneOfMessages>} [i18n] - Custom error messages for different locales
@@ -46,17 +49,20 @@ type OneOfOptions<IsRequired extends boolean = true, V extends readonly (string
46
49
  * Type alias for oneOf validation schema based on required flag
47
50
  *
48
51
  * @template IsRequired - Whether the field is required
49
- * @template T - The type of allowed values
52
+ * @template V - The tuple type of allowed values
50
53
  */
51
- type OneOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number]> : ZodType<V[number] | null>;
54
+ type OneOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number], V[number]> : ZodType<V[number] | null, V[number] | "" | null | undefined>;
52
55
  /**
53
56
  * Creates a Zod schema for single-select validation that restricts values to a predefined set
54
57
  *
58
+ * When required=true, z.input is narrowed to V[number] only (no empty/null/undefined),
59
+ * ensuring full compatibility with React Hook Form and other type-aware form libraries.
60
+ *
55
61
  * @template IsRequired - Whether the field is required (affects return type)
56
- * @template T - The type of allowed values (string | number)
62
+ * @template V - The tuple type of allowed values (inferred via const type parameter)
57
63
  * @param {IsRequired} [required=false] - Whether the field is required
58
- * @param {OneOfOptions<IsRequired, T>} options - Configuration options (values is required)
59
- * @returns {OneOfSchema<IsRequired, T>} Zod schema for oneOf validation
64
+ * @param {OneOfOptions<IsRequired, V>} options - Configuration options (values is required)
65
+ * @returns {OneOfSchema<IsRequired, V>} Zod schema for oneOf validation
60
66
  *
61
67
  * @example
62
68
  * ```typescript
@@ -65,7 +71,7 @@ type OneOfSchema<IsRequired extends boolean, V extends readonly (string | number
65
71
  * roleSchema.parse("admin") // ✓ "admin"
66
72
  * roleSchema.parse(null) // ✓ null
67
73
  *
68
- * // Required
74
+ * // Required — z.input and z.output are both "active" | "inactive" | "pending"
69
75
  * const statusSchema = oneOf(true, { values: ["active", "inactive", "pending"] })
70
76
  * statusSchema.parse("active") // ✓ "active"
71
77
  * statusSchema.parse(null) // ✗ Required
@@ -7,8 +7,11 @@ import { L as Locale } from '../config-CABSSvAp.js';
7
7
  * Provides single-select validation that restricts input to a predefined set of allowed values,
8
8
  * with support for case-insensitive matching, default values, and transformation.
9
9
  *
10
+ * When required=true, z.input is narrowed to V[number] (no empty/null/undefined),
11
+ * making it fully compatible with React Hook Form resolvers and type-aware form libraries.
12
+ *
10
13
  * @author Ong Hoe Yuan
11
- * @version 0.2.2
14
+ * @version 0.2.6
12
15
  */
13
16
 
14
17
  /**
@@ -26,11 +29,11 @@ type OneOfMessages = {
26
29
  * Configuration options for oneOf validation
27
30
  *
28
31
  * @template IsRequired - Whether the field is required (affects return type)
29
- * @template T - The type of allowed values
32
+ * @template V - The tuple type of allowed values, preserving literal types
30
33
  *
31
34
  * @interface OneOfOptions
32
- * @property {T[]} values - Array of allowed values
33
- * @property {T | null} [defaultValue] - Default value when input is empty
35
+ * @property {V} values - Array of allowed values
36
+ * @property {V[number] | null} [defaultValue] - Default value when input is empty
34
37
  * @property {boolean} [caseSensitive=true] - Whether string matching is case-sensitive
35
38
  * @property {Function} [transform] - Custom transformation function applied after validation
36
39
  * @property {Record<Locale, OneOfMessages>} [i18n] - Custom error messages for different locales
@@ -46,17 +49,20 @@ type OneOfOptions<IsRequired extends boolean = true, V extends readonly (string
46
49
  * Type alias for oneOf validation schema based on required flag
47
50
  *
48
51
  * @template IsRequired - Whether the field is required
49
- * @template T - The type of allowed values
52
+ * @template V - The tuple type of allowed values
50
53
  */
51
- type OneOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number]> : ZodType<V[number] | null>;
54
+ type OneOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number], V[number]> : ZodType<V[number] | null, V[number] | "" | null | undefined>;
52
55
  /**
53
56
  * Creates a Zod schema for single-select validation that restricts values to a predefined set
54
57
  *
58
+ * When required=true, z.input is narrowed to V[number] only (no empty/null/undefined),
59
+ * ensuring full compatibility with React Hook Form and other type-aware form libraries.
60
+ *
55
61
  * @template IsRequired - Whether the field is required (affects return type)
56
- * @template T - The type of allowed values (string | number)
62
+ * @template V - The tuple type of allowed values (inferred via const type parameter)
57
63
  * @param {IsRequired} [required=false] - Whether the field is required
58
- * @param {OneOfOptions<IsRequired, T>} options - Configuration options (values is required)
59
- * @returns {OneOfSchema<IsRequired, T>} Zod schema for oneOf validation
64
+ * @param {OneOfOptions<IsRequired, V>} options - Configuration options (values is required)
65
+ * @returns {OneOfSchema<IsRequired, V>} Zod schema for oneOf validation
60
66
  *
61
67
  * @example
62
68
  * ```typescript
@@ -65,7 +71,7 @@ type OneOfSchema<IsRequired extends boolean, V extends readonly (string | number
65
71
  * roleSchema.parse("admin") // ✓ "admin"
66
72
  * roleSchema.parse(null) // ✓ null
67
73
  *
68
- * // Required
74
+ * // Required — z.input and z.output are both "active" | "inactive" | "pending"
69
75
  * const statusSchema = oneOf(true, { values: ["active", "inactive", "pending"] })
70
76
  * statusSchema.parse("active") // ✓ "active"
71
77
  * statusSchema.parse(null) // ✗ Required
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  oneOf
3
- } from "../chunk-3ZF5JO3F.js";
3
+ } from "../chunk-YG6YHVAE.js";
4
4
  import "../chunk-POIDES2L.js";
5
5
  export {
6
6
  oneOf
package/dist/index.cjs CHANGED
@@ -60,7 +60,7 @@ var _chunkBZSPJJYTcjs = require('./chunk-BZSPJJYT.cjs');
60
60
  var _chunkG747FHUZcjs = require('./chunk-G747FHUZ.cjs');
61
61
 
62
62
 
63
- var _chunkFMPHW7IDcjs = require('./chunk-FMPHW7ID.cjs');
63
+ var _chunk5NBWPAHCcjs = require('./chunk-5NBWPAHC.cjs');
64
64
 
65
65
 
66
66
  var _chunkQ24GYUTOcjs = require('./chunk-Q24GYUTO.cjs');
@@ -114,7 +114,7 @@ var _chunkKIUO2HIRcjs = require('./chunk-KIUO2HIR.cjs');
114
114
  var _chunkJZEF5Q3Wcjs = require('./chunk-JZEF5Q3W.cjs');
115
115
 
116
116
 
117
- var _chunkBZWQPSHOcjs = require('./chunk-BZWQPSHO.cjs');
117
+ var _chunk5U5ERV2Pcjs = require('./chunk-5U5ERV2P.cjs');
118
118
 
119
119
 
120
120
 
@@ -185,4 +185,4 @@ var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
185
185
 
186
186
 
187
187
 
188
- exports.DATETIME_PATTERNS = _chunk53EEWALQcjs.DATETIME_PATTERNS; exports.ID_PATTERNS = _chunkJZEF5Q3Wcjs.ID_PATTERNS; exports.TAIWAN_BANK_CODES = _chunkQQWX3ICKcjs.TAIWAN_BANK_CODES; exports.TIME_PATTERNS = _chunkEDTNS2XLcjs.TIME_PATTERNS; exports.VALID_3_DIGIT_PREFIXES = _chunkFP4O2ICMcjs.VALID_3_DIGIT_PREFIXES; exports.boolean = _chunkLNWEJED7cjs.boolean; exports.color = _chunkA2GAEU4Ocjs.color; exports.coordinate = _chunkFEL432I2cjs.coordinate; exports.creditCard = _chunkZCX22PY4cjs.creditCard; exports.date = _chunk4AQB4RSUcjs.date; exports.datetime = _chunk53EEWALQcjs.datetime; exports.detectCardType = _chunkZCX22PY4cjs.detectCardType; exports.detectIdType = _chunkJZEF5Q3Wcjs.detectIdType; exports.email = _chunkH25N5GP6cjs.email; exports.file = _chunkKIUO2HIRcjs.file; exports.getLocale = _chunkQ7TUNJD4cjs.getLocale; exports.id = _chunkJZEF5Q3Wcjs.id; exports.ip = _chunkLXFRQLH4cjs.ip; exports.manyOf = _chunkBZWQPSHOcjs.manyOf; exports.normalizeDateTimeValue = _chunk53EEWALQcjs.normalizeDateTimeValue; exports.normalizeTime = _chunkEDTNS2XLcjs.normalizeTime; exports.number = _chunkG747FHUZcjs.number; exports.oneOf = _chunkFMPHW7IDcjs.oneOf; exports.parseDateTimeValue = _chunk53EEWALQcjs.parseDateTimeValue; exports.parseTimeToMinutes = _chunkEDTNS2XLcjs.parseTimeToMinutes; exports.password = _chunkQ24GYUTOcjs.password; exports.setLocale = _chunkQ7TUNJD4cjs.setLocale; exports.text = _chunkHZ2WESSLcjs.text; exports.time = _chunkEDTNS2XLcjs.time; exports.twBankAccount = _chunkQQWX3ICKcjs.twBankAccount; exports.twBusinessId = _chunkIWR3H7IHcjs.twBusinessId; exports.twFax = _chunkBZSPJJYTcjs.twFax; exports.twInvoice = _chunkV2KKGSKQcjs.twInvoice; exports.twLicensePlate = _chunkTPXRQT2Hcjs.twLicensePlate; exports.twMobile = _chunkAYCXAJRAcjs.twMobile; exports.twNationalId = _chunkUCPKW43Kcjs.twNationalId; exports.twPassport = _chunk5ZMTAI4Gcjs.twPassport; exports.twPostalCode = _chunkFP4O2ICMcjs.twPostalCode; exports.twTel = _chunkVKBNKPFOcjs.twTel; exports.url = _chunkTDEXEIHHcjs.url; exports.validate3DigitPostalCode = _chunkFP4O2ICMcjs.validate3DigitPostalCode; exports.validate5DigitPostalCode = _chunkFP4O2ICMcjs.validate5DigitPostalCode; exports.validate6DigitPostalCode = _chunkFP4O2ICMcjs.validate6DigitPostalCode; exports.validateCitizenId = _chunkUCPKW43Kcjs.validateCitizenId; exports.validateColor = _chunkA2GAEU4Ocjs.validateColor; exports.validateCreditCard = _chunkZCX22PY4cjs.validateCreditCard; exports.validateDateTimeFormat = _chunk53EEWALQcjs.validateDateTimeFormat; exports.validateIPv4 = _chunkLXFRQLH4cjs.validateIPv4; exports.validateIPv6 = _chunkLXFRQLH4cjs.validateIPv6; exports.validateIdType = _chunkJZEF5Q3Wcjs.validateIdType; exports.validateLatitude = _chunkFEL432I2cjs.validateLatitude; exports.validateLongitude = _chunkFEL432I2cjs.validateLongitude; exports.validateNewResidentId = _chunkUCPKW43Kcjs.validateNewResidentId; exports.validateOldResidentId = _chunkUCPKW43Kcjs.validateOldResidentId; exports.validateTaiwanBankAccount = _chunkQQWX3ICKcjs.validateTaiwanBankAccount; exports.validateTaiwanBusinessId = _chunkIWR3H7IHcjs.validateTaiwanBusinessId; exports.validateTaiwanFax = _chunkBZSPJJYTcjs.validateTaiwanFax; exports.validateTaiwanInvoice = _chunkV2KKGSKQcjs.validateTaiwanInvoice; exports.validateTaiwanLicensePlate = _chunkTPXRQT2Hcjs.validateTaiwanLicensePlate; exports.validateTaiwanMobile = _chunkAYCXAJRAcjs.validateTaiwanMobile; exports.validateTaiwanNationalId = _chunkUCPKW43Kcjs.validateTaiwanNationalId; exports.validateTaiwanPassport = _chunk5ZMTAI4Gcjs.validateTaiwanPassport; exports.validateTaiwanPostalCode = _chunkFP4O2ICMcjs.validateTaiwanPostalCode; exports.validateTaiwanTel = _chunkVKBNKPFOcjs.validateTaiwanTel; exports.validateTimeFormat = _chunkEDTNS2XLcjs.validateTimeFormat;
188
+ exports.DATETIME_PATTERNS = _chunk53EEWALQcjs.DATETIME_PATTERNS; exports.ID_PATTERNS = _chunkJZEF5Q3Wcjs.ID_PATTERNS; exports.TAIWAN_BANK_CODES = _chunkQQWX3ICKcjs.TAIWAN_BANK_CODES; exports.TIME_PATTERNS = _chunkEDTNS2XLcjs.TIME_PATTERNS; exports.VALID_3_DIGIT_PREFIXES = _chunkFP4O2ICMcjs.VALID_3_DIGIT_PREFIXES; exports.boolean = _chunkLNWEJED7cjs.boolean; exports.color = _chunkA2GAEU4Ocjs.color; exports.coordinate = _chunkFEL432I2cjs.coordinate; exports.creditCard = _chunkZCX22PY4cjs.creditCard; exports.date = _chunk4AQB4RSUcjs.date; exports.datetime = _chunk53EEWALQcjs.datetime; exports.detectCardType = _chunkZCX22PY4cjs.detectCardType; exports.detectIdType = _chunkJZEF5Q3Wcjs.detectIdType; exports.email = _chunkH25N5GP6cjs.email; exports.file = _chunkKIUO2HIRcjs.file; exports.getLocale = _chunkQ7TUNJD4cjs.getLocale; exports.id = _chunkJZEF5Q3Wcjs.id; exports.ip = _chunkLXFRQLH4cjs.ip; exports.manyOf = _chunk5U5ERV2Pcjs.manyOf; exports.normalizeDateTimeValue = _chunk53EEWALQcjs.normalizeDateTimeValue; exports.normalizeTime = _chunkEDTNS2XLcjs.normalizeTime; exports.number = _chunkG747FHUZcjs.number; exports.oneOf = _chunk5NBWPAHCcjs.oneOf; exports.parseDateTimeValue = _chunk53EEWALQcjs.parseDateTimeValue; exports.parseTimeToMinutes = _chunkEDTNS2XLcjs.parseTimeToMinutes; exports.password = _chunkQ24GYUTOcjs.password; exports.setLocale = _chunkQ7TUNJD4cjs.setLocale; exports.text = _chunkHZ2WESSLcjs.text; exports.time = _chunkEDTNS2XLcjs.time; exports.twBankAccount = _chunkQQWX3ICKcjs.twBankAccount; exports.twBusinessId = _chunkIWR3H7IHcjs.twBusinessId; exports.twFax = _chunkBZSPJJYTcjs.twFax; exports.twInvoice = _chunkV2KKGSKQcjs.twInvoice; exports.twLicensePlate = _chunkTPXRQT2Hcjs.twLicensePlate; exports.twMobile = _chunkAYCXAJRAcjs.twMobile; exports.twNationalId = _chunkUCPKW43Kcjs.twNationalId; exports.twPassport = _chunk5ZMTAI4Gcjs.twPassport; exports.twPostalCode = _chunkFP4O2ICMcjs.twPostalCode; exports.twTel = _chunkVKBNKPFOcjs.twTel; exports.url = _chunkTDEXEIHHcjs.url; exports.validate3DigitPostalCode = _chunkFP4O2ICMcjs.validate3DigitPostalCode; exports.validate5DigitPostalCode = _chunkFP4O2ICMcjs.validate5DigitPostalCode; exports.validate6DigitPostalCode = _chunkFP4O2ICMcjs.validate6DigitPostalCode; exports.validateCitizenId = _chunkUCPKW43Kcjs.validateCitizenId; exports.validateColor = _chunkA2GAEU4Ocjs.validateColor; exports.validateCreditCard = _chunkZCX22PY4cjs.validateCreditCard; exports.validateDateTimeFormat = _chunk53EEWALQcjs.validateDateTimeFormat; exports.validateIPv4 = _chunkLXFRQLH4cjs.validateIPv4; exports.validateIPv6 = _chunkLXFRQLH4cjs.validateIPv6; exports.validateIdType = _chunkJZEF5Q3Wcjs.validateIdType; exports.validateLatitude = _chunkFEL432I2cjs.validateLatitude; exports.validateLongitude = _chunkFEL432I2cjs.validateLongitude; exports.validateNewResidentId = _chunkUCPKW43Kcjs.validateNewResidentId; exports.validateOldResidentId = _chunkUCPKW43Kcjs.validateOldResidentId; exports.validateTaiwanBankAccount = _chunkQQWX3ICKcjs.validateTaiwanBankAccount; exports.validateTaiwanBusinessId = _chunkIWR3H7IHcjs.validateTaiwanBusinessId; exports.validateTaiwanFax = _chunkBZSPJJYTcjs.validateTaiwanFax; exports.validateTaiwanInvoice = _chunkV2KKGSKQcjs.validateTaiwanInvoice; exports.validateTaiwanLicensePlate = _chunkTPXRQT2Hcjs.validateTaiwanLicensePlate; exports.validateTaiwanMobile = _chunkAYCXAJRAcjs.validateTaiwanMobile; exports.validateTaiwanNationalId = _chunkUCPKW43Kcjs.validateTaiwanNationalId; exports.validateTaiwanPassport = _chunk5ZMTAI4Gcjs.validateTaiwanPassport; exports.validateTaiwanPostalCode = _chunkFP4O2ICMcjs.validateTaiwanPostalCode; exports.validateTaiwanTel = _chunkVKBNKPFOcjs.validateTaiwanTel; exports.validateTimeFormat = _chunkEDTNS2XLcjs.validateTimeFormat;
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ import {
60
60
  } from "./chunk-3QLUXIY2.js";
61
61
  import {
62
62
  oneOf
63
- } from "./chunk-3ZF5JO3F.js";
63
+ } from "./chunk-YG6YHVAE.js";
64
64
  import {
65
65
  password
66
66
  } from "./chunk-NWQSOSNF.js";
@@ -114,7 +114,7 @@ import {
114
114
  } from "./chunk-H6STFX4I.js";
115
115
  import {
116
116
  manyOf
117
- } from "./chunk-7X3XPK6A.js";
117
+ } from "./chunk-S4OJGXWC.js";
118
118
  import {
119
119
  getLocale,
120
120
  setLocale
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hy_ong/zod-kit",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "A comprehensive TypeScript library providing pre-built Zod validation schemas with full internationalization support for common data types and Taiwan-specific formats",
5
5
  "keywords": [
6
6
  "zod",
@@ -4,8 +4,10 @@
4
4
  * Provides multi-select validation that restricts input to an array of values
5
5
  * from a predefined set, with min/max selection, duplicate control, and transformation.
6
6
  *
7
+ * Avoids z.preprocess() to preserve z.input types for React Hook Form compatibility.
8
+ *
7
9
  * @author Ong Hoe Yuan
8
- * @version 0.2.2
10
+ * @version 0.2.6
9
11
  */
10
12
 
11
13
  import { z, ZodType } from "zod"
@@ -34,11 +36,11 @@ export type ManyOfMessages = {
34
36
  * Configuration options for manyOf validation
35
37
  *
36
38
  * @template IsRequired - Whether the field is required (affects return type)
37
- * @template T - The type of allowed values
39
+ * @template V - The tuple type of allowed values, preserving literal types
38
40
  *
39
41
  * @interface ManyOfOptions
40
- * @property {T[]} values - Array of allowed values
41
- * @property {T[] | null} [defaultValue] - Default value when input is empty
42
+ * @property {V} values - Array of allowed values
43
+ * @property {V[number][] | null} [defaultValue] - Default value when input is empty
42
44
  * @property {number} [min] - Minimum number of selections
43
45
  * @property {number} [max] - Maximum number of selections
44
46
  * @property {boolean} [allowDuplicates=false] - Whether to allow duplicate selections
@@ -61,18 +63,22 @@ export type ManyOfOptions<IsRequired extends boolean = true, V extends readonly
61
63
  * Type alias for manyOf validation schema based on required flag
62
64
  *
63
65
  * @template IsRequired - Whether the field is required
64
- * @template T - The type of allowed values
66
+ * @template V - The tuple type of allowed values
65
67
  */
66
- export type ManyOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number][]> : ZodType<V[number][] | null>
68
+ export type ManyOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true
69
+ ? ZodType<V[number][], V[number][]>
70
+ : ZodType<V[number][] | null, V[number][] | "" | null | undefined>
67
71
 
68
72
  /**
69
73
  * Creates a Zod schema for multi-select validation that restricts values to a predefined set
70
74
  *
75
+ * Avoids z.preprocess() to preserve z.input types for React Hook Form compatibility.
76
+ *
71
77
  * @template IsRequired - Whether the field is required (affects return type)
72
- * @template T - The type of allowed values (string | number)
78
+ * @template V - The tuple type of allowed values (inferred via const type parameter)
73
79
  * @param {IsRequired} [required=false] - Whether the field is required
74
- * @param {ManyOfOptions<IsRequired, T>} options - Configuration options (values is required)
75
- * @returns {ManyOfSchema<IsRequired, T>} Zod schema for manyOf validation
80
+ * @param {ManyOfOptions<IsRequired, V>} options - Configuration options (values is required)
81
+ * @returns {ManyOfSchema<IsRequired, V>} Zod schema for manyOf validation
76
82
  *
77
83
  * @example
78
84
  * ```typescript
@@ -117,6 +123,7 @@ export function manyOf<IsRequired extends boolean = false, const V extends reado
117
123
  const { values = [] as unknown as V, defaultValue = null, min, max, allowDuplicates = false, caseSensitive = true, transform, i18n } = options ?? {}
118
124
 
119
125
  const isRequired = required ?? (false as IsRequired)
126
+ const valuesArr = values as readonly (string | number)[]
120
127
 
121
128
  const getMessage = (key: keyof ManyOfMessages, params?: Record<string, any>) => {
122
129
  if (i18n) {
@@ -132,15 +139,15 @@ export function manyOf<IsRequired extends boolean = false, const V extends reado
132
139
 
133
140
  const normalizeItem = (item: unknown): unknown => {
134
141
  // Coerce number strings to numbers when values contains numbers
135
- const hasNumbers = values.some((v) => typeof v === "number")
142
+ const hasNumbers = valuesArr.some((v) => typeof v === "number")
136
143
  if (hasNumbers && typeof item === "string" && !isNaN(Number(item)) && item.trim() !== "") {
137
144
  const numVal = Number(item)
138
- if ((values as readonly number[]).includes(numVal)) return numVal
145
+ if ((valuesArr as readonly number[]).includes(numVal)) return numVal
139
146
  }
140
147
 
141
148
  // Case-insensitive normalization
142
149
  if (!caseSensitive && typeof item === "string") {
143
- const match = values.find((v) => typeof v === "string" && v.toLowerCase() === item.toLowerCase())
150
+ const match = valuesArr.find((v) => typeof v === "string" && v.toLowerCase() === item.toLowerCase())
144
151
  if (match !== undefined) return match
145
152
  return item
146
153
  }
@@ -148,72 +155,70 @@ export function manyOf<IsRequired extends boolean = false, const V extends reado
148
155
  return item
149
156
  }
150
157
 
151
- const preprocessFn = (val: unknown) => {
152
- if (val === null || val === undefined || val === "") {
153
- return defaultValue
154
- }
155
-
156
- if (!Array.isArray(val)) {
157
- // Accept single value and wrap in array
158
- return [normalizeItem(val)]
159
- }
160
-
161
- return val.map(normalizeItem)
162
- }
163
-
164
- const baseSchema = z.preprocess(preprocessFn, z.any())
165
-
166
- const schema = baseSchema.superRefine((val, ctx) => {
167
- if (val === null) {
168
- if (isRequired) {
169
- ctx.addIssue({ code: "custom", message: getMessage("required") })
158
+ // Build an array schema that validates + normalizes items, then applies superRefine for constraints
159
+ const arraySchema = z
160
+ .array(z.any())
161
+ .transform((arr) => arr.map(normalizeItem))
162
+ .superRefine((val, ctx) => {
163
+ // Check each item is in the allowed values
164
+ for (const item of val) {
165
+ if (!(valuesArr as readonly (string | number)[]).includes(item as string | number)) {
166
+ ctx.addIssue({
167
+ code: "custom",
168
+ message: getMessage("invalid", { values: valuesArr.join(", ") }),
169
+ })
170
+ return
171
+ }
170
172
  }
171
- return
172
- }
173
173
 
174
- if (!Array.isArray(val)) {
175
- ctx.addIssue({ code: "custom", message: getMessage("invalid", { values: values.join(", ") }) })
176
- return
177
- }
174
+ // Duplicate check
175
+ if (!allowDuplicates) {
176
+ const seen = new Set()
177
+ for (const item of val) {
178
+ if (seen.has(item)) {
179
+ ctx.addIssue({ code: "custom", message: getMessage("duplicate") })
180
+ return
181
+ }
182
+ seen.add(item)
183
+ }
184
+ }
178
185
 
179
- // Check each item is in the allowed values
180
- for (const item of val) {
181
- if (!(values as readonly (string | number)[]).includes(item as V[number])) {
182
- ctx.addIssue({
183
- code: "custom",
184
- message: getMessage("invalid", { values: values.join(", ") }),
185
- })
186
+ // Min/max selection count
187
+ if (min !== undefined && val.length < min) {
188
+ ctx.addIssue({ code: "custom", message: getMessage("minSelect", { min }) })
186
189
  return
187
190
  }
188
- }
189
191
 
190
- // Duplicate check
191
- if (!allowDuplicates) {
192
- const seen = new Set()
193
- for (const item of val) {
194
- if (seen.has(item)) {
195
- ctx.addIssue({ code: "custom", message: getMessage("duplicate") })
196
- return
197
- }
198
- seen.add(item)
192
+ if (max !== undefined && val.length > max) {
193
+ ctx.addIssue({ code: "custom", message: getMessage("maxSelect", { max }) })
194
+ return
199
195
  }
200
- }
201
-
202
- // Min/max selection count
203
- if (min !== undefined && val.length < min) {
204
- ctx.addIssue({ code: "custom", message: getMessage("minSelect", { min }) })
205
- return
206
- }
196
+ })
197
+ .transform((val) => {
198
+ if (!transform) return val as V[number][]
199
+ return transform(val as V[number][])
200
+ })
201
+
202
+ // Single value → wrap in array
203
+ const singleToArray = z.union([z.string(), z.number()]).transform((v) => [normalizeItem(v)])
204
+ .pipe(arraySchema)
205
+
206
+ // Handle required vs optional
207
+ const fallback = defaultValue as V[number][] | null
208
+
209
+ if (isRequired && fallback === null) {
210
+ // Required, no default: empty values should fail with "required" message
211
+ const emptyRejectSchema = z
212
+ .union([z.literal("" as const), z.null(), z.undefined()])
213
+ .refine(() => false, { message: getMessage("required") })
214
+
215
+ return z.union([arraySchema, singleToArray, emptyRejectSchema]) as unknown as ManyOfSchema<IsRequired, V>
216
+ }
207
217
 
208
- if (max !== undefined && val.length > max) {
209
- ctx.addIssue({ code: "custom", message: getMessage("maxSelect", { max }) })
210
- return
211
- }
212
- })
213
- .transform((val) => {
214
- if (val === null || !Array.isArray(val) || !transform) return val
215
- return transform(val as V[number][])
216
- })
218
+ // Optional or has default: empty values fallback
219
+ const emptySchema = z
220
+ .union([z.literal("" as const), z.null(), z.undefined()])
221
+ .transform(() => fallback)
217
222
 
218
- return schema as unknown as ManyOfSchema<IsRequired, V>
223
+ return z.union([arraySchema, singleToArray, emptySchema]) as unknown as ManyOfSchema<IsRequired, V>
219
224
  }
@@ -4,8 +4,11 @@
4
4
  * Provides single-select validation that restricts input to a predefined set of allowed values,
5
5
  * with support for case-insensitive matching, default values, and transformation.
6
6
  *
7
+ * When required=true, z.input is narrowed to V[number] (no empty/null/undefined),
8
+ * making it fully compatible with React Hook Form resolvers and type-aware form libraries.
9
+ *
7
10
  * @author Ong Hoe Yuan
8
- * @version 0.2.2
11
+ * @version 0.2.6
9
12
  */
10
13
 
11
14
  import { z, ZodType } from "zod"
@@ -28,11 +31,11 @@ export type OneOfMessages = {
28
31
  * Configuration options for oneOf validation
29
32
  *
30
33
  * @template IsRequired - Whether the field is required (affects return type)
31
- * @template T - The type of allowed values
34
+ * @template V - The tuple type of allowed values, preserving literal types
32
35
  *
33
36
  * @interface OneOfOptions
34
- * @property {T[]} values - Array of allowed values
35
- * @property {T | null} [defaultValue] - Default value when input is empty
37
+ * @property {V} values - Array of allowed values
38
+ * @property {V[number] | null} [defaultValue] - Default value when input is empty
36
39
  * @property {boolean} [caseSensitive=true] - Whether string matching is case-sensitive
37
40
  * @property {Function} [transform] - Custom transformation function applied after validation
38
41
  * @property {Record<Locale, OneOfMessages>} [i18n] - Custom error messages for different locales
@@ -49,18 +52,23 @@ export type OneOfOptions<IsRequired extends boolean = true, V extends readonly (
49
52
  * Type alias for oneOf validation schema based on required flag
50
53
  *
51
54
  * @template IsRequired - Whether the field is required
52
- * @template T - The type of allowed values
55
+ * @template V - The tuple type of allowed values
53
56
  */
54
- export type OneOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true ? ZodType<V[number]> : ZodType<V[number] | null>
57
+ export type OneOfSchema<IsRequired extends boolean, V extends readonly (string | number)[]> = IsRequired extends true
58
+ ? ZodType<V[number], V[number]>
59
+ : ZodType<V[number] | null, V[number] | "" | null | undefined>
55
60
 
56
61
  /**
57
62
  * Creates a Zod schema for single-select validation that restricts values to a predefined set
58
63
  *
64
+ * When required=true, z.input is narrowed to V[number] only (no empty/null/undefined),
65
+ * ensuring full compatibility with React Hook Form and other type-aware form libraries.
66
+ *
59
67
  * @template IsRequired - Whether the field is required (affects return type)
60
- * @template T - The type of allowed values (string | number)
68
+ * @template V - The tuple type of allowed values (inferred via const type parameter)
61
69
  * @param {IsRequired} [required=false] - Whether the field is required
62
- * @param {OneOfOptions<IsRequired, T>} options - Configuration options (values is required)
63
- * @returns {OneOfSchema<IsRequired, T>} Zod schema for oneOf validation
70
+ * @param {OneOfOptions<IsRequired, V>} options - Configuration options (values is required)
71
+ * @returns {OneOfSchema<IsRequired, V>} Zod schema for oneOf validation
64
72
  *
65
73
  * @example
66
74
  * ```typescript
@@ -69,7 +77,7 @@ export type OneOfSchema<IsRequired extends boolean, V extends readonly (string |
69
77
  * roleSchema.parse("admin") // ✓ "admin"
70
78
  * roleSchema.parse(null) // ✓ null
71
79
  *
72
- * // Required
80
+ * // Required — z.input and z.output are both "active" | "inactive" | "pending"
73
81
  * const statusSchema = oneOf(true, { values: ["active", "inactive", "pending"] })
74
82
  * statusSchema.parse("active") // ✓ "active"
75
83
  * statusSchema.parse(null) // ✗ Required
@@ -110,6 +118,8 @@ export function oneOf<IsRequired extends boolean = false, const V extends readon
110
118
  const { values = [] as unknown as V, defaultValue = null, caseSensitive = true, transform, i18n } = options ?? {}
111
119
 
112
120
  const isRequired = required ?? (false as IsRequired)
121
+ const valuesArr = values as readonly (string | number)[]
122
+ const isAllStrings = valuesArr.every((v) => typeof v === "string")
113
123
 
114
124
  const getMessage = (key: keyof OneOfMessages, params?: Record<string, any>) => {
115
125
  if (i18n) {
@@ -123,50 +133,82 @@ export function oneOf<IsRequired extends boolean = false, const V extends readon
123
133
  return t(`common.oneOf.${key}`, params)
124
134
  }
125
135
 
126
- const preprocessFn = (val: unknown) => {
127
- if (val === "" || val === null || val === undefined) {
128
- return defaultValue
129
- }
136
+ const isEmpty = (v: unknown) => v === "" || v === null || v === undefined
130
137
 
131
- // Coerce number strings to numbers when values contains numbers
132
- const hasNumbers = values.some((v) => typeof v === "number")
133
- if (hasNumbers && typeof val === "string" && !isNaN(Number(val)) && val.trim() !== "") {
134
- const numVal = Number(val)
135
- if ((values as readonly number[]).includes(numVal)) return numVal
136
- }
138
+ // Build the value-matching schema using z.enum() or z.union(z.literal())
139
+ let valueSchema: ZodType
140
+ if (isAllStrings && valuesArr.length > 0) {
141
+ valueSchema = z.enum(valuesArr as unknown as readonly [string, ...string[]], {
142
+ error: (issue) => {
143
+ if (isEmpty(issue.input)) return getMessage("required")
144
+ return getMessage("invalid", { values: valuesArr.join(", ") })
145
+ },
146
+ })
147
+ } else if (valuesArr.length >= 2) {
148
+ const literals = valuesArr.map((v) => z.literal(v))
149
+ valueSchema = z.union(literals as unknown as readonly [ZodType, ZodType, ...ZodType[]], {
150
+ error: () => getMessage("invalid", { values: valuesArr.join(", ") }),
151
+ })
152
+ } else if (valuesArr.length === 1) {
153
+ valueSchema = z.literal(valuesArr[0])
154
+ } else {
155
+ valueSchema = z.never()
156
+ }
137
157
 
138
- // Case-insensitive normalization for string values
139
- if (!caseSensitive && typeof val === "string") {
140
- const match = values.find((v) => typeof v === "string" && v.toLowerCase() === val.toLowerCase())
141
- if (match !== undefined) return match
142
- return val
143
- }
158
+ // Case-insensitive: pipe through a string normalizer
159
+ if (!caseSensitive) {
160
+ valueSchema = z
161
+ .string()
162
+ .transform((v) => {
163
+ const match = valuesArr.find((val) => typeof val === "string" && val.toLowerCase() === v.toLowerCase())
164
+ return (match ?? v) as string
165
+ })
166
+ .pipe(valueSchema as ZodType<any, any>)
167
+ }
144
168
 
145
- return val
169
+ // User transform (applied after validation)
170
+ if (transform) {
171
+ valueSchema = valueSchema.transform((v) => transform(v as V[number]))
146
172
  }
147
173
 
148
- const baseSchema = z.preprocess(preprocessFn, z.any())
174
+ // Handle required vs optional
175
+ const fallback = defaultValue as V[number] | null
176
+ const emptySchema = z
177
+ .union([z.literal("" as const), z.null(), z.undefined()])
178
+ .transform(() => fallback)
149
179
 
150
- const schema = baseSchema
151
- .superRefine((val, ctx) => {
152
- if (val === null) {
153
- if (isRequired) {
154
- ctx.addIssue({ code: "custom", message: getMessage("required") })
155
- }
156
- return
157
- }
180
+ if (isRequired && fallback === null) {
181
+ // Required, no default: empty values should fail with "required" message
182
+ const emptyRejectSchema = z
183
+ .union([z.literal("" as const), z.null(), z.undefined()])
184
+ .refine(() => false, { message: getMessage("required") })
158
185
 
159
- if (!(values as readonly (string | number)[]).includes(val as V[number])) {
160
- ctx.addIssue({
161
- code: "custom",
162
- message: getMessage("invalid", { values: values.join(", ") }),
186
+ // For numeric values from forms (string "3" number 3)
187
+ if (!isAllStrings) {
188
+ const numCoerceSchema = z
189
+ .string()
190
+ .transform((v) => {
191
+ const n = Number(v)
192
+ return isNaN(n) ? v : n
163
193
  })
164
- }
165
- })
166
- .transform((val) => {
167
- if (val === null || !transform) return val
168
- return transform(val as V[number])
169
- })
194
+ .pipe(valueSchema as ZodType<any, any>)
195
+ return z.union([valueSchema, numCoerceSchema, emptyRejectSchema]) as unknown as OneOfSchema<IsRequired, V>
196
+ }
197
+
198
+ return z.union([valueSchema, emptyRejectSchema]) as unknown as OneOfSchema<IsRequired, V>
199
+ }
200
+
201
+ // Required with default or optional: empty values → fallback
202
+ if (!isAllStrings) {
203
+ const numCoerceSchema = z
204
+ .string()
205
+ .transform((v) => {
206
+ const n = Number(v)
207
+ return isNaN(n) ? v : n
208
+ })
209
+ .pipe(valueSchema as ZodType<any, any>)
210
+ return z.union([valueSchema, numCoerceSchema, emptySchema]) as unknown as OneOfSchema<IsRequired, V>
211
+ }
170
212
 
171
- return schema as unknown as OneOfSchema<IsRequired, V>
213
+ return z.union([valueSchema, emptySchema]) as unknown as OneOfSchema<IsRequired, V>
172
214
  }
@@ -1,61 +0,0 @@
1
- import {
2
- getLocale,
3
- t
4
- } from "./chunk-POIDES2L.js";
5
-
6
- // src/validators/common/one-of.ts
7
- import { z } from "zod";
8
- function oneOf(required, options) {
9
- const { values = [], defaultValue = null, caseSensitive = true, transform, i18n } = options ?? {};
10
- const isRequired = required ?? false;
11
- const getMessage = (key, params) => {
12
- if (i18n) {
13
- const currentLocale = getLocale();
14
- const customMessages = i18n[currentLocale];
15
- if (customMessages && customMessages[key]) {
16
- const template = customMessages[key];
17
- return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
18
- }
19
- }
20
- return t(`common.oneOf.${key}`, params);
21
- };
22
- const preprocessFn = (val) => {
23
- if (val === "" || val === null || val === void 0) {
24
- return defaultValue;
25
- }
26
- const hasNumbers = values.some((v) => typeof v === "number");
27
- if (hasNumbers && typeof val === "string" && !isNaN(Number(val)) && val.trim() !== "") {
28
- const numVal = Number(val);
29
- if (values.includes(numVal)) return numVal;
30
- }
31
- if (!caseSensitive && typeof val === "string") {
32
- const match = values.find((v) => typeof v === "string" && v.toLowerCase() === val.toLowerCase());
33
- if (match !== void 0) return match;
34
- return val;
35
- }
36
- return val;
37
- };
38
- const baseSchema = z.preprocess(preprocessFn, z.any());
39
- const schema = baseSchema.superRefine((val, ctx) => {
40
- if (val === null) {
41
- if (isRequired) {
42
- ctx.addIssue({ code: "custom", message: getMessage("required") });
43
- }
44
- return;
45
- }
46
- if (!values.includes(val)) {
47
- ctx.addIssue({
48
- code: "custom",
49
- message: getMessage("invalid", { values: values.join(", ") })
50
- });
51
- }
52
- }).transform((val) => {
53
- if (val === null || !transform) return val;
54
- return transform(val);
55
- });
56
- return schema;
57
- }
58
-
59
- export {
60
- oneOf
61
- };
@@ -1,61 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
-
3
-
4
- var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
5
-
6
- // src/validators/common/one-of.ts
7
- var _zod = require('zod');
8
- function oneOf(required, options) {
9
- const { values = [], defaultValue = null, caseSensitive = true, transform, i18n } = _nullishCoalesce(options, () => ( {}));
10
- const isRequired = _nullishCoalesce(required, () => ( false));
11
- const getMessage = (key, params) => {
12
- if (i18n) {
13
- const currentLocale = _chunkQ7TUNJD4cjs.getLocale.call(void 0, );
14
- const customMessages = i18n[currentLocale];
15
- if (customMessages && customMessages[key]) {
16
- const template = customMessages[key];
17
- return template.replace(/\$\{(\w+)}/g, (_, k) => _nullishCoalesce(_optionalChain([params, 'optionalAccess', _2 => _2[k]]), () => ( "")));
18
- }
19
- }
20
- return _chunkQ7TUNJD4cjs.t.call(void 0, `common.oneOf.${key}`, params);
21
- };
22
- const preprocessFn = (val) => {
23
- if (val === "" || val === null || val === void 0) {
24
- return defaultValue;
25
- }
26
- const hasNumbers = values.some((v) => typeof v === "number");
27
- if (hasNumbers && typeof val === "string" && !isNaN(Number(val)) && val.trim() !== "") {
28
- const numVal = Number(val);
29
- if (values.includes(numVal)) return numVal;
30
- }
31
- if (!caseSensitive && typeof val === "string") {
32
- const match = values.find((v) => typeof v === "string" && v.toLowerCase() === val.toLowerCase());
33
- if (match !== void 0) return match;
34
- return val;
35
- }
36
- return val;
37
- };
38
- const baseSchema = _zod.z.preprocess(preprocessFn, _zod.z.any());
39
- const schema = baseSchema.superRefine((val, ctx) => {
40
- if (val === null) {
41
- if (isRequired) {
42
- ctx.addIssue({ code: "custom", message: getMessage("required") });
43
- }
44
- return;
45
- }
46
- if (!values.includes(val)) {
47
- ctx.addIssue({
48
- code: "custom",
49
- message: getMessage("invalid", { values: values.join(", ") })
50
- });
51
- }
52
- }).transform((val) => {
53
- if (val === null || !transform) return val;
54
- return transform(val);
55
- });
56
- return schema;
57
- }
58
-
59
-
60
-
61
- exports.oneOf = oneOf;