@formisch/svelte 0.8.0 → 0.10.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.
@@ -1,4 +1,4 @@
1
- import { BaseFormStore, DeepPartial, DirtyPath, FormSchema, PartialValues, PathValue, RequiredPath, SubmitEventHandler, SubmitHandler, ValidArrayPath, ValidPath } from "../core/index.svelte";
1
+ import { BaseFormStore, DeepPartial, DirtyPath, FieldPath, FormSchema, PartialValues, Path, PathValue, RequiredPath, SubmitEventHandler, SubmitHandler, ValidArrayPath, ValidPath } from "../core/index.svelte";
2
2
  import * as v from "valibot";
3
3
 
4
4
  //#region src/focus/focus.d.ts
@@ -13,7 +13,7 @@ interface FocusFieldConfig<TSchema extends FormSchema, TFieldPath extends Requir
13
13
  readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
14
14
  }
15
15
  /**
16
- * Focuses the first input element of a field. This is useful for
16
+ * Focuses the first focusable input element of a field. This is useful for
17
17
  * programmatically setting focus to a specific field, such as after
18
18
  * validation errors or user interactions.
19
19
  *
@@ -22,17 +22,104 @@ interface FocusFieldConfig<TSchema extends FormSchema, TFieldPath extends Requir
22
22
  */
23
23
  declare function focus<TSchema extends FormSchema, TFieldPath extends RequiredPath>(form: BaseFormStore<TSchema>, config: FocusFieldConfig<TSchema, TFieldPath>): void;
24
24
  //#endregion
25
- //#region src/getAllErrors/getAllErrors.d.ts
25
+ //#region src/getDeepErrorEntries/getDeepErrorEntries.d.ts
26
26
  /**
27
- * Retrieves all error messages from all fields in the form by walking through
28
- * the entire field store tree. This is useful for displaying a summary of all
29
- * validation errors across the form.
27
+ * Deep error entry interface.
28
+ */
29
+ interface DeepErrorEntry<TValue = unknown> {
30
+ /**
31
+ * The path to the field with errors, or an empty path for form-level errors.
32
+ */
33
+ readonly path: unknown extends TValue ? Path : readonly [] | FieldPath<TValue>;
34
+ /**
35
+ * The error messages of the field.
36
+ */
37
+ readonly errors: [string, ...string[]];
38
+ }
39
+ /**
40
+ * Get form deep error entries config interface.
41
+ */
42
+ interface GetFormDeepErrorEntriesConfig {
43
+ /**
44
+ * The path to a field. Leave undefined to get the entries of the entire form.
45
+ */
46
+ readonly path?: undefined;
47
+ }
48
+ /**
49
+ * Get field deep error entries config interface.
50
+ */
51
+ interface GetFieldDeepErrorEntriesConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
52
+ /**
53
+ * The path to the field to retrieve the entries from.
54
+ */
55
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
56
+ }
57
+ /**
58
+ * Retrieves the errors of a specific field or the entire form as a list of
59
+ * entries, each pairing the path to a field with its error messages. This is
60
+ * useful for building custom error summaries that link each message back to
61
+ * its field. Form-level errors are included with an empty path.
62
+ *
63
+ * @param form The form store to retrieve error entries from.
64
+ *
65
+ * @returns A list of path and error message entries.
66
+ */
67
+ declare function getDeepErrorEntries<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): DeepErrorEntry<v.InferInput<TSchema>>[];
68
+ /**
69
+ * Retrieves the errors of a specific field or the entire form as a list of
70
+ * entries, each pairing the path to a field with its error messages. This is
71
+ * useful for building custom error summaries that link each message back to
72
+ * its field. Form-level errors are included with an empty path.
73
+ *
74
+ * @param form The form store to retrieve error entries from.
75
+ * @param config The get deep error entries configuration.
76
+ *
77
+ * @returns A list of path and error message entries.
78
+ */
79
+ declare function getDeepErrorEntries<TSchema extends FormSchema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? GetFieldDeepErrorEntriesConfig<TSchema, TFieldPath> : GetFormDeepErrorEntriesConfig): DeepErrorEntry<v.InferInput<TSchema>>[];
80
+ //#endregion
81
+ //#region src/getDeepErrors/getDeepErrors.d.ts
82
+ /**
83
+ * Get form deep errors config interface.
84
+ */
85
+ interface GetFormDeepErrorsConfig {
86
+ /**
87
+ * The path to a field. Leave undefined to get the errors of the entire form.
88
+ */
89
+ readonly path?: undefined;
90
+ }
91
+ /**
92
+ * Get field deep errors config interface.
93
+ */
94
+ interface GetFieldDeepErrorsConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
95
+ /**
96
+ * The path to the field to retrieve the errors from.
97
+ */
98
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
99
+ }
100
+ /**
101
+ * Retrieves all error messages of a specific field or the entire form by
102
+ * walking through the field store and all its descendants. This is useful for
103
+ * displaying a summary of all validation errors within a section or the whole
104
+ * form. Form-level errors are included.
30
105
  *
31
106
  * @param form The form store to retrieve errors from.
32
107
  *
33
108
  * @returns A non-empty array of error messages, or null if no errors exist.
34
109
  */
