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,14 @@
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/esbuild@0.27.3/node_modules/esbuild/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/esbuild@0.27.3/node_modules/esbuild/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/esbuild@0.27.3/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/esbuild@0.27.3/node_modules/esbuild/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/esbuild@0.27.3/node_modules/esbuild/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/esbuild@0.27.3/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ "$basedir/../../../../node_modules/.pnpm/esbuild@0.27.3/node_modules/esbuild/bin/esbuild" "$@"
14
+ exit $?
@@ -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/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/typescript@5.9.3/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/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/typescript@5.9.3/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/../typescript/bin/tsc" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsc" "$@"
17
+ fi
@@ -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/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/typescript@5.9.3/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/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/typescript@5.9.3/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/../typescript/bin/tsserver" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
17
+ fi
@@ -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/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/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/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/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/../tsup/dist/cli-default.js" "$@"
15
+ else
16
+ exec node "$basedir/../tsup/dist/cli-default.js" "$@"
17
+ fi
@@ -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/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/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/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/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/../tsup/dist/cli-node.js" "$@"
15
+ else
16
+ exec node "$basedir/../tsup/dist/cli-node.js" "$@"
17
+ fi
@@ -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/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"
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"
12
+ fi
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" "$@"
15
+ else
16
+ exec node "$basedir/../../../../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/vite.js" "$@"
17
+ fi
@@ -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/vitest@1.6.1_@types+node@20.19.35_jsdom@22.1.0/node_modules/vitest/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.35_jsdom@22.1.0/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/vitest@1.6.1_@types+node@20.19.35_jsdom@22.1.0/node_modules/vitest/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.35_jsdom@22.1.0/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/../vitest/vitest.mjs" "$@"
15
+ else
16
+ exec node "$basedir/../vitest/vitest.mjs" "$@"
17
+ fi
@@ -0,0 +1 @@
1
+ {"version":"1.6.1","results":[[":src/DynamicForm.test.tsx",{"duration":26,"failed":false}]]}
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "pdyform-react",
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 src/index.tsx --format cjs,esm --dts --clean --external react --external pdyform",
18
+ "dev": "tsup src/index.tsx --format cjs,esm --watch --dts --external react --external pdyform",
19
+ "test": "vitest run",
20
+ "lint": "eslint src/**/*.{ts,tsx} --no-error-on-unmatched-pattern"
21
+ },
22
+ "dependencies": {
23
+ "clsx": "^2.0.0",
24
+ "lucide-react": "^0.300.0",
25
+ "pdyform": "workspace:*",
26
+ "tailwind-merge": "^2.0.0"
27
+ },
28
+ "peerDependencies": {
29
+ "react": "^18.0.0",
30
+ "react-dom": "^18.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@testing-library/jest-dom": "^6.9.1",
34
+ "@testing-library/react": "^16.3.2",
35
+ "@types/react": "^18.0.0",
36
+ "@types/react-dom": "^18.0.0",
37
+ "@vitejs/plugin-react": "^5.1.4",
38
+ "jsdom": "^22.1.0",
39
+ "react": "^18.0.0",
40
+ "react-dom": "^18.0.0",
41
+ "tsup": "^8.0.0",
42
+ "typescript": "^5.0.0",
43
+ "vitest": "^1.0.0"
44
+ }
45
+ }
@@ -0,0 +1,25 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect } from 'vitest';
3
+ import { render, screen, fireEvent } from '@testing-library/react';
4
+ import { DynamicForm } from '../src/DynamicForm';
5
+ import React from 'react';
6
+
7
+ describe('React DynamicForm', () => {
8
+ const schema: any = {
9
+ fields: [
10
+ { name: 'username', label: 'Username', type: 'text', validations: [{ type: 'required', message: 'Required' }] }
11
+ ]
12
+ };
13
+
14
+ it('renders correctly', () => {
15
+ render(<DynamicForm schema={schema} onSubmit={() => {}} />);
16
+ expect(screen.getByLabelText('Username')).toBeTruthy();
17
+ });
18
+
19
+ it('shows validation error on blur', async () => {
20
+ render(<DynamicForm schema={schema} onSubmit={() => {}} />);
21
+ const input = screen.getByLabelText('Username');
22
+ fireEvent.blur(input);
23
+ expect(await screen.findByText('Required')).toBeTruthy();
24
+ });
25
+ });
@@ -0,0 +1,93 @@
1
+ import React, { useState } from 'react';
2
+ import { FormSchema, validateField, getDefaultValues } from 'pdyform/core';
3
+ import { FormFieldRenderer } from './FormFieldRenderer';
4
+
5
+ interface DynamicFormProps {
6
+ schema: FormSchema;
7
+ onSubmit: (values: Record<string, any>) => void;
8
+ className?: string;
9
+ }
10
+
11
+ export const DynamicForm: React.FC<DynamicFormProps> = ({ schema, onSubmit, className }) => {
12
+ const [values, setValues] = useState<Record<string, any>>(getDefaultValues(schema.fields));
13
+ const [errors, setErrors] = useState<Record<string, string>>({});
14
+ const [isSubmitting, setIsSubmitting] = useState(false);
15
+
16
+ const handleFieldChange = (name: string, value: any) => {
17
+ setValues((prev) => ({ ...prev, [name]: value }));
18
+
19
+ // Validate on change
20
+ const field = schema.fields.find(f => f.name === name);
21
+ if (field) {
22
+ const error = validateField(value, field);
23
+ setErrors((prev) => ({
24
+ ...prev,
25
+ [name]: error || ''
26
+ }));
27
+ }
28
+ };
29
+
30
+ const handleFieldBlur = (name: string) => {
31
+ const field = schema.fields.find(f => f.name === name);
32
+ if (field) {
33
+ const error = validateField(values[name], field);
34
+ setErrors((prev) => ({
35
+ ...prev,
36
+ [name]: error || ''
37
+ }));
38
+ }
39
+ };
40
+
41
+ const handleSubmit = (e: React.FormEvent) => {
42
+ e.preventDefault();
43
+ setIsSubmitting(true);
44
+
45
+ const newErrors: Record<string, string> = {};
46
+ let hasError = false;
47
+
48
+ schema.fields.forEach((field) => {
49
+ const error = validateField(values[field.name], field);
50
+ if (error) {
51
+ newErrors[field.name] = error;
52
+ hasError = true;
53
+ }
54
+ });
55
+
56
+ setErrors(newErrors);
57
+
58
+ if (!hasError) {
59
+ onSubmit(values);
60
+ }
61
+ setIsSubmitting(false);
62
+ };
63
+
64
+ return (
65
+ <form onSubmit={handleSubmit} className={`space-y-6 ${className || ''}`}>
66
+ {schema.title && <h2 className="text-2xl font-bold tracking-tight">{schema.title}</h2>}
67
+ {schema.description && <p className="text-muted-foreground">{schema.description}</p>}
68
+
69
+ <div className="space-y-4">
70
+ {schema.fields.map((field) => (
71
+ !field.hidden && (
72
+ <FormFieldRenderer
73
+ key={field.name}
74
+ field={field}
75
+ value={values[field.name]}
76
+ onChange={(val) => handleFieldChange(field.name, val)}
77
+ onBlur={() => handleFieldBlur(field.name)}
78
+ error={errors[field.name]}
79
+ />
80
+ )
81
+ ))}
82
+ </div>
83
+
84
+ <button
85
+ type="submit"
86
+ disabled={isSubmitting}
87
+ 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"
88
+ >
89
+ {isSubmitting ? 'Submitting...' : (schema.submitButtonText || 'Submit')}
90
+ </button>
91
+ </form>
92
+ );
93
+ };
@@ -0,0 +1,130 @@
1
+ import React from 'react';
2
+ import { FormField } from 'pdyform/core';
3
+
4
+ interface FormFieldRendererProps {
5
+ field: FormField;
6
+ value: any;
7
+ onChange: (value: any) => void;
8
+ onBlur?: () => void;
9
+ error?: string;
10
+ }
11
+
12
+ export const FormFieldRenderer: React.FC<FormFieldRendererProps> = ({ field, value, onChange, onBlur, error }) => {
13
+ const { type, label, placeholder, options, description, disabled, name } = field;
14
+ const fieldId = `field-${name}`;
15
+
16
+ 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";
17
+
18
+ const renderInput = () => {
19
+ switch (type) {
20
+ case 'textarea':
21
+ return (
22
+ <textarea
23
+ id={fieldId}
24
+ className={`${baseInputClasses} min-h-[80px]`}
25
+ placeholder={placeholder}
26
+ value={value || ''}
27
+ onChange={(e) => onChange(e.target.value)}
28
+ onBlur={onBlur}
29
+ disabled={disabled}
30
+ name={name}
31
+ />
32
+ );
33
+ case 'select':
34
+ return (
35
+ <select
36
+ id={fieldId}
37
+ className={baseInputClasses}
38
+ value={value || ''}
39
+ onChange={(e) => onChange(e.target.value)}
40
+ onBlur={onBlur}
41
+ disabled={disabled}
42
+ name={name}
43
+ >
44
+ <option value="" disabled>{placeholder || 'Select an option'}</option>
45
+ {options?.map((opt) => (
46
+ <option key={opt.value} value={opt.value}>
47
+ {opt.label}
48
+ </option>
49
+ ))}
50
+ </select>
51
+ );
52
+ case 'checkbox':
53
+ return (
54
+ <div className="flex flex-wrap gap-4">
55
+ {options?.map((opt) => (
56
+ <label key={opt.value} className="flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
57
+ <input
58
+ type="checkbox"
59
+ className="h-4 w-4 rounded border-primary text-primary focus:ring-primary"
60
+ checked={Array.isArray(value) && value.includes(opt.value)}
61
+ onChange={(e) => {
62
+ const newValue = Array.isArray(value) ? [...value] : [];
63
+ if (e.target.checked) {
64
+ newValue.push(opt.value);
65
+ } else {
66
+ const index = newValue.indexOf(opt.value);
67
+ if (index > -1) newValue.splice(index, 1);
68
+ }
69
+ onChange(newValue);
70
+ }}
71
+ onBlur={onBlur}
72
+ disabled={disabled}
73
+ />
74
+ <span>{opt.label}</span>
75
+ </label>
76
+ ))}
77
+ </div>
78
+ );
79
+ case 'radio':
80
+ return (
81
+ <div className="flex flex-wrap gap-4">
82
+ {options?.map((opt) => (
83
+ <label key={opt.value} className="flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
84
+ <input
85
+ type="radio"
86
+ className="h-4 w-4 border-primary text-primary focus:ring-primary"
87
+ name={field.name}
88
+ checked={value === opt.value}
89
+ onChange={() => onChange(opt.value)}
90
+ onBlur={onBlur}
91
+ disabled={disabled}
92
+ />
93
+ <span>{opt.label}</span>
94
+ </label>
95
+ ))}
96
+ </div>
97
+ );
98
+ default:
99
+ return (
100
+ <input
101
+ id={fieldId}
102
+ type={type}
103
+ className={baseInputClasses}
104
+ placeholder={placeholder}
105
+ value={value || ''}
106
+ onChange={(e) => onChange(e.target.value)}
107
+ onBlur={onBlur}
108
+ disabled={disabled}
109
+ name={name}
110
+ />
111
+ );
112
+ }
113
+ };
114
+
115
+ return (
116
+ <div className={`space-y-2 ${field.className || ''}`}>
117
+ {label && (
118
+ <label
119
+ htmlFor={fieldId}
120
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
121
+ >
122
+ {label}
123
+ </label>
124
+ )}
125
+ {renderInput()}
126
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
127
+ {error && <p className="text-sm font-medium text-destructive">{error}</p>}
128
+ </div>
129
+ );
130
+ };
@@ -0,0 +1,2 @@
1
+ export * from './DynamicForm';
2
+ export * from './FormFieldRenderer';
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": false,
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "outDir": "./dist",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ },
13
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
14
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
15
+ }
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ test: {
8
+ environment: 'jsdom',
9
+ globals: true,
10
+ },
11
+ resolve: {
12
+ alias: {
13
+ 'pdyform/core': path.resolve(__dirname, '../core/src'),
14
+ },
15
+ },
16
+ });
@@ -0,0 +1 @@
1
+ (function(d,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("pdyform/core")):typeof define=="function"&&define.amd?define(["exports","vue","pdyform/core"],e):(d=typeof globalThis<"u"?globalThis:d||self,e(d.PdDynamicFormVue={},d.Vue,d.PdyformCore))})(this,function(d,e,u){"use strict";const g=["for"],p=["id","placeholder","disabled","name","value"],B=["id","disabled","name","value"],x={value:"",disabled:""},E=["value"],V={key:3,class:"flex flex-wrap gap-4"},C=["disabled","checked","onChange"],S={key:4,class:"flex flex-wrap gap-4"},N=["disabled","name","checked","onChange"],F=["id","type","placeholder","disabled","name","value"],D={key:6,class:"text-sm text-muted-foreground"},w={key:7,class:"text-sm font-medium text-destructive"},b="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",h=e.defineComponent({__name:"FormFieldRenderer",props:{field:{},modelValue:{},error:{}},emits:["update:modelValue"],setup(l,{emit:k}){const c=l,y=k,o=e.computed(()=>`field-${c.field.name}`),r=m=>{y("update:modelValue",m)},f=(m,i)=>{const t=Array.isArray(c.modelValue)?[...c.modelValue]:[];if(i)t.push(m);else{const a=t.indexOf(m);a>-1&&t.splice(a,1)}r(t)};return(m,i)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["space-y-2",l.field.className])},[l.field.label?(e.openBlock(),e.createElementBlock("label",{key:0,for:o.value,class:"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"},e.toDisplayString(l.field.label),9,g)):e.createCommentVNode("",!0),l.field.type==="textarea"?(e.openBlock(),e.createElementBlock("textarea",{key:1,id:o.value,class:e.normalizeClass([b,"min-h-[80px]"]),placeholder:l.field.placeholder,disabled:l.field.disabled,name:l.field.name,value:l.modelValue,onInput:i[0]||(i[0]=t=>r(t.target.value))},null,42,p)):l.field.type==="select"?(e.openBlock(),e.createElementBlock("select",{key:2,id:o.value,class:e.normalizeClass(b),disabled:l.field.disabled,name:l.field.name,value:l.modelValue,onChange:i[1]||(i[1]=t=>r(t.target.value))},[e.createElementVNode("option",x,e.toDisplayString(l.field.placeholder||"Select an option"),1),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(l.field.options,t=>(e.openBlock(),e.createElementBlock("option",{key:t.value,value:t.value},e.toDisplayString(t.label),9,E))),128))],40,B)):l.field.type==="checkbox"?(e.openBlock(),e.createElementBlock("div",V,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(l.field.options,t=>(e.openBlock(),e.createElementBlock("label",{key:t.value,class:"flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"},[e.createElementVNode("input",{type:"checkbox",class:"h-4 w-4 rounded border-primary text-primary focus:ring-primary",disabled:l.field.disabled,checked:Array.isArray(l.modelValue)&&l.modelValue.includes(t.value),onChange:a=>f(t.value,a.target.checked)},null,40,C),e.createElementVNode("span",null,e.toDisplayString(t.label),1)]))),128))])):l.field.type==="radio"?(e.openBlock(),e.createElementBlock("div",S,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(l.field.options,t=>(e.openBlock(),e.createElementBlock("label",{key:t.value,class:"flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"},[e.createElementVNode("input",{type:"radio",class:"h-4 w-4 border-primary text-primary focus:ring-primary",disabled:l.field.disabled,name:l.field.name,checked:l.modelValue===t.value,onChange:a=>r(t.value)},null,40,N),e.createElementVNode("span",null,e.toDisplayString(t.label),1)]))),128))])):(e.openBlock(),e.createElementBlock("input",{key:5,id:o.value,type:l.field.type,class:e.normalizeClass(b),placeholder:l.field.placeholder,disabled:l.field.disabled,name:l.field.name,value:l.modelValue,onInput:i[2]||(i[2]=t=>r(t.target.value))},null,40,F)),l.field.description?(e.openBlock(),e.createElementBlock("p",D,e.toDisplayString(l.field.description),1)):e.createCommentVNode("",!0),l.error?(e.openBlock(),e.createElementBlock("p",w,e.toDisplayString(l.error),1)):e.createCommentVNode("",!0)],2))}}),$={key:0,class:"space-y-1"},z={key:0,class:"text-2xl font-bold tracking-tight"},A={key:1,class:"text-muted-foreground"},I={class:"space-y-4"},L=["disabled"],T=e.defineComponent({__name:"DynamicForm",props:{schema:{},className:{}},emits:["submit"],setup(l,{emit:k}){const c=l,y=k,o=e.ref({}),r=e.ref({}),f=e.ref(!1);e.onMounted(()=>{o.value=u.getDefaultValues(c.schema.fields)});const m=(t,a)=>{o.value[t]=a;const n=c.schema.fields.find(s=>s.name===t);if(n){const s=u.validateField(a,n);r.value[t]=s||""}},i=()=>{f.value=!0;const t={};let a=!1;c.schema.fields.forEach(n=>{const s=u.validateField(o.value[n.name],n);s&&(t[n.name]=s,a=!0)}),r.value=t,a||y("submit",{...o.value}),f.value=!1};return(t,a)=>(e.openBlock(),e.createElementBlock("form",{class:e.normalizeClass(["space-y-6",l.className]),onSubmit:e.withModifiers(i,["prevent"])},[l.schema.title||l.schema.description?(e.openBlock(),e.createElementBlock("div",$,[l.schema.title?(e.openBlock(),e.createElementBlock("h2",z,e.toDisplayString(l.schema.title),1)):e.createCommentVNode("",!0),l.schema.description?(e.openBlock(),e.createElementBlock("p",A,e.toDisplayString(l.schema.description),1)):e.createCommentVNode("",!0)])):e.createCommentVNode("",!0),e.createElementVNode("div",I,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(l.schema.fields,n=>(e.openBlock(),e.createElementBlock(e.Fragment,{key:n.id},[n.hidden?e.createCommentVNode("",!0):(e.openBlock(),e.createBlock(h,{key:0,field:n,"model-value":o.value[n.name],error:r.value[n.name],"onUpdate:modelValue":s=>m(n.name,s)},null,8,["field","model-value","error","onUpdate:modelValue"]))],64))),128))]),e.createElementVNode("button",{type:"submit",disabled:f.value,class:"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"},e.toDisplayString(f.value?"Submitting...":l.schema.submitButtonText||"Submit"),9,L)],34))}});d.DynamicForm=T,d.FormFieldRenderer=h,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});