pdyform 1.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 (76) hide show
  1. package/README.md +34 -0
  2. package/eslint.config.mjs +53 -0
  3. package/package.json +45 -0
  4. package/packages/core/dist/chunk-KQR3LFND.js +60 -0
  5. package/packages/core/dist/index.cjs +87 -0
  6. package/packages/core/dist/index.d.cts +2 -0
  7. package/packages/core/dist/index.d.ts +2 -0
  8. package/packages/core/dist/index.js +8 -0
  9. package/packages/core/dist/parser.cjs +1 -0
  10. package/packages/core/dist/parser.d.cts +2 -0
  11. package/packages/core/dist/parser.d.ts +2 -0
  12. package/packages/core/dist/parser.js +0 -0
  13. package/packages/core/dist/types.cjs +18 -0
  14. package/packages/core/dist/types.d.cts +39 -0
  15. package/packages/core/dist/types.d.ts +39 -0
  16. package/packages/core/dist/types.js +0 -0
  17. package/packages/core/dist/utils.cjs +85 -0
  18. package/packages/core/dist/utils.d.cts +6 -0
  19. package/packages/core/dist/utils.d.ts +6 -0
  20. package/packages/core/dist/utils.js +8 -0
  21. package/packages/core/node_modules/.bin/esbuild +14 -0
  22. package/packages/core/node_modules/.bin/tsc +17 -0
  23. package/packages/core/node_modules/.bin/tsserver +17 -0
  24. package/packages/core/node_modules/.bin/tsup +17 -0
  25. package/packages/core/node_modules/.bin/tsup-node +17 -0
  26. package/packages/core/node_modules/.bin/vitest +17 -0
  27. package/packages/core/node_modules/.vite/vitest/results.json +1 -0
  28. package/packages/core/package.json +30 -0
  29. package/packages/core/src/index.test.ts +37 -0
  30. package/packages/core/src/index.ts +2 -0
  31. package/packages/core/src/parser.ts +0 -0
  32. package/packages/core/src/types.ts +42 -0
  33. package/packages/core/src/utils.ts +59 -0
  34. package/packages/core/tsconfig.json +15 -0
  35. package/packages/core/tsup.config.ts +9 -0
  36. package/packages/react/dist/index.cjs +217 -0
  37. package/packages/react/dist/index.d.cts +20 -0
  38. package/packages/react/dist/index.d.ts +20 -0
  39. package/packages/react/dist/index.js +189 -0
  40. package/packages/react/node_modules/.bin/browserslist +17 -0
  41. package/packages/react/node_modules/.bin/esbuild +14 -0
  42. package/packages/react/node_modules/.bin/tsc +17 -0
  43. package/packages/react/node_modules/.bin/tsserver +17 -0
  44. package/packages/react/node_modules/.bin/tsup +17 -0
  45. package/packages/react/node_modules/.bin/tsup-node +17 -0
  46. package/packages/react/node_modules/.bin/vite +17 -0
  47. package/packages/react/node_modules/.bin/vitest +17 -0
  48. package/packages/react/node_modules/.vite/vitest/results.json +1 -0
  49. package/packages/react/package.json +45 -0
  50. package/packages/react/src/DynamicForm.test.tsx +25 -0
  51. package/packages/react/src/DynamicForm.tsx +93 -0
  52. package/packages/react/src/FormFieldRenderer.tsx +130 -0
  53. package/packages/react/src/index.tsx +2 -0
  54. package/packages/react/tsconfig.json +15 -0
  55. package/packages/react/vitest.config.ts +16 -0
  56. package/packages/vue/dist/index.js +1 -0
  57. package/packages/vue/dist/index.mjs +183 -0
  58. package/packages/vue/node_modules/.bin/tsc +17 -0
  59. package/packages/vue/node_modules/.bin/tsserver +17 -0
  60. package/packages/vue/node_modules/.bin/vite +17 -0
  61. package/packages/vue/node_modules/.bin/vitest +17 -0
  62. package/packages/vue/node_modules/.bin/vue-tsc +17 -0
  63. package/packages/vue/node_modules/.vite/vitest/results.json +1 -0
  64. package/packages/vue/node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts +118 -0
  65. package/packages/vue/package.json +43 -0
  66. package/packages/vue/src/DynamicForm.test.ts +19 -0
  67. package/packages/vue/src/DynamicForm.vue +81 -0
  68. package/packages/vue/src/FormFieldRenderer.vue +114 -0
  69. package/packages/vue/src/env.d.ts +7 -0
  70. package/packages/vue/src/index.ts +2 -0
  71. package/packages/vue/tsconfig.json +15 -0
  72. package/packages/vue/vite.config.ts +28 -0
  73. package/packages/vue/vitest.config.ts +16 -0
  74. package/pnpm-workspace.yaml +4 -0
  75. package/tsconfig.json +21 -0
  76. package/turbo.json +17 -0
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "pdyform-core",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "homepage": "https://github.com/LaoChen1994/pdyform",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/LaoChen1994/pdyform"
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "dev": "tsup --watch",
19
+ "test": "vitest run",
20
+ "lint": "eslint src/**/*.ts --no-error-on-unmatched-pattern"
21
+ },
22
+ "dependencies": {
23
+ "pdyform": "workspace:*"
24
+ },
25
+ "devDependencies": {
26
+ "tsup": "^8.0.0",
27
+ "typescript": "^5.0.0",
28
+ "vitest": "^1.0.0"
29
+ }
30
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateField, getDefaultValues } from '../src';
3
+ import { FormField } from '../src/types';
4
+
5
+ describe('core utils', () => {
6
+ it('should validate required fields', () => {
7
+ const field: FormField = {
8
+ name: 'test',
9
+ label: 'Test',
10
+ type: 'text',
11
+ validations: [{ type: 'required', message: 'Required' }]
12
+ };
13
+ expect(validateField('', field)).toBe('Required');
14
+ expect(validateField('val', field)).toBeNull();
15
+ });
16
+
17
+ it('should validate regex', () => {
18
+ const field: FormField = {
19
+ name: 'test',
20
+ label: 'Test',
21
+ type: 'text',
22
+ validations: [{ type: 'pattern', value: '^[0-9]+$', message: 'Only numbers' }]
23
+ };
24
+ expect(validateField('abc', field)).toBe('Only numbers');
25
+ expect(validateField('123', field)).toBeNull();
26
+ });
27
+
28
+ it('should get default values', () => {
29
+ const schema = {
30
+ fields: [
31
+ { name: 'f1', label: 'L1', type: 'text', defaultValue: 'v1' },
32
+ { name: 'f2', label: 'L2', type: 'text' }
33
+ ]
34
+ } as any;
35
+ expect(getDefaultValues(schema.fields)).toEqual({ f1: 'v1', f2: '' });
36
+ });
37
+ });
@@ -0,0 +1,2 @@
1
+ export type * from './types';
2
+ export * from './utils';
File without changes
@@ -0,0 +1,42 @@
1
+ export type FieldType = 'text' | 'number' | 'email' | 'password' | 'select' | 'checkbox' | 'radio' | 'textarea' | 'date';
2
+
3
+ export interface ValidationRule {
4
+ type: 'required' | 'min' | 'max' | 'pattern' | 'email' | 'custom';
5
+ value?: any;
6
+ message?: string;
7
+ validator?: (value: any) => boolean | string;
8
+ }
9
+
10
+ export interface Option {
11
+ label: string;
12
+ value: string | number;
13
+ }
14
+
15
+ export interface FormField {
16
+ id: string;
17
+ name: string;
18
+ label: string;
19
+ type: FieldType;
20
+ placeholder?: string;
21
+ description?: string;
22
+ defaultValue?: any;
23
+ options?: Option[]; // For select, radio, checkbox
24
+ validations?: ValidationRule[];
25
+ hidden?: boolean;
26
+ disabled?: boolean;
27
+ className?: string; // CSS class for custom styling
28
+ }
29
+
30
+ export interface FormSchema {
31
+ title?: string;
32
+ description?: string;
33
+ fields: FormField[];
34
+ submitButtonText?: string;
35
+ }
36
+
37
+ export interface FormState {
38
+ values: Record<string, any>;
39
+ errors: Record<string, string>;
40
+ isSubmitting: boolean;
41
+ isValid: boolean;
42
+ }
@@ -0,0 +1,59 @@
1
+ import type { FormField } from './types';
2
+
3
+ export function validateField(value: any, field: FormField): string | null {
4
+ if (!field.validations) return null;
5
+
6
+ for (const rule of field.validations) {
7
+ switch (rule.type) {
8
+ case 'required':
9
+ if (value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0)) {
10
+ return rule.message || `${field.label} is required`;
11
+ }
12
+ break;
13
+ case 'min':
14
+ if (typeof value === 'number' && value < rule.value) {
15
+ return rule.message || `${field.label} must be at least ${rule.value}`;
16
+ }
17
+ if (typeof value === 'string' && value.length < rule.value) {
18
+ return rule.message || `${field.label} must be at least ${rule.value} characters`;
19
+ }
20
+ break;
21
+ case 'max':
22
+ if (typeof value === 'number' && value > rule.value) {
23
+ return rule.message || `${field.label} must be at most ${rule.value}`;
24
+ }
25
+ if (typeof value === 'string' && value.length > rule.value) {
26
+ return rule.message || `${field.label} must be at most ${rule.value} characters`;
27
+ }
28
+ break;
29
+ case 'email': {
30
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
31
+ if (value && !emailRegex.test(value)) {
32
+ return rule.message || 'Invalid email address';
33
+ }
34
+ break;
35
+ }
36
+ case 'pattern':
37
+ if (value && rule.value && !new RegExp(rule.value).test(value)) {
38
+ return rule.message || 'Invalid format';
39
+ }
40
+ break;
41
+ case 'custom':
42
+ if (rule.validator) {
43
+ const result = rule.validator(value);
44
+ if (typeof result === 'string') return result;
45
+ if (!result) return rule.message || 'Invalid value';
46
+ }
47
+ break;
48
+ }
49
+ }
50
+
51
+ return null;
52
+ }
53
+
54
+ export function getDefaultValues(fields: FormField[]): Record<string, any> {
55
+ return fields.reduce((acc, field) => {
56
+ acc[field.name] = field.defaultValue !== undefined ? field.defaultValue : (field.type === 'checkbox' ? [] : '');
57
+ return acc;
58
+ }, {} as Record<string, any>);
59
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": false,
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src/**/*.ts"],
14
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
15
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts', 'src/types.ts', 'src/utils.ts', 'src/parser.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ clean: true,
8
+ tsconfig: './tsconfig.json'
9
+ });
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.tsx
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DynamicForm: () => DynamicForm,
24
+ FormFieldRenderer: () => FormFieldRenderer
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/DynamicForm.tsx
29
+ var import_react = require("react");
30
+ var import_core = require("pdyform/core");
31
+
32
+ // src/FormFieldRenderer.tsx
33
+ var import_jsx_runtime = require("react/jsx-runtime");
34
+ var FormFieldRenderer = ({ field, value, onChange, onBlur, error }) => {
35
+ const { type, label, placeholder, options, description, disabled, name } = field;
36
+ const fieldId = `field-${name}`;
37
+ const baseInputClasses = "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50";
38
+ const renderInput = () => {
39
+ switch (type) {
40
+ case "textarea":
41
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
42
+ "textarea",
43
+ {
44
+ id: fieldId,
45
+ className: `${baseInputClasses} min-h-[80px]`,
46
+ placeholder,
47
+ value: value || "",
48
+ onChange: (e) => onChange(e.target.value),
49
+ onBlur,
50
+ disabled,
51
+ name
52
+ }
53
+ );
54
+ case "select":
55
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
56
+ "select",
57
+ {
58
+ id: fieldId,
59
+ className: baseInputClasses,
60
+ value: value || "",
61
+ onChange: (e) => onChange(e.target.value),
62
+ onBlur,
63
+ disabled,
64
+ name,
65
+ children: [
66
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", disabled: true, children: placeholder || "Select an option" }),
67
+ options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
68
+ ]
69
+ }
70
+ );
71
+ case "checkbox":
72
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-4", children: options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: [
73
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
74
+ "input",
75
+ {
76
+ type: "checkbox",
77
+ className: "h-4 w-4 rounded border-primary text-primary focus:ring-primary",
78
+ checked: Array.isArray(value) && value.includes(opt.value),
79
+ onChange: (e) => {
80
+ const newValue = Array.isArray(value) ? [...value] : [];
81
+ if (e.target.checked) {
82
+ newValue.push(opt.value);
83
+ } else {
84
+ const index = newValue.indexOf(opt.value);
85
+ if (index > -1) newValue.splice(index, 1);
86
+ }
87
+ onChange(newValue);
88
+ },
89
+ onBlur,
90
+ disabled
91
+ }
92
+ ),
93
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: opt.label })
94
+ ] }, opt.value)) });
95
+ case "radio":
96
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-4", children: options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: [
97
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
98
+ "input",
99
+ {
100
+ type: "radio",
101
+ className: "h-4 w-4 border-primary text-primary focus:ring-primary",
102
+ name: field.name,
103
+ checked: value === opt.value,
104
+ onChange: () => onChange(opt.value),
105
+ onBlur,
106
+ disabled
107
+ }
108
+ ),
109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: opt.label })
110
+ ] }, opt.value)) });
111
+ default:
112
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
113
+ "input",
114
+ {
115
+ id: fieldId,
116
+ type,
117
+ className: baseInputClasses,
118
+ placeholder,
119
+ value: value || "",
120
+ onChange: (e) => onChange(e.target.value),
121
+ onBlur,
122
+ disabled,
123
+ name
124
+ }
125
+ );
126
+ }
127
+ };
128
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `space-y-2 ${field.className || ""}`, children: [
129
+ label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
130
+ "label",
131
+ {
132
+ htmlFor: fieldId,
133
+ className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
134
+ children: label
135
+ }
136
+ ),
137
+ renderInput(),
138
+ description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm text-muted-foreground", children: description }),
139
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm font-medium text-destructive", children: error })
140
+ ] });
141
+ };
142
+
143
+ // src/DynamicForm.tsx
144
+ var import_jsx_runtime2 = require("react/jsx-runtime");
145
+ var DynamicForm = ({ schema, onSubmit, className }) => {
146
+ const [values, setValues] = (0, import_react.useState)((0, import_core.getDefaultValues)(schema.fields));
147
+ const [errors, setErrors] = (0, import_react.useState)({});
148
+ const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
149
+ const handleFieldChange = (name, value) => {
150
+ setValues((prev) => ({ ...prev, [name]: value }));
151
+ const field = schema.fields.find((f) => f.name === name);
152
+ if (field) {
153
+ const error = (0, import_core.validateField)(value, field);
154
+ setErrors((prev) => ({
155
+ ...prev,
156
+ [name]: error || ""
157
+ }));
158
+ }
159
+ };
160
+ const handleFieldBlur = (name) => {
161
+ const field = schema.fields.find((f) => f.name === name);
162
+ if (field) {
163
+ const error = (0, import_core.validateField)(values[name], field);
164
+ setErrors((prev) => ({
165
+ ...prev,
166
+ [name]: error || ""
167
+ }));
168
+ }
169
+ };
170
+ const handleSubmit = (e) => {
171
+ e.preventDefault();
172
+ setIsSubmitting(true);
173
+ const newErrors = {};
174
+ let hasError = false;
175
+ schema.fields.forEach((field) => {
176
+ const error = (0, import_core.validateField)(values[field.name], field);
177
+ if (error) {
178
+ newErrors[field.name] = error;
179
+ hasError = true;
180
+ }
181
+ });
182
+ setErrors(newErrors);
183
+ if (!hasError) {
184
+ onSubmit(values);
185
+ }
186
+ setIsSubmitting(false);
187
+ };
188
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, className: `space-y-6 ${className || ""}`, children: [
189
+ schema.title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "text-2xl font-bold tracking-tight", children: schema.title }),
190
+ schema.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-muted-foreground", children: schema.description }),
191
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-4", children: schema.fields.map((field) => !field.hidden && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
192
+ FormFieldRenderer,
193
+ {
194
+ field,
195
+ value: values[field.name],
196
+ onChange: (val) => handleFieldChange(field.name, val),
197
+ onBlur: () => handleFieldBlur(field.name),
198
+ error: errors[field.name]
199
+ },
200
+ field.name
201
+ )) }),
202
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
203
+ "button",
204
+ {
205
+ type: "submit",
206
+ disabled: isSubmitting,
207
+ className: "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full",
208
+ children: isSubmitting ? "Submitting..." : schema.submitButtonText || "Submit"
209
+ }
210
+ )
211
+ ] });
212
+ };
213
+ // Annotate the CommonJS export names for ESM import in node:
214
+ 0 && (module.exports = {
215
+ DynamicForm,
216
+ FormFieldRenderer
217
+ });
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { FormSchema, FormField } from 'pdyform/core';
3
+
4
+ interface DynamicFormProps {
5
+ schema: FormSchema;
6
+ onSubmit: (values: Record<string, any>) => void;
7
+ className?: string;
8
+ }
9
+ declare const DynamicForm: React.FC<DynamicFormProps>;
10
+
11
+ interface FormFieldRendererProps {
12
+ field: FormField;
13
+ value: any;
14
+ onChange: (value: any) => void;
15
+ onBlur?: () => void;
16
+ error?: string;
17
+ }
18
+ declare const FormFieldRenderer: React.FC<FormFieldRendererProps>;
19
+
20
+ export { DynamicForm, FormFieldRenderer };
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { FormSchema, FormField } from 'pdyform/core';
3
+
4
+ interface DynamicFormProps {
5
+ schema: FormSchema;
6
+ onSubmit: (values: Record<string, any>) => void;
7
+ className?: string;
8
+ }
9
+ declare const DynamicForm: React.FC<DynamicFormProps>;
10
+
11
+ interface FormFieldRendererProps {
12
+ field: FormField;
13
+ value: any;
14
+ onChange: (value: any) => void;
15
+ onBlur?: () => void;
16
+ error?: string;
17
+ }
18
+ declare const FormFieldRenderer: React.FC<FormFieldRendererProps>;
19
+
20
+ export { DynamicForm, FormFieldRenderer };
@@ -0,0 +1,189 @@
1
+ // src/DynamicForm.tsx
2
+ import { useState } from "react";
3
+ import { validateField, getDefaultValues } from "pdyform/core";
4
+
5
+ // src/FormFieldRenderer.tsx
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var FormFieldRenderer = ({ field, value, onChange, onBlur, error }) => {
8
+ const { type, label, placeholder, options, description, disabled, name } = field;
9
+ const fieldId = `field-${name}`;
10
+ const baseInputClasses = "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50";
11
+ const renderInput = () => {
12
+ switch (type) {
13
+ case "textarea":
14
+ return /* @__PURE__ */ jsx(
15
+ "textarea",
16
+ {
17
+ id: fieldId,
18
+ className: `${baseInputClasses} min-h-[80px]`,
19
+ placeholder,
20
+ value: value || "",
21
+ onChange: (e) => onChange(e.target.value),
22
+ onBlur,
23
+ disabled,
24
+ name
25
+ }
26
+ );
27
+ case "select":
28
+ return /* @__PURE__ */ jsxs(
29
+ "select",
30
+ {
31
+ id: fieldId,
32
+ className: baseInputClasses,
33
+ value: value || "",
34
+ onChange: (e) => onChange(e.target.value),
35
+ onBlur,
36
+ disabled,
37
+ name,
38
+ children: [
39
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder || "Select an option" }),
40
+ options?.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
41
+ ]
42
+ }
43
+ );
44
+ case "checkbox":
45
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-4", children: options?.map((opt) => /* @__PURE__ */ jsxs("label", { className: "flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: [
46
+ /* @__PURE__ */ jsx(
47
+ "input",
48
+ {
49
+ type: "checkbox",
50
+ className: "h-4 w-4 rounded border-primary text-primary focus:ring-primary",
51
+ checked: Array.isArray(value) && value.includes(opt.value),
52
+ onChange: (e) => {
53
+ const newValue = Array.isArray(value) ? [...value] : [];
54
+ if (e.target.checked) {
55
+ newValue.push(opt.value);
56
+ } else {
57
+ const index = newValue.indexOf(opt.value);
58
+ if (index > -1) newValue.splice(index, 1);
59
+ }
60
+ onChange(newValue);
61
+ },
62
+ onBlur,
63
+ disabled
64
+ }
65
+ ),
66
+ /* @__PURE__ */ jsx("span", { children: opt.label })
67
+ ] }, opt.value)) });
68
+ case "radio":
69
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-4", children: options?.map((opt) => /* @__PURE__ */ jsxs("label", { className: "flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: [
70
+ /* @__PURE__ */ jsx(
71
+ "input",
72
+ {
73
+ type: "radio",
74
+ className: "h-4 w-4 border-primary text-primary focus:ring-primary",
75
+ name: field.name,
76
+ checked: value === opt.value,
77
+ onChange: () => onChange(opt.value),
78
+ onBlur,
79
+ disabled
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsx("span", { children: opt.label })
83
+ ] }, opt.value)) });
84
+ default:
85
+ return /* @__PURE__ */ jsx(
86
+ "input",
87
+ {
88
+ id: fieldId,
89
+ type,
90
+ className: baseInputClasses,
91
+ placeholder,
92
+ value: value || "",
93
+ onChange: (e) => onChange(e.target.value),
94
+ onBlur,
95
+ disabled,
96
+ name
97
+ }
98
+ );
99
+ }
100
+ };
101
+ return /* @__PURE__ */ jsxs("div", { className: `space-y-2 ${field.className || ""}`, children: [
102
+ label && /* @__PURE__ */ jsx(
103
+ "label",
104
+ {
105
+ htmlFor: fieldId,
106
+ className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
107
+ children: label
108
+ }
109
+ ),
110
+ renderInput(),
111
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }),
112
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: error })
113
+ ] });
114
+ };
115
+
116
+ // src/DynamicForm.tsx
117
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
118
+ var DynamicForm = ({ schema, onSubmit, className }) => {
119
+ const [values, setValues] = useState(getDefaultValues(schema.fields));
120
+ const [errors, setErrors] = useState({});
121
+ const [isSubmitting, setIsSubmitting] = useState(false);
122
+ const handleFieldChange = (name, value) => {
123
+ setValues((prev) => ({ ...prev, [name]: value }));
124
+ const field = schema.fields.find((f) => f.name === name);
125
+ if (field) {
126
+ const error = validateField(value, field);
127
+ setErrors((prev) => ({
128
+ ...prev,
129
+ [name]: error || ""
130
+ }));
131
+ }
132
+ };
133
+ const handleFieldBlur = (name) => {
134
+ const field = schema.fields.find((f) => f.name === name);
135
+ if (field) {
136
+ const error = validateField(values[name], field);
137
+ setErrors((prev) => ({
138
+ ...prev,
139
+ [name]: error || ""
140
+ }));
141
+ }
142
+ };
143
+ const handleSubmit = (e) => {
144
+ e.preventDefault();
145
+ setIsSubmitting(true);
146
+ const newErrors = {};
147
+ let hasError = false;
148
+ schema.fields.forEach((field) => {
149
+ const error = validateField(values[field.name], field);
150
+ if (error) {
151
+ newErrors[field.name] = error;
152
+ hasError = true;
153
+ }
154
+ });
155
+ setErrors(newErrors);
156
+ if (!hasError) {
157
+ onSubmit(values);
158
+ }
159
+ setIsSubmitting(false);
160
+ };
161
+ return /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, className: `space-y-6 ${className || ""}`, children: [
162
+ schema.title && /* @__PURE__ */ jsx2("h2", { className: "text-2xl font-bold tracking-tight", children: schema.title }),
163
+ schema.description && /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground", children: schema.description }),
164
+ /* @__PURE__ */ jsx2("div", { className: "space-y-4", children: schema.fields.map((field) => !field.hidden && /* @__PURE__ */ jsx2(
165
+ FormFieldRenderer,
166
+ {
167
+ field,
168
+ value: values[field.name],
169
+ onChange: (val) => handleFieldChange(field.name, val),
170
+ onBlur: () => handleFieldBlur(field.name),
171
+ error: errors[field.name]
172
+ },
173
+ field.name
174
+ )) }),
175
+ /* @__PURE__ */ jsx2(
176
+ "button",
177
+ {
178
+ type: "submit",
179
+ disabled: isSubmitting,
180
+ className: "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full",
181
+ children: isSubmitting ? "Submitting..." : schema.submitButtonText || "Submit"
182
+ }
183
+ )
184
+ ] });
185
+ };
186
+ export {
187
+ DynamicForm,
188
+ FormFieldRenderer
189
+ };
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/browserslist@4.28.1/node_modules/browserslist/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/browserslist@4.28.1/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/browserslist@4.28.1/node_modules/browserslist/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/browserslist@4.28.1/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/browserslist@4.28.1/node_modules/browserslist/cli.js" "$@"
15
+ else
16
+ exec node "$basedir/../../../../node_modules/.pnpm/browserslist@4.28.1/node_modules/browserslist/cli.js" "$@"
17
+ fi