@octanejs/formisch 0.0.1

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.
Files changed (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/UPSTREAM.md +95 -0
  4. package/package.json +65 -0
  5. package/src/components/Field/Field.tsrx +22 -0
  6. package/src/components/Field/Field.tsrx.d.ts +17 -0
  7. package/src/components/Field/index.ts +1 -0
  8. package/src/components/FieldArray/FieldArray.tsrx +22 -0
  9. package/src/components/FieldArray/FieldArray.tsrx.d.ts +18 -0
  10. package/src/components/FieldArray/index.ts +1 -0
  11. package/src/components/Form/Form.tsrx +28 -0
  12. package/src/components/Form/Form.tsrx.d.ts +15 -0
  13. package/src/components/Form/index.ts +1 -0
  14. package/src/components/index.ts +3 -0
  15. package/src/core/array/copyItemState/copyItemState.ts +101 -0
  16. package/src/core/array/copyItemState/index.ts +1 -0
  17. package/src/core/array/index.ts +3 -0
  18. package/src/core/array/resetItemState/index.ts +1 -0
  19. package/src/core/array/resetItemState/resetItemState.ts +172 -0
  20. package/src/core/array/swapItemState/index.ts +1 -0
  21. package/src/core/array/swapItemState/swapItemState.ts +138 -0
  22. package/src/core/field/focusFieldElement/focusFieldElement.ts +32 -0
  23. package/src/core/field/focusFieldElement/index.ts +1 -0
  24. package/src/core/field/getDirtyFieldInput/getDirtyFieldInput.ts +66 -0
  25. package/src/core/field/getDirtyFieldInput/index.ts +1 -0
  26. package/src/core/field/getElementInput/getElementInput.ts +78 -0
  27. package/src/core/field/getElementInput/index.ts +1 -0
  28. package/src/core/field/getFieldBool/getFieldBool.ts +22 -0
  29. package/src/core/field/getFieldBool/index.ts +1 -0
  30. package/src/core/field/getFieldInput/getFieldInput.ts +52 -0
  31. package/src/core/field/getFieldInput/index.ts +1 -0
  32. package/src/core/field/getFieldStore/getFieldStore.ts +34 -0
  33. package/src/core/field/getFieldStore/index.ts +1 -0
  34. package/src/core/field/index.ts +11 -0
  35. package/src/core/field/initializeFieldStore/index.ts +1 -0
  36. package/src/core/field/initializeFieldStore/initializeFieldStore.ts +325 -0
  37. package/src/core/field/setFieldBool/index.ts +1 -0
  38. package/src/core/field/setFieldBool/setFieldBool.ts +29 -0
  39. package/src/core/field/setFieldInput/index.ts +1 -0
  40. package/src/core/field/setFieldInput/setFieldInput.ts +180 -0
  41. package/src/core/field/setInitialFieldInput/index.ts +1 -0
  42. package/src/core/field/setInitialFieldInput/setInitialFieldInput.ts +99 -0
  43. package/src/core/field/walkFieldStore/index.ts +1 -0
  44. package/src/core/field/walkFieldStore/walkFieldStore.ts +49 -0
  45. package/src/core/form/createFormStore/createFormStore.ts +56 -0
  46. package/src/core/form/createFormStore/index.ts +1 -0
  47. package/src/core/form/decodeFormData/decodeFormData.ts +436 -0
  48. package/src/core/form/decodeFormData/index.ts +1 -0
  49. package/src/core/form/index.ts +4 -0
  50. package/src/core/form/validateFormInput/index.ts +1 -0
  51. package/src/core/form/validateFormInput/validateFormInput.ts +138 -0
  52. package/src/core/form/validateIfRequired/index.ts +1 -0
  53. package/src/core/form/validateIfRequired/validateIfRequired.ts +31 -0
  54. package/src/core/framework/index.ts +80 -0
  55. package/src/core/index.ts +6 -0
  56. package/src/core/types/field/field.ts +201 -0
  57. package/src/core/types/field/index.ts +1 -0
  58. package/src/core/types/form/form.ts +140 -0
  59. package/src/core/types/form/index.ts +1 -0
  60. package/src/core/types/index.ts +6 -0
  61. package/src/core/types/path/index.ts +10 -0
  62. package/src/core/types/path/path.ts +301 -0
  63. package/src/core/types/schema/index.ts +1 -0
  64. package/src/core/types/schema/schema.ts +18 -0
  65. package/src/core/types/signal/index.ts +1 -0
  66. package/src/core/types/signal/signal.ts +23 -0
  67. package/src/core/types/utils/index.ts +1 -0
  68. package/src/core/types/utils/utils.ts +46 -0
  69. package/src/core/values.ts +4 -0
  70. package/src/hooks/index.ts +3 -0
  71. package/src/hooks/useField/index.ts +1 -0
  72. package/src/hooks/useField/useField.ts +114 -0
  73. package/src/hooks/useFieldArray/index.ts +1 -0
  74. package/src/hooks/useFieldArray/useFieldArray.ts +63 -0
  75. package/src/hooks/useForm/index.ts +1 -0
  76. package/src/hooks/useForm/useForm.ts +68 -0
  77. package/src/hooks/useSignals/index.ts +1 -0
  78. package/src/hooks/useSignals/useSignals.ts +31 -0
  79. package/src/index.ts +19 -0
  80. package/src/internal.ts +32 -0
  81. package/src/methods/focus/focus.ts +35 -0
  82. package/src/methods/focus/index.ts +1 -0
  83. package/src/methods/getDeepErrorEntries/getDeepErrorEntries.ts +108 -0
  84. package/src/methods/getDeepErrorEntries/index.ts +1 -0
  85. package/src/methods/getDeepErrors/getDeepErrors.ts +90 -0
  86. package/src/methods/getDeepErrors/index.ts +1 -0
  87. package/src/methods/getDirtyInput/getDirtyInput.ts +87 -0
  88. package/src/methods/getDirtyInput/index.ts +1 -0
  89. package/src/methods/getDirtyPaths/getDirtyPaths.ts +123 -0
  90. package/src/methods/getDirtyPaths/index.ts +1 -0
  91. package/src/methods/getErrors/getErrors.ts +70 -0
  92. package/src/methods/getErrors/index.ts +1 -0
  93. package/src/methods/getInput/getInput.ts +75 -0
  94. package/src/methods/getInput/index.ts +1 -0
  95. package/src/methods/handleSubmit/handleSubmit.ts +83 -0
  96. package/src/methods/handleSubmit/index.ts +1 -0
  97. package/src/methods/index.ts +23 -0
  98. package/src/methods/insert/index.ts +1 -0
  99. package/src/methods/insert/insert.ts +134 -0
  100. package/src/methods/isDirty/index.ts +1 -0
  101. package/src/methods/isDirty/isDirty.ts +72 -0
  102. package/src/methods/isEdited/index.ts +1 -0
  103. package/src/methods/isEdited/isEdited.ts +72 -0
  104. package/src/methods/isTouched/index.ts +1 -0
  105. package/src/methods/isTouched/isTouched.ts +72 -0
  106. package/src/methods/isValid/index.ts +1 -0
  107. package/src/methods/isValid/isValid.ts +74 -0
  108. package/src/methods/move/index.ts +1 -0
  109. package/src/methods/move/move.ts +124 -0
  110. package/src/methods/pickDirty/index.ts +1 -0
  111. package/src/methods/pickDirty/pickDirty.ts +87 -0
  112. package/src/methods/remove/index.ts +1 -0
  113. package/src/methods/remove/remove.ts +76 -0
  114. package/src/methods/replace/index.ts +1 -0
  115. package/src/methods/replace/replace.ts +80 -0
  116. package/src/methods/reset/index.ts +1 -0
  117. package/src/methods/reset/reset.ts +216 -0
  118. package/src/methods/setErrors/index.ts +1 -0
  119. package/src/methods/setErrors/setErrors.ts +63 -0
  120. package/src/methods/setInput/index.ts +1 -0
  121. package/src/methods/setInput/setInput.ts +87 -0
  122. package/src/methods/submit/index.ts +1 -0
  123. package/src/methods/submit/submit.ts +11 -0
  124. package/src/methods/swap/index.ts +1 -0
  125. package/src/methods/swap/swap.ts +85 -0
  126. package/src/methods/validate/index.ts +1 -0
  127. package/src/methods/validate/validate.ts +34 -0
  128. package/src/types/field.ts +48 -0
  129. package/src/types/form.ts +12 -0
  130. package/src/types/index.ts +2 -0
@@ -0,0 +1 @@
1
+ export * from './getInput.ts';
@@ -0,0 +1,83 @@
1
+ import {
2
+ type BaseFormStore,
3
+ type FormSchema,
4
+ INTERNAL,
5
+ type SubmitEventHandler,
6
+ type SubmitHandler,
7
+ validateFormInput,
8
+ } from '../../core/index.ts';
9
+
10
+ /**
11
+ * Creates a submit event handler for the form that validates the form input,
12
+ * and calls the provided handler if validation succeeds. This is designed to
13
+ * be used with the form's onsubmit event.
14
+ *
15
+ * @param form The form store to handle submission for.
16
+ * @param handler The submit handler function called with validated output if validation succeeds.
17
+ *
18
+ * @returns A submit event handler function to attach to the form element.
19
+ */
20
+ export function handleSubmit<TSchema extends FormSchema>(
21
+ form: BaseFormStore<TSchema>,
22
+ handler: SubmitHandler<TSchema>,
23
+ ): () => Promise<void>;
24
+
25
+ /**
26
+ * Creates a submit event handler for the form that prevents default browser
27
+ * submission, validates the form input, and calls the provided handler if
28
+ * validation succeeds. This is designed to be used with the form's onsubmit event.
29
+ *
30
+ * @param form The form store to handle submission for.
31
+ * @param handler The submit handler function called with validated output if validation succeeds.
32
+ *
33
+ * @returns A submit event handler function to attach to the form element.
34
+ */
35
+ export function handleSubmit<TSchema extends FormSchema>(
36
+ form: BaseFormStore<TSchema>,
37
+ handler: SubmitEventHandler<TSchema>,
38
+ ): (event: SubmitEvent) => Promise<void>;
39
+
40
+ // @__NO_SIDE_EFFECTS__
41
+ export function handleSubmit(
42
+ form: BaseFormStore,
43
+ handler: SubmitHandler<FormSchema> | SubmitEventHandler<FormSchema>,
44
+ ): (event?: SubmitEvent) => Promise<void> {
45
+ return async (event?: SubmitEvent) => {
46
+ // Prevent default browser form submission
47
+ event?.preventDefault();
48
+
49
+ // Get internal form store
50
+ const internalFormStore = form[INTERNAL];
51
+
52
+ // Mark form as submitted and submitting
53
+ internalFormStore.isSubmitted.value = true;
54
+ internalFormStore.isSubmitting.value = true;
55
+
56
+ // Try to run submit actions if form is valid
57
+ try {
58
+ const result = await validateFormInput(internalFormStore, {
59
+ shouldFocus: true,
60
+ });
61
+
62
+ if (result.success) {
63
+ // @ts-expect-error - union of SubmitHandler and SubmitEventHandler
64
+ await handler(result.output, event);
65
+ }
66
+
67
+ // If an error occurred, set form errors
68
+ } catch (error: unknown) {
69
+ internalFormStore.errors.value = [
70
+ error &&
71
+ typeof error === 'object' &&
72
+ 'message' in error &&
73
+ typeof error.message === 'string'
74
+ ? error.message
75
+ : 'An unknown error has occurred.',
76
+ ];
77
+
78
+ // Finally reset submitting state
79
+ } finally {
80
+ internalFormStore.isSubmitting.value = false;
81
+ }
82
+ };
83
+ }
@@ -0,0 +1 @@
1
+ export * from './handleSubmit.ts';
@@ -0,0 +1,23 @@
1
+ export * from './focus/index.ts';
2
+ export * from './getDeepErrorEntries/index.ts';
3
+ export * from './getDeepErrors/index.ts';
4
+ export * from './getDirtyInput/index.ts';
5
+ export * from './getDirtyPaths/index.ts';
6
+ export * from './getErrors/index.ts';
7
+ export * from './getInput/index.ts';
8
+ export * from './handleSubmit/index.ts';
9
+ export * from './insert/index.ts';
10
+ export * from './isDirty/index.ts';
11
+ export * from './isEdited/index.ts';
12
+ export * from './isTouched/index.ts';
13
+ export * from './isValid/index.ts';
14
+ export * from './move/index.ts';
15
+ export * from './pickDirty/index.ts';
16
+ export * from './remove/index.ts';
17
+ export * from './replace/index.ts';
18
+ export * from './reset/index.ts';
19
+ export * from './setErrors/index.ts';
20
+ export * from './setInput/index.ts';
21
+ export * from './submit/index.ts';
22
+ export * from './swap/index.ts';
23
+ export * from './validate/index.ts';
@@ -0,0 +1 @@
1
+ export * from './insert.ts';
@@ -0,0 +1,134 @@
1
+ import {
2
+ type BaseFormStore,
3
+ batch,
4
+ copyItemState,
5
+ createId,
6
+ type DeepPartial,
7
+ type FormSchema,
8
+ initializeFieldStore,
9
+ INTERNAL,
10
+ type InternalArrayStore,
11
+ type InternalFieldStore,
12
+ type PathValue,
13
+ type RequiredPath,
14
+ resetItemState,
15
+ untrack,
16
+ type ValidArrayPath,
17
+ validateIfRequired,
18
+ } from '../../core/index.ts';
19
+ import type * as v from 'valibot';
20
+
21
+ /**
22
+ * Insert array field config interface.
23
+ */
24
+ export interface InsertConfig<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath> {
25
+ /**
26
+ * The path to the field array to insert into.
27
+ */
28
+ readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
29
+ /**
30
+ * The index to insert the new item at. If undefined, appends to the end.
31
+ */
32
+ readonly at?: number | undefined;
33
+ /**
34
+ * The partial initial input value for the new item.
35
+ */
36
+ readonly initialInput?:
37
+ DeepPartial<PathValue<v.InferInput<TSchema>, [...TFieldArrayPath, number]>> | undefined;
38
+ }
39
+
40
+ /**
41
+ * Inserts a new item into a field array at the specified index. All items at
42
+ * or after the insertion point are shifted up by one index.
43
+ *
44
+ * @param form The form store containing the field array.
45
+ * @param config The insert configuration specifying the path, index, and initial value.
46
+ */
47
+ export function insert<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath>(
48
+ form: BaseFormStore<TSchema>,
49
+ config: InsertConfig<TSchema, TFieldArrayPath>,
50
+ ): void {
51
+ // Get internal form store
52
+ const internalFormStore = form[INTERNAL];
53
+
54
+ // Walk path to get internal field store and mark all parents as having input
55
+ let internalFieldStore: InternalFieldStore = internalFormStore;
56
+ for (let index = 0; index < config.path.length; index++) {
57
+ // @ts-expect-error
58
+ internalFieldStore = internalFieldStore.children[config.path[index]];
59
+ if (index < config.path.length - 1) {
60
+ internalFieldStore.input.value = true;
61
+ }
62
+ }
63
+
64
+ // Last internal field store of path is array store
65
+ const internalArrayStore = internalFieldStore as InternalArrayStore;
66
+
67
+ // Get current items of field array
68
+ const items = untrack(() => internalArrayStore.items.value);
69
+
70
+ // Determine insertion index (default to end of array)
71
+ const insertIndex = config.at === undefined ? items.length : config.at;
72
+
73
+ // Continue if insertion index is valid
74
+ if (insertIndex >= 0 && insertIndex <= items.length) {
75
+ batch(() => {
76
+ // Insert new item ID at the specified index
77
+ const newItems = [...items];
78
+ newItems.splice(insertIndex, 0, createId());
79
+ internalArrayStore.items.value = newItems;
80
+
81
+ // Move all child stores after the insertion point one index up
82
+ for (let index = items.length; index > insertIndex; index--) {
83
+ if (!internalArrayStore.children[index]) {
84
+ // @ts-expect-error
85
+ internalArrayStore.children[index] = {};
86
+ initializeFieldStore(
87
+ internalFormStore,
88
+ internalArrayStore.children[index],
89
+ // @ts-expect-error
90
+ internalArrayStore.schema.item,
91
+ undefined,
92
+ [...internalArrayStore.path, index],
93
+ );
94
+ }
95
+ copyItemState(
96
+ internalFormStore,
97
+ internalArrayStore.children[index - 1],
98
+ internalArrayStore.children[index],
99
+ );
100
+ }
101
+
102
+ if (!internalArrayStore.children[insertIndex]) {
103
+ // @ts-expect-error
104
+ internalArrayStore.children[insertIndex] = {};
105
+ initializeFieldStore(
106
+ internalFormStore,
107
+ internalArrayStore.children[insertIndex],
108
+ // @ts-expect-error
109
+ internalArrayStore.schema.item,
110
+ config.initialInput,
111
+ [...internalArrayStore.path, insertIndex],
112
+ );
113
+ } else {
114
+ resetItemState(
115
+ internalFormStore,
116
+ internalArrayStore.children[insertIndex],
117
+ config.initialInput,
118
+ );
119
+ }
120
+
121
+ // Mark array input as present in children
122
+ internalArrayStore.input.value = true;
123
+
124
+ // Mark field array as touched, edited and dirty
125
+ internalArrayStore.isTouched.value = true;
126
+ internalArrayStore.isEdited.value = true;
127
+ internalArrayStore.isDirty.value = true;
128
+
129
+ // Validate if required
130
+ // TODO: Should we validate on touch, change and blur too?
131
+ validateIfRequired(internalFormStore, internalArrayStore, 'input');
132
+ });
133
+ }
134
+ }
@@ -0,0 +1 @@
1
+ export * from './isDirty.ts';
@@ -0,0 +1,72 @@
1
+ import {
2
+ type BaseFormStore,
3
+ type FormSchema,
4
+ getFieldBool,
5
+ getFieldStore,
6
+ INTERNAL,
7
+ type RequiredPath,
8
+ type ValidPath,
9
+ } from '../../core/index.ts';
10
+ import type * as v from 'valibot';
11
+
12
+ /**
13
+ * Is form dirty config interface.
14
+ */
15
+ export interface IsFormDirtyConfig {
16
+ /**
17
+ * The path to a field. Leave undefined to check the entire form.
18
+ */
19
+ readonly path?: undefined;
20
+ }
21
+
22
+ /**
23
+ * Is field dirty config interface.
24
+ */
25
+ export interface IsFieldDirtyConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
26
+ /**
27
+ * The path to the field to check for dirtiness.
28
+ */
29
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
30
+ }
31
+
32
+ /**
33
+ * Checks whether a specific field or the entire form is dirty by walking
34
+ * through the field store and all its descendants. A field is dirty when its
35
+ * input differs from its initial value.
36
+ *
37
+ * @param form The form store to check for dirtiness.
38
+ *
39
+ * @returns Whether the field or form is dirty.
40
+ */
41
+ export function isDirty<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): boolean;
42
+
43
+ /**
44
+ * Checks whether a specific field or the entire form is dirty by walking
45
+ * through the field store and all its descendants. A field is dirty when its
46
+ * input differs from its initial value.
47
+ *
48
+ * @param form The form store to check for dirtiness.
49
+ * @param config The is dirty configuration.
50
+ *
51
+ * @returns Whether the field or form is dirty.
52
+ */
53
+ export function isDirty<
54
+ TSchema extends FormSchema,
55
+ TFieldPath extends RequiredPath | undefined = undefined,
56
+ >(
57
+ form: BaseFormStore<TSchema>,
58
+ config: TFieldPath extends RequiredPath
59
+ ? IsFieldDirtyConfig<TSchema, TFieldPath>
60
+ : IsFormDirtyConfig,
61
+ ): boolean;
62
+
63
+ // @__NO_SIDE_EFFECTS__
64
+ export function isDirty(
65
+ form: BaseFormStore,
66
+ config?: IsFormDirtyConfig | IsFieldDirtyConfig<FormSchema, RequiredPath>,
67
+ ): boolean {
68
+ return getFieldBool(
69
+ config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
70
+ 'isDirty',
71
+ );
72
+ }
@@ -0,0 +1 @@
1
+ export * from './isEdited.ts';
@@ -0,0 +1,72 @@
1
+ import {
2
+ type BaseFormStore,
3
+ type FormSchema,
4
+ getFieldBool,
5
+ getFieldStore,
6
+ INTERNAL,
7
+ type RequiredPath,
8
+ type ValidPath,
9
+ } from '../../core/index.ts';
10
+ import type * as v from 'valibot';
11
+
12
+ /**
13
+ * Is form edited config interface.
14
+ */
15
+ export interface IsFormEditedConfig {
16
+ /**
17
+ * The path to a field. Leave undefined to check the entire form.
18
+ */
19
+ readonly path?: undefined;
20
+ }
21
+
22
+ /**
23
+ * Is field edited config interface.
24
+ */
25
+ export interface IsFieldEditedConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
26
+ /**
27
+ * The path to the field to check for being edited.
28
+ */
29
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
30
+ }
31
+
32
+ /**
33
+ * Checks whether a specific field or the entire form is edited by walking
34
+ * through the field store and all its descendants. A field is edited once its
35
+ * value has been changed by the user.
36
+ *
37
+ * @param form The form store to check for being edited.
38
+ *
39
+ * @returns Whether the field or form is edited.
40
+ */
41
+ export function isEdited<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): boolean;
42
+
43
+ /**
44
+ * Checks whether a specific field or the entire form is edited by walking
45
+ * through the field store and all its descendants. A field is edited once its
46
+ * value has been changed by the user.
47
+ *
48
+ * @param form The form store to check for being edited.
49
+ * @param config The is edited configuration.
50
+ *
51
+ * @returns Whether the field or form is edited.
52
+ */
53
+ export function isEdited<
54
+ TSchema extends FormSchema,
55
+ TFieldPath extends RequiredPath | undefined = undefined,
56
+ >(
57
+ form: BaseFormStore<TSchema>,
58
+ config: TFieldPath extends RequiredPath
59
+ ? IsFieldEditedConfig<TSchema, TFieldPath>
60
+ : IsFormEditedConfig,
61
+ ): boolean;
62
+
63
+ // @__NO_SIDE_EFFECTS__
64
+ export function isEdited(
65
+ form: BaseFormStore,
66
+ config?: IsFormEditedConfig | IsFieldEditedConfig<FormSchema, RequiredPath>,
67
+ ): boolean {
68
+ return getFieldBool(
69
+ config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
70
+ 'isEdited',
71
+ );
72
+ }
@@ -0,0 +1 @@
1
+ export * from './isTouched.ts';
@@ -0,0 +1,72 @@
1
+ import {
2
+ type BaseFormStore,
3
+ type FormSchema,
4
+ getFieldBool,
5
+ getFieldStore,
6
+ INTERNAL,
7
+ type RequiredPath,
8
+ type ValidPath,
9
+ } from '../../core/index.ts';
10
+ import type * as v from 'valibot';
11
+
12
+ /**
13
+ * Is form touched config interface.
14
+ */
15
+ export interface IsFormTouchedConfig {
16
+ /**
17
+ * The path to a field. Leave undefined to check the entire form.
18
+ */
19
+ readonly path?: undefined;
20
+ }
21
+
22
+ /**
23
+ * Is field touched config interface.
24
+ */
25
+ export interface IsFieldTouchedConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
26
+ /**
27
+ * The path to the field to check for being touched.
28
+ */
29
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
30
+ }
31
+
32
+ /**
33
+ * Checks whether a specific field or the entire form is touched by walking
34
+ * through the field store and all its descendants. A field is touched once it
35
+ * has received and lost focus.
36
+ *
37
+ * @param form The form store to check for being touched.
38
+ *
39
+ * @returns Whether the field or form is touched.
40
+ */
41
+ export function isTouched<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): boolean;
42
+
43
+ /**
44
+ * Checks whether a specific field or the entire form is touched by walking
45
+ * through the field store and all its descendants. A field is touched once it
46
+ * has received and lost focus.
47
+ *
48
+ * @param form The form store to check for being touched.
49
+ * @param config The is touched configuration.
50
+ *
51
+ * @returns Whether the field or form is touched.
52
+ */
53
+ export function isTouched<
54
+ TSchema extends FormSchema,
55
+ TFieldPath extends RequiredPath | undefined = undefined,
56
+ >(
57
+ form: BaseFormStore<TSchema>,
58
+ config: TFieldPath extends RequiredPath
59
+ ? IsFieldTouchedConfig<TSchema, TFieldPath>
60
+ : IsFormTouchedConfig,
61
+ ): boolean;
62
+
63
+ // @__NO_SIDE_EFFECTS__
64
+ export function isTouched(
65
+ form: BaseFormStore,
66
+ config?: IsFormTouchedConfig | IsFieldTouchedConfig<FormSchema, RequiredPath>,
67
+ ): boolean {
68
+ return getFieldBool(
69
+ config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
70
+ 'isTouched',
71
+ );
72
+ }
@@ -0,0 +1 @@
1
+ export * from './isValid.ts';
@@ -0,0 +1,74 @@
1
+ import {
2
+ type BaseFormStore,
3
+ type FormSchema,
4
+ getFieldBool,
5
+ getFieldStore,
6
+ INTERNAL,
7
+ type RequiredPath,
8
+ type ValidPath,
9
+ } from '../../core/index.ts';
10
+ import type * as v from 'valibot';
11
+
12
+ /**
13
+ * Is form valid config interface.
14
+ */
15
+ export interface IsFormValidConfig {
16
+ /**
17
+ * The path to a field. Leave undefined to check the entire form.
18
+ */
19
+ readonly path?: undefined;
20
+ }
21
+
22
+ /**
23
+ * Is field valid config interface.
24
+ */
25
+ export interface IsFieldValidConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
26
+ /**
27
+ * The path to the field to check for validity.
28
+ */
29
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
30
+ }
31
+
32
+ /**
33
+ * Checks whether a specific field or the entire form is valid by walking
34
+ * through the field store and all its descendants. A field is valid when
35
+ * neither it nor any of its descendants contains an error. Form-level errors
36
+ * are included.
37
+ *
38
+ * @param form The form store to check for validity.
39
+ *
40
+ * @returns Whether the field or form is valid.
41
+ */
42
+ export function isValid<TSchema extends FormSchema>(form: BaseFormStore<TSchema>): boolean;
43
+
44
+ /**
45
+ * Checks whether a specific field or the entire form is valid by walking
46
+ * through the field store and all its descendants. A field is valid when
47
+ * neither it nor any of its descendants contains an error. Form-level errors
48
+ * are included.
49
+ *
50
+ * @param form The form store to check for validity.
51
+ * @param config The is valid configuration.
52
+ *
53
+ * @returns Whether the field or form is valid.
54
+ */
55
+ export function isValid<
56
+ TSchema extends FormSchema,
57
+ TFieldPath extends RequiredPath | undefined = undefined,
58
+ >(
59
+ form: BaseFormStore<TSchema>,
60
+ config: TFieldPath extends RequiredPath
61
+ ? IsFieldValidConfig<TSchema, TFieldPath>
62
+ : IsFormValidConfig,
63
+ ): boolean;
64
+
65
+ // @__NO_SIDE_EFFECTS__
66
+ export function isValid(
67
+ form: BaseFormStore,
68
+ config?: IsFormValidConfig | IsFieldValidConfig<FormSchema, RequiredPath>,
69
+ ): boolean {
70
+ return !getFieldBool(
71
+ config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
72
+ 'errors',
73
+ );
74
+ }
@@ -0,0 +1 @@
1
+ export * from './move.ts';
@@ -0,0 +1,124 @@
1
+ import {
2
+ type BaseFormStore,
3
+ batch,
4
+ copyItemState,
5
+ type FormSchema,
6
+ getFieldStore,
7
+ initializeFieldStore,
8
+ INTERNAL,
9
+ type InternalArrayStore,
10
+ type InternalFieldStore,
11
+ type RequiredPath,
12
+ untrack,
13
+ type ValidArrayPath,
14
+ validateIfRequired,
15
+ } from '../../core/index.ts';
16
+ import type * as v from 'valibot';
17
+
18
+ /**
19
+ * Move array field config interface.
20
+ */
21
+ export interface MoveConfig<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath> {
22
+ /**
23
+ * The path to the field array to move an item within.
24
+ */
25
+ readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
26
+ /**
27
+ * The index of the item to move from.
28
+ */
29
+ readonly from: number;
30
+ /**
31
+ * The index to move the item to.
32
+ */
33
+ readonly to: number;
34
+ }
35
+
36
+ /**
37
+ * Moves an item from one index to another within a field array. All items
38
+ * between the source and destination indices are shifted accordingly.
39
+ *
40
+ * @param form The form store containing the field array.
41
+ * @param config The move configuration specifying the path and source/destination indices.
42
+ */
43
+ export function move<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath>(
44
+ form: BaseFormStore<TSchema>,
45
+ config: MoveConfig<TSchema, TFieldArrayPath>,
46
+ ): void {
47
+ // Get internal form and array store
48
+ const internalFormStore = form[INTERNAL];
49
+ const internalArrayStore = getFieldStore(internalFormStore, config.path) as InternalArrayStore;
50
+
51
+ // Get current items of field array
52
+ const items = untrack(() => internalArrayStore.items.value);
53
+
54
+ // Continue if both indices are valid and different
55
+ if (
56
+ config.from >= 0 &&
57
+ config.from <= items.length - 1 &&
58
+ config.to >= 0 &&
59
+ config.to <= items.length - 1 &&
60
+ config.from !== config.to
61
+ ) {
62
+ batch(() => {
63
+ // Move item ID in the items array
64
+ const newItems = [...items];
65
+ newItems.splice(config.to, 0, newItems.splice(config.from, 1)[0]);
66
+ internalArrayStore.items.value = newItems;
67
+
68
+ // Create temporary internal field store
69
+ const tempInternalFieldStore = {} as InternalFieldStore;
70
+ initializeFieldStore(
71
+ internalFormStore,
72
+ tempInternalFieldStore,
73
+ // @ts-expect-error
74
+ internalArrayStore.schema.item,
75
+ undefined,
76
+ [],
77
+ );
78
+
79
+ // Copy item state that gets overwritten to temporary store
80
+ copyItemState(
81
+ internalFormStore,
82
+ internalArrayStore.children[config.from],
83
+ tempInternalFieldStore,
84
+ );
85
+
86
+ if (config.from < config.to) {
87
+ // Move child stores between 'from' and 'to' one index down
88
+ for (let index = config.from; index < config.to; index++) {
89
+ copyItemState(
90
+ internalFormStore,
91
+ internalArrayStore.children[index + 1],
92
+ internalArrayStore.children[index],
93
+ );
94
+ }
95
+ } else {
96
+ // Move child stores between 'to' and 'from' one index up
97
+ for (let index = config.from; index > config.to; index--) {
98
+ copyItemState(
99
+ internalFormStore,
100
+ internalArrayStore.children[index - 1],
101
+ internalArrayStore.children[index],
102
+ );
103
+ }
104
+ }
105
+
106
+ // Copy item state from temporary store to new position
107
+ copyItemState(
108
+ internalFormStore,
109
+ tempInternalFieldStore,
110
+ internalArrayStore.children[config.to],
111
+ );
112
+
113
+ // Mark field array as touched and edited and update dirty state
114
+ internalArrayStore.isTouched.value = true;
115
+ internalArrayStore.isEdited.value = true;
116
+ internalArrayStore.isDirty.value =
117
+ internalArrayStore.startItems.value.join() !== newItems.join();
118
+
119
+ // Validate if required
120
+ // TODO: Should we validate on touch, change and blur too?
121
+ validateIfRequired(internalFormStore, internalArrayStore, 'input');
122
+ });
123
+ }
124
+ }
@@ -0,0 +1 @@
1
+ export * from './pickDirty.ts';