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.
- package/README.md +34 -0
- package/eslint.config.mjs +53 -0
- package/package.json +45 -0
- package/packages/core/dist/chunk-KQR3LFND.js +60 -0
- package/packages/core/dist/index.cjs +87 -0
- package/packages/core/dist/index.d.cts +2 -0
- package/packages/core/dist/index.d.ts +2 -0
- package/packages/core/dist/index.js +8 -0
- package/packages/core/dist/parser.cjs +1 -0
- package/packages/core/dist/parser.d.cts +2 -0
- package/packages/core/dist/parser.d.ts +2 -0
- package/packages/core/dist/parser.js +0 -0
- package/packages/core/dist/types.cjs +18 -0
- package/packages/core/dist/types.d.cts +39 -0
- package/packages/core/dist/types.d.ts +39 -0
- package/packages/core/dist/types.js +0 -0
- package/packages/core/dist/utils.cjs +85 -0
- package/packages/core/dist/utils.d.cts +6 -0
- package/packages/core/dist/utils.d.ts +6 -0
- package/packages/core/dist/utils.js +8 -0
- package/packages/core/node_modules/.bin/esbuild +14 -0
- package/packages/core/node_modules/.bin/tsc +17 -0
- package/packages/core/node_modules/.bin/tsserver +17 -0
- package/packages/core/node_modules/.bin/tsup +17 -0
- package/packages/core/node_modules/.bin/tsup-node +17 -0
- package/packages/core/node_modules/.bin/vitest +17 -0
- package/packages/core/node_modules/.vite/vitest/results.json +1 -0
- package/packages/core/package.json +30 -0
- package/packages/core/src/index.test.ts +37 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/parser.ts +0 -0
- package/packages/core/src/types.ts +42 -0
- package/packages/core/src/utils.ts +59 -0
- package/packages/core/tsconfig.json +15 -0
- package/packages/core/tsup.config.ts +9 -0
- package/packages/react/dist/index.cjs +217 -0
- package/packages/react/dist/index.d.cts +20 -0
- package/packages/react/dist/index.d.ts +20 -0
- package/packages/react/dist/index.js +189 -0
- package/packages/react/node_modules/.bin/browserslist +17 -0
- package/packages/react/node_modules/.bin/esbuild +14 -0
- package/packages/react/node_modules/.bin/tsc +17 -0
- package/packages/react/node_modules/.bin/tsserver +17 -0
- package/packages/react/node_modules/.bin/tsup +17 -0
- package/packages/react/node_modules/.bin/tsup-node +17 -0
- package/packages/react/node_modules/.bin/vite +17 -0
- package/packages/react/node_modules/.bin/vitest +17 -0
- package/packages/react/node_modules/.vite/vitest/results.json +1 -0
- package/packages/react/package.json +45 -0
- package/packages/react/src/DynamicForm.test.tsx +25 -0
- package/packages/react/src/DynamicForm.tsx +93 -0
- package/packages/react/src/FormFieldRenderer.tsx +130 -0
- package/packages/react/src/index.tsx +2 -0
- package/packages/react/tsconfig.json +15 -0
- package/packages/react/vitest.config.ts +16 -0
- package/packages/vue/dist/index.js +1 -0
- package/packages/vue/dist/index.mjs +183 -0
- package/packages/vue/node_modules/.bin/tsc +17 -0
- package/packages/vue/node_modules/.bin/tsserver +17 -0
- package/packages/vue/node_modules/.bin/vite +17 -0
- package/packages/vue/node_modules/.bin/vitest +17 -0
- package/packages/vue/node_modules/.bin/vue-tsc +17 -0
- package/packages/vue/node_modules/.vite/vitest/results.json +1 -0
- package/packages/vue/node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts +118 -0
- package/packages/vue/package.json +43 -0
- package/packages/vue/src/DynamicForm.test.ts +19 -0
- package/packages/vue/src/DynamicForm.vue +81 -0
- package/packages/vue/src/FormFieldRenderer.vue +114 -0
- package/packages/vue/src/env.d.ts +7 -0
- package/packages/vue/src/index.ts +2 -0
- package/packages/vue/tsconfig.json +15 -0
- package/packages/vue/vite.config.ts +28 -0
- package/packages/vue/vitest.config.ts +16 -0
- package/pnpm-workspace.yaml +4 -0
- package/tsconfig.json +21 -0
- 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
|
+
});
|
|
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,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
|