35
- declare function getAllErrors(form: BaseFormStore): [string, ...string[]] | null;
110
+ declare function getDeepErrors<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): [string, ...string[]] | null;
111
+ /**
112
+ * Retrieves all error messages of a specific field or the entire form by
113
+ * walking through the field store and all its descendants. This is useful for
114
+ * displaying a summary of all validation errors within a section or the whole
115
+ * form. Form-level errors are included.
116
+ *
117
+ * @param form The form store to retrieve errors from.
118
+ * @param config The get deep errors configuration.
119
+ *
120
+ * @returns A non-empty array of error messages, or null if no errors exist.
121
+ */
122
+ declare function getDeepErrors<TSchema extends FormSchema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? GetFieldDeepErrorsConfig<TSchema, TFieldPath> : GetFormDeepErrorsConfig): [string, ...string[]] | null;
36
123
  //#endregion
37
124
  //#region src/getDirtyInput/getDirtyInput.d.ts
38
125
  /**
@@ -252,6 +339,172 @@ interface InsertConfig<TSchema extends FormSchema, TFieldArrayPath extends Requi
252
339
  */
253
340
  declare function insert<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath>(form: BaseFormStore<TSchema>, config: InsertConfig<TSchema, TFieldArrayPath>): void;
254
341
  //#endregion
