@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.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/UPSTREAM.md +95 -0
- package/package.json +65 -0
- package/src/components/Field/Field.tsrx +22 -0
- package/src/components/Field/Field.tsrx.d.ts +17 -0
- package/src/components/Field/index.ts +1 -0
- package/src/components/FieldArray/FieldArray.tsrx +22 -0
- package/src/components/FieldArray/FieldArray.tsrx.d.ts +18 -0
- package/src/components/FieldArray/index.ts +1 -0
- package/src/components/Form/Form.tsrx +28 -0
- package/src/components/Form/Form.tsrx.d.ts +15 -0
- package/src/components/Form/index.ts +1 -0
- package/src/components/index.ts +3 -0
- package/src/core/array/copyItemState/copyItemState.ts +101 -0
- package/src/core/array/copyItemState/index.ts +1 -0
- package/src/core/array/index.ts +3 -0
- package/src/core/array/resetItemState/index.ts +1 -0
- package/src/core/array/resetItemState/resetItemState.ts +172 -0
- package/src/core/array/swapItemState/index.ts +1 -0
- package/src/core/array/swapItemState/swapItemState.ts +138 -0
- package/src/core/field/focusFieldElement/focusFieldElement.ts +32 -0
- package/src/core/field/focusFieldElement/index.ts +1 -0
- package/src/core/field/getDirtyFieldInput/getDirtyFieldInput.ts +66 -0
- package/src/core/field/getDirtyFieldInput/index.ts +1 -0
- package/src/core/field/getElementInput/getElementInput.ts +78 -0
- package/src/core/field/getElementInput/index.ts +1 -0
- package/src/core/field/getFieldBool/getFieldBool.ts +22 -0
- package/src/core/field/getFieldBool/index.ts +1 -0
- package/src/core/field/getFieldInput/getFieldInput.ts +52 -0
- package/src/core/field/getFieldInput/index.ts +1 -0
- package/src/core/field/getFieldStore/getFieldStore.ts +34 -0
- package/src/core/field/getFieldStore/index.ts +1 -0
- package/src/core/field/index.ts +11 -0
- package/src/core/field/initializeFieldStore/index.ts +1 -0
- package/src/core/field/initializeFieldStore/initializeFieldStore.ts +325 -0
- package/src/core/field/setFieldBool/index.ts +1 -0
- package/src/core/field/setFieldBool/setFieldBool.ts +29 -0
- package/src/core/field/setFieldInput/index.ts +1 -0
- package/src/core/field/setFieldInput/setFieldInput.ts +180 -0
- package/src/core/field/setInitialFieldInput/index.ts +1 -0
- package/src/core/field/setInitialFieldInput/setInitialFieldInput.ts +99 -0
- package/src/core/field/walkFieldStore/index.ts +1 -0
- package/src/core/field/walkFieldStore/walkFieldStore.ts +49 -0
- package/src/core/form/createFormStore/createFormStore.ts +56 -0
- package/src/core/form/createFormStore/index.ts +1 -0
- package/src/core/form/decodeFormData/decodeFormData.ts +436 -0
- package/src/core/form/decodeFormData/index.ts +1 -0
- package/src/core/form/index.ts +4 -0
- package/src/core/form/validateFormInput/index.ts +1 -0
- package/src/core/form/validateFormInput/validateFormInput.ts +138 -0
- package/src/core/form/validateIfRequired/index.ts +1 -0
- package/src/core/form/validateIfRequired/validateIfRequired.ts +31 -0
- package/src/core/framework/index.ts +80 -0
- package/src/core/index.ts +6 -0
- package/src/core/types/field/field.ts +201 -0
- package/src/core/types/field/index.ts +1 -0
- package/src/core/types/form/form.ts +140 -0
- package/src/core/types/form/index.ts +1 -0
- package/src/core/types/index.ts +6 -0
- package/src/core/types/path/index.ts +10 -0
- package/src/core/types/path/path.ts +301 -0
- package/src/core/types/schema/index.ts +1 -0
- package/src/core/types/schema/schema.ts +18 -0
- package/src/core/types/signal/index.ts +1 -0
- package/src/core/types/signal/signal.ts +23 -0
- package/src/core/types/utils/index.ts +1 -0
- package/src/core/types/utils/utils.ts +46 -0
- package/src/core/values.ts +4 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useField/index.ts +1 -0
- package/src/hooks/useField/useField.ts +114 -0
- package/src/hooks/useFieldArray/index.ts +1 -0
- package/src/hooks/useFieldArray/useFieldArray.ts +63 -0
- package/src/hooks/useForm/index.ts +1 -0
- package/src/hooks/useForm/useForm.ts +68 -0
- package/src/hooks/useSignals/index.ts +1 -0
- package/src/hooks/useSignals/useSignals.ts +31 -0
- package/src/index.ts +19 -0
- package/src/internal.ts +32 -0
- package/src/methods/focus/focus.ts +35 -0
- package/src/methods/focus/index.ts +1 -0
- package/src/methods/getDeepErrorEntries/getDeepErrorEntries.ts +108 -0
- package/src/methods/getDeepErrorEntries/index.ts +1 -0
- package/src/methods/getDeepErrors/getDeepErrors.ts +90 -0
- package/src/methods/getDeepErrors/index.ts +1 -0
- package/src/methods/getDirtyInput/getDirtyInput.ts +87 -0
- package/src/methods/getDirtyInput/index.ts +1 -0
- package/src/methods/getDirtyPaths/getDirtyPaths.ts +123 -0
- package/src/methods/getDirtyPaths/index.ts +1 -0
- package/src/methods/getErrors/getErrors.ts +70 -0
- package/src/methods/getErrors/index.ts +1 -0
- package/src/methods/getInput/getInput.ts +75 -0
- package/src/methods/getInput/index.ts +1 -0
- package/src/methods/handleSubmit/handleSubmit.ts +83 -0
- package/src/methods/handleSubmit/index.ts +1 -0
- package/src/methods/index.ts +23 -0
- package/src/methods/insert/index.ts +1 -0
- package/src/methods/insert/insert.ts +134 -0
- package/src/methods/isDirty/index.ts +1 -0
- package/src/methods/isDirty/isDirty.ts +72 -0
- package/src/methods/isEdited/index.ts +1 -0
- package/src/methods/isEdited/isEdited.ts +72 -0
- package/src/methods/isTouched/index.ts +1 -0
- package/src/methods/isTouched/isTouched.ts +72 -0
- package/src/methods/isValid/index.ts +1 -0
- package/src/methods/isValid/isValid.ts +74 -0
- package/src/methods/move/index.ts +1 -0
- package/src/methods/move/move.ts +124 -0
- package/src/methods/pickDirty/index.ts +1 -0
- package/src/methods/pickDirty/pickDirty.ts +87 -0
- package/src/methods/remove/index.ts +1 -0
- package/src/methods/remove/remove.ts +76 -0
- package/src/methods/replace/index.ts +1 -0
- package/src/methods/replace/replace.ts +80 -0
- package/src/methods/reset/index.ts +1 -0
- package/src/methods/reset/reset.ts +216 -0
- package/src/methods/setErrors/index.ts +1 -0
- package/src/methods/setErrors/setErrors.ts +63 -0
- package/src/methods/setInput/index.ts +1 -0
- package/src/methods/setInput/setInput.ts +87 -0
- package/src/methods/submit/index.ts +1 -0
- package/src/methods/submit/submit.ts +11 -0
- package/src/methods/swap/index.ts +1 -0
- package/src/methods/swap/swap.ts +85 -0
- package/src/methods/validate/index.ts +1 -0
- package/src/methods/validate/validate.ts +34 -0
- package/src/types/field.ts +48 -0
- package/src/types/form.ts +12 -0
- 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';
|