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,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,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"})});
|