@vertz/ui 0.2.25 → 0.2.26
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.
|
@@ -103,6 +103,74 @@ function validate(schema, data) {
|
|
|
103
103
|
}
|
|
104
104
|
return { success: false, data: undefined, errors: { _form: "Validation failed" } };
|
|
105
105
|
}
|
|
106
|
+
function isSchemaLike(value) {
|
|
107
|
+
return value !== null && typeof value === "object" && "parse" in value && typeof value.parse === "function";
|
|
108
|
+
}
|
|
109
|
+
function resolveFieldSchema(schema, fieldPath) {
|
|
110
|
+
const shape = schema.shape;
|
|
111
|
+
if (!shape || typeof shape !== "object")
|
|
112
|
+
return;
|
|
113
|
+
const segments = fieldPath.split(".");
|
|
114
|
+
let current = shape;
|
|
115
|
+
for (let i = 0;i < segments.length; i++) {
|
|
116
|
+
if (!current || typeof current !== "object")
|
|
117
|
+
return;
|
|
118
|
+
const segment = segments[i];
|
|
119
|
+
let fieldSchema = current[segment];
|
|
120
|
+
if (i < segments.length - 1) {
|
|
121
|
+
let unwrapCount = 0;
|
|
122
|
+
while (fieldSchema && typeof fieldSchema === "object" && "unwrap" in fieldSchema && typeof fieldSchema.unwrap === "function" && !fieldSchema.shape && unwrapCount < 10) {
|
|
123
|
+
fieldSchema = fieldSchema.unwrap();
|
|
124
|
+
unwrapCount++;
|
|
125
|
+
}
|
|
126
|
+
if (fieldSchema && typeof fieldSchema === "object" && "shape" in fieldSchema && typeof fieldSchema.shape === "object") {
|
|
127
|
+
current = fieldSchema.shape;
|
|
128
|
+
} else {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
if (isSchemaLike(fieldSchema)) {
|
|
133
|
+
return fieldSchema;
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
function extractErrorFromParseResult(result) {
|
|
141
|
+
const err = result.error;
|
|
142
|
+
if (err instanceof Error) {
|
|
143
|
+
const issues = err.issues;
|
|
144
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
145
|
+
return issues[0].message ?? "Validation failed";
|
|
146
|
+
}
|
|
147
|
+
return err.message;
|
|
148
|
+
}
|
|
149
|
+
return "Validation failed";
|
|
150
|
+
}
|
|
151
|
+
function validateField(schema, fieldName, value, formData) {
|
|
152
|
+
const fieldSchema = resolveFieldSchema(schema, fieldName);
|
|
153
|
+
if (fieldSchema) {
|
|
154
|
+
const result = fieldSchema.parse(value);
|
|
155
|
+
if (result.ok) {
|
|
156
|
+
return { valid: true, error: undefined };
|
|
157
|
+
}
|
|
158
|
+
const error = extractErrorFromParseResult(result);
|
|
159
|
+
return { valid: false, error };
|
|
160
|
+
}
|
|
161
|
+
if (formData) {
|
|
162
|
+
const result = validate(schema, formData);
|
|
163
|
+
if (result.success) {
|
|
164
|
+
return { valid: true, error: undefined };
|
|
165
|
+
}
|
|
166
|
+
const fieldError = result.errors[fieldName];
|
|
167
|
+
if (fieldError) {
|
|
168
|
+
return { valid: false, error: fieldError };
|
|
169
|
+
}
|
|
170
|
+
return { valid: true, error: undefined };
|
|
171
|
+
}
|
|
172
|
+
return { valid: true, error: undefined };
|
|
173
|
+
}
|
|
106
174
|
|
|
107
175
|
// src/form/form.ts
|
|
108
176
|
var FIELD_STATE_SIGNALS = new Set(["error", "dirty", "touched", "value"]);
|
|
@@ -112,6 +180,8 @@ function form(sdkMethod, options) {
|
|
|
112
180
|
const chainProxyCache = new Map;
|
|
113
181
|
const submitting = signal(false);
|
|
114
182
|
const fieldGeneration = signal(0);
|
|
183
|
+
const revalidateOn = options?.revalidateOn ?? "blur";
|
|
184
|
+
let hasSubmitted = false;
|
|
115
185
|
const dirty = computed(() => {
|
|
116
186
|
fieldGeneration.value;
|
|
117
187
|
for (const field of fieldCache.values()) {
|
|
@@ -178,6 +248,7 @@ function form(sdkMethod, options) {
|
|
|
178
248
|
}
|
|
179
249
|
const resolvedSchema = options?.schema ?? sdkMethod.meta?.bodySchema;
|
|
180
250
|
async function submitPipeline(formData) {
|
|
251
|
+
hasSubmitted = true;
|
|
181
252
|
const data = formDataToObject(formData, { nested: true });
|
|
182
253
|
if (resolvedSchema) {
|
|
183
254
|
const result2 = validate(resolvedSchema, data);
|
|
@@ -209,10 +280,42 @@ function form(sdkMethod, options) {
|
|
|
209
280
|
}
|
|
210
281
|
let boundElement;
|
|
211
282
|
function resetForm() {
|
|
283
|
+
hasSubmitted = false;
|
|
212
284
|
for (const field of fieldCache.values()) {
|
|
213
285
|
field.reset();
|
|
214
286
|
}
|
|
215
287
|
}
|
|
288
|
+
function assembleFormData() {
|
|
289
|
+
const result = {};
|
|
290
|
+
for (const [name, f] of fieldCache) {
|
|
291
|
+
const value = f.value.peek();
|
|
292
|
+
if (!name.includes(".")) {
|
|
293
|
+
result[name] = value;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const segments = name.split(".");
|
|
297
|
+
let current = result;
|
|
298
|
+
for (let i = 0;i < segments.length - 1; i++) {
|
|
299
|
+
const seg = segments[i];
|
|
300
|
+
if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
|
|
301
|
+
current[seg] = {};
|
|
302
|
+
}
|
|
303
|
+
current = current[seg];
|
|
304
|
+
}
|
|
305
|
+
current[segments[segments.length - 1]] = value;
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
function revalidateFieldIfNeeded(fieldName) {
|
|
310
|
+
if (!hasSubmitted || revalidateOn === "submit" || !resolvedSchema)
|
|
311
|
+
return;
|
|
312
|
+
const field = fieldCache.get(fieldName);
|
|
313
|
+
if (!field || field.error.peek() === undefined)
|
|
314
|
+
return;
|
|
315
|
+
const formData = assembleFormData();
|
|
316
|
+
const result = validateField(resolvedSchema, fieldName, field.value.peek(), formData);
|
|
317
|
+
field.error.value = result.valid ? undefined : result.error;
|
|
318
|
+
}
|
|
216
319
|
async function submitPipelineWithReset(formData) {
|
|
217
320
|
await submitPipeline(formData);
|
|
218
321
|
if (options?.resetOnSuccess && !submitting.peek() && boundElement) {
|
|
@@ -228,6 +331,9 @@ function form(sdkMethod, options) {
|
|
|
228
331
|
return;
|
|
229
332
|
const field = getOrCreateField(target.name);
|
|
230
333
|
field.setValue(target.value);
|
|
334
|
+
if (revalidateOn === "change") {
|
|
335
|
+
revalidateFieldIfNeeded(target.name);
|
|
336
|
+
}
|
|
231
337
|
}
|
|
232
338
|
function handleFocusout(e) {
|
|
233
339
|
const target = e.target;
|
|
@@ -235,6 +341,9 @@ function form(sdkMethod, options) {
|
|
|
235
341
|
return;
|
|
236
342
|
const field = getOrCreateField(target.name);
|
|
237
343
|
field.touched.value = true;
|
|
344
|
+
if (revalidateOn === "blur") {
|
|
345
|
+
revalidateFieldIfNeeded(target.name);
|
|
346
|
+
}
|
|
238
347
|
}
|
|
239
348
|
const baseProperties = {
|
|
240
349
|
action: sdkMethod.url,
|
|
@@ -156,6 +156,17 @@ interface FormOptions<
|
|
|
156
156
|
onError?: (errors: Record<string, string>) => void;
|
|
157
157
|
/** When true, reset the form after a successful submission. */
|
|
158
158
|
resetOnSuccess?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Controls when fields with errors are re-validated after the first form submission.
|
|
161
|
+
*
|
|
162
|
+
* - `'blur'` (default) — Re-validates flagged fields when the user blurs them.
|
|
163
|
+
* - `'change'` — Re-validates flagged fields on every input/change event.
|
|
164
|
+
* - `'submit'` — No re-validation between submissions; errors only update on submit.
|
|
165
|
+
*
|
|
166
|
+
* Re-validation only activates after the first submit attempt. Fields without prior
|
|
167
|
+
* errors are never re-validated on blur/change (no premature validation).
|
|
168
|
+
*/
|
|
169
|
+
revalidateOn?: "submit" | "blur" | "change";
|
|
159
170
|
}
|
|
160
171
|
/**
|
|
161
172
|
* Create a form instance bound to an SDK method.
|
package/dist/src/form/public.js
CHANGED
package/dist/src/index.d.ts
CHANGED
|
@@ -964,6 +964,17 @@ interface FormOptions<
|
|
|
964
964
|
onError?: (errors: Record<string, string>) => void;
|
|
965
965
|
/** When true, reset the form after a successful submission. */
|
|
966
966
|
resetOnSuccess?: boolean;
|
|
967
|
+
/**
|
|
968
|
+
* Controls when fields with errors are re-validated after the first form submission.
|
|
969
|
+
*
|
|
970
|
+
* - `'blur'` (default) — Re-validates flagged fields when the user blurs them.
|
|
971
|
+
* - `'change'` — Re-validates flagged fields on every input/change event.
|
|
972
|
+
* - `'submit'` — No re-validation between submissions; errors only update on submit.
|
|
973
|
+
*
|
|
974
|
+
* Re-validation only activates after the first submit attempt. Fields without prior
|
|
975
|
+
* errors are never re-validated on blur/change (no premature validation).
|
|
976
|
+
*/
|
|
977
|
+
revalidateOn?: "submit" | "blur" | "change";
|
|
967
978
|
}
|
|
968
979
|
/**
|
|
969
980
|
* Create a form instance bound to an SDK method.
|
package/dist/src/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Vertz UI framework — signals, components, JSX runtime",
|
|
@@ -74,11 +74,11 @@
|
|
|
74
74
|
"typecheck": "tsc --noEmit"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@vertz/fetch": "^0.2.
|
|
77
|
+
"@vertz/fetch": "^0.2.25"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@happy-dom/global-registrator": "^20.7.0",
|
|
81
|
-
"@vertz/schema": "^0.2.
|
|
81
|
+
"@vertz/schema": "^0.2.25",
|
|
82
82
|
"bunup": "^0.16.31",
|
|
83
83
|
"happy-dom": "^20.7.0",
|
|
84
84
|
"typescript": "^5.7.0"
|