pdyform 2.2.1 → 2.3.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 +25 -6
- package/package.json +38 -2
- package/packages/core/dist/{chunk-B7OMM2UC.js → chunk-T3LQTNYY.js} +1 -0
- package/packages/core/dist/chunk-V4VK2GZU.js +108 -0
- package/packages/core/dist/formState.cjs +88 -60
- package/packages/core/dist/formState.d.cts +12 -5
- package/packages/core/dist/formState.d.ts +12 -5
- package/packages/core/dist/formState.js +6 -4
- package/packages/core/dist/index.cjs +88 -60
- package/packages/core/dist/index.d.cts +1 -2
- package/packages/core/dist/index.d.ts +1 -2
- package/packages/core/dist/index.js +6 -4
- package/packages/core/dist/types.d.cts +7 -7
- package/packages/core/dist/types.d.ts +7 -7
- package/packages/core/dist/utils.cjs +1 -0
- package/packages/core/dist/utils.d.cts +6 -6
- package/packages/core/dist/utils.d.ts +6 -6
- package/packages/core/dist/utils.js +1 -1
- package/packages/react/dist/index.cjs +1 -1
- package/packages/react/dist/index.d.cts +6 -7
- package/packages/react/dist/index.d.ts +6 -7
- package/packages/react/dist/index.js +1 -1
- package/packages/vue/dist/index.d.ts +7 -22
- package/packages/vue/dist/index.js +1 -1
- package/packages/vue/dist/index.mjs +383 -380
- package/packages/core/dist/chunk-J6ESJZ4U.js +0 -82
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# pdyform
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/pdyform)
|
|
4
|
-
[]()
|
|
5
|
+
[]()
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
8
|
[English](#english) | [中文说明](#中文说明)
|
|
@@ -16,10 +17,19 @@ A high-performance, schema-driven dynamic form system with **React** and **Vue**
|
|
|
16
17
|
|
|
17
18
|
- **Schema-Driven**: Define your forms using a simple and intuitive JSON/JS schema.
|
|
18
19
|
- **Framework Agnostic Core**: Core logic is entirely framework-free, making it extremely lightweight and portable.
|
|
20
|
+
- **Vanilla Pub-Sub Store**: Built-in 40-line `VanillaStore` ensures high-performance updates without relying on heavy state-management libraries like Zustand or Redux.
|
|
19
21
|
- **Conditional Logic**: Supports dynamic `hidden` and `disabled` states based on form values (supports both boolean and functions).
|
|
20
|
-
- **React & Vue Support**: Out-of-the-box UI components
|
|
21
|
-
- **Type Safety**: Built with TypeScript for excellent developer experience and
|
|
22
|
-
- **High
|
|
22
|
+
- **React & Vue Support**: Out-of-the-box UI components. (Moving towards a pure Headless approach to decouple from Tailwind/Shadcn).
|
|
23
|
+
- **Type Safety**: Built with strict TypeScript for excellent developer experience and early error catching.
|
|
24
|
+
- **High Test Coverage**: Core logic is heavily tested (>85% coverage) ensuring robustness against edge cases like network-level async validation and race conditions.
|
|
25
|
+
- **Zero Side Effects**: Configured for aggressive tree-shaking with `"sideEffects": false`.
|
|
26
|
+
|
|
27
|
+
### 🏗 Architecture & Roadmap
|
|
28
|
+
|
|
29
|
+
pdyform is built with a strict separation of concerns:
|
|
30
|
+
1. **Core Data Engine (`pdyform-core`)**: Maintains the schema structure, parses definitions, runs asynchronous/synchronous validations, and manages state using an internal Vanilla Pub-Sub model.
|
|
31
|
+
2. **Framework Adapters (`pdyform-react` / `pdyform-vue`)**: Wraps the core engine with localized hooks (e.g. `useSyncExternalStore` for React) and provides base components.
|
|
32
|
+
3. **Headless Roadmap**: Currently, the React/Vue components bundle specific UI elements (Radix, Tailwind). We are moving towards a true Headless architecture where the packages expose only accessible logic components (`<Field>`) and hooks (`useForm`), leaving the styling implementation entirely up to the user (similar to unstyled Shadcn UI).
|
|
23
33
|
|
|
24
34
|
### 📦 Packages
|
|
25
35
|
|
|
@@ -116,10 +126,19 @@ const handleSubmit = (values: any) => console.log(values);
|
|
|
116
126
|
|
|
117
127
|
- **Schema 驱动**: 使用简单直观的 JSON/JS 对象定义你的表单。
|
|
118
128
|
- **框架无关核心**: 核心逻辑完全独立于 UI 框架,极其轻量且易于移植。
|
|
129
|
+
- **原生的防抖状态引擎**: 内置不到 40 行的 `VanillaStore` 来完成高性能的局部渲染与订阅,告别由于集成 Zustand 等库带来的体积膨胀。
|
|
119
130
|
- **联动逻辑**: 支持基于表单实时数值动态控制字段的 `hidden`(隐藏)和 `disabled`(禁用)状态(支持布尔值或函数)。
|
|
120
|
-
- **支持 React & Vue**:
|
|
131
|
+
- **支持 React & Vue**: 提供开箱即用的基础组件 (未来的路线图将向纯 Headless UI 组件过渡,解耦 Tailwind 样式依赖)。
|
|
121
132
|
- **类型安全**: 全量 TypeScript 编写,提供极佳的开发体验。
|
|
122
|
-
-
|
|
133
|
+
- **高测试覆盖**: 核心逻辑配备了超过 85% 的单元测试与异步并发校验的健壮性控制。
|
|
134
|
+
- **零副作用**: 各包均通过 `"sideEffects": false` 配置,对构建工具进行深度 Tree-Shaking 优化。
|
|
135
|
+
|
|
136
|
+
### 🏗 架构与路线图设计
|
|
137
|
+
|
|
138
|
+
整个引擎保持了极其严格的职责划分:
|
|
139
|
+
1. **核心数据引擎 (`pdyform-core`)**: 管理表单 Schema 结构的解析,处理异步和同步的各项校验机制。利用手写原生 Pub-Sub 处理多级数据变化拦截。
|
|
140
|
+
2. **框架适配 Hook (`pdyform-react` / `pdyform-vue`)**: 将状态和渲染树绑定(如在 React 中使用 `useSyncExternalStore`)。
|
|
141
|
+
3. **Headless 演进**: 尽全力提供类似无头表单 (Headless Form) 的钩子调用,不再强绑定各类 UI 组件库和样式设定。
|
|
123
142
|
|
|
124
143
|
### 📦 包结构
|
|
125
144
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdyform",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
3
6
|
"description": "A high-performance, schema-driven dynamic form system with React and Vue support.",
|
|
4
7
|
"keywords": [
|
|
5
8
|
"dynamic-form",
|
|
@@ -15,7 +18,28 @@
|
|
|
15
18
|
"packages/react/dist/**",
|
|
16
19
|
"packages/vue/dist/**"
|
|
17
20
|
],
|
|
21
|
+
"main": "./packages/core/dist/index.cjs",
|
|
22
|
+
"module": "./packages/core/dist/index.js",
|
|
23
|
+
"types": "./packages/core/dist/index.d.ts",
|
|
24
|
+
"typesVersions": {
|
|
25
|
+
"*": {
|
|
26
|
+
"core": [
|
|
27
|
+
"./packages/core/dist/index.d.ts"
|
|
28
|
+
],
|
|
29
|
+
"react": [
|
|
30
|
+
"./packages/react/dist/index.d.ts"
|
|
31
|
+
],
|
|
32
|
+
"vue": [
|
|
33
|
+
"./packages/vue/dist/index.d.ts"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
18
37
|
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./packages/core/dist/index.d.ts",
|
|
40
|
+
"import": "./packages/core/dist/index.js",
|
|
41
|
+
"require": "./packages/core/dist/index.cjs"
|
|
42
|
+
},
|
|
19
43
|
"./core": {
|
|
20
44
|
"types": "./packages/core/dist/index.d.ts",
|
|
21
45
|
"import": "./packages/core/dist/index.js",
|
|
@@ -39,17 +63,24 @@
|
|
|
39
63
|
"url": "https://github.com/LaoChen1994/pdyform"
|
|
40
64
|
},
|
|
41
65
|
"devDependencies": {
|
|
66
|
+
"@changesets/cli": "^2.30.0",
|
|
67
|
+
"@commitlint/cli": "^20.5.0",
|
|
68
|
+
"@commitlint/config-conventional": "^20.5.0",
|
|
42
69
|
"@eslint/js": "^9.39.3",
|
|
70
|
+
"@size-limit/preset-small-lib": "^12.0.1",
|
|
43
71
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
44
72
|
"@typescript-eslint/parser": "^8.56.1",
|
|
73
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
45
74
|
"autoprefixer": "^10.4.27",
|
|
46
75
|
"eslint": "^9.39.3",
|
|
47
76
|
"eslint-config-prettier": "^10.1.8",
|
|
48
77
|
"eslint-plugin-react": "^7.37.5",
|
|
49
78
|
"eslint-plugin-vue": "^10.8.0",
|
|
50
79
|
"globals": "^15.15.0",
|
|
80
|
+
"husky": "^9.1.7",
|
|
51
81
|
"postcss": "^8.5.8",
|
|
52
82
|
"prettier": "^3.8.1",
|
|
83
|
+
"size-limit": "^12.0.1",
|
|
53
84
|
"tailwindcss": "^3.4.0",
|
|
54
85
|
"turbo": "latest",
|
|
55
86
|
"typescript": "^5.0.0",
|
|
@@ -57,16 +88,21 @@
|
|
|
57
88
|
"vitest": "^1.0.0",
|
|
58
89
|
"vue": "^3.5.0"
|
|
59
90
|
},
|
|
60
|
-
"version": "2.
|
|
91
|
+
"version": "2.3.0",
|
|
61
92
|
"scripts": {
|
|
62
93
|
"build:all": "turbo run build",
|
|
63
94
|
"dev:all": "turbo run dev",
|
|
64
95
|
"dev:example": "turbo run dev --filter=@example/*",
|
|
65
96
|
"test:all": "turbo run test",
|
|
97
|
+
"test:coverage:all": "turbo run test:coverage",
|
|
66
98
|
"lint:all": "turbo run lint",
|
|
67
99
|
"dev:example:react": "pnpm --filter @example/react-demo dev",
|
|
68
100
|
"dev:example:vue": "pnpm --filter @example/vue-demo dev",
|
|
69
101
|
"build:example:react": "pnpm --filter @example/react-demo build",
|
|
70
|
-
"build:example:vue": "pnpm --filter @example/vue-demo build"
|
|
102
|
+
"build:example:vue": "pnpm --filter @example/vue-demo build",
|
|
103
|
+
"changeset": "changeset",
|
|
104
|
+
"version-packages": "changeset version",
|
|
105
|
+
"release": "pnpm build:all && changeset publish",
|
|
106
|
+
"size": "size-limit"
|
|
71
107
|
}
|
|
72
108
|
}
|
|
@@ -14,6 +14,7 @@ var defaultErrorMessages = {
|
|
|
14
14
|
custom: "Invalid value"
|
|
15
15
|
};
|
|
16
16
|
function formatMessage(template, field, rule) {
|
|
17
|
+
if (!template) return "";
|
|
17
18
|
return template.replace("{label}", field.label).replace("{value}", String(rule.value || ""));
|
|
18
19
|
}
|
|
19
20
|
function get(obj, path, defaultValue) {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
get,
|
|
3
|
+
getDefaultValues,
|
|
4
|
+
normalizeFieldValue,
|
|
5
|
+
set,
|
|
6
|
+
validateFieldByName,
|
|
7
|
+
validateForm
|
|
8
|
+
} from "./chunk-T3LQTNYY.js";
|
|
9
|
+
|
|
10
|
+
// src/formState.ts
|
|
11
|
+
function createStore(initialState) {
|
|
12
|
+
let state = initialState;
|
|
13
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
14
|
+
const getState = () => state;
|
|
15
|
+
const setState = (partial) => {
|
|
16
|
+
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
17
|
+
if (!Object.is(nextState, state)) {
|
|
18
|
+
const prevState = state;
|
|
19
|
+
state = { ...state, ...nextState };
|
|
20
|
+
listeners.forEach((listener) => listener(state, prevState));
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const subscribe = (listener) => {
|
|
24
|
+
listeners.add(listener);
|
|
25
|
+
return () => listeners.delete(listener);
|
|
26
|
+
};
|
|
27
|
+
return { getState, setState, subscribe };
|
|
28
|
+
}
|
|
29
|
+
function createFormEngine(fields, resolver, errorMessages) {
|
|
30
|
+
const store = createStore({
|
|
31
|
+
values: getDefaultValues(fields),
|
|
32
|
+
errors: {},
|
|
33
|
+
validatingFields: [],
|
|
34
|
+
isSubmitting: false
|
|
35
|
+
});
|
|
36
|
+
const { getState, setState } = store;
|
|
37
|
+
const setFieldValue = async (name, rawValue) => {
|
|
38
|
+
const field = fields.find((f) => f.name === name);
|
|
39
|
+
const normalizedValue = field ? normalizeFieldValue(field, rawValue) : rawValue;
|
|
40
|
+
setState((s) => ({
|
|
41
|
+
values: set(s.values, name, normalizedValue)
|
|
42
|
+
}));
|
|
43
|
+
const hasExistingError = !!getState().errors[name];
|
|
44
|
+
const shouldValidateImmediately = field && ["select", "checkbox", "radio", "switch", "date"].includes(field.type);
|
|
45
|
+
if (shouldValidateImmediately || hasExistingError) {
|
|
46
|
+
setState((s) => ({
|
|
47
|
+
validatingFields: [...s.validatingFields, name]
|
|
48
|
+
}));
|
|
49
|
+
try {
|
|
50
|
+
const currentValues = getState().values;
|
|
51
|
+
const error = await validateFieldByName(fields, name, normalizedValue, resolver, currentValues, errorMessages);
|
|
52
|
+
setState((s) => ({
|
|
53
|
+
errors: { ...s.errors, [name]: error || "" },
|
|
54
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
55
|
+
}));
|
|
56
|
+
} catch {
|
|
57
|
+
setState((s) => ({
|
|
58
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const setFieldBlur = async (name) => {
|
|
64
|
+
setState((s) => ({
|
|
65
|
+
validatingFields: [...s.validatingFields, name]
|
|
66
|
+
}));
|
|
67
|
+
try {
|
|
68
|
+
const currentValues = getState().values;
|
|
69
|
+
const value = get(currentValues, name);
|
|
70
|
+
const error = await validateFieldByName(fields, name, value, resolver, currentValues, errorMessages);
|
|
71
|
+
setState((s) => ({
|
|
72
|
+
errors: { ...s.errors, [name]: error || "" },
|
|
73
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
74
|
+
}));
|
|
75
|
+
} catch {
|
|
76
|
+
setState((s) => ({
|
|
77
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const setSubmitting = (isSubmitting) => setState({ isSubmitting });
|
|
82
|
+
const runSubmitValidation = async () => {
|
|
83
|
+
setState({ isSubmitting: true });
|
|
84
|
+
const currentValues = getState().values;
|
|
85
|
+
const errors = await validateForm(fields, currentValues, resolver, errorMessages);
|
|
86
|
+
const hasError = Object.keys(errors).length > 0;
|
|
87
|
+
setState({
|
|
88
|
+
errors,
|
|
89
|
+
isSubmitting: false
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
state: getState(),
|
|
93
|
+
hasError
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
store,
|
|
98
|
+
setFieldValue,
|
|
99
|
+
setFieldBlur,
|
|
100
|
+
setSubmitting,
|
|
101
|
+
runSubmitValidation
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
createStore,
|
|
107
|
+
createFormEngine
|
|
108
|
+
};
|
|
@@ -20,10 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/formState.ts
|
|
21
21
|
var formState_exports = {};
|
|
22
22
|
__export(formState_exports, {
|
|
23
|
-
|
|
23
|
+
createFormEngine: () => createFormEngine,
|
|
24
|
+
createStore: () => createStore
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(formState_exports);
|
|
26
|
-
var import_vanilla = require("zustand/vanilla");
|
|
27
27
|
|
|
28
28
|
// src/utils.ts
|
|
29
29
|
function parseNumberish(value) {
|
|
@@ -41,6 +41,7 @@ var defaultErrorMessages = {
|
|
|
41
41
|
custom: "Invalid value"
|
|
42
42
|
};
|
|
43
43
|
function formatMessage(template, field, rule) {
|
|
44
|
+
if (!template) return "";
|
|
44
45
|
return template.replace("{label}", field.label).replace("{value}", String(rule.value || ""));
|
|
45
46
|
}
|
|
46
47
|
function get(obj, path, defaultValue) {
|
|
@@ -176,74 +177,101 @@ function getDefaultValues(fields) {
|
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
// src/formState.ts
|
|
179
|
-
function
|
|
180
|
-
|
|
180
|
+
function createStore(initialState) {
|
|
181
|
+
let state = initialState;
|
|
182
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
183
|
+
const getState = () => state;
|
|
184
|
+
const setState = (partial) => {
|
|
185
|
+
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
186
|
+
if (!Object.is(nextState, state)) {
|
|
187
|
+
const prevState = state;
|
|
188
|
+
state = { ...state, ...nextState };
|
|
189
|
+
listeners.forEach((listener) => listener(state, prevState));
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const subscribe = (listener) => {
|
|
193
|
+
listeners.add(listener);
|
|
194
|
+
return () => listeners.delete(listener);
|
|
195
|
+
};
|
|
196
|
+
return { getState, setState, subscribe };
|
|
197
|
+
}
|
|
198
|
+
function createFormEngine(fields, resolver, errorMessages) {
|
|
199
|
+
const store = createStore({
|
|
181
200
|
values: getDefaultValues(fields),
|
|
182
201
|
errors: {},
|
|
183
202
|
validatingFields: [],
|
|
184
|
-
isSubmitting: false
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const currentValues = getStore().values;
|
|
199
|
-
const error = await validateFieldByName(fields, name, normalizedValue, resolver, currentValues, errorMessages);
|
|
200
|
-
set2((state) => ({
|
|
201
|
-
errors: { ...state.errors, [name]: error || "" },
|
|
202
|
-
validatingFields: state.validatingFields.filter((f) => f !== name)
|
|
203
|
-
}));
|
|
204
|
-
} catch (err) {
|
|
205
|
-
set2((state) => ({
|
|
206
|
-
validatingFields: state.validatingFields.filter((f) => f !== name)
|
|
207
|
-
}));
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
setFieldBlur: async (name) => {
|
|
212
|
-
set2((state) => ({
|
|
213
|
-
validatingFields: [...state.validatingFields, name]
|
|
203
|
+
isSubmitting: false
|
|
204
|
+
});
|
|
205
|
+
const { getState, setState } = store;
|
|
206
|
+
const setFieldValue = async (name, rawValue) => {
|
|
207
|
+
const field = fields.find((f) => f.name === name);
|
|
208
|
+
const normalizedValue = field ? normalizeFieldValue(field, rawValue) : rawValue;
|
|
209
|
+
setState((s) => ({
|
|
210
|
+
values: set(s.values, name, normalizedValue)
|
|
211
|
+
}));
|
|
212
|
+
const hasExistingError = !!getState().errors[name];
|
|
213
|
+
const shouldValidateImmediately = field && ["select", "checkbox", "radio", "switch", "date"].includes(field.type);
|
|
214
|
+
if (shouldValidateImmediately || hasExistingError) {
|
|
215
|
+
setState((s) => ({
|
|
216
|
+
validatingFields: [...s.validatingFields, name]
|
|
214
217
|
}));
|
|
215
218
|
try {
|
|
216
|
-
const currentValues =
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
validatingFields: state.validatingFields.filter((f) => f !== name)
|
|
219
|
+
const currentValues = getState().values;
|
|
220
|
+
const error = await validateFieldByName(fields, name, normalizedValue, resolver, currentValues, errorMessages);
|
|
221
|
+
setState((s) => ({
|
|
222
|
+
errors: { ...s.errors, [name]: error || "" },
|
|
223
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
222
224
|
}));
|
|
223
|
-
} catch
|
|
224
|
-
|
|
225
|
-
validatingFields:
|
|
225
|
+
} catch {
|
|
226
|
+
setState((s) => ({
|
|
227
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
226
228
|
}));
|
|
227
229
|
}
|
|
228
|
-
},
|
|
229
|
-
setSubmitting: (isSubmitting) => set2({ isSubmitting }),
|
|
230
|
-
runSubmitValidation: async () => {
|
|
231
|
-
set2({ isSubmitting: true });
|
|
232
|
-
const state = getStore();
|
|
233
|
-
const errors = await validateForm(fields, state.values, resolver, errorMessages);
|
|
234
|
-
const hasError = Object.keys(errors).length > 0;
|
|
235
|
-
set2({
|
|
236
|
-
errors,
|
|
237
|
-
isSubmitting: false
|
|
238
|
-
});
|
|
239
|
-
return {
|
|
240
|
-
state: getStore(),
|
|
241
|
-
hasError
|
|
242
|
-
};
|
|
243
230
|
}
|
|
244
|
-
}
|
|
231
|
+
};
|
|
232
|
+
const setFieldBlur = async (name) => {
|
|
233
|
+
setState((s) => ({
|
|
234
|
+
validatingFields: [...s.validatingFields, name]
|
|
235
|
+
}));
|
|
236
|
+
try {
|
|
237
|
+
const currentValues = getState().values;
|
|
238
|
+
const value = get(currentValues, name);
|
|
239
|
+
const error = await validateFieldByName(fields, name, value, resolver, currentValues, errorMessages);
|
|
240
|
+
setState((s) => ({
|
|
241
|
+
errors: { ...s.errors, [name]: error || "" },
|
|
242
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
243
|
+
}));
|
|
244
|
+
} catch {
|
|
245
|
+
setState((s) => ({
|
|
246
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const setSubmitting = (isSubmitting) => setState({ isSubmitting });
|
|
251
|
+
const runSubmitValidation = async () => {
|
|
252
|
+
setState({ isSubmitting: true });
|
|
253
|
+
const currentValues = getState().values;
|
|
254
|
+
const errors = await validateForm(fields, currentValues, resolver, errorMessages);
|
|
255
|
+
const hasError = Object.keys(errors).length > 0;
|
|
256
|
+
setState({
|
|
257
|
+
errors,
|
|
258
|
+
isSubmitting: false
|
|
259
|
+
});
|
|
260
|
+
return {
|
|
261
|
+
state: getState(),
|
|
262
|
+
hasError
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
return {
|
|
266
|
+
store,
|
|
267
|
+
setFieldValue,
|
|
268
|
+
setFieldBlur,
|
|
269
|
+
setSubmitting,
|
|
270
|
+
runSubmitValidation
|
|
271
|
+
};
|
|
245
272
|
}
|
|
246
273
|
// Annotate the CommonJS export names for ESM import in node:
|
|
247
274
|
0 && (module.exports = {
|
|
248
|
-
|
|
275
|
+
createFormEngine,
|
|
276
|
+
createStore
|
|
249
277
|
});
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import * as zustand_vanilla from 'zustand/vanilla';
|
|
2
1
|
import { FormField, FormResolver, ErrorMessageTemplates } from './types.cjs';
|
|
3
2
|
|
|
3
|
+
type StoreListener<T> = (state: T, prevState: T) => void;
|
|
4
|
+
interface VanillaStore<T> {
|
|
5
|
+
getState: () => T;
|
|
6
|
+
setState: (partial: Partial<T> | ((state: T) => Partial<T>)) => void;
|
|
7
|
+
subscribe: (listener: StoreListener<T>) => () => void;
|
|
8
|
+
}
|
|
9
|
+
declare function createStore<T>(initialState: T): VanillaStore<T>;
|
|
4
10
|
interface FormRuntimeState {
|
|
5
|
-
values: Record<string,
|
|
11
|
+
values: Record<string, unknown>;
|
|
6
12
|
errors: Record<string, string>;
|
|
7
13
|
validatingFields: string[];
|
|
8
14
|
isSubmitting: boolean;
|
|
9
15
|
}
|
|
10
|
-
interface
|
|
16
|
+
interface FormEngine {
|
|
17
|
+
store: VanillaStore<FormRuntimeState>;
|
|
11
18
|
setFieldValue: (name: string, rawValue: unknown) => Promise<void>;
|
|
12
19
|
setFieldBlur: (name: string) => Promise<void>;
|
|
13
20
|
setSubmitting: (isSubmitting: boolean) => void;
|
|
@@ -16,6 +23,6 @@ interface FormStore extends FormRuntimeState {
|
|
|
16
23
|
hasError: boolean;
|
|
17
24
|
}>;
|
|
18
25
|
}
|
|
19
|
-
declare function
|
|
26
|
+
declare function createFormEngine(fields: FormField[], resolver?: FormResolver, errorMessages?: ErrorMessageTemplates): FormEngine;
|
|
20
27
|
|
|
21
|
-
export { type FormRuntimeState, type
|
|
28
|
+
export { type FormEngine, type FormRuntimeState, type StoreListener, type VanillaStore, createFormEngine, createStore };
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import * as zustand_vanilla from 'zustand/vanilla';
|
|
2
1
|
import { FormField, FormResolver, ErrorMessageTemplates } from './types.js';
|
|
3
2
|
|
|
3
|
+
type StoreListener<T> = (state: T, prevState: T) => void;
|
|
4
|
+
interface VanillaStore<T> {
|
|
5
|
+
getState: () => T;
|
|
6
|
+
setState: (partial: Partial<T> | ((state: T) => Partial<T>)) => void;
|
|
7
|
+
subscribe: (listener: StoreListener<T>) => () => void;
|
|
8
|
+
}
|
|
9
|
+
declare function createStore<T>(initialState: T): VanillaStore<T>;
|
|
4
10
|
interface FormRuntimeState {
|
|
5
|
-
values: Record<string,
|
|
11
|
+
values: Record<string, unknown>;
|
|
6
12
|
errors: Record<string, string>;
|
|
7
13
|
validatingFields: string[];
|
|
8
14
|
isSubmitting: boolean;
|
|
9
15
|
}
|
|
10
|
-
interface
|
|
16
|
+
interface FormEngine {
|
|
17
|
+
store: VanillaStore<FormRuntimeState>;
|
|
11
18
|
setFieldValue: (name: string, rawValue: unknown) => Promise<void>;
|
|
12
19
|
setFieldBlur: (name: string) => Promise<void>;
|
|
13
20
|
setSubmitting: (isSubmitting: boolean) => void;
|
|
@@ -16,6 +23,6 @@ interface FormStore extends FormRuntimeState {
|
|
|
16
23
|
hasError: boolean;
|
|
17
24
|
}>;
|
|
18
25
|
}
|
|
19
|
-
declare function
|
|
26
|
+
declare function createFormEngine(fields: FormField[], resolver?: FormResolver, errorMessages?: ErrorMessageTemplates): FormEngine;
|
|
20
27
|
|
|
21
|
-
export { type FormRuntimeState, type
|
|
28
|
+
export { type FormEngine, type FormRuntimeState, type StoreListener, type VanillaStore, createFormEngine, createStore };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
createFormEngine,
|
|
3
|
+
createStore
|
|
4
|
+
} from "./chunk-V4VK2GZU.js";
|
|
5
|
+
import "./chunk-T3LQTNYY.js";
|
|
5
6
|
export {
|
|
6
|
-
|
|
7
|
+
createFormEngine,
|
|
8
|
+
createStore
|
|
7
9
|
};
|