342
+ //#region src/isDirty/isDirty.d.ts
343
+ /**
344
+ * Is form dirty config interface.
345
+ */
346
+ interface IsFormDirtyConfig {
347
+ /**
348
+ * The path to a field. Leave undefined to check the entire form.
349
+ */
350
+ readonly path?: undefined;
351
+ }
352
+ /**
353
+ * Is field dirty config interface.
354
+ */
355
+ interface IsFieldDirtyConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
356
+ /**
357
+ * The path to the field to check for dirtiness.
358
+ */
359
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
360
+ }
361
+ /**
362
+ * Checks whether a specific field or the entire form is dirty by walking
363
+ * through the field store and all its descendants. A field is dirty when its
364
+ * input differs from its initial value.
365
+ *
366
+ * @param form The form store to check for dirtiness.
367
+ *
368
+ * @returns Whether the field or form is dirty.
369
+ */
370
+ declare function isDirty<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): boolean;
371
+ /**
372
+ * Checks whether a specific field or the entire form is dirty by walking
373
+ * through the field store and all its descendants. A field is dirty when its
374
+ * input differs from its initial value.
375
+ *
376
+ * @param form The form store to check for dirtiness.
377
+ * @param config The is dirty configuration.
378
+ *
379
+ * @returns Whether the field or form is dirty.
380
+ */
381
+ declare function isDirty<TSchema extends FormSchema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? IsFieldDirtyConfig<TSchema, TFieldPath> : IsFormDirtyConfig): boolean;
382
+ //#endregion
383
+ //#region src/isEdited/isEdited.d.ts
384
+ /**
385
+ * Is form edited config interface.
386
+ */
387
+ interface IsFormEditedConfig {
388
+ /**
389
+ * The path to a field. Leave undefined to check the entire form.
390
+ */
391
+ readonly path?: undefined;
392
+ }
393
+ /**
394
+ * Is field edited config interface.
395
+ */
396
+ interface IsFieldEditedConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
397
+ /**
398
+ * The path to the field to check for being edited.
399
+ */
400
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
401
+ }
402
+ /**
403
+ * Checks whether a specific field or the entire form is edited by walking
404
+ * through the field store and all its descendants. A field is edited once its
405
+ * value has been changed by the user.
406
+ *
407
+ * @param form The form store to check for being edited.
408
+ *
409
+ * @returns Whether the field or form is edited.
410
+ */
411
+ declare function isEdited<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): boolean;
412
+ /**
413
+ * Checks whether a specific field or the entire form is edited by walking
414
+ * through the field store and all its descendants. A field is edited once its
415
+ * value has been changed by the user.
416
+ *
417
+ * @param form The form store to check for being edited.
418
+ * @param config The is edited configuration.
419
+ *
420
+ * @returns Whether the field or form is edited.
421
+ */
422
+ declare function isEdited<TSchema extends FormSchema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? IsFieldEditedConfig<TSchema, TFieldPath> : IsFormEditedConfig): boolean;
423
+ //#endregion
424
+ //#region src/isTouched/isTouched.d.ts
425
+ /**
426
+ * Is form touched config interface.
427
+ */
428
+ interface IsFormTouchedConfig {
429
+ /**
430
+ * The path to a field. Leave undefined to check the entire form.
431
+ */
432
+ readonly path?: undefined;
433
+ }
434
+ /**
435
+ * Is field touched config interface.
436
+ */
437
+ interface IsFieldTouchedConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
438
+ /**
439
+ * The path to the field to check for being touched.
440
+ */
441
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
442
+ }
443
+ /**
444
+ * Checks whether a specific field or the entire form is touched by walking
445
+ * through the field store and all its descendants. A field is touched once it
446
+ * has received and lost focus.
447
+ *
448
+ * @param form The form store to check for being touched.
449
+ *
450
+ * @returns Whether the field or form is touched.
451
+ */
452
+ declare function isTouched<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): boolean;
453
+ /**
454
+ * Checks whether a specific field or the entire form is touched by walking
455
+ * through the field store and all its descendants. A field is touched once it
456
+ * has received and lost focus.
457
+ *
458
+ * @param form The form store to check for being touched.
459
+ * @param config The is touched configuration.
460
+ *
461
+ * @returns Whether the field or form is touched.
462
+ */
463
+ declare function isTouched<TSchema extends FormSchema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? IsFieldTouchedConfig<TSchema, TFieldPath> : IsFormTouchedConfig): boolean;
464
+ //#endregion
465
+ //#region src/isValid/isValid.d.ts
466
+ /**
467
+ * Is form valid config interface.
468
+ */
469
+ interface IsFormValidConfig {
470
+ /**
471
+ * The path to a field. Leave undefined to check the entire form.
472
+ */
473
+ readonly path?: undefined;
474
+ }
475
+ /**
476
+ * Is field valid config interface.
477
+ */
478
+ interface IsFieldValidConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
479
+ /**
480
+ * The path to the field to check for validity.
481
+ */
482
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
483
+ }
484
+ /**
485
+ * Checks whether a specific field or the entire form is valid by walking
486
+ * through the field store and all its descendants. A field is valid when
487
+ * neither it nor any of its descendants contains an error. Form-level errors
488
+ * are included.
489
+ *
490
+ * @param form The form store to check for validity.
491
+ *
492
+ * @returns Whether the field or form is valid.
493
+ */
494
+ declare function isValid<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): boolean;
495
+ /**
496
+ * Checks whether a specific field or the entire form is valid by walking
497
+ * through the field store and all its descendants. A field is valid when
498
+ * neither it nor any of its descendants contains an error. Form-level errors
499
+ * are included.
500
+ *
501
+ * @param form The form store to check for validity.
502
+ * @param config The is valid configuration.
503
+ *
504
+ * @returns Whether the field or form is valid.
505
+ */
506
+ declare function isValid<TSchema extends FormSchema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? IsFieldValidConfig<TSchema, TFieldPath> : IsFormValidConfig): boolean;
507
+ //#endregion
255
508
  //#region src/move/move.d.ts
256
509
  /**
257
510
  * Move array field config interface.
@@ -367,6 +620,10 @@ interface ResetBaseConfig {
367
620
  */
368
621
  readonly keepTouched?: boolean | undefined;
369
622
  /**
623
+ * Whether to keep the edited state during reset. Defaults to false.
624
+ */
625
+ readonly keepEdited?: boolean | undefined;
626
+ /**
370
627
  * Whether to keep the error messages during reset. Defaults to false.
371
628
  */
372
629
  readonly keepErrors?: boolean | undefined;
