@vuehookform/core 0.1.1 → 0.1.2
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/LICENSE +21 -0
- package/dist/core/formContext.d.ts +11 -2
- package/dist/utils/paths.d.ts +1 -1
- package/dist/vuehookform.cjs +109 -89
- package/dist/vuehookform.js +109 -89
- package/package.json +4 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 vuehookform
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -8,6 +8,14 @@ export interface FieldArrayState {
|
|
|
8
8
|
items: Ref<FieldArrayItem[]>;
|
|
9
9
|
values: unknown[];
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Cached event handlers for a field to prevent recreation on every render
|
|
13
|
+
*/
|
|
14
|
+
export interface FieldHandlers {
|
|
15
|
+
onInput: (e: Event) => Promise<void>;
|
|
16
|
+
onBlur: (e: Event) => Promise<void>;
|
|
17
|
+
refCallback: (el: unknown) => void;
|
|
18
|
+
}
|
|
11
19
|
/**
|
|
12
20
|
* Shared form context containing all reactive state
|
|
13
21
|
* This is passed to sub-modules via dependency injection
|
|
@@ -16,14 +24,15 @@ export interface FormContext<FormValues> {
|
|
|
16
24
|
formData: Record<string, unknown>;
|
|
17
25
|
defaultValues: Record<string, unknown>;
|
|
18
26
|
errors: ShallowRef<FieldErrors<FormValues>>;
|
|
19
|
-
touchedFields:
|
|
20
|
-
dirtyFields:
|
|
27
|
+
touchedFields: ShallowRef<Record<string, boolean>>;
|
|
28
|
+
dirtyFields: ShallowRef<Record<string, boolean>>;
|
|
21
29
|
isSubmitting: Ref<boolean>;
|
|
22
30
|
isLoading: Ref<boolean>;
|
|
23
31
|
submitCount: Ref<number>;
|
|
24
32
|
fieldRefs: Map<string, Ref<HTMLInputElement | null>>;
|
|
25
33
|
fieldOptions: Map<string, RegisterOptions>;
|
|
26
34
|
fieldArrays: Map<string, FieldArrayState>;
|
|
35
|
+
fieldHandlers: Map<string, FieldHandlers>;
|
|
27
36
|
debounceTimers: Map<string, ReturnType<typeof setTimeout>>;
|
|
28
37
|
validationRequestIds: Map<string, number>;
|
|
29
38
|
options: UseFormOptions<ZodType>;
|
package/dist/utils/paths.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Get value from object using dot notation path
|
|
3
3
|
* @example get({ user: { name: 'John' } }, 'user.name') => 'John'
|
|
4
4
|
*/
|
|
5
|
-
export declare function get(obj:
|
|
5
|
+
export declare function get(obj: unknown, path: string): unknown;
|
|
6
6
|
/**
|
|
7
7
|
* Set value in object using dot notation path
|
|
8
8
|
* @example set({}, 'user.name', 'John') => { user: { name: 'John' } }
|
package/dist/vuehookform.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let vue = require("vue");
|
|
3
3
|
function get(obj, path) {
|
|
4
|
-
if (!path) return obj;
|
|
4
|
+
if (!path || obj === null || obj === void 0) return obj;
|
|
5
5
|
const keys = path.split(".");
|
|
6
6
|
let result = obj;
|
|
7
7
|
for (const key of keys) {
|
|
@@ -68,14 +68,15 @@ function createFormContext(options) {
|
|
|
68
68
|
formData,
|
|
69
69
|
defaultValues,
|
|
70
70
|
errors: (0, vue.shallowRef)({}),
|
|
71
|
-
touchedFields: (0, vue.
|
|
72
|
-
dirtyFields: (0, vue.
|
|
71
|
+
touchedFields: (0, vue.shallowRef)({}),
|
|
72
|
+
dirtyFields: (0, vue.shallowRef)({}),
|
|
73
73
|
isSubmitting: (0, vue.ref)(false),
|
|
74
74
|
isLoading,
|
|
75
75
|
submitCount: (0, vue.ref)(0),
|
|
76
76
|
fieldRefs: /* @__PURE__ */ new Map(),
|
|
77
77
|
fieldOptions: /* @__PURE__ */ new Map(),
|
|
78
78
|
fieldArrays: /* @__PURE__ */ new Map(),
|
|
79
|
+
fieldHandlers: /* @__PURE__ */ new Map(),
|
|
79
80
|
debounceTimers: /* @__PURE__ */ new Map(),
|
|
80
81
|
validationRequestIds: /* @__PURE__ */ new Map(),
|
|
81
82
|
options
|
|
@@ -100,7 +101,9 @@ function groupErrorsByPath(issues) {
|
|
|
100
101
|
return grouped;
|
|
101
102
|
}
|
|
102
103
|
function createFieldError(errors) {
|
|
103
|
-
|
|
104
|
+
const firstError = errors[0];
|
|
105
|
+
if (!firstError) return "";
|
|
106
|
+
if (errors.length === 1) return firstError.message;
|
|
104
107
|
const types = {};
|
|
105
108
|
for (const err of errors) {
|
|
106
109
|
const existing = types[err.type];
|
|
@@ -108,8 +111,8 @@ function createFieldError(errors) {
|
|
|
108
111
|
else types[err.type] = err.message;
|
|
109
112
|
}
|
|
110
113
|
return {
|
|
111
|
-
type:
|
|
112
|
-
message:
|
|
114
|
+
type: firstError.type,
|
|
115
|
+
message: firstError.message,
|
|
113
116
|
types
|
|
114
117
|
};
|
|
115
118
|
}
|
|
@@ -131,7 +134,7 @@ function createValidation(ctx) {
|
|
|
131
134
|
ctx.errors.value = clearFieldErrors(ctx.errors.value, fieldPath);
|
|
132
135
|
return true;
|
|
133
136
|
}
|
|
134
|
-
|
|
137
|
+
const newErrors$1 = clearFieldErrors(ctx.errors.value, fieldPath);
|
|
135
138
|
const grouped$1 = groupErrorsByPath(fieldErrors);
|
|
136
139
|
for (const [path, errors] of grouped$1) set(newErrors$1, path, createFieldError(errors));
|
|
137
140
|
ctx.errors.value = newErrors$1;
|
|
@@ -147,96 +150,112 @@ function createValidation(ctx) {
|
|
|
147
150
|
}
|
|
148
151
|
function createFieldRegistration(ctx, validate) {
|
|
149
152
|
function register(name, registerOptions) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
let fieldRef = ctx.fieldRefs.get(name);
|
|
154
|
+
if (!fieldRef) {
|
|
155
|
+
fieldRef = (0, vue.ref)(null);
|
|
156
|
+
ctx.fieldRefs.set(name, fieldRef);
|
|
157
|
+
if (get(ctx.formData, name) === void 0) {
|
|
158
|
+
const defaultValue = get(ctx.defaultValues, name);
|
|
159
|
+
if (defaultValue !== void 0) set(ctx.formData, name, defaultValue);
|
|
160
|
+
}
|
|
156
161
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
if (registerOptions) ctx.fieldOptions.set(name, registerOptions);
|
|
163
|
+
let handlers = ctx.fieldHandlers.get(name);
|
|
164
|
+
if (!handlers) {
|
|
165
|
+
const runCustomValidation = async (fieldName, value, requestId) => {
|
|
166
|
+
const fieldOpts = ctx.fieldOptions.get(fieldName);
|
|
167
|
+
if (!fieldOpts?.validate || fieldOpts.disabled) return;
|
|
168
|
+
const error = await fieldOpts.validate(value);
|
|
169
|
+
if (requestId !== ctx.validationRequestIds.get(fieldName)) return;
|
|
170
|
+
if (error) ctx.errors.value = {
|
|
171
|
+
...ctx.errors.value,
|
|
172
|
+
[fieldName]: error
|
|
173
|
+
};
|
|
174
|
+
else {
|
|
175
|
+
const newErrors = { ...ctx.errors.value };
|
|
176
|
+
delete newErrors[fieldName];
|
|
177
|
+
ctx.errors.value = newErrors;
|
|
178
|
+
}
|
|
165
179
|
};
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
ctx.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
const onInput = async (e) => {
|
|
181
|
+
const target = e.target;
|
|
182
|
+
const value = target.type === "checkbox" ? target.checked : target.value;
|
|
183
|
+
set(ctx.formData, name, value);
|
|
184
|
+
ctx.dirtyFields.value = {
|
|
185
|
+
...ctx.dirtyFields.value,
|
|
186
|
+
[name]: true
|
|
187
|
+
};
|
|
188
|
+
if (ctx.options.mode === "onChange" || ctx.options.mode === "onTouched" && ctx.touchedFields.value[name] || ctx.touchedFields.value[name] && ctx.options.reValidateMode === "onChange") await validate(name);
|
|
189
|
+
const fieldOpts = ctx.fieldOptions.get(name);
|
|
190
|
+
if (fieldOpts?.validate && !fieldOpts.disabled) {
|
|
191
|
+
const requestId = Date.now() + Math.random();
|
|
192
|
+
ctx.validationRequestIds.set(name, requestId);
|
|
193
|
+
const debounceMs = fieldOpts.validateDebounce || 0;
|
|
194
|
+
if (debounceMs > 0) {
|
|
195
|
+
const existingTimer = ctx.debounceTimers.get(name);
|
|
196
|
+
if (existingTimer) clearTimeout(existingTimer);
|
|
197
|
+
const timer = setTimeout(() => {
|
|
198
|
+
ctx.debounceTimers.delete(name);
|
|
199
|
+
runCustomValidation(name, value, requestId);
|
|
200
|
+
}, debounceMs);
|
|
201
|
+
ctx.debounceTimers.set(name, timer);
|
|
202
|
+
} else await runCustomValidation(name, value, requestId);
|
|
203
|
+
}
|
|
179
204
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (debounceMs > 0) {
|
|
187
|
-
const existingTimer = ctx.debounceTimers.get(name);
|
|
188
|
-
if (existingTimer) clearTimeout(existingTimer);
|
|
189
|
-
const timer = setTimeout(() => {
|
|
190
|
-
ctx.debounceTimers.delete(name);
|
|
191
|
-
runCustomValidation(name, value, requestId);
|
|
192
|
-
}, debounceMs);
|
|
193
|
-
ctx.debounceTimers.set(name, timer);
|
|
194
|
-
} else await runCustomValidation(name, value, requestId);
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
const onBlur = async (_e) => {
|
|
198
|
-
ctx.touchedFields.value = {
|
|
199
|
-
...ctx.touchedFields.value,
|
|
200
|
-
[name]: true
|
|
205
|
+
const onBlur = async (_e) => {
|
|
206
|
+
ctx.touchedFields.value = {
|
|
207
|
+
...ctx.touchedFields.value,
|
|
208
|
+
[name]: true
|
|
209
|
+
};
|
|
210
|
+
if (ctx.options.mode === "onBlur" || ctx.options.mode === "onTouched" || ctx.submitCount.value > 0 && ctx.options.reValidateMode === "onBlur") await validate(name);
|
|
201
211
|
};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
212
|
+
const refCallback = (el) => {
|
|
213
|
+
const currentFieldRef = ctx.fieldRefs.get(name);
|
|
214
|
+
if (!currentFieldRef) return;
|
|
215
|
+
const previousEl = currentFieldRef.value;
|
|
216
|
+
currentFieldRef.value = el;
|
|
217
|
+
const opts = ctx.fieldOptions.get(name);
|
|
218
|
+
if (el && !opts?.controlled && el instanceof HTMLInputElement) {
|
|
219
|
+
const value = get(ctx.formData, name);
|
|
220
|
+
if (value !== void 0) if (el.type === "checkbox") el.checked = value;
|
|
221
|
+
else el.value = value;
|
|
222
|
+
}
|
|
223
|
+
if (previousEl && !el) {
|
|
224
|
+
if (opts?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
|
|
225
|
+
unset(ctx.formData, name);
|
|
226
|
+
const newErrors = { ...ctx.errors.value };
|
|
227
|
+
delete newErrors[name];
|
|
228
|
+
ctx.errors.value = newErrors;
|
|
229
|
+
const newTouched = { ...ctx.touchedFields.value };
|
|
230
|
+
delete newTouched[name];
|
|
231
|
+
ctx.touchedFields.value = newTouched;
|
|
232
|
+
const newDirty = { ...ctx.dirtyFields.value };
|
|
233
|
+
delete newDirty[name];
|
|
234
|
+
ctx.dirtyFields.value = newDirty;
|
|
235
|
+
ctx.fieldRefs.delete(name);
|
|
236
|
+
ctx.fieldOptions.delete(name);
|
|
237
|
+
ctx.fieldHandlers.delete(name);
|
|
238
|
+
const timer = ctx.debounceTimers.get(name);
|
|
239
|
+
if (timer) {
|
|
240
|
+
clearTimeout(timer);
|
|
241
|
+
ctx.debounceTimers.delete(name);
|
|
242
|
+
}
|
|
243
|
+
ctx.validationRequestIds.delete(name);
|
|
230
244
|
}
|
|
231
|
-
ctx.validationRequestIds.delete(name);
|
|
232
245
|
}
|
|
233
|
-
}
|
|
234
|
-
|
|
246
|
+
};
|
|
247
|
+
handlers = {
|
|
248
|
+
onInput,
|
|
249
|
+
onBlur,
|
|
250
|
+
refCallback
|
|
251
|
+
};
|
|
252
|
+
ctx.fieldHandlers.set(name, handlers);
|
|
253
|
+
}
|
|
235
254
|
return {
|
|
236
255
|
name,
|
|
237
|
-
ref: refCallback,
|
|
238
|
-
onInput,
|
|
239
|
-
onBlur,
|
|
256
|
+
ref: handlers.refCallback,
|
|
257
|
+
onInput: handlers.onInput,
|
|
258
|
+
onBlur: handlers.onBlur,
|
|
240
259
|
...registerOptions?.controlled && { value: (0, vue.computed)({
|
|
241
260
|
get: () => get(ctx.formData, name),
|
|
242
261
|
set: (val) => {
|
|
@@ -252,6 +271,7 @@ function createFieldRegistration(ctx, validate) {
|
|
|
252
271
|
function unregister(name) {
|
|
253
272
|
ctx.fieldRefs.delete(name);
|
|
254
273
|
ctx.fieldOptions.delete(name);
|
|
274
|
+
ctx.fieldHandlers.delete(name);
|
|
255
275
|
const timer = ctx.debounceTimers.get(name);
|
|
256
276
|
if (timer) {
|
|
257
277
|
clearTimeout(timer);
|
package/dist/vuehookform.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { computed, inject, provide, reactive, ref, shallowRef } from "vue";
|
|
2
2
|
function get(obj, path) {
|
|
3
|
-
if (!path) return obj;
|
|
3
|
+
if (!path || obj === null || obj === void 0) return obj;
|
|
4
4
|
const keys = path.split(".");
|
|
5
5
|
let result = obj;
|
|
6
6
|
for (const key of keys) {
|
|
@@ -67,14 +67,15 @@ function createFormContext(options) {
|
|
|
67
67
|
formData,
|
|
68
68
|
defaultValues,
|
|
69
69
|
errors: shallowRef({}),
|
|
70
|
-
touchedFields:
|
|
71
|
-
dirtyFields:
|
|
70
|
+
touchedFields: shallowRef({}),
|
|
71
|
+
dirtyFields: shallowRef({}),
|
|
72
72
|
isSubmitting: ref(false),
|
|
73
73
|
isLoading,
|
|
74
74
|
submitCount: ref(0),
|
|
75
75
|
fieldRefs: /* @__PURE__ */ new Map(),
|
|
76
76
|
fieldOptions: /* @__PURE__ */ new Map(),
|
|
77
77
|
fieldArrays: /* @__PURE__ */ new Map(),
|
|
78
|
+
fieldHandlers: /* @__PURE__ */ new Map(),
|
|
78
79
|
debounceTimers: /* @__PURE__ */ new Map(),
|
|
79
80
|
validationRequestIds: /* @__PURE__ */ new Map(),
|
|
80
81
|
options
|
|
@@ -99,7 +100,9 @@ function groupErrorsByPath(issues) {
|
|
|
99
100
|
return grouped;
|
|
100
101
|
}
|
|
101
102
|
function createFieldError(errors) {
|
|
102
|
-
|
|
103
|
+
const firstError = errors[0];
|
|
104
|
+
if (!firstError) return "";
|
|
105
|
+
if (errors.length === 1) return firstError.message;
|
|
103
106
|
const types = {};
|
|
104
107
|
for (const err of errors) {
|
|
105
108
|
const existing = types[err.type];
|
|
@@ -107,8 +110,8 @@ function createFieldError(errors) {
|
|
|
107
110
|
else types[err.type] = err.message;
|
|
108
111
|
}
|
|
109
112
|
return {
|
|
110
|
-
type:
|
|
111
|
-
message:
|
|
113
|
+
type: firstError.type,
|
|
114
|
+
message: firstError.message,
|
|
112
115
|
types
|
|
113
116
|
};
|
|
114
117
|
}
|
|
@@ -130,7 +133,7 @@ function createValidation(ctx) {
|
|
|
130
133
|
ctx.errors.value = clearFieldErrors(ctx.errors.value, fieldPath);
|
|
131
134
|
return true;
|
|
132
135
|
}
|
|
133
|
-
|
|
136
|
+
const newErrors$1 = clearFieldErrors(ctx.errors.value, fieldPath);
|
|
134
137
|
const grouped$1 = groupErrorsByPath(fieldErrors);
|
|
135
138
|
for (const [path, errors] of grouped$1) set(newErrors$1, path, createFieldError(errors));
|
|
136
139
|
ctx.errors.value = newErrors$1;
|
|
@@ -146,96 +149,112 @@ function createValidation(ctx) {
|
|
|
146
149
|
}
|
|
147
150
|
function createFieldRegistration(ctx, validate) {
|
|
148
151
|
function register(name, registerOptions) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
let fieldRef = ctx.fieldRefs.get(name);
|
|
153
|
+
if (!fieldRef) {
|
|
154
|
+
fieldRef = ref(null);
|
|
155
|
+
ctx.fieldRefs.set(name, fieldRef);
|
|
156
|
+
if (get(ctx.formData, name) === void 0) {
|
|
157
|
+
const defaultValue = get(ctx.defaultValues, name);
|
|
158
|
+
if (defaultValue !== void 0) set(ctx.formData, name, defaultValue);
|
|
159
|
+
}
|
|
155
160
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
if (registerOptions) ctx.fieldOptions.set(name, registerOptions);
|
|
162
|
+
let handlers = ctx.fieldHandlers.get(name);
|
|
163
|
+
if (!handlers) {
|
|
164
|
+
const runCustomValidation = async (fieldName, value, requestId) => {
|
|
165
|
+
const fieldOpts = ctx.fieldOptions.get(fieldName);
|
|
166
|
+
if (!fieldOpts?.validate || fieldOpts.disabled) return;
|
|
167
|
+
const error = await fieldOpts.validate(value);
|
|
168
|
+
if (requestId !== ctx.validationRequestIds.get(fieldName)) return;
|
|
169
|
+
if (error) ctx.errors.value = {
|
|
170
|
+
...ctx.errors.value,
|
|
171
|
+
[fieldName]: error
|
|
172
|
+
};
|
|
173
|
+
else {
|
|
174
|
+
const newErrors = { ...ctx.errors.value };
|
|
175
|
+
delete newErrors[fieldName];
|
|
176
|
+
ctx.errors.value = newErrors;
|
|
177
|
+
}
|
|
164
178
|
};
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
ctx.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
const onInput = async (e) => {
|
|
180
|
+
const target = e.target;
|
|
181
|
+
const value = target.type === "checkbox" ? target.checked : target.value;
|
|
182
|
+
set(ctx.formData, name, value);
|
|
183
|
+
ctx.dirtyFields.value = {
|
|
184
|
+
...ctx.dirtyFields.value,
|
|
185
|
+
[name]: true
|
|
186
|
+
};
|
|
187
|
+
if (ctx.options.mode === "onChange" || ctx.options.mode === "onTouched" && ctx.touchedFields.value[name] || ctx.touchedFields.value[name] && ctx.options.reValidateMode === "onChange") await validate(name);
|
|
188
|
+
const fieldOpts = ctx.fieldOptions.get(name);
|
|
189
|
+
if (fieldOpts?.validate && !fieldOpts.disabled) {
|
|
190
|
+
const requestId = Date.now() + Math.random();
|
|
191
|
+
ctx.validationRequestIds.set(name, requestId);
|
|
192
|
+
const debounceMs = fieldOpts.validateDebounce || 0;
|
|
193
|
+
if (debounceMs > 0) {
|
|
194
|
+
const existingTimer = ctx.debounceTimers.get(name);
|
|
195
|
+
if (existingTimer) clearTimeout(existingTimer);
|
|
196
|
+
const timer = setTimeout(() => {
|
|
197
|
+
ctx.debounceTimers.delete(name);
|
|
198
|
+
runCustomValidation(name, value, requestId);
|
|
199
|
+
}, debounceMs);
|
|
200
|
+
ctx.debounceTimers.set(name, timer);
|
|
201
|
+
} else await runCustomValidation(name, value, requestId);
|
|
202
|
+
}
|
|
178
203
|
};
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (debounceMs > 0) {
|
|
186
|
-
const existingTimer = ctx.debounceTimers.get(name);
|
|
187
|
-
if (existingTimer) clearTimeout(existingTimer);
|
|
188
|
-
const timer = setTimeout(() => {
|
|
189
|
-
ctx.debounceTimers.delete(name);
|
|
190
|
-
runCustomValidation(name, value, requestId);
|
|
191
|
-
}, debounceMs);
|
|
192
|
-
ctx.debounceTimers.set(name, timer);
|
|
193
|
-
} else await runCustomValidation(name, value, requestId);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
const onBlur = async (_e) => {
|
|
197
|
-
ctx.touchedFields.value = {
|
|
198
|
-
...ctx.touchedFields.value,
|
|
199
|
-
[name]: true
|
|
204
|
+
const onBlur = async (_e) => {
|
|
205
|
+
ctx.touchedFields.value = {
|
|
206
|
+
...ctx.touchedFields.value,
|
|
207
|
+
[name]: true
|
|
208
|
+
};
|
|
209
|
+
if (ctx.options.mode === "onBlur" || ctx.options.mode === "onTouched" || ctx.submitCount.value > 0 && ctx.options.reValidateMode === "onBlur") await validate(name);
|
|
200
210
|
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
211
|
+
const refCallback = (el) => {
|
|
212
|
+
const currentFieldRef = ctx.fieldRefs.get(name);
|
|
213
|
+
if (!currentFieldRef) return;
|
|
214
|
+
const previousEl = currentFieldRef.value;
|
|
215
|
+
currentFieldRef.value = el;
|
|
216
|
+
const opts = ctx.fieldOptions.get(name);
|
|
217
|
+
if (el && !opts?.controlled && el instanceof HTMLInputElement) {
|
|
218
|
+
const value = get(ctx.formData, name);
|
|
219
|
+
if (value !== void 0) if (el.type === "checkbox") el.checked = value;
|
|
220
|
+
else el.value = value;
|
|
221
|
+
}
|
|
222
|
+
if (previousEl && !el) {
|
|
223
|
+
if (opts?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
|
|
224
|
+
unset(ctx.formData, name);
|
|
225
|
+
const newErrors = { ...ctx.errors.value };
|
|
226
|
+
delete newErrors[name];
|
|
227
|
+
ctx.errors.value = newErrors;
|
|
228
|
+
const newTouched = { ...ctx.touchedFields.value };
|
|
229
|
+
delete newTouched[name];
|
|
230
|
+
ctx.touchedFields.value = newTouched;
|
|
231
|
+
const newDirty = { ...ctx.dirtyFields.value };
|
|
232
|
+
delete newDirty[name];
|
|
233
|
+
ctx.dirtyFields.value = newDirty;
|
|
234
|
+
ctx.fieldRefs.delete(name);
|
|
235
|
+
ctx.fieldOptions.delete(name);
|
|
236
|
+
ctx.fieldHandlers.delete(name);
|
|
237
|
+
const timer = ctx.debounceTimers.get(name);
|
|
238
|
+
if (timer) {
|
|
239
|
+
clearTimeout(timer);
|
|
240
|
+
ctx.debounceTimers.delete(name);
|
|
241
|
+
}
|
|
242
|
+
ctx.validationRequestIds.delete(name);
|
|
229
243
|
}
|
|
230
|
-
ctx.validationRequestIds.delete(name);
|
|
231
244
|
}
|
|
232
|
-
}
|
|
233
|
-
|
|
245
|
+
};
|
|
246
|
+
handlers = {
|
|
247
|
+
onInput,
|
|
248
|
+
onBlur,
|
|
249
|
+
refCallback
|
|
250
|
+
};
|
|
251
|
+
ctx.fieldHandlers.set(name, handlers);
|
|
252
|
+
}
|
|
234
253
|
return {
|
|
235
254
|
name,
|
|
236
|
-
ref: refCallback,
|
|
237
|
-
onInput,
|
|
238
|
-
onBlur,
|
|
255
|
+
ref: handlers.refCallback,
|
|
256
|
+
onInput: handlers.onInput,
|
|
257
|
+
onBlur: handlers.onBlur,
|
|
239
258
|
...registerOptions?.controlled && { value: computed({
|
|
240
259
|
get: () => get(ctx.formData, name),
|
|
241
260
|
set: (val) => {
|
|
@@ -251,6 +270,7 @@ function createFieldRegistration(ctx, validate) {
|
|
|
251
270
|
function unregister(name) {
|
|
252
271
|
ctx.fieldRefs.delete(name);
|
|
253
272
|
ctx.fieldOptions.delete(name);
|
|
273
|
+
ctx.fieldHandlers.delete(name);
|
|
254
274
|
const timer = ctx.debounceTimers.get(name);
|
|
255
275
|
if (timer) {
|
|
256
276
|
clearTimeout(timer);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vuehookform/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "TypeScript-first form library for Vue 3, inspired by React Hook Form. Form-level state management with Zod validation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/vuehookform.cjs",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"hook",
|
|
51
51
|
"react-hook-form"
|
|
52
52
|
],
|
|
53
|
-
"author": "",
|
|
53
|
+
"author": "jonnasson",
|
|
54
54
|
"license": "MIT",
|
|
55
55
|
"bugs": {
|
|
56
56
|
"url": "https://github.com/vuehookform/core/issues"
|
|
57
57
|
},
|
|
58
|
-
"homepage": "https://
|
|
58
|
+
"homepage": "https://vuehookform.com",
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"vue": "^3.3.0",
|
|
61
61
|
"zod": "^3.0.0 || ^4.0.0"
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"prettier": "3.6.2",
|
|
82
82
|
"typescript": "~5.9.0",
|
|
83
83
|
"vite": "npm:rolldown-vite@latest",
|
|
84
|
+
"vite-plugin-compression2": "^2.4.0",
|
|
84
85
|
"vite-plugin-dts": "^4.5.4",
|
|
85
86
|
"vitest": "^4.0.16",
|
|
86
87
|
"vue": "^3.5.25",
|