@formisch/vue 0.8.0 → 0.9.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 +1 -0
- package/dist/index.d.ts +21 -11
- package/dist/index.js +117 -74
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Formisch is also available for [Preact][formisch-preact], [Qwik][formisch-qwik],
|
|
|
9
9
|
- Small bundle size starting at 2.5 kB
|
|
10
10
|
- Schema-based validation with Valibot
|
|
11
11
|
- Type safety with autocompletion in editor
|
|
12
|
+
- Open source and fully tested with 100 % coverage
|
|
12
13
|
- It's fast – DOM updates are fine-grained
|
|
13
14
|
- Minimal, readable and well thought out API
|
|
14
15
|
- Supports all native HTML form fields
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
-
import * as
|
|
2
|
+
import * as vue0 from "vue";
|
|
3
3
|
import { ComponentPublicInstance, MaybeRefOrGetter, ShallowRef as Signal } from "vue";
|
|
4
4
|
|
|
5
5
|
//#region ../../packages/core/dist/index.vue.d.ts
|
|
@@ -64,6 +64,13 @@ interface InternalBaseStore {
|
|
|
64
64
|
schema: Schema;
|
|
65
65
|
/**
|
|
66
66
|
* The initial elements of the field.
|
|
67
|
+
*
|
|
68
|
+
* Hint: This may look unused, but do not remove it. `copyItemState` and
|
|
69
|
+
* `swapItemState` move the `elements` reference between field stores when
|
|
70
|
+
* array items are inserted, moved, removed or swapped, and `reset` restores
|
|
71
|
+
* each field's original element via `elements = initialElements`. Without it,
|
|
72
|
+
* focus and file reset target the wrong element after a reorder followed by a
|
|
73
|
+
* reset.
|
|
67
74
|
*/
|
|
68
75
|
initialElements: FieldElement[];
|
|
69
76
|
/**
|
|
@@ -385,7 +392,10 @@ type ExactRequired<TValue> = TValue extends Record<PropertyKey, unknown> ? IsExa
|
|
|
385
392
|
*/
|
|
386
393
|
type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey, ...infer TRest extends Path] ? TKey extends ExactKeysOf<ExactRequired<TValue>> ? PathValue<PropertiesOf<ExactRequired<TValue>>[TKey], TRest> : unknown : TValue;
|
|
387
394
|
/**
|
|
388
|
-
* Checks whether a value is
|
|
395
|
+
* Checks whether a value is a dynamic array or contains one anywhere in its
|
|
396
|
+
* shape. A fixed-length tuple is not itself a dynamic array, but it counts when
|
|
397
|
+
* it contains one, so paths can still navigate through tuples to reach nested
|
|
398
|
+
* arrays.
|
|
389
399
|
*
|
|
390
400
|
* Hint: The inner conditionals (`TValue extends readonly unknown[]` and
|
|
391
401
|
* `TValue extends Record<PropertyKey, unknown>`) distribute over union members,
|
|
@@ -396,7 +406,7 @@ type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey,
|
|
|
396
406
|
* `true extends ...`, which is `true` whenever at least one union member
|
|
397
407
|
* contributed `true`.
|
|
398
408
|
*/
|
|
399
|
-
type IsOrHasArray<TValue> = true extends (IsAny<TValue> extends true ? false : TValue extends readonly unknown[] ? true : TValue extends Record<PropertyKey, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> }[keyof TValue] : false) ? true : false;
|
|
409
|
+
type IsOrHasArray<TValue> = true extends (IsAny<TValue> extends true ? false : TValue extends readonly unknown[] ? number extends TValue["length"] ? true : IsOrHasArray<TValue[number]> : TValue extends Record<PropertyKey, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> }[keyof TValue] : false) ? true : false;
|
|
400
410
|
/**
|
|
401
411
|
* Extracts the exact keys of a tuple, array or object that contain arrays.
|
|
402
412
|
*/
|
|
@@ -411,7 +421,7 @@ type PropertiesOfArrayPath<TValue> = { [TKey in ExactKeysOfArrayPath<TValue>]: T
|
|
|
411
421
|
/**
|
|
412
422
|
* Lazily evaluates only the first valid array path segment based on the given value.
|
|
413
423
|
*/
|
|
414
|
-
type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? TValidPath : readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : TPathToCheck extends readonly [infer TFirstKey extends ExactKeysOfArrayPath<TValue>, ...infer TPathRest extends Path] ? LazyArrayPath<Required<PropertiesOfArrayPath<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<ExactKeysOfArrayPath<TValue>> extends false ? readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : never;
|
|
424
|
+
type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? number extends TValue["length"] ? TValidPath : IsNever<ExactKeysOfArrayPath<TValue>> extends false ? readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : never : readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : TPathToCheck extends readonly [infer TFirstKey extends ExactKeysOfArrayPath<TValue>, ...infer TPathRest extends Path] ? LazyArrayPath<Required<PropertiesOfArrayPath<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<ExactKeysOfArrayPath<TValue>> extends false ? readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : never;
|
|
415
425
|
/**
|
|
416
426
|
* Returns the path if valid, otherwise the first possible valid array path
|
|
417
427
|
* based on the given value.
|
|
@@ -464,7 +474,7 @@ interface FocusFieldConfig<TSchema extends FormSchema, TFieldPath extends Requir
|
|
|
464
474
|
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
465
475
|
}
|
|
466
476
|
/**
|
|
467
|
-
* Focuses the first input element of a field. This is useful for
|
|
477
|
+
* Focuses the first focusable input element of a field. This is useful for
|
|
468
478
|
* programmatically setting focus to a specific field, such as after
|
|
469
479
|
* validation errors or user interactions.
|
|
470
480
|
*
|
|
@@ -1163,14 +1173,14 @@ interface FieldProps<TSchema extends FormSchema = FormSchema, TFieldPath extends
|
|
|
1163
1173
|
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
1164
1174
|
}
|
|
1165
1175
|
declare const __VLS_export$2: <TSchema extends FormSchema, TFieldPath extends RequiredPath>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$2<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
1166
|
-
props: __VLS_PrettifyLocal$2<FieldProps<TSchema, TFieldPath>> &
|
|
1176
|
+
props: __VLS_PrettifyLocal$2<FieldProps<TSchema, TFieldPath>> & vue0.PublicProps;
|
|
1167
1177
|
expose: (exposed: {}) => void;
|
|
1168
1178
|
attrs: any;
|
|
1169
1179
|
slots: {
|
|
1170
1180
|
default(props: FieldStore<TSchema, TFieldPath>): any;
|
|
1171
1181
|
};
|
|
1172
1182
|
emit: {};
|
|
1173
|
-
}>) =>
|
|
1183
|
+
}>) => vue0.VNode & {
|
|
1174
1184
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
1175
1185
|
};
|
|
1176
1186
|
declare const _default: typeof __VLS_export$2;
|
|
@@ -1191,14 +1201,14 @@ interface FieldArrayProps<TSchema extends FormSchema = FormSchema, TFieldArrayPa
|
|
|
1191
1201
|
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
1192
1202
|
}
|
|
1193
1203
|
declare const __VLS_export$1: <TSchema extends FormSchema, TFieldArrayPath extends RequiredPath>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$1<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
1194
|
-
props: __VLS_PrettifyLocal$1<FieldArrayProps<TSchema, TFieldArrayPath>> &
|
|
1204
|
+
props: __VLS_PrettifyLocal$1<FieldArrayProps<TSchema, TFieldArrayPath>> & vue0.PublicProps;
|
|
1195
1205
|
expose: (exposed: {}) => void;
|
|
1196
1206
|
attrs: any;
|
|
1197
1207
|
slots: {
|
|
1198
1208
|
default(props: FieldArrayStore<TSchema, TFieldArrayPath>): any;
|
|
1199
1209
|
};
|
|
1200
1210
|
emit: {};
|
|
1201
|
-
}>) =>
|
|
1211
|
+
}>) => vue0.VNode & {
|
|
1202
1212
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
1203
1213
|
};
|
|
1204
1214
|
declare const _default$1: typeof __VLS_export$1;
|
|
@@ -1219,14 +1229,14 @@ interface FormProps<TSchema extends FormSchema = FormSchema> {
|
|
|
1219
1229
|
onSubmit: SubmitEventHandler<TSchema>;
|
|
1220
1230
|
}
|
|
1221
1231
|
declare const __VLS_export: <TSchema extends FormSchema = FormSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
1222
|
-
props: __VLS_PrettifyLocal<FormProps<TSchema>> &
|
|
1232
|
+
props: __VLS_PrettifyLocal<FormProps<TSchema>> & vue0.PublicProps;
|
|
1223
1233
|
expose: (exposed: {}) => void;
|
|
1224
1234
|
attrs: any;
|
|
1225
1235
|
slots: {
|
|
1226
1236
|
default?: (props: {}) => any;
|
|
1227
1237
|
};
|
|
1228
1238
|
emit: {};
|
|
1229
|
-
}>) =>
|
|
1239
|
+
}>) => vue0.VNode & {
|
|
1230
1240
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
1231
1241
|
};
|
|
1232
1242
|
declare const _default$2: typeof __VLS_export;
|
package/dist/index.js
CHANGED
|
@@ -162,31 +162,45 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
162
162
|
* form reset functionality.
|
|
163
163
|
*
|
|
164
164
|
* @param internalFieldStore The field store to reset.
|
|
165
|
-
* @param
|
|
165
|
+
* @param input The new input value (can be any type including array or object).
|
|
166
|
+
* @param keepStart Whether to keep `startInput` and `startItems` as the dirty
|
|
167
|
+
* baseline instead of resetting them to the new input. Used when a field store
|
|
168
|
+
* is reused for an in-place edit so its dirty state is detected correctly.
|
|
166
169
|
*/
|
|
167
|
-
function resetItemState(internalFieldStore,
|
|
170
|
+
function resetItemState(internalFieldStore, input, keepStart = false) {
|
|
168
171
|
batch(() => {
|
|
169
|
-
|
|
172
|
+
const elements = [];
|
|
173
|
+
if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
|
|
174
|
+
internalFieldStore.elements = elements;
|
|
170
175
|
internalFieldStore.errors.value = null;
|
|
171
176
|
internalFieldStore.isTouched.value = false;
|
|
172
177
|
internalFieldStore.isDirty.value = false;
|
|
173
178
|
if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
|
|
174
|
-
const objectInput =
|
|
175
|
-
internalFieldStore.startInput.value = objectInput;
|
|
179
|
+
const objectInput = input == null ? input : true;
|
|
180
|
+
if (!keepStart) internalFieldStore.startInput.value = objectInput;
|
|
176
181
|
internalFieldStore.input.value = objectInput;
|
|
177
|
-
if (internalFieldStore.kind === "array") if (
|
|
178
|
-
const
|
|
179
|
-
|
|
182
|
+
if (internalFieldStore.kind === "array") if (input) {
|
|
183
|
+
const length = internalFieldStore.schema.type === "array" ? input.length : internalFieldStore.children.length;
|
|
184
|
+
const newItems = Array.from({ length }, createId);
|
|
185
|
+
if (!keepStart) internalFieldStore.startItems.value = newItems;
|
|
180
186
|
internalFieldStore.items.value = newItems;
|
|
181
|
-
|
|
187
|
+
let path;
|
|
188
|
+
for (let index = 0; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], input[index], keepStart);
|
|
189
|
+
else {
|
|
190
|
+
path ??= JSON.parse(internalFieldStore.name);
|
|
191
|
+
internalFieldStore.children[index] = {};
|
|
192
|
+
path.push(index);
|
|
193
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
|
|
194
|
+
path.pop();
|
|
195
|
+
}
|
|
182
196
|
} else {
|
|
183
|
-
internalFieldStore.startItems.value = [];
|
|
197
|
+
if (!keepStart) internalFieldStore.startItems.value = [];
|
|
184
198
|
internalFieldStore.items.value = [];
|
|
185
199
|
}
|
|
186
|
-
else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key],
|
|
200
|
+
else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
|
|
187
201
|
} else {
|
|
188
|
-
internalFieldStore.startInput.value =
|
|
189
|
-
internalFieldStore.input.value =
|
|
202
|
+
if (!keepStart) internalFieldStore.startInput.value = input;
|
|
203
|
+
internalFieldStore.input.value = input;
|
|
190
204
|
}
|
|
191
205
|
});
|
|
192
206
|
}
|
|
@@ -253,6 +267,28 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
253
267
|
});
|
|
254
268
|
}
|
|
255
269
|
/**
|
|
270
|
+
* Focuses the first focusable element of a field store. The elements are tried
|
|
271
|
+
* in order and the first one that actually receives focus wins, so detached,
|
|
272
|
+
* disabled or hidden elements are skipped. The browser decides focusability,
|
|
273
|
+
* which is read back via the element's root `activeElement` so elements in a
|
|
274
|
+
* shadow root or another document are handled correctly.
|
|
275
|
+
*
|
|
276
|
+
* Hint: A `display: none` or `hidden` element is correctly skipped in real
|
|
277
|
+
* browsers, but jsdom has no layout and focuses it anyway, so that case cannot
|
|
278
|
+
* be covered by unit tests.
|
|
279
|
+
*
|
|
280
|
+
* @param internalFieldStore The field store to focus.
|
|
281
|
+
*
|
|
282
|
+
* @returns Whether an element was focused.
|
|
283
|
+
*/
|
|
284
|
+
function focusFieldElement(internalFieldStore) {
|
|
285
|
+
for (const element of internalFieldStore.elements) {
|
|
286
|
+
element.focus();
|
|
287
|
+
if (element.getRootNode().activeElement === element) return true;
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
256
292
|
* Returns whether the specified boolean property is true for the field store
|
|
257
293
|
* or any of its nested children. Recursively checks arrays and objects.
|
|
258
294
|
*
|
|
@@ -363,11 +399,9 @@ function getFieldStore(internalFormStore, path) {
|
|
|
363
399
|
*/
|
|
364
400
|
function setFieldBool(internalFieldStore, type, bool) {
|
|
365
401
|
batch(() => {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
} else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
370
|
-
else internalFieldStore[type].value = bool;
|
|
402
|
+
internalFieldStore[type].value = bool;
|
|
403
|
+
if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
|
|
404
|
+
else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
371
405
|
});
|
|
372
406
|
}
|
|
373
407
|
/**
|
|
@@ -382,20 +416,20 @@ function setNestedInput(internalFieldStore, input) {
|
|
|
382
416
|
if (internalFieldStore.kind === "array") {
|
|
383
417
|
const arrayInput = input ?? [];
|
|
384
418
|
const items = internalFieldStore.items.value;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
419
|
+
const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
|
|
420
|
+
if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
|
|
421
|
+
else if (length > items.length) {
|
|
422
|
+
const path = JSON.parse(internalFieldStore.name);
|
|
423
|
+
for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
|
|
424
|
+
else {
|
|
425
|
+
internalFieldStore.children[index] = {};
|
|
426
|
+
path.push(index);
|
|
427
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
|
|
428
|
+
path.pop();
|
|
395
429
|
}
|
|
396
|
-
internalFieldStore.items.value = [...items, ...
|
|
430
|
+
internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
|
|
397
431
|
}
|
|
398
|
-
for (let index = 0; index <
|
|
432
|
+
for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
|
|
399
433
|
internalFieldStore.input.value = input == null ? input : true;
|
|
400
434
|
internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
|
|
401
435
|
} else if (internalFieldStore.kind === "object") {
|
|
@@ -439,21 +473,22 @@ function setFieldInput(internalFormStore, path, input) {
|
|
|
439
473
|
function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
440
474
|
batch(() => {
|
|
441
475
|
if (internalFieldStore.kind === "array") {
|
|
442
|
-
internalFieldStore.
|
|
476
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
443
477
|
const initialArrayInput = initialInput ?? [];
|
|
444
|
-
|
|
478
|
+
const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
|
|
479
|
+
if (length > internalFieldStore.children.length) {
|
|
445
480
|
const path = JSON.parse(internalFieldStore.name);
|
|
446
|
-
for (let index = internalFieldStore.children.length; index <
|
|
481
|
+
for (let index = internalFieldStore.children.length; index < length; index++) {
|
|
447
482
|
internalFieldStore.children[index] = {};
|
|
448
483
|
path.push(index);
|
|
449
484
|
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
|
|
450
485
|
path.pop();
|
|
451
486
|
}
|
|
452
487
|
}
|
|
453
|
-
internalFieldStore.initialItems.value =
|
|
488
|
+
internalFieldStore.initialItems.value = Array.from({ length }, createId);
|
|
454
489
|
for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
|
|
455
490
|
} else if (internalFieldStore.kind === "object") {
|
|
456
|
-
internalFieldStore.
|
|
491
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
457
492
|
for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
|
|
458
493
|
} else internalFieldStore.initialInput.value = initialInput;
|
|
459
494
|
});
|
|
@@ -505,44 +540,49 @@ function createFormStore(config, parse) {
|
|
|
505
540
|
async function validateFormInput(internalFormStore, config) {
|
|
506
541
|
internalFormStore.validators++;
|
|
507
542
|
internalFormStore.isValidating.value = true;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const path
|
|
515
|
-
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const name = JSON.stringify(path);
|
|
523
|
-
const fieldErrors = nestedErrors[name];
|
|
524
|
-
if (fieldErrors) fieldErrors.push(issue.message);
|
|
525
|
-
else nestedErrors[name] = [issue.message];
|
|
526
|
-
} else if (rootErrors) rootErrors.push(issue.message);
|
|
527
|
-
else rootErrors = [issue.message];
|
|
528
|
-
}
|
|
529
|
-
let shouldFocus = config?.shouldFocus ?? false;
|
|
530
|
-
batch(() => {
|
|
531
|
-
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
532
|
-
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
533
|
-
else {
|
|
534
|
-
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
535
|
-
internalFieldStore.errors.value = fieldErrors;
|
|
536
|
-
if (shouldFocus && fieldErrors) {
|
|
537
|
-
internalFieldStore.elements[0]?.focus();
|
|
538
|
-
shouldFocus = false;
|
|
543
|
+
try {
|
|
544
|
+
const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
|
|
545
|
+
let rootErrors;
|
|
546
|
+
let nestedErrors;
|
|
547
|
+
if (result.issues) {
|
|
548
|
+
nestedErrors = {};
|
|
549
|
+
for (const issue of result.issues) if (issue.path) {
|
|
550
|
+
const path = [];
|
|
551
|
+
for (const pathItem of issue.path) {
|
|
552
|
+
const key = pathItem.key;
|
|
553
|
+
const keyType = typeof key;
|
|
554
|
+
const itemType = pathItem.type;
|
|
555
|
+
if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
|
|
556
|
+
path.push(key);
|
|
539
557
|
}
|
|
540
|
-
|
|
558
|
+
const name = JSON.stringify(path);
|
|
559
|
+
const fieldErrors = nestedErrors[name];
|
|
560
|
+
if (fieldErrors) fieldErrors.push(issue.message);
|
|
561
|
+
else nestedErrors[name] = [issue.message];
|
|
562
|
+
} else if (rootErrors) rootErrors.push(issue.message);
|
|
563
|
+
else rootErrors = [issue.message];
|
|
564
|
+
}
|
|
565
|
+
let shouldFocus = config?.shouldFocus ?? false;
|
|
566
|
+
batch(() => {
|
|
567
|
+
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
568
|
+
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
569
|
+
else {
|
|
570
|
+
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
571
|
+
internalFieldStore.errors.value = fieldErrors;
|
|
572
|
+
if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
internalFormStore.validators--;
|
|
576
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
541
577
|
});
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
578
|
+
return result;
|
|
579
|
+
} catch (error) {
|
|
580
|
+
batch(() => {
|
|
581
|
+
internalFormStore.validators--;
|
|
582
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
583
|
+
});
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
546
586
|
}
|
|
547
587
|
/**
|
|
548
588
|
* Validates the form input if required based on the validation mode and form
|
|
@@ -564,7 +604,7 @@ const INTERNAL = "~internal";
|
|
|
564
604
|
//#endregion
|
|
565
605
|
//#region ../../packages/methods/dist/index.vue.js
|
|
566
606
|
/**
|
|
567
|
-
* Focuses the first input element of a field. This is useful for
|
|
607
|
+
* Focuses the first focusable input element of a field. This is useful for
|
|
568
608
|
* programmatically setting focus to a specific field, such as after
|
|
569
609
|
* validation errors or user interactions.
|
|
570
610
|
*
|
|
@@ -572,7 +612,7 @@ const INTERNAL = "~internal";
|
|
|
572
612
|
* @param config The focus field configuration.
|
|
573
613
|
*/
|
|
574
614
|
function focus(form, config) {
|
|
575
|
-
getFieldStore(form[INTERNAL], config.path)
|
|
615
|
+
focusFieldElement(getFieldStore(form[INTERNAL], config.path));
|
|
576
616
|
}
|
|
577
617
|
/**
|
|
578
618
|
* Retrieves all error messages from all fields in the form by walking through
|
|
@@ -875,7 +915,10 @@ function useField(form, config) {
|
|
|
875
915
|
const internalFormStore = computed(() => toValue(form)[INTERNAL]);
|
|
876
916
|
const internalFieldStore = computed(() => getFieldStore(internalFormStore.value, path.value));
|
|
877
917
|
onUnmounted(() => {
|
|
878
|
-
|
|
918
|
+
const internalFieldStoreValue = internalFieldStore.value;
|
|
919
|
+
const elements = internalFieldStoreValue.elements.filter((element) => element.isConnected);
|
|
920
|
+
if (internalFieldStoreValue.elements === internalFieldStoreValue.initialElements) internalFieldStoreValue.initialElements = elements;
|
|
921
|
+
internalFieldStoreValue.elements = elements;
|
|
879
922
|
});
|
|
880
923
|
const input = computed(() => getFieldInput(internalFieldStore.value));
|
|
881
924
|
const isTouched = computed(() => getFieldBool(internalFieldStore.value, "isTouched"));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formisch/vue",
|
|
3
3
|
"description": "The lightweight, schema-first, and fully type-safe form library for Vue",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Fabian Hiller",
|
|
7
7
|
"homepage": "https://formisch.dev",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@formisch/methods": "workspace:*",
|
|
48
48
|
"@testing-library/jest-dom": "^6.6.0",
|
|
49
49
|
"@types/node": "^24.1.0",
|
|
50
|
-
"@vitest/coverage-v8": "^
|
|
50
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
51
51
|
"@vue/eslint-config-typescript": "^14.6.0",
|
|
52
52
|
"@vue/test-utils": "^2.4.6",
|
|
53
53
|
"eslint": "^9.32.0",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"typescript": "~5.8.3",
|
|
58
58
|
"unplugin-vue": "^7.0.0",
|
|
59
59
|
"valibot": "^1.4.1",
|
|
60
|
-
"vitest": "^
|
|
60
|
+
"vitest": "^4.1.7",
|
|
61
61
|
"vue": "^3.5.18",
|
|
62
62
|
"vue-tsc": "^3.0.4"
|
|
63
63
|
},
|