@@ -561,4 +818,4 @@ interface ValidateFormConfig {
561
818
  */
562
819
  declare function validate<TSchema extends FormSchema>(form: BaseFormStore<TSchema>, config?: ValidateFormConfig): Promise<v.SafeParseResult<TSchema>>;
563
820
  //#endregion
564
- export { FocusFieldConfig, GetFieldDirtyInputConfig, GetFieldDirtyPathsConfig, GetFieldErrorsConfig, GetFieldInputConfig, GetFormDirtyInputConfig, GetFormDirtyPathsConfig, GetFormErrorsConfig, GetFormInputConfig, InsertConfig, MoveConfig, PickDirtyConfig, RemoveConfig, ReplaceConfig, ResetFieldConfig, ResetFormConfig, SetFieldErrorsConfig, SetFieldInputConfig, SetFormErrorsConfig, SetFormInputConfig, SwapConfig, ValidateFormConfig, focus, getAllErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, validate };
821
+ export { DeepErrorEntry, FocusFieldConfig, GetFieldDeepErrorEntriesConfig, GetFieldDeepErrorsConfig, GetFieldDirtyInputConfig, GetFieldDirtyPathsConfig, GetFieldErrorsConfig, GetFieldInputConfig, GetFormDeepErrorEntriesConfig, GetFormDeepErrorsConfig, GetFormDirtyInputConfig, GetFormDirtyPathsConfig, GetFormErrorsConfig, GetFormInputConfig, InsertConfig, IsFieldDirtyConfig, IsFieldEditedConfig, IsFieldTouchedConfig, IsFieldValidConfig, IsFormDirtyConfig, IsFormEditedConfig, IsFormTouchedConfig, IsFormValidConfig, MoveConfig, PickDirtyConfig, RemoveConfig, ReplaceConfig, ResetFieldConfig, ResetFormConfig, SetFieldErrorsConfig, SetFieldInputConfig, SetFormErrorsConfig, SetFormInputConfig, SwapConfig, ValidateFormConfig, focus, getDeepErrorEntries, getDeepErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, isDirty, isEdited, isTouched, isValid, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, validate };
@@ -1,8 +1,8 @@
1
- import { INTERNAL, batch, copyItemState, createId, getDirtyFieldInput, getFieldBool, getFieldInput, getFieldStore, initializeFieldStore, resetItemState, setFieldInput, setInitialFieldInput, swapItemState, untrack, validateFormInput, validateIfRequired, walkFieldStore } from "../core/index.svelte";
1
+ import { INTERNAL, batch, copyItemState, createId, focusFieldElement, getDirtyFieldInput, getFieldBool, getFieldInput, getFieldStore, initializeFieldStore, resetItemState, setFieldInput, setInitialFieldInput, swapItemState, untrack, validateFormInput, validateIfRequired, walkFieldStore } from "../core/index.svelte";
2
2
 
3
3
  //#region src/focus/focus.ts
4
4
  /**
5
- * Focuses the first input element of a field. This is useful for
5
+ * Focuses the first focusable input element of a field. This is useful for
6
6
  * programmatically setting focus to a specific field, such as after
7
7
  * validation errors or user interactions.
8
8
  *
@@ -10,30 +10,35 @@ import { INTERNAL, batch, copyItemState, createId, getDirtyFieldInput, getFieldB
10
10
  * @param config The focus field configuration.
11
11
  */
12
12
  function focus(form, config) {
13
- getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
13
+ focusFieldElement(getFieldStore(form[INTERNAL], config.path));
14
14
  }
15
15
 
16
16
  //#endregion
17
- //#region src/getAllErrors/getAllErrors.ts
18
- /**
19
- * Retrieves all error messages from all fields in the form by walking through
20
- * the entire field store tree. This is useful for displaying a summary of all
21
- * validation errors across the form.
22
- *
23
- * @param form The form store to retrieve errors from.
24
- *
25
- * @returns A non-empty array of error messages, or null if no errors exist.
26
- */
17
+ //#region src/getDeepErrorEntries/getDeepErrorEntries.ts
27
18
  /* @__NO_SIDE_EFFECTS__ */
28
- function getAllErrors(form) {
29
- let allErrors = null;
30
- walkFieldStore(form[INTERNAL], (internalFieldStore) => {
31
- if (internalFieldStore.kind === "array") internalFieldStore.items.value;
19
+ function getDeepErrorEntries(form, config) {
20
+ const entries = [];
21
+ walkFieldStore(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], (internalFieldStore) => {
32
22
  const errors = internalFieldStore.errors.value;
33
- if (errors) if (allErrors) allErrors.push(...errors);
34
- else allErrors = [...errors];
23
+ if (errors) entries.push({
24
+ path: internalFieldStore.path,
25
+ errors
26
+ });
35
27
  });
36
- return allErrors;
28
+ return entries;
29
+ }
30
+
31
+ //#endregion
32
+ //#region src/getDeepErrors/getDeepErrors.ts
33
+ /* @__NO_SIDE_EFFECTS__ */
34
+ function getDeepErrors(form, config) {
35
+ let deepErrors = null;
36
+ walkFieldStore(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], (internalFieldStore) => {
37
+ const errors = internalFieldStore.errors.value;
38
+ if (errors) if (deepErrors) deepErrors.push(...errors);
39
+ else deepErrors = [...errors];
40
+ });
41
+ return deepErrors;
37
42
  }
38
43
 
39
44
  //#endregion
@@ -47,9 +52,8 @@ function getDirtyInput(form, config) {
47
52
  //#region src/getDirtyPaths/getDirtyPaths.ts
48
53
  /* @__NO_SIDE_EFFECTS__ */
49
54
  function getDirtyPaths(form, config) {
50
- config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
51
55
  const paths = [];
52
- config?.path && [...config.path];
56
+ config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
53
57
  return paths;
54
58
  }
55
59
 
@@ -112,26 +116,51 @@ function insert(form, config) {
112
116
  internalArrayStore.items.value = newItems;
113
117
  for (let index = items.length; index > insertIndex; index--) {
114
118
  if (!internalArrayStore.children[index]) {
115
- const path = JSON.parse(internalArrayStore.name);
116
119
  internalArrayStore.children[index] = {};
117
- path.push(index);
118
- initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, path);
120
+ initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, [...internalArrayStore.path, index]);
119
121
  }
120
122
  copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
121
123
  }
122
124
  if (!internalArrayStore.children[insertIndex]) {
123
- const path = JSON.parse(internalArrayStore.name);
124
125
  internalArrayStore.children[insertIndex] = {};
125
- path.push(insertIndex);
126
- initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, path);
126
+ initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, [...internalArrayStore.path, insertIndex]);
127
127
  } else resetItemState(internalArrayStore.children[insertIndex], config.initialInput);
