@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 @@
|
|
|
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';
|