@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,87 @@
1
+ import {
2
+ type BaseFormStore,
3
+ type DeepPartial,
4
+ type FormSchema,
5
+ getFieldBool,
6
+ INTERNAL,
7
+ type InternalFieldStore,
8
+ } from '../../core/index.ts';
9
+
10
+ /**
11
+ * Pick dirty config interface.
12
+ */
13
+ export interface PickDirtyConfig<TValue extends object> {
14
+ /**
15
+ * The value to filter down to its dirty parts. Must be structurally
16
+ * compatible with the form's schema.
17
+ */
18
+ readonly from: TValue;
19
+ }
20
+
21
+ /**
22
+ * Picks only the dirty parts of the given value, using the form's dirty fields
23
+ * as a structural mask. Arrays are treated as atomic and object keys without a
24
+ * dirty descendant are omitted. Returns `undefined` if no field is dirty.
25
+ * Useful for filtering a validated output down to its changed parts before
26
+ * submitting.
27
+ *
28
+ * @param form The form store providing the dirty mask.
29
+ * @param config The pick dirty configuration.
30
+ *
31
+ * @returns The dirty parts of the value, or `undefined`.
32
+ */
33
+ // @__NO_SIDE_EFFECTS__
34
+ export function pickDirty<TSchema extends FormSchema, TValue extends object>(
35
+ form: BaseFormStore<TSchema>,
36
+ config: PickDirtyConfig<TValue>,
37
+ ): DeepPartial<TValue> | undefined {
38
+ // If no field is dirty, return undefined
39
+ if (!getFieldBool(form[INTERNAL], 'isDirty')) {
40
+ return undefined;
41
+ }
42
+
43
+ // Pick the dirty parts of the value using the form as a mask
44
+ const result = pickFieldValue(form[INTERNAL], config.from);
45
+
46
+ // Return undefined if no dirty property ended up in the result, which can
47
+ // happen when every dirty key is absent from the supplied value
48
+ return Object.keys(result as object).length ? (result as DeepPartial<TValue>) : undefined;
49
+ }
50
+
51
+ /**
52
+ * Recursively picks the dirty parts of a value using the field store as a
53
+ * structural mask, reading from the supplied value rather than the form's own
54
+ * input. Objects with non-nullish input recurse into their dirty children that
55
+ * are present in the value, while arrays, primitives, nullish-cleared fields
56
+ * and shape-diverging values are returned as-is.
57
+ *
58
+ * @param internalFieldStore The field store used as the dirty mask.
59
+ * @param value The value to pick the dirty parts from.
60
+ *
61
+ * @returns The dirty parts of the value.
62
+ */
63
+ // @__NO_SIDE_EFFECTS__
64
+ function pickFieldValue(internalFieldStore: InternalFieldStore, value: unknown): unknown {
65
+ // If field store is object with non-nullish input and the value is a
66
+ // matching (non-array) object, recurse into children
67
+ if (
68
+ internalFieldStore.kind === 'object' &&
69
+ internalFieldStore.input.value &&
70
+ value &&
71
+ typeof value === 'object' &&
72
+ !Array.isArray(value)
73
+ ) {
74
+ // Collect dirty parts from each dirty property present in value
75
+ const result: Record<string, unknown> = {};
76
+ for (const key in internalFieldStore.children) {
77
+ const child = internalFieldStore.children[key];
78
+ if (getFieldBool(child, 'isDirty') && key in value) {
79
+ result[key] = pickFieldValue(child, (value as Record<string, unknown>)[key]);
80
+ }
81
+ }
82
+ return result;
83
+ }
84
+
85
+ // Otherwise, field is atomic or its shape diverges, so return as-is
86
+ return value;
87
+ }
@@ -0,0 +1 @@
1
+ export * from './remove.ts';
@@ -0,0 +1,76 @@
1
+ import {
2
+ type BaseFormStore,
3
+ batch,
4
+ copyItemState,
5
+ type FormSchema,
6
+ getFieldStore,
7
+ INTERNAL,
8
+ type InternalArrayStore,
9
+ type RequiredPath,
10
+ untrack,
11
+ type ValidArrayPath,
12
+ validateIfRequired,
13
+ } from '../../core/index.ts';
14
+ import type * as v from 'valibot';
15
+
16
+ /**
17
+ * Remove array field config interface.
18
+ */
19
+ export interface RemoveConfig<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath> {
20
+ /**
21
+ * The path to the field array to remove an item from.
22
+ */
23
+ readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
24
+ /**
25
+ * The index of the item to remove.
26
+ */
27
+ readonly at: number;
28
+ }
29
+
30
+ /**
31
+ * Removes an item from a field array at the specified index. All items after
32
+ * the removed item are shifted down by one index.
33
+ *
34
+ * @param form The form store containing the field array.
35
+ * @param config The remove configuration specifying the path and index.
36
+ */
37
+ export function remove<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath>(
38
+ form: BaseFormStore<TSchema>,
39
+ config: RemoveConfig<TSchema, TFieldArrayPath>,
40
+ ): void {
41
+ // Get internal form and array store
42
+ const internalFormStore = form[INTERNAL];
43
+ const internalArrayStore = getFieldStore(internalFormStore, config.path) as InternalArrayStore;
44
+
45
+ // Get current items of field array
46
+ const items = untrack(() => internalArrayStore.items.value);
47
+
48
+ // Continue if specified index is valid
49
+ if (config.at >= 0 && config.at <= items.length - 1) {
50
+ batch(() => {
51
+ // Remove item ID from the items array
52
+ const newItems = [...items];
53
+ newItems.splice(config.at, 1);
54
+ internalArrayStore.items.value = newItems;
55
+
56
+ // Move all child stores after the removed item one index down
57
+ for (let index = config.at; index < items.length - 1; index++) {
58
+ copyItemState(
59
+ internalFormStore,
60
+ internalArrayStore.children[index + 1],
61
+ internalArrayStore.children[index],
62
+ );
63
+ }
64
+
65
+ // Mark field array as touched and edited and update dirty state
66
+ internalArrayStore.isTouched.value = true;
67
+ internalArrayStore.isEdited.value = true;
68
+ internalArrayStore.isDirty.value =
69
+ internalArrayStore.startItems.value.join() !== newItems.join();
70
+
71
+ // Validate if required
72
+ // TODO: Should we validate on touch, change and blur too?
73
+ validateIfRequired(internalFormStore, internalArrayStore, 'input');
74
+ });
75
+ }
76
+ }
@@ -0,0 +1 @@
1
+ export * from './replace.ts';
@@ -0,0 +1,80 @@
1
+ import {
2
+ type BaseFormStore,
3
+ batch,
4
+ createId,
5
+ type DeepPartial,
6
+ type FormSchema,
7
+ getFieldStore,
8
+ INTERNAL,
9
+ type InternalArrayStore,
10
+ type PathValue,
11
+ type RequiredPath,
12
+ resetItemState,
13
+ untrack,
14
+ type ValidArrayPath,
15
+ validateIfRequired,
16
+ } from '../../core/index.ts';
17
+ import type * as v from 'valibot';
18
+
19
+ /**
20
+ * Replace array field config interface.
21
+ */
22
+ export interface ReplaceConfig<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath> {
23
+ /**
24
+ * The path to the field array to replace an item within.
25
+ */
26
+ readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
27
+ /**
28
+ * The index of the item to replace.
29
+ */
30
+ readonly at: number;
31
+ /**
32
+ * The partial initial input value for the replacement item.
33
+ */
34
+ readonly initialInput?:
35
+ DeepPartial<PathValue<v.InferInput<TSchema>, [...TFieldArrayPath, number]>> | undefined;
36
+ }
37
+
38
+ /**
39
+ * Replaces an item in a field array at the specified index with new initial input.
40
+ *
41
+ * @param form The form store containing the field array.
42
+ * @param config The replace configuration specifying the path, index, and initial input.
43
+ */
44
+ export function replace<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath>(
45
+ form: BaseFormStore<TSchema>,
46
+ config: ReplaceConfig<TSchema, TFieldArrayPath>,
47
+ ): void {
48
+ // Get internal form and array store
49
+ const internalFormStore = form[INTERNAL];
50
+ const internalArrayStore = getFieldStore(internalFormStore, config.path) as InternalArrayStore;
51
+
52
+ // Get current items of field array
53
+ const items = untrack(() => internalArrayStore.items.value);
54
+
55
+ // Continue if specified index is valid
56
+ if (config.at >= 0 && config.at <= items.length - 1) {
57
+ batch(() => {
58
+ // Replace item ID to trigger reactivity
59
+ const newItems = [...items];
60
+ newItems[config.at] = createId();
61
+ internalArrayStore.items.value = newItems;
62
+
63
+ // Replace input of field array item
64
+ resetItemState(
65
+ internalFormStore,
66
+ internalArrayStore.children[config.at],
67
+ config.initialInput,
68
+ );
69
+
70
+ // Mark field array as touched, edited and dirty
71
+ internalArrayStore.isTouched.value = true;
72
+ internalArrayStore.isEdited.value = true;
73
+ internalArrayStore.isDirty.value = true;
74
+
75
+ // Validate if required
76
+ // TODO: Should we validate on touch, change and blur too?
77
+ validateIfRequired(internalFormStore, internalArrayStore, 'input');
78
+ });
79
+ }
80
+ }
@@ -0,0 +1 @@
1
+ export * from './reset.ts';
@@ -0,0 +1,216 @@
1
+ import {
2
+ type BaseFormStore,
3
+ batch,
4
+ type DeepPartial,
5
+ type FormSchema,
6
+ getFieldStore,
7
+ INTERNAL,
8
+ type PathValue,
9
+ type RequiredPath,
10
+ setInitialFieldInput,
11
+ untrack,
12
+ validateFormInput,
13
+ type ValidPath,
14
+ walkFieldStore,
15
+ } from '../../core/index.ts';
16
+ import type * as v from 'valibot';
17
+
18
+ /**
19
+ * Reset base config interface.
20
+ */
21
+ interface ResetBaseConfig {
22
+ /**
23
+ * Whether to keep the current input values during reset. Defaults to false.
24
+ */
25
+ readonly keepInput?: boolean | undefined;
26
+ /**
27
+ * Whether to keep the touched state during reset. Defaults to false.
28
+ */
29
+ readonly keepTouched?: boolean | undefined;
30
+ /**
31
+ * Whether to keep the edited state during reset. Defaults to false.
32
+ */
33
+ readonly keepEdited?: boolean | undefined;
34
+ /**
35
+ * Whether to keep the error messages during reset. Defaults to false.
36
+ */
37
+ readonly keepErrors?: boolean | undefined;
38
+ }
39
+
40
+ /**
41
+ * Reset form config interface.
42
+ */
43
+ export interface ResetFormConfig<TSchema extends FormSchema> extends ResetBaseConfig {
44
+ /**
45
+ * The path to a field. Leave undefined to reset the entire form.
46
+ */
47
+ readonly path?: undefined;
48
+ /**
49
+ * The new initial input to reset to. If provided, replaces the form's
50
+ * initial input.
51
+ */
52
+ readonly initialInput?: DeepPartial<v.InferInput<TSchema>> | undefined;
53
+ /**
54
+ * Whether to keep the submitted state during reset. Defaults to false.
55
+ */
56
+ readonly keepSubmitted?: boolean | undefined;
57
+ }
58
+
59
+ /**
60
+ * Reset field config interface.
61
+ */
62
+ export interface ResetFieldConfig<
63
+ TSchema extends FormSchema,
64
+ TFieldPath extends RequiredPath,
65
+ > extends ResetBaseConfig {
66
+ /**
67
+ * The path to the field to reset.
68
+ */
69
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
70
+ /**
71
+ * The new initial input to reset the field to. If provided, replaces the
72
+ * field's initial input.
73
+ */
74
+ readonly initialInput?: DeepPartial<PathValue<v.InferInput<TSchema>, TFieldPath>> | undefined;
75
+ }
76
+
77
+ /**
78
+ * Resets a specific field or the entire form to its initial state. Provides
79
+ * fine-grained control over which state to preserve during reset through the
80
+ * configuration options.
81
+ *
82
+ * @param form The form store to reset.
83
+ */
84
+ export function reset(form: BaseFormStore): void;
85
+
86
+ /**
87
+ * Resets a specific field or the entire form to its initial state. Provides
88
+ * fine-grained control over which state to preserve during reset through the
89
+ * configuration options.
90
+ *
91
+ * @param form The form store to reset.
92
+ * @param config The reset configuration specifying what to reset and what to keep.
93
+ */
94
+ export function reset<
95
+ TSchema extends FormSchema,
96
+ TFieldPath extends RequiredPath | undefined = undefined,
97
+ >(
98
+ form: BaseFormStore<TSchema>,
99
+ config: TFieldPath extends RequiredPath
100
+ ? ResetFieldConfig<TSchema, TFieldPath>
101
+ : ResetFormConfig<TSchema>,
102
+ ): void;
103
+
104
+ export function reset(
105
+ form: BaseFormStore,
106
+ config?: ResetFormConfig<FormSchema> | ResetFieldConfig<FormSchema, RequiredPath>,
107
+ ): void {
108
+ batch(() => {
109
+ untrack(() => {
110
+ // Get internal form and field store
111
+ const internalFormStore = form[INTERNAL];
112
+ const internalFieldStore = config?.path
113
+ ? getFieldStore(internalFormStore, config.path)
114
+ : internalFormStore;
115
+
116
+ // If initial input is provided, set it
117
+ if (config && 'initialInput' in config) {
118
+ setInitialFieldInput(internalFormStore, internalFieldStore, config.initialInput);
119
+ }
120
+
121
+ // Reset state of fields by walking field store
122
+ walkFieldStore(internalFieldStore, (internalFieldStore) => {
123
+ // Reset elements to initial elements
124
+ // Hint: `copyItemState` and `swapItemState` move elements between field
125
+ // stores during array methods, so this restores each field's original
126
+ // element. Without it, focus and file reset target the wrong element
127
+ // after a reorder followed by a reset.
128
+ internalFieldStore.elements = internalFieldStore.initialElements;
129
+
130
+ // Reset errors if it is not to be kept
131
+ if (!config?.keepErrors) {
132
+ internalFieldStore.errors.value = null;
133
+ }
134
+
135
+ // Reset is touched if it is not to be kept
136
+ if (!config?.keepTouched) {
137
+ internalFieldStore.isTouched.value = false;
138
+ }
139
+
140
+ // Reset is edited if it is not to be kept
141
+ if (!config?.keepEdited) {
142
+ internalFieldStore.isEdited.value = false;
143
+ }
144
+
145
+ // Reset start input to initial input
146
+ internalFieldStore.startInput.value = internalFieldStore.initialInput.value;
147
+
148
+ // Reset input if it is not to be kept
149
+ if (!config?.keepInput) {
150
+ internalFieldStore.input.value = internalFieldStore.initialInput.value;
151
+ }
152
+
153
+ // If it is an array, reset array specific state
154
+ if (internalFieldStore.kind === 'array') {
155
+ // Reset start items to initial items
156
+ internalFieldStore.startItems.value = internalFieldStore.initialItems.value;
157
+
158
+ // Reset items if it is not to be kept
159
+ if (
160
+ !config?.keepInput ||
161
+ // Hint: The array items are just an internal concept used to
162
+ // store and track changes. We reset anyway if the lengths are
163
+ // equal because otherwise, the field may be in a dirty state
164
+ // even though there is no visible change for the end user.
165
+ internalFieldStore.startItems.value.length === internalFieldStore.items.value.length
166
+ ) {
167
+ internalFieldStore.items.value = internalFieldStore.initialItems.value;
168
+ }
169
+
170
+ // Update is dirty to reflect changes
171
+ internalFieldStore.isDirty.value =
172
+ internalFieldStore.startInput.value !== internalFieldStore.input.value ||
173
+ internalFieldStore.startItems.value !== internalFieldStore.items.value;
174
+
175
+ // If it is an object, reset object specific state
176
+ } else if (internalFieldStore.kind === 'object') {
177
+ // Update is dirty to reflect changes
178
+ internalFieldStore.isDirty.value =
179
+ internalFieldStore.startInput.value !== internalFieldStore.input.value;
180
+
181
+ // If it is a value, reset value specific state
182
+ } else {
183
+ // Update is dirty to reflect changes
184
+ // TODO: Should we add support for Dates and Files?
185
+ const startInput = internalFieldStore.startInput.value;
186
+ const input = internalFieldStore.input.value;
187
+ internalFieldStore.isDirty.value =
188
+ startInput !== input &&
189
+ // Hint: This check ensures that an empty string or `NaN` does not mark
190
+ // the field as dirty if the start input was `undefined` or `null`.
191
+ (startInput != null || (input !== '' && !Number.isNaN(input)));
192
+
193
+ // Reset file inputs as they can't be controlled
194
+ for (const element of internalFieldStore.elements) {
195
+ if (element.type === 'file') {
196
+ element.value = '';
197
+ }
198
+ }
199
+ }
200
+ });
201
+
202
+ // If path is not defined, reset form specific state
203
+ if (!config?.path) {
204
+ // Reset is submitted if it is not to be kept
205
+ if (!config?.keepSubmitted) {
206
+ internalFormStore.isSubmitted.value = false;
207
+ }
208
+
209
+ // Validate form input if configured
210
+ if (internalFormStore.validate === 'initial') {
211
+ validateFormInput(internalFormStore);
212
+ }
213
+ }
214
+ });
215
+ });
216
+ }
@@ -0,0 +1 @@
1
+ export * from './setErrors.ts';
@@ -0,0 +1,63 @@
1
+ import {
2
+ type BaseFormStore,
3
+ type FormSchema,
4
+ getFieldStore,
5
+ INTERNAL,
6
+ type RequiredPath,
7
+ type ValidPath,
8
+ } from '../../core/index.ts';
9
+ import type * as v from 'valibot';
10
+
11
+ /**
12
+ * Set form errors config interface.
13
+ */
14
+ export interface SetFormErrorsConfig {
15
+ /**
16
+ * The path to a field. Leave undefined to set form-level errors.
17
+ */
18
+ readonly path?: undefined;
19
+ /**
20
+ * The error messages to set, or null to clear errors.
21
+ */
22
+ readonly errors: [string, ...string[]] | null;
23
+ }
24
+
25
+ /**
26
+ * Set field errors config interface.
27
+ */
28
+ export interface SetFieldErrorsConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
29
+ /**
30
+ * The path to the field to set errors on.
31
+ */
32
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
33
+ /**
34
+ * The error messages to set, or null to clear errors.
35
+ */
36
+ readonly errors: [string, ...string[]] | null;
37
+ }
38
+
39
+ /**
40
+ * Sets or clears error messages on the form or a specific field. This is
41
+ * useful for setting custom validation errors that don't come from schema
42
+ * validation.
43
+ *
44
+ * @param form The form store to set errors on.
45
+ * @param config The set errors configuration specifying the path and error messages.
46
+ */
47
+ export function setErrors<
48
+ TSchema extends FormSchema,
49
+ TFieldPath extends RequiredPath | undefined = undefined,
50
+ >(
51
+ form: BaseFormStore<TSchema>,
52
+ config: TFieldPath extends RequiredPath
53
+ ? SetFieldErrorsConfig<TSchema, TFieldPath>
54
+ : SetFormErrorsConfig,
55
+ ): void;
56
+
57
+ export function setErrors(
58
+ form: BaseFormStore,
59
+ config: SetFormErrorsConfig | SetFieldErrorsConfig<FormSchema, RequiredPath>,
60
+ ): void {
61
+ (config.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]).errors.value =
62
+ config.errors;
63
+ }
@@ -0,0 +1 @@
1
+ export * from './setInput.ts';
@@ -0,0 +1,87 @@
1
+ import {
2
+ type BaseFormStore,
3
+ batch,
4
+ type FormSchema,
5
+ getFieldStore,
6
+ INTERNAL,
7
+ type PathValue,
8
+ type RequiredPath,
9
+ setFieldInput,
10
+ validateIfRequired,
11
+ type ValidPath,
12
+ } from '../../core/index.ts';
13
+ import type * as v from 'valibot';
14
+
15
+ /**
16
+ * Set form input config interface.
17
+ */
18
+ export interface SetFormInputConfig<TSchema extends FormSchema> {
19
+ /**
20
+ * The path to a field. Leave undefined to set the entire form input.
21
+ */
22
+ readonly path?: undefined;
23
+ /**
24
+ * The input value to set for the form.
25
+ */
26
+ readonly input: v.InferInput<TSchema>;
27
+ }
28
+
29
+ /**
30
+ * Set field input config interface.
31
+ */
32
+ export interface SetFieldInputConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
33
+ /**
34
+ * The path to the field to set input on.
35
+ */
36
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
37
+ /**
38
+ * The input value to set for the field.
39
+ */
40
+ readonly input: PathValue<v.InferInput<TSchema>, TFieldPath>;
41
+ }
42
+
43
+ /**
44
+ * Sets the input value of a specific field or the entire form. This updates
45
+ * the field value(s) and triggers validation if required by the form's
46
+ * validation mode.
47
+ *
48
+ * @param form The form store to set input on.
49
+ * @param config The set form input configuration specifying the new input values.
50
+ */
51
+ export function setInput<TSchema extends FormSchema>(
52
+ form: BaseFormStore<TSchema>,
53
+ config: SetFormInputConfig<TSchema>,
54
+ ): void;
55
+
56
+ /**
57
+ * Sets the input value of a specific field or the entire form. This updates
58
+ * the field value(s) and triggers validation if required by the form's
59
+ * validation mode.
60
+ *
61
+ * @param form The form store to set input on.
62
+ * @param config The set input configuration specifying the path and new value.
63
+ */
64
+ export function setInput<
65
+ TSchema extends FormSchema,
66
+ TFieldPath extends RequiredPath | undefined = undefined,
67
+ >(
68
+ form: BaseFormStore<TSchema>,
69
+ config: TFieldPath extends RequiredPath
70
+ ? SetFieldInputConfig<TSchema, TFieldPath>
71
+ : SetFormInputConfig<TSchema>,
72
+ ): void;
73
+
74
+ export function setInput(
75
+ form: BaseFormStore,
76
+ config: SetFormInputConfig<FormSchema> | SetFieldInputConfig<FormSchema, RequiredPath>,
77
+ ): void {
78
+ batch(() => {
79
+ const internalFormStore = form[INTERNAL];
80
+ setFieldInput(internalFormStore, config.path ?? [], config.input);
81
+ const fieldOrFormStore = config.path
82
+ ? getFieldStore(internalFormStore, config.path)
83
+ : internalFormStore;
84
+ validateIfRequired(internalFormStore, fieldOrFormStore, 'input');
85
+ validateIfRequired(internalFormStore, fieldOrFormStore, 'change');
86
+ });
87
+ }
@@ -0,0 +1 @@
1
+ export * from './submit.ts';
@@ -0,0 +1,11 @@
1
+ import { type BaseFormStore, INTERNAL } from '../../core/index.ts';
2
+
3
+ /**
4
+ * Programmatically requests form submission by calling the native
5
+ * `requestSubmit()` method on the underlying form element.
6
+ *
7
+ * @param form The form store to submit.
8
+ */
9
+ export function submit(form: BaseFormStore): void {
10
+ form[INTERNAL].element?.requestSubmit();
11
+ }
@@ -0,0 +1 @@
1
+ export * from './swap.ts';