128
128
  internalArrayStore.input.value = true;
129
129
  internalArrayStore.isTouched.value = true;
130
+ internalArrayStore.isEdited.value = true;
130
131
  internalArrayStore.isDirty.value = true;
131
132
  validateIfRequired(internalFormStore, internalArrayStore, "input");
132
133
  });
133
134
  }
134
135
 
136
+ //#endregion
137
+ //#region src/isDirty/isDirty.ts
138
+ /* @__NO_SIDE_EFFECTS__ */
139
+ function isDirty(form, config) {
140
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isDirty");
141
+ }
142
+
143
+ //#endregion
144
+ //#region src/isEdited/isEdited.ts
145
+ /* @__NO_SIDE_EFFECTS__ */
146
+ function isEdited(form, config) {
147
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isEdited");
148
+ }
149
+
150
+ //#endregion
151
+ //#region src/isTouched/isTouched.ts
152
+ /* @__NO_SIDE_EFFECTS__ */
153
+ function isTouched(form, config) {
154
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isTouched");
155
+ }
156
+
157
+ //#endregion
158
+ //#region src/isValid/isValid.ts
159
+ /* @__NO_SIDE_EFFECTS__ */
160
+ function isValid(form, config) {
161
+ return !getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "errors");
162
+ }
163
+
135
164
  //#endregion
136
165
  //#region src/move/move.ts
