pdyform 1.2.0 → 2.0.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.
Files changed (118) hide show
  1. package/README.md +28 -1
  2. package/example/README.md +36 -0
  3. package/example/react-demo/dist/assets/index-BBU9cJqy.css +1 -0
  4. package/example/react-demo/dist/assets/index-DeJS8UcQ.js +105 -0
  5. package/example/react-demo/dist/index.html +13 -0
  6. package/example/react-demo/index.html +12 -0
  7. package/example/react-demo/node_modules/.bin/browserslist +17 -0
  8. package/example/react-demo/node_modules/.bin/tsc +17 -0
  9. package/example/react-demo/node_modules/.bin/tsserver +17 -0
  10. package/example/react-demo/node_modules/.bin/vite +17 -0
  11. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-checkbox.js +300 -0
  12. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-checkbox.js.map +7 -0
  13. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-label.js +194 -0
  14. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-label.js.map +7 -0
  15. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-radio-group.js +530 -0
  16. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-radio-group.js.map +7 -0
  17. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-select.js +4808 -0
  18. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-select.js.map +7 -0
  19. package/example/react-demo/node_modules/.vite/deps/_metadata.json +115 -0
  20. package/example/react-demo/node_modules/.vite/deps/chunk-3D5PZ6F6.js +49 -0
  21. package/example/react-demo/node_modules/.vite/deps/chunk-3D5PZ6F6.js.map +7 -0
  22. package/example/react-demo/node_modules/.vite/deps/chunk-5Q2RBQLA.js +127 -0
  23. package/example/react-demo/node_modules/.vite/deps/chunk-5Q2RBQLA.js.map +7 -0
  24. package/example/react-demo/node_modules/.vite/deps/chunk-G3PMV62Z.js +36 -0
  25. package/example/react-demo/node_modules/.vite/deps/chunk-G3PMV62Z.js.map +7 -0
  26. package/example/react-demo/node_modules/.vite/deps/chunk-GX7YZ5KV.js +370 -0
  27. package/example/react-demo/node_modules/.vite/deps/chunk-GX7YZ5KV.js.map +7 -0
  28. package/example/react-demo/node_modules/.vite/deps/chunk-PUFJGYAC.js +928 -0
  29. package/example/react-demo/node_modules/.vite/deps/chunk-PUFJGYAC.js.map +7 -0
  30. package/example/react-demo/node_modules/.vite/deps/chunk-SIU35MPB.js +21 -0
  31. package/example/react-demo/node_modules/.vite/deps/chunk-SIU35MPB.js.map +7 -0
  32. package/example/react-demo/node_modules/.vite/deps/chunk-TOMGVNQP.js +1906 -0
  33. package/example/react-demo/node_modules/.vite/deps/chunk-TOMGVNQP.js.map +7 -0
  34. package/example/react-demo/node_modules/.vite/deps/chunk-YYN6DZAU.js +21628 -0
  35. package/example/react-demo/node_modules/.vite/deps/chunk-YYN6DZAU.js.map +7 -0
  36. package/example/react-demo/node_modules/.vite/deps/chunk-ZE5VSJFE.js +144 -0
  37. package/example/react-demo/node_modules/.vite/deps/chunk-ZE5VSJFE.js.map +7 -0
  38. package/example/react-demo/node_modules/.vite/deps/class-variance-authority.js +51 -0
  39. package/example/react-demo/node_modules/.vite/deps/class-variance-authority.js.map +7 -0
  40. package/example/react-demo/node_modules/.vite/deps/clsx.js +10 -0
  41. package/example/react-demo/node_modules/.vite/deps/clsx.js.map +7 -0
  42. package/example/react-demo/node_modules/.vite/deps/lucide-react.js +29725 -0
  43. package/example/react-demo/node_modules/.vite/deps/lucide-react.js.map +7 -0
  44. package/example/react-demo/node_modules/.vite/deps/package.json +3 -0
  45. package/example/react-demo/node_modules/.vite/deps/react-dom.js +7 -0
  46. package/example/react-demo/node_modules/.vite/deps/react-dom.js.map +7 -0
  47. package/example/react-demo/node_modules/.vite/deps/react-dom_client.js +39 -0
  48. package/example/react-demo/node_modules/.vite/deps/react-dom_client.js.map +7 -0
  49. package/example/react-demo/node_modules/.vite/deps/react.js +6 -0
  50. package/example/react-demo/node_modules/.vite/deps/react.js.map +7 -0
  51. package/example/react-demo/node_modules/.vite/deps/react_jsx-dev-runtime.js +913 -0
  52. package/example/react-demo/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  53. package/example/react-demo/node_modules/.vite/deps/react_jsx-runtime.js +7 -0
  54. package/example/react-demo/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
  55. package/example/react-demo/node_modules/.vite/deps/tailwind-merge.js +2534 -0
  56. package/example/react-demo/node_modules/.vite/deps/tailwind-merge.js.map +7 -0
  57. package/example/react-demo/package.json +23 -0
  58. package/example/react-demo/postcss.config.mjs +6 -0
  59. package/example/react-demo/src/App.tsx +64 -0
  60. package/example/react-demo/src/main.tsx +10 -0
  61. package/example/react-demo/src/styles.css +102 -0
  62. package/example/react-demo/tailwind.config.mjs +50 -0
  63. package/example/react-demo/tsconfig.json +16 -0
  64. package/example/react-demo/vite.config.ts +14 -0
  65. package/example/shared/defaultSchema.ts +68 -0
  66. package/example/vue-demo/dist/assets/index-BhWj3D5x.css +1 -0
  67. package/example/vue-demo/dist/assets/index-Bw3THsrD.js +44 -0
  68. package/example/vue-demo/dist/index.html +13 -0
  69. package/example/vue-demo/index.html +12 -0
  70. package/example/vue-demo/node_modules/.bin/tsc +17 -0
  71. package/example/vue-demo/node_modules/.bin/tsserver +17 -0
  72. package/example/vue-demo/node_modules/.bin/vite +17 -0
  73. package/example/vue-demo/node_modules/.vite/deps/_metadata.json +46 -0
  74. package/example/vue-demo/node_modules/.vite/deps/chunk-PZ5AY32C.js +10 -0
  75. package/example/vue-demo/node_modules/.vite/deps/chunk-PZ5AY32C.js.map +7 -0
  76. package/example/vue-demo/node_modules/.vite/deps/chunk-TCXBSQ4M.js +12877 -0
  77. package/example/vue-demo/node_modules/.vite/deps/chunk-TCXBSQ4M.js.map +7 -0
  78. package/example/vue-demo/node_modules/.vite/deps/clsx.js +22 -0
  79. package/example/vue-demo/node_modules/.vite/deps/clsx.js.map +7 -0
  80. package/example/vue-demo/node_modules/.vite/deps/lucide-vue-next.js +29720 -0
  81. package/example/vue-demo/node_modules/.vite/deps/lucide-vue-next.js.map +7 -0
  82. package/example/vue-demo/node_modules/.vite/deps/package.json +3 -0
  83. package/example/vue-demo/node_modules/.vite/deps/radix-vue.js +24321 -0
  84. package/example/vue-demo/node_modules/.vite/deps/radix-vue.js.map +7 -0
  85. package/example/vue-demo/node_modules/.vite/deps/tailwind-merge.js +2534 -0
  86. package/example/vue-demo/node_modules/.vite/deps/tailwind-merge.js.map +7 -0
  87. package/example/vue-demo/node_modules/.vite/deps/vue.js +348 -0
  88. package/example/vue-demo/node_modules/.vite/deps/vue.js.map +7 -0
  89. package/example/vue-demo/package.json +20 -0
  90. package/example/vue-demo/postcss.config.mjs +6 -0
  91. package/example/vue-demo/src/App.vue +61 -0
  92. package/example/vue-demo/src/env.d.ts +1 -0
  93. package/example/vue-demo/src/main.ts +5 -0
  94. package/example/vue-demo/src/style.css +102 -0
  95. package/example/vue-demo/tailwind.config.mjs +50 -0
  96. package/example/vue-demo/tsconfig.json +15 -0
  97. package/example/vue-demo/vite.config.ts +14 -0
  98. package/package.json +7 -2
  99. package/packages/core/dist/{chunk-KQR3LFND.js → chunk-GQASS6PM.js} +20 -0
  100. package/packages/core/dist/index.cjs +20 -0
  101. package/packages/core/dist/index.js +1 -1
  102. package/packages/core/dist/utils.cjs +20 -0
  103. package/packages/core/dist/utils.js +1 -1
  104. package/packages/core/node_modules/.vite/vitest/results.json +1 -1
  105. package/packages/core/src/utils.ts +21 -0
  106. package/packages/core/test/utils.test.ts +6 -0
  107. package/packages/react/dist/index.cjs +22 -13
  108. package/packages/react/dist/index.js +22 -13
  109. package/packages/react/node_modules/.bin/vite +4 -4
  110. package/packages/react/node_modules/.vite/vitest/results.json +1 -1
  111. package/packages/react/src/components/InputRenderer.tsx +23 -12
  112. package/packages/react/test/FormFieldRenderer.test.tsx +10 -0
  113. package/packages/vue/dist/index.js +1 -1
  114. package/packages/vue/dist/index.mjs +9 -3
  115. package/packages/vue/node_modules/.vite/vitest/results.json +1 -1
  116. package/packages/vue/src/components/InputRenderer.vue +10 -1
  117. package/packages/vue/test/FormFieldRenderer.test.ts +11 -0
  118. package/pnpm-workspace.yaml +1 -1
