pdyform 2.2.0 → 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-KA6QUMVR.js → chunk-T3LQTNYY.js} +3 -0
- package/packages/core/dist/chunk-V4VK2GZU.js +108 -0
- package/packages/core/dist/formState.cjs +88 -52
- 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 -52
- 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 +3 -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 +388 -380
- package/packages/core/dist/chunk-REHKL5VH.js +0 -76
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) {
|
|
@@ -133,6 +134,8 @@ async function validateForm(fields, values, resolver, customMessages) {
|
|
|
133
134
|
}
|
|
134
135
|
const validationPromises = fields.map(async (field) => {
|
|
135
136
|
if (errors[field.name]) return;
|
|
137
|
+
const isHidden = typeof field.hidden === "function" ? field.hidden(values) : field.hidden;
|
|
138
|
+
if (isHidden) return;
|
|
136
139
|
const error = await validateField(get(values, field.name), field, customMessages);
|
|
137
140
|
if (error) errors[field.name] = error;
|
|
138
141
|
});
|
|
@@ -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) {
|
|
@@ -160,6 +161,8 @@ async function validateForm(fields, values, resolver, customMessages) {
|
|
|
160
161
|
}
|
|
161
162
|
const validationPromises = fields.map(async (field) => {
|
|
162
163
|
if (errors[field.name]) return;
|
|
164
|
+
const isHidden = typeof field.hidden === "function" ? field.hidden(values) : field.hidden;
|
|
165
|
+
if (isHidden) return;
|
|
163
166
|
const error = await validateField(get(values, field.name), field, customMessages);
|
|
164
167
|
if (error) errors[field.name] = error;
|
|
165
168
|
});
|
|
@@ -174,68 +177,101 @@ function getDefaultValues(fields) {
|
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
// src/formState.ts
|
|
177
|
-
function
|
|
178
|
-
|
|
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({
|
|
179
200
|
values: getDefaultValues(fields),
|
|
180
201
|
errors: {},
|
|
181
202
|
validatingFields: [],
|
|
182
|
-
isSubmitting: false
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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]
|
|
189
217
|
}));
|
|
190
218
|
try {
|
|
191
|
-
const currentValues =
|
|
219
|
+
const currentValues = getState().values;
|
|
192
220
|
const error = await validateFieldByName(fields, name, normalizedValue, resolver, currentValues, errorMessages);
|
|
193
|
-
|
|
194
|
-
errors: { ...
|
|
195
|
-
validatingFields:
|
|
221
|
+
setState((s) => ({
|
|
222
|
+
errors: { ...s.errors, [name]: error || "" },
|
|
223
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
196
224
|
}));
|
|
197
|
-
} catch
|
|
198
|
-
|
|
199
|
-
validatingFields:
|
|
225
|
+
} catch {
|
|
226
|
+
setState((s) => ({
|
|
227
|
+
validatingFields: s.validatingFields.filter((f) => f !== name)
|
|
200
228
|
}));
|
|
201
229
|
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
}
|
|
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)
|
|
206
247
|
}));
|
|
207
|
-
try {
|
|
208
|
-
const currentValues = getStore().values;
|
|
209
|
-
const value = get(currentValues, name);
|
|
210
|
-
const error = await validateFieldByName(fields, name, value, resolver, currentValues, errorMessages);
|
|
211
|
-
set2((state) => ({
|
|
212
|
-
errors: { ...state.errors, [name]: error || "" },
|
|
213
|
-
validatingFields: state.validatingFields.filter((f) => f !== name)
|
|
214
|
-
}));
|
|
215
|
-
} catch (err) {
|
|
216
|
-
set2((state) => ({
|
|
217
|
-
validatingFields: state.validatingFields.filter((f) => f !== name)
|
|
218
|
-
}));
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
setSubmitting: (isSubmitting) => set2({ isSubmitting }),
|
|
222
|
-
runSubmitValidation: async () => {
|
|
223
|
-
set2({ isSubmitting: true });
|
|
224
|
-
const state = getStore();
|
|
225
|
-
const errors = await validateForm(fields, state.values, resolver, errorMessages);
|
|
226
|
-
const hasError = Object.keys(errors).length > 0;
|
|
227
|
-
set2({
|
|
228
|
-
errors,
|
|
229
|
-
isSubmitting: false
|
|
230
|
-
});
|
|
231
|
-
return {
|
|
232
|
-
state: getStore(),
|
|
233
|
-
hasError
|
|
234
|
-
};
|
|
235
248
|
}
|
|
236
|
-
}
|
|
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
|
+
};
|
|
237
272
|
}
|
|
238
273
|
// Annotate the CommonJS export names for ESM import in node:
|
|
239
274
|
0 && (module.exports = {
|
|
240
|
-
|
|
275
|
+
createFormEngine,
|
|
276
|
+
createStore
|
|
241
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
|
};
|