137
166
  /**
@@ -156,6 +185,7 @@ function move(form, config) {
156
185
  else for (let index = config.from; index > config.to; index--) copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
157
186
  copyItemState(tempInternalFieldStore, internalArrayStore.children[config.to]);
158
187
  internalArrayStore.isTouched.value = true;
188
+ internalArrayStore.isEdited.value = true;
159
189
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
160
190
  validateIfRequired(internalFormStore, internalArrayStore, "input");
161
191
  });
@@ -225,6 +255,7 @@ function remove(form, config) {
225
255
  internalArrayStore.items.value = newItems;
226
256
  for (let index = config.at; index < items.length - 1; index++) copyItemState(internalArrayStore.children[index + 1], internalArrayStore.children[index]);
227
257
  internalArrayStore.isTouched.value = true;
258
+ internalArrayStore.isEdited.value = true;
228
259
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
229
260
  validateIfRequired(internalFormStore, internalArrayStore, "input");
230
261
  });
@@ -248,6 +279,7 @@ function replace(form, config) {
248
279
  internalArrayStore.items.value = newItems;
249
280
  resetItemState(internalArrayStore.children[config.at], config.initialInput);
250
281
  internalArrayStore.isTouched.value = true;
282
+ internalArrayStore.isEdited.value = true;
251
283
  internalArrayStore.isDirty.value = true;
252
284
  validateIfRequired(internalFormStore, internalArrayStore, "input");
253
285
  });
@@ -265,6 +297,7 @@ function reset(form, config) {
265
297
  internalFieldStore$1.elements = internalFieldStore$1.initialElements;
266
298
  if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
267
299
  if (!config?.keepTouched) internalFieldStore$1.isTouched.value = false;
300
+ if (!config?.keepEdited) internalFieldStore$1.isEdited.value = false;
268
301
  internalFieldStore$1.startInput.value = internalFieldStore$1.initialInput.value;
269
302
  if (!config?.keepInput) internalFieldStore$1.input.value = internalFieldStore$1.initialInput.value;
270
303
  if (internalFieldStore$1.kind === "array") {
@@ -335,6 +368,7 @@ function swap(form, config) {
335
368
  internalArrayStore.items.value = newItems;
336
369
  swapItemState(internalArrayStore.children[config.at], internalArrayStore.children[config.and]);
337
370
  internalArrayStore.isTouched.value = true;
371
+ internalArrayStore.isEdited.value = true;
338
372
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
339
373
  validateIfRequired(internalFormStore, internalArrayStore, "input");
340
374
  });
@@ -357,4 +391,4 @@ function validate(form, config) {
357
391
  }
358
392
 
359
393
  //#endregion
360
- export { focus, getAllErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, validate };
394
+ export { focus, getDeepErrorEntries, getDeepErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, isDirty, isEdited, isTouched, isValid, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, validate };
@@ -10,6 +10,7 @@ export function createForm(config) {
10
10
  }
11
11
  });
12
12
  const isTouched = $derived(getFieldBool(internalFormStore, 'isTouched'));
13
+ const isEdited = $derived(getFieldBool(internalFormStore, 'isEdited'));
13
14
  const isDirty = $derived(getFieldBool(internalFormStore, 'isDirty'));
14
15
  const isValid = $derived(!getFieldBool(internalFormStore, 'errors'));
15
16
  return {
@@ -26,6 +27,9 @@ export function createForm(config) {
26
27
  get isTouched() {
27
28
  return isTouched;
28
29
  },
30
+ get isEdited() {
31
+ return isEdited;
32
+ },
29
33
  get isDirty() {
30
34
  return isDirty;
31
35
  },
@@ -8,6 +8,7 @@ export function useField(form, config) {
8
8
  const internalFieldStore = $derived(getFieldStore(internalFormStore, path));
9
9
  const input = $derived(getFieldInput(internalFieldStore));
10
10
  const isTouched = $derived(getFieldBool(internalFieldStore, 'isTouched'));
11
+ const isEdited = $derived(getFieldBool(internalFieldStore, 'isEdited'));
11
12
  const isDirty = $derived(getFieldBool(internalFieldStore, 'isDirty'));
12
13
  const isValid = $derived(!getFieldBool(internalFieldStore, 'errors'));
13
14
  return {
@@ -23,6 +24,9 @@ export function useField(form, config) {
23
24
  get isTouched() {
24
25
  return isTouched;
25
26
  },
27
+ get isEdited() {
28
+ return isEdited;
29
+ },
26
30
  get isDirty() {
27
31
  return isDirty;
28
32
  },
@@ -41,7 +45,13 @@ export function useField(form, config) {
41
45
  [createAttachmentKey()](element) {
42
46
  internalFieldStore.elements.push(element);
43
47
  return () => {
44
- internalFieldStore.elements = internalFieldStore.elements.filter((el) => el !== element);
48
+ const elements = internalFieldStore.elements.filter((el) => el !== element);
49
+ // Keep `initialElements` in sync unless a reorder has moved the
50
+ // elements, so resetting a remounted field restores its live element
51
+ if (internalFieldStore.elements === internalFieldStore.initialElements) {
52
+ internalFieldStore.initialElements = elements;
53
+ }
54
+ internalFieldStore.elements = elements;
45
55
  };
46
56
  },
47
57
  onfocus() {
@@ -5,6 +5,7 @@ export function useFieldArray(form, config) {
5
5
  const path = $derived(unwrap(config).path);
6
6
  const internalFieldStore = $derived(getFieldStore(unwrap(form)[INTERNAL], path));
7
7
  const isTouched = $derived(getFieldBool(internalFieldStore, 'isTouched'));
8
+ const isEdited = $derived(getFieldBool(internalFieldStore, 'isEdited'));
8
9
  const isDirty = $derived(getFieldBool(internalFieldStore, 'isDirty'));
9
10
  const isValid = $derived(!getFieldBool(internalFieldStore, 'errors'));
10
11
  return {
@@ -20,6 +21,9 @@ export function useFieldArray(form, config) {
20
21
  get isTouched() {
21
22
  return isTouched;
22
23
  },
24
+ get isEdited() {
25
+ return isEdited;
26
+ },
23
27
  get isDirty() {
24
28
  return isDirty;
25
29
  },
@@ -54,6 +54,10 @@ export interface FieldStore<TSchema extends FormSchema = FormSchema, TFieldPath
54
54
  * Whether the field has been touched.
55
55
  */
