diff --git a/packages/form/README.md b/packages/form/README.md
new file mode 100644
index 000000000..3848b9012
--- /dev/null
+++ b/packages/form/README.md
@@ -0,0 +1 @@
+# PrimeVue Form
diff --git a/packages/form/package.json b/packages/form/package.json
new file mode 100644
index 000000000..ce886d0ad
--- /dev/null
+++ b/packages/form/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@primevue/form",
+ "version": "4.1.0",
+ "author": "PrimeTek Informatics",
+ "description": "",
+ "homepage": "https://primevue.org/",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/primefaces/primevue.git",
+ "directory": "packages/form"
+ },
+ "bugs": {
+ "url": "https://github.com/primefaces/primevue/issues"
+ },
+ "main": "./src/index.js",
+ "module": "./src/index.js",
+ "types": "./src/index.d.ts",
+ "exports": {
+ ".": "./src/index.js",
+ "./form/style": "./src/form/style/FormStyle.js",
+ "./form": "./src/form/Form.vue",
+ "./*": "./src/*/index.js"
+ },
+ "publishConfig": {
+ "main": "./index.mjs",
+ "module": "./index.mjs",
+ "types": "./index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./index.d.ts",
+ "import": "./index.mjs"
+ },
+ "./*": {
+ "types": "./*/index.d.ts",
+ "import": "./*/index.mjs"
+ }
+ },
+ "directory": "dist",
+ "linkDirectory": false,
+ "access": "public"
+ },
+ "scripts": {
+ "build": "NODE_ENV=production INPUT_DIR=src/ OUTPUT_DIR=dist/ pnpm run build:package",
+ "build:package": "pnpm run build:prebuild && rollup -c && pnpm run build:postbuild",
+ "build:prebuild": "node ./scripts/prebuild.mjs",
+ "build:postbuild": "node ./scripts/postbuild.mjs",
+ "dev:link": "pnpm link --global && npm link"
+ },
+ "dependencies": {
+ "@primeuix/utils": "catalog:",
+ "@primeuix/form": "catalog:",
+ "@primevue/core": "workspace:*"
+ },
+ "engines": {
+ "node": ">=12.11.0"
+ }
+}
diff --git a/packages/form/rollup.config.mjs b/packages/form/rollup.config.mjs
new file mode 100644
index 000000000..a08826f70
--- /dev/null
+++ b/packages/form/rollup.config.mjs
@@ -0,0 +1,201 @@
+import alias from '@rollup/plugin-alias';
+import { babel } from '@rollup/plugin-babel';
+import resolve from '@rollup/plugin-node-resolve';
+import terser from '@rollup/plugin-terser';
+import postcss from 'rollup-plugin-postcss';
+import vue from 'rollup-plugin-vue';
+
+import fs from 'fs-extra';
+import path, { dirname } from 'path';
+import { fileURLToPath } from 'url';
+
+// @todo - Remove
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+// globals
+const GLOBALS = {
+ vue: 'Vue'
+};
+
+// externals
+const GLOBAL_EXTERNALS = ['vue'];
+const INLINE_EXTERNALS = [/@primevue\/core\/.*/];
+const EXTERNALS = [...GLOBAL_EXTERNALS, ...INLINE_EXTERNALS];
+
+// alias
+const ALIAS_ENTRIES = [
+ {
+ find: /^@primevue\/icons\/(.*)$/,
+ replacement: path.resolve(__dirname, './src/$1/index.vue')
+ },
+ { find: '@primevue/icons/baseicon/style', replacement: path.resolve(__dirname, './src/baseicon/style/BaseIconStyle.js') },
+ { find: '@primevue/icons/baseicon', replacement: path.resolve(__dirname, './src/baseicon/BaseIcon.vue') }
+];
+
+// plugins
+const BABEL_PLUGIN_OPTIONS = {
+ extensions: ['.js', '.vue'],
+ exclude: 'node_modules/**',
+ presets: ['@babel/preset-env'],
+ plugins: [],
+ skipPreflightCheck: true,
+ babelHelpers: 'runtime',
+ babelrc: false
+};
+
+const ALIAS_PLUGIN_OPTIONS = {
+ entries: ALIAS_ENTRIES
+};
+
+const POSTCSS_PLUGIN_OPTIONS = {
+ sourceMap: false
+};
+
+const TERSER_PLUGIN_OPTIONS = {
+ compress: {
+ keep_infinity: true,
+ pure_getters: true,
+ reduce_funcs: true
+ },
+ mangle: {
+ reserved: ['theme', 'css']
+ }
+};
+
+const PLUGINS = [vue(), postcss(POSTCSS_PLUGIN_OPTIONS), babel(BABEL_PLUGIN_OPTIONS)];
+
+const ENTRY = {
+ entries: [],
+ onwarn(warning) {
+ if (warning.code === 'CIRCULAR_DEPENDENCY') {
+ //console.error(`(!) ${warning.message}`);
+ return;
+ }
+ },
+ format: {
+ cjs_es(options) {
+ return ENTRY.format.cjs(options).es(options);
+ },
+ cjs({ input, output, minify }) {
+ ENTRY.entries.push({
+ onwarn: ENTRY.onwarn,
+ input,
+ plugins: [...PLUGINS, minify && terser(TERSER_PLUGIN_OPTIONS)],
+ external: EXTERNALS,
+ inlineDynamicImports: true,
+ output: [
+ {
+ format: 'cjs',
+ file: `${output}${minify ? '.min' : ''}.cjs`,
+ sourcemap: true,
+ exports: 'auto'
+ }
+ ]
+ });
+
+ ENTRY.update.packageJson({ input, output, options: { main: `${output}.cjs` } });
+
+ return ENTRY.format;
+ },
+ es({ input, output, minify }) {
+ ENTRY.entries.push({
+ onwarn: ENTRY.onwarn,
+ input,
+ plugins: [...PLUGINS, minify && terser(TERSER_PLUGIN_OPTIONS)],
+ external: EXTERNALS,
+ inlineDynamicImports: true,
+ output: [
+ {
+ format: 'es',
+ file: `${output}${minify ? '.min' : ''}.mjs`,
+ sourcemap: true,
+ exports: 'auto'
+ }
+ ]
+ });
+
+ ENTRY.update.packageJson({ input, output, options: { main: `${output}.mjs`, module: `${output}.mjs` } });
+
+ return ENTRY.format;
+ },
+ umd({ name, input, output, minify }) {
+ ENTRY.entries.push({
+ onwarn: ENTRY.onwarn,
+ input,
+ plugins: [alias(ALIAS_PLUGIN_OPTIONS), resolve(), ...PLUGINS, minify && terser(TERSER_PLUGIN_OPTIONS)],
+ external: GLOBAL_EXTERNALS,
+ inlineDynamicImports: true,
+ output: [
+ {
+ format: 'umd',
+ name: name ?? 'PrimeVue',
+ file: `${output}${minify ? '.min' : ''}.js`,
+ globals: GLOBALS,
+ exports: 'auto'
+ }
+ ]
+ });
+
+ return ENTRY.format;
+ }
+ },
+ update: {
+ packageJson({ input, output, options }) {
+ try {
+ const inputDir = path.resolve(__dirname, path.dirname(input));
+ const outputDir = path.resolve(__dirname, path.dirname(output));
+ const packageJson = path.resolve(outputDir, 'package.json');
+
+ !fs.existsSync(packageJson) && fs.copySync(path.resolve(inputDir, './package.json'), packageJson);
+
+ const pkg = JSON.parse(fs.readFileSync(packageJson, { encoding: 'utf8', flag: 'r' }));
+
+ !pkg?.main?.includes('.cjs') && (pkg.main = path.basename(options?.main) ? `./${path.basename(options.main)}` : pkg.main);
+ pkg.module = path.basename(options?.module) ? `./${path.basename(options.module)}` : packageJson.module;
+ pkg.types && (pkg.types = './index.d.ts');
+
+ fs.writeFileSync(packageJson, JSON.stringify(pkg, null, 4));
+ } catch {}
+ }
+ }
+};
+
+function addIcons() {
+ const iconDir = path.resolve(__dirname, process.env.INPUT_DIR);
+
+ fs.readdirSync(path.resolve(__dirname, iconDir), { withFileTypes: true })
+ .filter((dir) => dir.isDirectory())
+ .forEach(({ name: folderName }) => {
+ fs.readdirSync(path.resolve(__dirname, iconDir + '/' + folderName)).forEach((file) => {
+ if (/\.vue$/.test(file)) {
+ const input = process.env.INPUT_DIR + folderName + '/' + file;
+ const output = process.env.OUTPUT_DIR + folderName + '/index';
+
+ ENTRY.format.es({ input, output });
+ }
+ });
+ });
+}
+
+function addStyle() {
+ fs.readdirSync(path.resolve(__dirname, process.env.INPUT_DIR), { withFileTypes: true })
+ .filter((dir) => dir.isDirectory())
+ .forEach(({ name: folderName }) => {
+ try {
+ fs.readdirSync(path.resolve(__dirname, process.env.INPUT_DIR + folderName + '/style')).forEach((file) => {
+ if (/\.js$/.test(file)) {
+ const name = file.split(/(.js)$/)[0].toLowerCase();
+ const input = process.env.INPUT_DIR + folderName + '/style/' + file;
+ const output = process.env.OUTPUT_DIR + folderName + '/style/index';
+
+ ENTRY.format.es({ input, output });
+ }
+ });
+ } catch {}
+ });
+}
+
+addIcons();
+addStyle();
+
+export default ENTRY.entries;
diff --git a/packages/form/scripts/postbuild.mjs b/packages/form/scripts/postbuild.mjs
new file mode 100644
index 000000000..6ba562151
--- /dev/null
+++ b/packages/form/scripts/postbuild.mjs
@@ -0,0 +1,14 @@
+import fs from 'fs-extra';
+import path from 'path';
+import { clearPackageJson, copyDependencies, renameDTSFile, resolvePath } from '../../../scripts/build-helper.mjs';
+
+const { __dirname, __workspace, INPUT_DIR, OUTPUT_DIR } = resolvePath(import.meta.url);
+
+copyDependencies(INPUT_DIR, OUTPUT_DIR, '/style');
+renameDTSFile(OUTPUT_DIR, 'index');
+
+fs.copySync(path.resolve(__dirname, '../package.json'), `${OUTPUT_DIR}/package.json`);
+fs.copySync(path.resolve(__dirname, '../README.md'), `${OUTPUT_DIR}/README.md`);
+fs.copySync(path.resolve(__workspace, './LICENSE.md'), `${OUTPUT_DIR}/LICENSE.md`);
+
+clearPackageJson(path.resolve(__dirname, `../${OUTPUT_DIR}/package.json`));
diff --git a/packages/form/scripts/prebuild.mjs b/packages/form/scripts/prebuild.mjs
new file mode 100644
index 000000000..e8449e469
--- /dev/null
+++ b/packages/form/scripts/prebuild.mjs
@@ -0,0 +1,5 @@
+import path from 'path';
+import { removeBuild, resolvePath, updatePackageJson } from '../../../scripts/build-helper.mjs';
+
+removeBuild(import.meta.url);
+updatePackageJson(path.resolve(resolvePath(import.meta.url).__dirname, '../package.json'));
diff --git a/packages/form/src/form/BaseForm.vue b/packages/form/src/form/BaseForm.vue
new file mode 100644
index 000000000..3112e2091
--- /dev/null
+++ b/packages/form/src/form/BaseForm.vue
@@ -0,0 +1,42 @@
+
diff --git a/packages/form/src/form/Form.d.ts b/packages/form/src/form/Form.d.ts
new file mode 100644
index 000000000..41590557d
--- /dev/null
+++ b/packages/form/src/form/Form.d.ts
@@ -0,0 +1,130 @@
+/**
+ *
+ * Fluid is a layout component to make descendant components span full width of their container.
+ *
+ * [Live Demo](https://www.primevue.org/fluid/)
+ *
+ * @module fluid
+ *
+ */
+import type { DefineComponent, DesignToken, EmitFn, PassThrough } from '@primevue/core';
+import type { ComponentHooks } from '@primevue/core/basecomponent';
+import type { PassThroughOptions } from 'primevue/passthrough';
+import { TransitionProps, VNode } from 'vue';
+
+export declare type FluidPassThroughOptionType = FluidPassThroughAttributes | ((options: FluidPassThroughMethodOptions) => FluidPassThroughAttributes | string) | string | null | undefined;
+
+export declare type FluidPassThroughTransitionType = TransitionProps | ((options: FluidPassThroughMethodOptions) => TransitionProps) | undefined;
+
+/**
+ * Custom passthrough(pt) option method.
+ */
+export interface FluidPassThroughMethodOptions {
+ /**
+ * Defines instance.
+ */
+ instance: any;
+ /**
+ * Defines valid properties.
+ */
+ props: FluidProps;
+ /**
+ * Defines valid attributes.
+ */
+ attrs: any;
+ /**
+ * Defines parent options.
+ */
+ parent: any;
+ /**
+ * Defines passthrough(pt) options in global config.
+ */
+ global: object | undefined;
+}
+
+/**
+ * Custom passthrough(pt) options.
+ * @see {@link FluidProps.pt}
+ */
+export interface FluidPassThroughOptions {
+ /**
+ * Used to pass attributes to the root's DOM element.
+ */
+ root?: FluidPassThroughOptionType;
+ /**
+ * Used to manage all lifecycle hooks.
+ * @see {@link BaseComponent.ComponentHooks}
+ */
+ hooks?: ComponentHooks;
+}
+
+/**
+ * Custom passthrough attributes for each DOM elements
+ */
+export interface FluidPassThroughAttributes {
+ [key: string]: any;
+}
+
+/**
+ * Defines valid properties in Fluid component.
+ */
+export interface FluidProps {
+ /**
+ * It generates scoped CSS variables using design tokens for the component.
+ */
+ dt?: DesignToken;
+ /**
+ * Used to pass attributes to DOM elements inside the component.
+ * @type {FluidPassThroughOptions}
+ */
+ pt?: PassThrough;
+ /**
+ * Used to configure passthrough(pt) options of the component.
+ * @type {PassThroughOptions}
+ */
+ ptOptions?: PassThroughOptions;
+ /**
+ * When enabled, it removes component related styles in the core.
+ * @defaultValue false
+ */
+ unstyled?: boolean;
+}
+
+/**
+ * Defines valid slots in Fluid component.
+ */
+export interface FluidSlots {
+ /**
+ * Default content slot.
+ */
+ default: () => VNode[];
+}
+
+/**
+ * Defines valid emits in Fluid component.
+ */
+export interface FluidEmitsOptions {}
+
+export declare type FluidEmits = EmitFn;
+
+/**
+ * **PrimeVue - Fluid**
+ *
+ * _Fluid is a layout component to make descendant components span full width of their container._
+ *
+ * [Live Demo](https://www.primevue.org/fluid/)
+ * --- ---
+ * ![PrimeVue](https://primefaces.org/cdn/primevue/images/logo-100.png)
+ *
+ * @group Component
+ *
+ */
+declare const Fluid: DefineComponent;
+
+declare module 'vue' {
+ export interface GlobalComponents {
+ Fluid: DefineComponent;
+ }
+}
+
+export default Fluid;
diff --git a/packages/form/src/form/Form.vue b/packages/form/src/form/Form.vue
new file mode 100644
index 000000000..819f13e0e
--- /dev/null
+++ b/packages/form/src/form/Form.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
diff --git a/packages/form/src/form/package.json b/packages/form/src/form/package.json
new file mode 100644
index 000000000..1c8ef855d
--- /dev/null
+++ b/packages/form/src/form/package.json
@@ -0,0 +1,11 @@
+{
+ "main": "./Form.vue",
+ "module": "./Form.vue",
+ "types": "./Form.d.ts",
+ "browser": {
+ "./sfc": "./Form.vue"
+ },
+ "sideEffects": [
+ "*.vue"
+ ]
+}
diff --git a/packages/form/src/form/style/FormStyle.d.ts b/packages/form/src/form/style/FormStyle.d.ts
new file mode 100644
index 000000000..9ae1b3400
--- /dev/null
+++ b/packages/form/src/form/style/FormStyle.d.ts
@@ -0,0 +1,12 @@
+/**
+ *
+ *
+ *
+ * [Live Demo](https://www.primevue.org/form/)
+ *
+ * @module formstyle
+ *
+ */
+import type { BaseStyle } from '@primevue/core/base/style';
+
+export interface FormStyle extends BaseStyle {}
diff --git a/packages/form/src/form/style/FormStyle.js b/packages/form/src/form/style/FormStyle.js
new file mode 100644
index 000000000..4f5dbdfc8
--- /dev/null
+++ b/packages/form/src/form/style/FormStyle.js
@@ -0,0 +1,5 @@
+import BaseStyle from '@primevue/core/base/style';
+
+export default BaseStyle.extend({
+ name: 'form'
+});
diff --git a/packages/form/src/form/style/package.json b/packages/form/src/form/style/package.json
new file mode 100644
index 000000000..31fe6b840
--- /dev/null
+++ b/packages/form/src/form/style/package.json
@@ -0,0 +1,6 @@
+{
+ "main": "./FormStyle.js",
+ "module": "./FormStyle.js",
+ "types": "./FormStyle.d.ts",
+ "sideEffects": false
+}
diff --git a/packages/form/src/index.d.ts b/packages/form/src/index.d.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/form/src/index.js b/packages/form/src/index.js
new file mode 100644
index 000000000..7e19301ce
--- /dev/null
+++ b/packages/form/src/index.js
@@ -0,0 +1,3 @@
+export { default as Form } from '@primevue/form/form';
+export * from '@primevue/form/resolvers';
+export * from '@primevue/form/useform';
diff --git a/packages/form/src/resolvers/index.js b/packages/form/src/resolvers/index.js
new file mode 100644
index 000000000..17e6c1b9c
--- /dev/null
+++ b/packages/form/src/resolvers/index.js
@@ -0,0 +1 @@
+export * from '@primeuix/form/resolvers';
diff --git a/packages/form/src/resolvers/package.json b/packages/form/src/resolvers/package.json
new file mode 100644
index 000000000..7604ba623
--- /dev/null
+++ b/packages/form/src/resolvers/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "./index.js",
+ "module": "./index.js"
+}
diff --git a/packages/form/src/useform/index.js b/packages/form/src/useform/index.js
new file mode 100644
index 000000000..920ea0f7f
--- /dev/null
+++ b/packages/form/src/useform/index.js
@@ -0,0 +1,121 @@
+import { resolve } from '@primeuix/utils';
+import { computed, mergeProps, nextTick, onMounted, reactive, toValue, watch } from 'vue';
+
+function tryOnMounted(fn, sync = true) {
+ if (getCurrentInstance()) onMounted(fn);
+ else if (sync) fn();
+ else nextTick(fn);
+}
+
+export const useForm = (options = {}) => {
+ const states = reactive({});
+ const valid = computed(() => Object.values(states).every((field) => !field.invalid));
+
+ const getInitialState = (field) => {
+ return {
+ value: options.defaultValues?.[field],
+ touched: false,
+ dirty: false,
+ pristine: true,
+ valid: true,
+ invalid: false,
+ errors: []
+ };
+ };
+
+ const isFieldValidate = (field, validateOn) => {
+ const value = resolve(validateOn, field);
+
+ return value === true || (Array.isArray(value) && value.includes(field));
+ };
+
+ const defineField = (field, fieldOptions) => {
+ states[field] ||= getInitialState(field);
+
+ const props = mergeProps(resolve(fieldOptions, states[field])?.props, resolve(fieldOptions?.props, states[field]), {
+ name: field,
+ onBlur: () => {
+ states[field].touched = true;
+ (fieldOptions?.validateOnBlur ?? isFieldValidate(field, options.validateOnBlur)) && validate(field);
+ },
+ onChange: (event) => {
+ states[field].value = event.hasOwnProperty('value') ? event.value : event.target.type === 'checkbox' || event.target.type === 'radio' ? event.target.checked : event.target.value;
+ },
+ onInvalid: (errors) => {
+ states[field].invalid = true;
+ states[field].errors = errors;
+ }
+ });
+
+ watch(
+ () => states[field].value,
+ (newValue, oldValue) => {
+ if (states[field].pristine) {
+ states[field].pristine = false;
+ }
+
+ if (newValue !== oldValue) {
+ states[field].dirty = true;
+ }
+
+ (fieldOptions?.validateOnBlur ?? isFieldValidate(field, options.validateOnValueUpdate ?? true)) && validate(field);
+ }
+ );
+
+ return [states[field], props];
+ };
+
+ const handleSubmit = (callback) => {
+ return async (event) => {
+ let results = undefined;
+
+ (options.validateOnSubmit ?? true) && (results = await validate());
+
+ return callback({
+ originalEvent: event,
+ valid: toValue(valid),
+ states: toValue(states),
+ ...results
+ });
+ };
+ };
+
+ const validate = async (field) => {
+ const values = Object.entries(states).reduce((acc, [key, val]) => {
+ acc[key] = val.value;
+
+ return acc;
+ }, {});
+
+ const result = (await options.resolver?.({ values })) ?? {};
+
+ for (const sField of Object.keys(states)) {
+ if (sField === field || !field) {
+ const errors = result.errors?.[sField] ?? [];
+ const value = result.values?.[sField] ?? states[sField].value;
+
+ states[sField].invalid = errors.length > 0;
+ states[sField].valid = !states[sField].invalid;
+ states[sField].errors = errors;
+ states[sField].value = value;
+ }
+ }
+
+ return result;
+ };
+
+ const reset = () => {
+ Object.keys(states).forEach((field) => (states[field] = getInitialState(field)));
+ };
+
+ options.validateOnMount && tryOnMounted(validate);
+
+ return {
+ defineField,
+ handleSubmit,
+ validate,
+ reset,
+ valid: toValue(valid),
+ states: toValue(states)
+ };
+};
diff --git a/packages/form/src/useform/package.json b/packages/form/src/useform/package.json
new file mode 100644
index 000000000..7604ba623
--- /dev/null
+++ b/packages/form/src/useform/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "./index.js",
+ "module": "./index.js"
+}
diff --git a/packages/form/tsconfig.json b/packages/form/tsconfig.json
new file mode 100644
index 000000000..8a364744c
--- /dev/null
+++ b/packages/form/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": false,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": false,
+ "jsx": "preserve",
+ "incremental": true,
+ "baseUrl": ".",
+ "paths": {
+ "@primevue/form/*": ["./src/*"],
+ "@primevue/core/*": ["../../packages/core/src/*"]
+ }
+ },
+ "include": ["**/*.ts", "src/*"],
+ "exclude": ["node_modules", "dist"]
+}