@@ -26,6 +26,12 @@ __export(index_exports, {
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
28
28
  // src/utils.ts
29
+ function parseNumberish(value) {
30
+ if (typeof value === "number") return Number.isNaN(value) ? null : value;
31
+ if (typeof value !== "string" || value.trim() === "") return null;
32
+ const parsed = Number(value);
33
+ return Number.isNaN(parsed) ? null : parsed;
34
+ }
29
35
  function validateField(value, field) {
30
36
  if (!field.validations) return null;
31
37
  for (const rule of field.validations) {
@@ -36,6 +42,13 @@ function validateField(value, field) {
36
42
  }
37
43
  break;
38
44
  case "min":
45
+ if (field.type === "number") {
46
+ const numericValue = parseNumberish(value);
47
+ if (numericValue !== null && numericValue < rule.value) {
48
+ return rule.message || `${field.label} must be at least ${rule.value}`;
49
+ }
50
+ break;
51
+ }
39
52
  if (typeof value === "number" && value < rule.value) {
40
53
  return rule.message || `${field.label} must be at least ${rule.value}`;
41
54
  }
@@ -44,6 +57,13 @@ function validateField(value, field) {
44
57
  }
45
58
  break;
46
59
  case "max":
60
+ if (field.type === "number") {
61
+ const numericValue = parseNumberish(value);
62
+ if (numericValue !== null && numericValue > rule.value) {
63
+ return rule.message || `${field.label} must be at most ${rule.value}`;
64
+ }
65
+ break;
66
+ }
47
67
  if (typeof value === "number" && value > rule.value) {
48
68
  return rule.message || `${field.label} must be at most ${rule.value}`;
49
69
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getDefaultValues,
3
3
  validateField
4
- } from "./chunk-KQR3LFND.js";
4
+ } from "./chunk-GQASS6PM.js";
5
5
  export {
6
6
  getDefaultValues,
7
7
  validateField
@@ -24,6 +24,12 @@ __export(utils_exports, {
24
24
  validateField: () => validateField
25
25
  });
26
26
  module.exports = __toCommonJS(utils_exports);
27
+ function parseNumberish(value) {
28
+ if (typeof value === "number") return Number.isNaN(value) ? null : value;
29
+ if (typeof value !== "string" || value.trim() === "") return null;
30
+ const parsed = Number(value);
31
+ return Number.isNaN(parsed) ? null : parsed;
32
+ }
27
33
  function validateField(value, field) {
28
34
  if (!field.validations) return null;
29
35
  for (const rule of field.validations) {
@@ -34,6 +40,13 @@ function validateField(value, field) {
34
40
  }
35
41
  break;
36
42
  case "min":
43
+ if (field.type === "number") {
44
+ const numericValue = parseNumberish(value);
45
+ if (numericValue !== null && numericValue < rule.value) {
46
+ return rule.message || `${field.label} must be at least ${rule.value}`;
47
+ }
48
+ break;
49
+ }
37
50
  if (typeof value === "number" && value < rule.value) {
38
51
  return rule.message || `${field.label} must be at least ${rule.value}`;
39
52
  }
@@ -42,6 +55,13 @@ function validateField(value, field) {
42
55
  }
43
56
  break;
44
57
  case "max":
58
+ if (field.type === "number") {
59
+ const numericValue = parseNumberish(value);
60
+ if (numericValue !== null && numericValue > rule.value) {
61
+ return rule.message || `${field.label} must be at most ${rule.value}`;
62
+ }
63
+ break;
64
+ }
45
65
  if (typeof value === "number" && value > rule.value) {
46
66
  return rule.message || `${field.label} must be at most ${rule.value}`;
47
67
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getDefaultValues,
3
3
  validateField
4
- } from "./chunk-KQR3LFND.js";
4
+ } from "./chunk-GQASS6PM.js";
5
5
  export {
6
6
  getDefaultValues,
7
7
  validateField
@@ -1 +1 @@
1
- {"version":"1.6.1","results":[[":test/utils.test.ts",{"duration":2,"failed":false}]]}
1
+ {"version":"1.6.1","results":[[":test/utils.test.ts",{"duration":3,"failed":false}]]}
@@ -1,5 +1,12 @@
1
1
  import type { FormField } from './types';
2
2
 
3
+ function parseNumberish(value: unknown): number | null {
4
+ if (typeof value === 'number') return Number.isNaN(value) ? null : value;
5
+ if (typeof value !== 'string' || value.trim() === '') return null;
6
+ const parsed = Number(value);
7
+ return Number.isNaN(parsed) ? null : parsed;
8
+ }
9
+
3
10
  export function validateField(value: any, field: FormField): string | null {
4
11
  if (!field.validations) return null;
5
12
 
@@ -11,6 +18,13 @@ export function validateField(value: any, field: FormField): string | null {
11
18
  }
12
19
  break;
13
20
  case 'min':
21
+ if (field.type === 'number') {
22
+ const numericValue = parseNumberish(value);
23
+ if (numericValue !== null && numericValue < rule.value) {
24
+ return rule.message || `${field.label} must be at least ${rule.value}`;
25
+ }
26
+ break;
27
+ }
14
28
  if (typeof value === 'number' && value < rule.value) {
15
29
  return rule.message || `${field.label} must be at least ${rule.value}`;
16
30
  }
@@ -19,6 +33,13 @@ export function validateField(value: any, field: FormField): string | null {
19
33
  }
20
34
  break;
21
35
  case 'max':
36
+ if (field.type === 'number') {
37
+ const numericValue = parseNumberish(value);
38
+ if (numericValue !== null && numericValue > rule.value) {
39
+ return rule.message || `${field.label} must be at most ${rule.value}`;
40
+ }
41
+ break;
42
+ }
22
43
  if (typeof value === 'number' && value > rule.value) {
23
44
  return rule.message || `${field.label} must be at most ${rule.value}`;
24
45
  }
@@ -40,6 +40,12 @@ describe('core utils - validateField', () => {
40
40
  expect(validateField(10, fieldNum)).toBeNull();
41
41
  expect(validateField(21, fieldNum)).toBe('F2 must be at most 20');
42
42
  });
43
+
44
+ it('treats numeric strings as numbers for number fields', () => {
45
+ expect(validateField('9', fieldNum)).toBe('F2 must be at least 10');
46
+ expect(validateField('10', fieldNum)).toBeNull();
47
+ expect(validateField('22', fieldNum)).toBe('F2 must be at most 20');
48
+ });
43
49
  });
44
50
 
45
51
  describe('email', () => {
@@ -249,19 +249,28 @@ Label.displayName = LabelPrimitive.Root.displayName;
249
249
 
250
250
  // src/components/InputRenderer.tsx
251
251
  var import_jsx_runtime7 = require("react/jsx-runtime");
252
- var InputRenderer = ({ field, value, onChange, onBlur, fieldId }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
253
- Input,
254
- {
255
- id: fieldId,
256
- type: field.type,
257
- placeholder: field.placeholder,
258
- value: value ?? "",
259
- onChange: (e) => onChange(e.target.value),
260
- onBlur,
261
- disabled: field.disabled,
262
- name: field.name
263
- }
264
- );
252
+ var InputRenderer = ({ field, value, onChange, onBlur, fieldId }) => {
253
+ const handleChange = (nextValue) => {
254
+ if (field.type !== "number") {
255
+ onChange(nextValue);
256
+ return;
257
+ }
258
+ onChange(nextValue === "" ? "" : Number(nextValue));
259
+ };
260
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
261
+ Input,
262
+ {
263
+ id: fieldId,
264
+ type: field.type,
265
+ placeholder: field.placeholder,
266
+ value: value ?? "",
267
+ onChange: (e) => handleChange(e.target.value),
268
+ onBlur,
269
+ disabled: field.disabled,
270
+ name: field.name
271
+ }
272
+ );
273
+ };
265
274
  var InputRenderer_default = InputRenderer;
266
275
 
267
276
  // src/components/TextareaRenderer.tsx
@@ -194,19 +194,28 @@ Label.displayName = LabelPrimitive.Root.displayName;
194
194
 
195
195
  // src/components/InputRenderer.tsx
196
196
  import { jsx as jsx7 } from "react/jsx-runtime";
197
- var InputRenderer = ({ field, value, onChange, onBlur, fieldId }) => /* @__PURE__ */ jsx7(
198
- Input,
199
- {
200
- id: fieldId,
201
- type: field.type,
202
- placeholder: field.placeholder,
203
- value: value ?? "",
204
- onChange: (e) => onChange(e.target.value),
205
- onBlur,
206
- disabled: field.disabled,
207
- name: field.name
208
- }
209
- );
197
+ var InputRenderer = ({ field, value, onChange, onBlur, fieldId }) => {
198
+ const handleChange = (nextValue) => {
199
+ if (field.type !== "number") {
200
+ onChange(nextValue);
201
+ return;
202
+ }
203
+ onChange(nextValue === "" ? "" : Number(nextValue));
204
+ };
205
+ return /* @__PURE__ */ jsx7(
206
+ Input,
207
+ {
208
+ id: fieldId,
209
+ type: field.type,
210
+ placeholder: field.placeholder,
211
+ value: value ?? "",
212
+ onChange: (e) => handleChange(e.target.value),
213
+ onBlur,
214
+ disabled: field.disabled,
215
+ name: field.name
216
+ }
217
+ );
218
+ };
210
219
  var InputRenderer_default = InputRenderer;
211
220
 
212
221
  // src/components/TextareaRenderer.tsx
@@ -6,12 +6,12 @@ case `uname` in
6
6
  esac
7
7
 
8
8
  if [ -z "$NODE_PATH" ]; then
9
- export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules"
9
+ export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules"
10
10
  else
11
- export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules:$NODE_PATH"
11
+ export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules:$NODE_PATH"
12
12
  fi
13
13
  if [ -x "$basedir/node" ]; then
14
- exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/vite.js" "$@"
14
+ exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/bin/vite.js" "$@"
15
15
  else
16
- exec node "$basedir/../../../../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/vite.js" "$@"
16
+ exec node "$basedir/../../../../node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/bin/vite.js" "$@"
17
17
  fi
@@ -1 +1 @@
1
- {"version":"1.6.1","results":[[":test/FormFieldRenderer.test.tsx",{"duration":123,"failed":false}],[":test/DynamicForm.test.tsx",{"duration":29,"failed":false}]]}
1
+ {"version":"1.6.1","results":[[":test/DynamicForm.test.tsx",{"duration":30,"failed":false}],[":test/FormFieldRenderer.test.tsx",{"duration":129,"failed":false}]]}
@@ -2,17 +2,28 @@ import React from 'react';
2
2
  import type { FieldRenderContext } from './types';
3
3
  import { Input } from './Input';
4
4
 
5
- const InputRenderer: React.FC<FieldRenderContext> = ({ field, value, onChange, onBlur, fieldId }) => (
6
- <Input
7
- id={fieldId}
8
- type={field.type}
9
- placeholder={field.placeholder}
10
- value={value ?? ''}
11
- onChange={(e) => onChange(e.target.value)}
12
- onBlur={onBlur}
13
- disabled={field.disabled}
14
- name={field.name}
15
- />
16
- );
5
+ const InputRenderer: React.FC<FieldRenderContext> = ({ field, value, onChange, onBlur, fieldId }) => {
6
+ const handleChange = (nextValue: string) => {
7
+ if (field.type !== 'number') {
8
+ onChange(nextValue);
9
+ return;
10
+ }
11
+
12
+ onChange(nextValue === '' ? '' : Number(nextValue));
13
+ };
14
+
15
+ return (
16
+ <Input
17
+ id={fieldId}
18
+ type={field.type}
19
+ placeholder={field.placeholder}
20
+ value={value ?? ''}
21
+ onChange={(e) => handleChange(e.target.value)}
22
+ onBlur={onBlur}
23
+ disabled={field.disabled}
24
+ name={field.name}
25
+ />
26
+ );
27
+ };
17
28
 
18
29
  export default InputRenderer;
@@ -25,6 +25,16 @@ describe('FormFieldRenderer', () => {
25
25
  expect(onChange).toHaveBeenCalledWith('new value');
26
26
  });
27
27
 
28
+ it('emits number for number inputs', () => {
29
+ const field: FormField = { ...defaultField, type: 'number' };
30
+ const onChange = vi.fn();
31
+ render(<FormFieldRenderer field={field} value={22} onChange={onChange} />);
32
+
33
+ const input = screen.getByLabelText('Test Label') as HTMLInputElement;
34
+ fireEvent.change(input, { target: { value: '23' } });
35
+ expect(onChange).toHaveBeenCalledWith(23);
36
+ });
37
+
28
38
  it('renders a textarea correctly', () => {
29
39
  const field: FormField = { ...defaultField, type: 'textarea' };
30
40
  const onChange = vi.fn();