56
56
  readonly isTouched: boolean;
57
+ /**
58
+ * Whether the field value has been edited.
59
+ */
60
+ readonly isEdited: boolean;
57
61
  /**
58
62
  * Whether the field input differs from its initial value.
59
63
  */
@@ -91,6 +95,10 @@ export interface FieldArrayStore<TSchema extends FormSchema = FormSchema, TField
91
95
  * Whether the field array has been touched.
92
96
  */
93
97
  readonly isTouched: boolean;
98
+ /**
99
+ * Whether the field array value has been edited.
100
+ */
101
+ readonly isEdited: boolean;
94
102
  /**
95
103
  * Whether the field array input differs from its initial value.
96
104
  */
@@ -19,6 +19,10 @@ export interface FormStore<TSchema extends FormSchema = FormSchema> extends Base
19
19
  * Whether any field in the form has been touched.
20
20
  */
21
21
  readonly isTouched: boolean;
22
+ /**
23
+ * Whether any field in the form has been edited.
24
+ */
25
+ readonly isEdited: boolean;
22
26
  /**
23
27
  * Whether any field in the form differs from its initial value.
24
28
  */
@@ -31,7 +35,7 @@ export interface FormStore<TSchema extends FormSchema = FormSchema> extends Base
31
35
  * The current error messages of the form.
32
36
  *
33
37
  * Hint: This property only contains validation errors at the root level
34
- * of the form. To get all errors from all fields, use `getAllErrors`.
38
+ * of the form. To get all errors from all fields, use `getDeepErrors`.
35
39
  */
36
40
  readonly errors: [string, ...string[]] | null;
37
41
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formisch/svelte",
3
3
  "description": "The lightweight, schema-first, and fully type-safe form library for Svelte",
4
- "version": "0.8.0",
4
+ "version": "0.10.0",
5
5
  "license": "MIT",
6
6
  "author": "Fabian Hiller",
7
7
  "homepage": "https://formisch.dev",
@@ -58,7 +58,8 @@
58
58
  "@sveltejs/vite-plugin-svelte": "^6.2.0",
59
59
  "@testing-library/jest-dom": "^6.6.0",
60
60
  "@testing-library/svelte": "^5.2.4",
61
- "@vitest/coverage-v8": "^3.2.4",
61
+ "@types/node": "24.0.13",
62
+ "@vitest/coverage-v8": "^4.1.7",
62
63
  "eslint": "^9.35.0",
63
64
  "eslint-plugin-svelte": "^3.12.2",
64
65
  "globals": "^16.4.0",
@@ -69,7 +70,7 @@
69
70
  "typescript": "^5.9.2",
70
71
  "valibot": "^1.4.1",
71
72
  "vite": "^7.1.5",
72
- "vitest": "^3.2.4"
73
+ "vitest": "^4.1.7"
73
74
  },
74
75
  "peerDependencies": {
75
76
  "svelte": "^5.29.0",