@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,31 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useState } from 'octane';
|
|
2
|
+
import { setListener, type Listener } from '../../core/index.ts';
|
|
3
|
+
import { subSlot } from '../../internal.ts';
|
|
4
|
+
|
|
5
|
+
export function useSignals(...rest: [slot?: symbol]): void {
|
|
6
|
+
const slot = typeof rest[0] === 'symbol' ? rest[0] : undefined;
|
|
7
|
+
const [, setVersion] = useState(0, subSlot(slot, 'version'));
|
|
8
|
+
const listener = useMemo<Listener>(
|
|
9
|
+
() => [() => setVersion((version) => version + 1), new Set()],
|
|
10
|
+
[],
|
|
11
|
+
subSlot(slot, 'listener'),
|
|
12
|
+
);
|
|
13
|
+
const cleanSubscribers = () => {
|
|
14
|
+
for (const subscriber of listener[1]) subscriber.delete(listener);
|
|
15
|
+
listener[1].clear();
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
cleanSubscribers();
|
|
19
|
+
|
|
20
|
+
const isServer = typeof document === 'undefined';
|
|
21
|
+
if (!isServer) setListener(listener);
|
|
22
|
+
|
|
23
|
+
useLayoutEffect(
|
|
24
|
+
() => {
|
|
25
|
+
if (!isServer) setListener(undefined);
|
|
26
|
+
},
|
|
27
|
+
null,
|
|
28
|
+
subSlot(slot, 'clear-listener'),
|
|
29
|
+
);
|
|
30
|
+
useEffect(() => cleanSubscribers, [listener], subSlot(slot, 'cleanup'));
|
|
31
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
DeepPartial,
|
|
3
|
+
FieldElement,
|
|
4
|
+
FormConfig,
|
|
5
|
+
PartialValues,
|
|
6
|
+
PathValue,
|
|
7
|
+
RequiredPath,
|
|
8
|
+
FormSchema,
|
|
9
|
+
Schema,
|
|
10
|
+
SubmitEventHandler,
|
|
11
|
+
SubmitHandler,
|
|
12
|
+
ValidArrayPath,
|
|
13
|
+
ValidationMode,
|
|
14
|
+
ValidPath,
|
|
15
|
+
} from './core/index.ts';
|
|
16
|
+
export * from './components/index.ts';
|
|
17
|
+
export * from './hooks/index.ts';
|
|
18
|
+
export * from './methods/index.ts';
|
|
19
|
+
export * from './types/index.ts';
|
package/src/internal.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const subSlotCache = new Map<symbol, Map<string, symbol>>();
|
|
2
|
+
const bareTagCache = new Map<string, symbol>();
|
|
3
|
+
|
|
4
|
+
export function subSlot(slot: symbol | undefined, tag: string): symbol {
|
|
5
|
+
if (slot === undefined) {
|
|
6
|
+
let bare = bareTagCache.get(tag);
|
|
7
|
+
if (bare === undefined) {
|
|
8
|
+
bare = Symbol.for(`@octanejs/formisch:${tag}`);
|
|
9
|
+
bareTagCache.set(tag, bare);
|
|
10
|
+
}
|
|
11
|
+
return bare;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let byTag = subSlotCache.get(slot);
|
|
15
|
+
if (byTag === undefined) {
|
|
16
|
+
byTag = new Map();
|
|
17
|
+
subSlotCache.set(slot, byTag);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let child = byTag.get(tag);
|
|
21
|
+
if (child === undefined) {
|
|
22
|
+
child = Symbol.for(`${slot.description ?? ''}:${tag}`);
|
|
23
|
+
byTag.set(tag, child);
|
|
24
|
+
}
|
|
25
|
+
return child;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function splitSlot(args: unknown[]): [unknown[], symbol | undefined] {
|
|
29
|
+
const tail = args.at(-1);
|
|
30
|
+
const slot = typeof tail === 'symbol' ? tail : undefined;
|
|
31
|
+
return [slot === undefined ? args : args.slice(0, -1), slot];
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BaseFormStore,
|
|
3
|
+
focusFieldElement,
|
|
4
|
+
type FormSchema,
|
|
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
|
+
* Focus field config interface.
|
|
14
|
+
*/
|
|
15
|
+
export interface FocusFieldConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
|
|
16
|
+
/**
|
|
17
|
+
* The path to the field to focus.
|
|
18
|
+
*/
|
|
19
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Focuses the first focusable input element of a field. This is useful for
|
|
24
|
+
* programmatically setting focus to a specific field, such as after
|
|
25
|
+
* validation errors or user interactions.
|
|
26
|
+
*
|
|
27
|
+
* @param form The form store containing the field.
|
|
28
|
+
* @param config The focus field configuration.
|
|
29
|
+
*/
|
|
30
|
+
export function focus<TSchema extends FormSchema, TFieldPath extends RequiredPath>(
|
|
31
|
+
form: BaseFormStore<TSchema>,
|
|
32
|
+
config: FocusFieldConfig<TSchema, TFieldPath>,
|
|
33
|
+
): void {
|
|
34
|
+
focusFieldElement(getFieldStore(form[INTERNAL], config.path));
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './focus.ts';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BaseFormStore,
|
|
3
|
+
type FieldPath,
|
|
4
|
+
type FormSchema,
|
|
5
|
+
getFieldStore,
|
|
6
|
+
INTERNAL,
|
|
7
|
+
type Path,
|
|
8
|
+
type RequiredPath,
|
|
9
|
+
type ValidPath,
|
|
10
|
+
walkFieldStore,
|
|
11
|
+
} from '../../core/index.ts';
|
|
12
|
+
import type * as v from 'valibot';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Deep error entry interface.
|
|
16
|
+
*/
|
|
17
|
+
export interface DeepErrorEntry<TValue = unknown> {
|
|
18
|
+
/**
|
|
19
|
+
* The path to the field with errors, or an empty path for form-level errors.
|
|
20
|
+
*/
|
|
21
|
+
readonly path: unknown extends TValue ? Path : readonly [] | FieldPath<TValue>;
|
|
22
|
+
/**
|
|
23
|
+
* The error messages of the field.
|
|
24
|
+
*/
|
|
25
|
+
readonly errors: [string, ...string[]];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get form deep error entries config interface.
|
|
30
|
+
*/
|
|
31
|
+
export interface GetFormDeepErrorEntriesConfig {
|
|
32
|
+
/**
|
|
33
|
+
* The path to a field. Leave undefined to get the entries of the entire form.
|
|
34
|
+
*/
|
|
35
|
+
readonly path?: undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get field deep error entries config interface.
|
|
40
|
+
*/
|
|
41
|
+
export interface GetFieldDeepErrorEntriesConfig<
|
|
42
|
+
TSchema extends FormSchema,
|
|
43
|
+
TFieldPath extends RequiredPath,
|
|
44
|
+
> {
|
|
45
|
+
/**
|
|
46
|
+
* The path to the field to retrieve the entries from.
|
|
47
|
+
*/
|
|
48
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Retrieves the errors of a specific field or the entire form as a list of
|
|
53
|
+
* entries, each pairing the path to a field with its error messages. This is
|
|
54
|
+
* useful for building custom error summaries that link each message back to
|
|
55
|
+
* its field. Form-level errors are included with an empty path.
|
|
56
|
+
*
|
|
57
|
+
* @param form The form store to retrieve error entries from.
|
|
58
|
+
*
|
|
59
|
+
* @returns A list of path and error message entries.
|
|
60
|
+
*/
|
|
61
|
+
export function getDeepErrorEntries<TSchema extends FormSchema>(
|
|
62
|
+
form: BaseFormStore<TSchema>,
|
|
63
|
+
): DeepErrorEntry<v.InferInput<TSchema>>[];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Retrieves the errors of a specific field or the entire form as a list of
|
|
67
|
+
* entries, each pairing the path to a field with its error messages. This is
|
|
68
|
+
* useful for building custom error summaries that link each message back to
|
|
69
|
+
* its field. Form-level errors are included with an empty path.
|
|
70
|
+
*
|
|
71
|
+
* @param form The form store to retrieve error entries from.
|
|
72
|
+
* @param config The get deep error entries configuration.
|
|
73
|
+
*
|
|
74
|
+
* @returns A list of path and error message entries.
|
|
75
|
+
*/
|
|
76
|
+
export function getDeepErrorEntries<
|
|
77
|
+
TSchema extends FormSchema,
|
|
78
|
+
TFieldPath extends RequiredPath | undefined = undefined,
|
|
79
|
+
>(
|
|
80
|
+
form: BaseFormStore<TSchema>,
|
|
81
|
+
config: TFieldPath extends RequiredPath
|
|
82
|
+
? GetFieldDeepErrorEntriesConfig<TSchema, TFieldPath>
|
|
83
|
+
: GetFormDeepErrorEntriesConfig,
|
|
84
|
+
): DeepErrorEntry<v.InferInput<TSchema>>[];
|
|
85
|
+
|
|
86
|
+
// @__NO_SIDE_EFFECTS__
|
|
87
|
+
export function getDeepErrorEntries(
|
|
88
|
+
form: BaseFormStore,
|
|
89
|
+
config?: GetFormDeepErrorEntriesConfig | GetFieldDeepErrorEntriesConfig<FormSchema, RequiredPath>,
|
|
90
|
+
): DeepErrorEntry[] {
|
|
91
|
+
// Collect entries of errored fields by walking the field store tree, reading
|
|
92
|
+
// each field's own path instead of reconstructing it during the walk
|
|
93
|
+
const entries: DeepErrorEntry[] = [];
|
|
94
|
+
walkFieldStore(
|
|
95
|
+
config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
|
|
96
|
+
(internalFieldStore) => {
|
|
97
|
+
// Emit an entry if field has errors; form-level errors are emitted
|
|
98
|
+
const errors = internalFieldStore.errors.value;
|
|
99
|
+
if (errors) {
|
|
100
|
+
entries.push({
|
|
101
|
+
path: internalFieldStore.path,
|
|
102
|
+
errors,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
return entries;
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './getDeepErrorEntries.ts';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BaseFormStore,
|
|
3
|
+
type FormSchema,
|
|
4
|
+
getFieldStore,
|
|
5
|
+
INTERNAL,
|
|
6
|
+
type RequiredPath,
|
|
7
|
+
type ValidPath,
|
|
8
|
+
walkFieldStore,
|
|
9
|
+
} from '../../core/index.ts';
|
|
10
|
+
import type * as v from 'valibot';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get form deep errors config interface.
|
|
14
|
+
*/
|
|
15
|
+
export interface GetFormDeepErrorsConfig {
|
|
16
|
+
/**
|
|
17
|
+
* The path to a field. Leave undefined to get the errors of the entire form.
|
|
18
|
+
*/
|
|
19
|
+
readonly path?: undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get field deep errors config interface.
|
|
24
|
+
*/
|
|
25
|
+
export interface GetFieldDeepErrorsConfig<
|
|
26
|
+
TSchema extends FormSchema,
|
|
27
|
+
TFieldPath extends RequiredPath,
|
|
28
|
+
> {
|
|
29
|
+
/**
|
|
30
|
+
* The path to the field to retrieve the errors from.
|
|
31
|
+
*/
|
|
32
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Retrieves all error messages of a specific field or the entire form by
|
|
37
|
+
* walking through the field store and all its descendants. This is useful for
|
|
38
|
+
* displaying a summary of all validation errors within a section or the whole
|
|
39
|
+
* form. Form-level errors are included.
|
|
40
|
+
*
|
|
41
|
+
* @param form The form store to retrieve errors from.
|
|
42
|
+
*
|
|
43
|
+
* @returns A non-empty array of error messages, or null if no errors exist.
|
|
44
|
+
*/
|
|
45
|
+
export function getDeepErrors<TSchema extends FormSchema>(
|
|
46
|
+
form: BaseFormStore<TSchema>,
|
|
47
|
+
): [string, ...string[]] | null;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves all error messages of a specific field or the entire form by
|
|
51
|
+
* walking through the field store and all its descendants. This is useful for
|
|
52
|
+
* displaying a summary of all validation errors within a section or the whole
|
|
53
|
+
* form. Form-level errors are included.
|
|
54
|
+
*
|
|
55
|
+
* @param form The form store to retrieve errors from.
|
|
56
|
+
* @param config The get deep errors configuration.
|
|
57
|
+
*
|
|
58
|
+
* @returns A non-empty array of error messages, or null if no errors exist.
|
|
59
|
+
*/
|
|
60
|
+
export function getDeepErrors<
|
|
61
|
+
TSchema extends FormSchema,
|
|
62
|
+
TFieldPath extends RequiredPath | undefined = undefined,
|
|
63
|
+
>(
|
|
64
|
+
form: BaseFormStore<TSchema>,
|
|
65
|
+
config: TFieldPath extends RequiredPath
|
|
66
|
+
? GetFieldDeepErrorsConfig<TSchema, TFieldPath>
|
|
67
|
+
: GetFormDeepErrorsConfig,
|
|
68
|
+
): [string, ...string[]] | null;
|
|
69
|
+
|
|
70
|
+
// @__NO_SIDE_EFFECTS__
|
|
71
|
+
export function getDeepErrors(
|
|
72
|
+
form: BaseFormStore,
|
|
73
|
+
config?: GetFormDeepErrorsConfig | GetFieldDeepErrorsConfig<FormSchema, RequiredPath>,
|
|
74
|
+
): [string, ...string[]] | null {
|
|
75
|
+
let deepErrors: [string, ...string[]] | null = null;
|
|
76
|
+
walkFieldStore(
|
|
77
|
+
config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
|
|
78
|
+
(internalFieldStore) => {
|
|
79
|
+
const errors = internalFieldStore.errors.value;
|
|
80
|
+
if (errors) {
|
|
81
|
+
if (deepErrors) {
|
|
82
|
+
deepErrors.push(...errors);
|
|
83
|
+
} else {
|
|
84
|
+
deepErrors = [...errors];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
return deepErrors;
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './getDeepErrors.ts';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BaseFormStore,
|
|
3
|
+
type DeepPartial,
|
|
4
|
+
type FormSchema,
|
|
5
|
+
getDirtyFieldInput,
|
|
6
|
+
getFieldStore,
|
|
7
|
+
INTERNAL,
|
|
8
|
+
type PathValue,
|
|
9
|
+
type RequiredPath,
|
|
10
|
+
type ValidPath,
|
|
11
|
+
} from '../../core/index.ts';
|
|
12
|
+
import type * as v from 'valibot';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get form dirty input config interface.
|
|
16
|
+
*/
|
|
17
|
+
export interface GetFormDirtyInputConfig {
|
|
18
|
+
/**
|
|
19
|
+
* The path to a field. Leave undefined to get the dirty input of the entire
|
|
20
|
+
* form.
|
|
21
|
+
*/
|
|
22
|
+
readonly path?: undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get field dirty input config interface.
|
|
27
|
+
*/
|
|
28
|
+
export interface GetFieldDirtyInputConfig<
|
|
29
|
+
TSchema extends FormSchema,
|
|
30
|
+
TFieldPath extends RequiredPath,
|
|
31
|
+
> {
|
|
32
|
+
/**
|
|
33
|
+
* The path to the field to retrieve the dirty input from.
|
|
34
|
+
*/
|
|
35
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves only the dirty input values of a specific field or the entire
|
|
40
|
+
* form. Arrays are treated as atomic and returned in full if any item is
|
|
41
|
+
* dirty, while object keys without a dirty descendant are omitted. Returns
|
|
42
|
+
* `undefined` if no field in the inspected subtree is dirty.
|
|
43
|
+
*
|
|
44
|
+
* @param form The form store to retrieve dirty input from.
|
|
45
|
+
*
|
|
46
|
+
* @returns The dirty input of the form or specified field, or `undefined`.
|
|
47
|
+
*/
|
|
48
|
+
export function getDirtyInput<TSchema extends FormSchema>(
|
|
49
|
+
form: BaseFormStore<TSchema>,
|
|
50
|
+
): DeepPartial<v.InferInput<TSchema>> | undefined;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Retrieves only the dirty input values of a specific field or the entire
|
|
54
|
+
* form. Arrays are treated as atomic and returned in full if any item is
|
|
55
|
+
* dirty, while object keys without a dirty descendant are omitted. Returns
|
|
56
|
+
* `undefined` if no field in the inspected subtree is dirty.
|
|
57
|
+
*
|
|
58
|
+
* @param form The form store to retrieve dirty input from.
|
|
59
|
+
* @param config The get dirty input configuration.
|
|
60
|
+
*
|
|
61
|
+
* @returns The dirty input of the form or specified field, or `undefined`.
|
|
62
|
+
*/
|
|
63
|
+
export function getDirtyInput<
|
|
64
|
+
TSchema extends FormSchema,
|
|
65
|
+
TFieldPath extends RequiredPath | undefined = undefined,
|
|
66
|
+
>(
|
|
67
|
+
form: BaseFormStore<TSchema>,
|
|
68
|
+
config: TFieldPath extends RequiredPath
|
|
69
|
+
? GetFieldDirtyInputConfig<TSchema, TFieldPath>
|
|
70
|
+
: GetFormDirtyInputConfig,
|
|
71
|
+
):
|
|
72
|
+
| DeepPartial<
|
|
73
|
+
TFieldPath extends RequiredPath
|
|
74
|
+
? PathValue<v.InferInput<TSchema>, TFieldPath>
|
|
75
|
+
: v.InferInput<TSchema>
|
|
76
|
+
>
|
|
77
|
+
| undefined;
|
|
78
|
+
|
|
79
|
+
// @__NO_SIDE_EFFECTS__
|
|
80
|
+
export function getDirtyInput(
|
|
81
|
+
form: BaseFormStore,
|
|
82
|
+
config?: GetFormDirtyInputConfig | GetFieldDirtyInputConfig<FormSchema, RequiredPath>,
|
|
83
|
+
): unknown {
|
|
84
|
+
return getDirtyFieldInput(
|
|
85
|
+
config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './getDirtyInput.ts';
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BaseFormStore,
|
|
3
|
+
type DirtyPath,
|
|
4
|
+
type FormSchema,
|
|
5
|
+
getFieldBool,
|
|
6
|
+
getFieldStore,
|
|
7
|
+
INTERNAL,
|
|
8
|
+
type InternalFieldStore,
|
|
9
|
+
type RequiredPath,
|
|
10
|
+
type ValidPath,
|
|
11
|
+
} from '../../core/index.ts';
|
|
12
|
+
import type * as v from 'valibot';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get form dirty paths config interface.
|
|
16
|
+
*/
|
|
17
|
+
export interface GetFormDirtyPathsConfig {
|
|
18
|
+
/**
|
|
19
|
+
* The path to a field. Leave undefined to inspect the entire form.
|
|
20
|
+
*/
|
|
21
|
+
readonly path?: undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get field dirty paths config interface.
|
|
26
|
+
*/
|
|
27
|
+
export interface GetFieldDirtyPathsConfig<
|
|
28
|
+
TSchema extends FormSchema,
|
|
29
|
+
TFieldPath extends RequiredPath,
|
|
30
|
+
> {
|
|
31
|
+
/**
|
|
32
|
+
* The path to the field to inspect.
|
|
33
|
+
*/
|
|
34
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns a list of paths to the dirty fields of a specific field or the
|
|
39
|
+
* entire form. Arrays are treated as atomic and contribute only their own
|
|
40
|
+
* path if any item is dirty, while object branches are recursed into. Returns
|
|
41
|
+
* an empty list if no field in the inspected subtree is dirty.
|
|
42
|
+
*
|
|
43
|
+
* @param form The form store to inspect.
|
|
44
|
+
*
|
|
45
|
+
* @returns The list of paths to the dirty fields.
|
|
46
|
+
*/
|
|
47
|
+
export function getDirtyPaths<TSchema extends FormSchema>(
|
|
48
|
+
form: BaseFormStore<TSchema>,
|
|
49
|
+
): DirtyPath<v.InferInput<TSchema>>[];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns a list of paths to the dirty fields of a specific field or the
|
|
53
|
+
* entire form. Arrays are treated as atomic and contribute only their own
|
|
54
|
+
* path if any item is dirty, while object branches are recursed into. Returns
|
|
55
|
+
* an empty list if no field in the inspected subtree is dirty.
|
|
56
|
+
*
|
|
57
|
+
* @param form The form store to inspect.
|
|
58
|
+
* @param config The get dirty paths configuration.
|
|
59
|
+
*
|
|
60
|
+
* @returns The list of paths to the dirty fields.
|
|
61
|
+
*/
|
|
62
|
+
export function getDirtyPaths<
|
|
63
|
+
TSchema extends FormSchema,
|
|
64
|
+
TFieldPath extends RequiredPath | undefined = undefined,
|
|
65
|
+
>(
|
|
66
|
+
form: BaseFormStore<TSchema>,
|
|
67
|
+
config: TFieldPath extends RequiredPath
|
|
68
|
+
? GetFieldDirtyPathsConfig<TSchema, TFieldPath>
|
|
69
|
+
: GetFormDirtyPathsConfig,
|
|
70
|
+
): DirtyPath<v.InferInput<TSchema>>[];
|
|
71
|
+
|
|
72
|
+
// @__NO_SIDE_EFFECTS__
|
|
73
|
+
export function getDirtyPaths(
|
|
74
|
+
form: BaseFormStore,
|
|
75
|
+
config?: GetFormDirtyPathsConfig | GetFieldDirtyPathsConfig<FormSchema, RequiredPath>,
|
|
76
|
+
): RequiredPath[] {
|
|
77
|
+
// Collect paths of dirty fields via a single recursive walk, reading each
|
|
78
|
+
// field's own path instead of reconstructing it during the walk
|
|
79
|
+
const paths: RequiredPath[] = [];
|
|
80
|
+
collectDirtyPaths(
|
|
81
|
+
config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
|
|
82
|
+
paths,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Return collected paths
|
|
86
|
+
return paths;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// @__NO_SIDE_EFFECTS__
|
|
90
|
+
function collectDirtyPaths(internalFieldStore: InternalFieldStore, paths: RequiredPath[]): void {
|
|
91
|
+
// If field store is object with non-nullish input, recurse into children
|
|
92
|
+
if (internalFieldStore.kind === 'object' && internalFieldStore.input.value) {
|
|
93
|
+
// Hint: We skip a per-child `getFieldBool` pre-check because the
|
|
94
|
+
// recursion already prunes clean subtrees. Pre-checking would walk
|
|
95
|
+
// every dirty subtree twice.
|
|
96
|
+
const lengthBefore = paths.length;
|
|
97
|
+
for (const key in internalFieldStore.children) {
|
|
98
|
+
collectDirtyPaths(internalFieldStore.children[key], paths);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// If no descendant emitted a path but the object itself flipped dirty
|
|
102
|
+
// (e.g. transitioned from nullish to a non-nullish object), emit the
|
|
103
|
+
// object's own path so the change isn't silently dropped.
|
|
104
|
+
if (
|
|
105
|
+
paths.length === lengthBefore &&
|
|
106
|
+
internalFieldStore.isDirty.value &&
|
|
107
|
+
internalFieldStore.path.length > 0
|
|
108
|
+
) {
|
|
109
|
+
paths.push([...internalFieldStore.path] as unknown as RequiredPath);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Otherwise, if field store is a value, emit its path if dirty
|
|
113
|
+
} else if (internalFieldStore.kind === 'value') {
|
|
114
|
+
if (internalFieldStore.isDirty.value && internalFieldStore.path.length > 0) {
|
|
115
|
+
paths.push([...internalFieldStore.path] as unknown as RequiredPath);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Otherwise, field is atomic (array or cleared object) — emit its path
|
|
119
|
+
// if any dirty content exists
|
|
120
|
+
} else if (getFieldBool(internalFieldStore, 'isDirty') && internalFieldStore.path.length > 0) {
|
|
121
|
+
paths.push([...internalFieldStore.path] as unknown as RequiredPath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './getDirtyPaths.ts';
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
* Get form errors config interface.
|
|
13
|
+
*/
|
|
14
|
+
export interface GetFormErrorsConfig {
|
|
15
|
+
/**
|
|
16
|
+
* The path to a field. Leave undefined to get form-level errors.
|
|
17
|
+
*/
|
|
18
|
+
readonly path?: undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get field errors config interface.
|
|
23
|
+
*/
|
|
24
|
+
export interface GetFieldErrorsConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
|
|
25
|
+
/**
|
|
26
|
+
* The path to the field to retrieve errors from.
|
|
27
|
+
*/
|
|
28
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Retrieves error messages from the form. When called without a config,
|
|
33
|
+
* returns form-level errors. When called with a path, returns errors for
|
|
34
|
+
* that specific field.
|
|
35
|
+
*
|
|
36
|
+
* @param form The form store to retrieve errors from.
|
|
37
|
+
*
|
|
38
|
+
* @returns A non-empty array of error messages, or null if no errors exist.
|
|
39
|
+
*/
|
|
40
|
+
export function getErrors<TSchema extends FormSchema>(
|
|
41
|
+
form: BaseFormStore<TSchema>,
|
|
42
|
+
): [string, ...string[]] | null;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Retrieves error messages from the form. When called without a config,
|
|
46
|
+
* returns form-level errors. When called with a path, returns errors for
|
|
47
|
+
* that specific field.
|
|
48
|
+
*
|
|
49
|
+
* @param form The form store to retrieve errors from.
|
|
50
|
+
* @param config The get errors configuration.
|
|
51
|
+
*
|
|
52
|
+
* @returns A non-empty array of error messages, or null if no errors exist.
|
|
53
|
+
*/
|
|
54
|
+
export function getErrors<
|
|
55
|
+
TSchema extends FormSchema,
|
|
56
|
+
TFieldPath extends RequiredPath | undefined = undefined,
|
|
57
|
+
>(
|
|
58
|
+
form: BaseFormStore<TSchema>,
|
|
59
|
+
config: TFieldPath extends RequiredPath
|
|
60
|
+
? GetFieldErrorsConfig<TSchema, TFieldPath>
|
|
61
|
+
: GetFormErrorsConfig,
|
|
62
|
+
): [string, ...string[]] | null;
|
|
63
|
+
|
|
64
|
+
// @__NO_SIDE_EFFECTS__
|
|
65
|
+
export function getErrors(
|
|
66
|
+
form: BaseFormStore,
|
|
67
|
+
config?: GetFormErrorsConfig | GetFieldErrorsConfig<FormSchema, RequiredPath>,
|
|
68
|
+
): [string, ...string[]] | null {
|
|
69
|
+
return (config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]).errors.value;
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './getErrors.ts';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BaseFormStore,
|
|
3
|
+
type FormSchema,
|
|
4
|
+
getFieldInput,
|
|
5
|
+
getFieldStore,
|
|
6
|
+
INTERNAL,
|
|
7
|
+
type PartialValues,
|
|
8
|
+
type PathValue,
|
|
9
|
+
type RequiredPath,
|
|
10
|
+
type ValidPath,
|
|
11
|
+
} from '../../core/index.ts';
|
|
12
|
+
import type * as v from 'valibot';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get form input config interface.
|
|
16
|
+
*/
|
|
17
|
+
export interface GetFormInputConfig {
|
|
18
|
+
/**
|
|
19
|
+
* The path to a field. Leave undefined to get the entire form input.
|
|
20
|
+
*/
|
|
21
|
+
readonly path?: undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get field input config interface.
|
|
26
|
+
*/
|
|
27
|
+
export interface GetFieldInputConfig<TSchema extends FormSchema, TFieldPath extends RequiredPath> {
|
|
28
|
+
/**
|
|
29
|
+
* The path to the field to retrieve input from.
|
|
30
|
+
*/
|
|
31
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Retrieves the current input value of a specific field or the entire form.
|
|
36
|
+
* Returns a partial object as not all fields may have been set.
|
|
37
|
+
*
|
|
38
|
+
* @param form The form store to retrieve input from.
|
|
39
|
+
*
|
|
40
|
+
* @returns The partial input values of the form or the specified field.
|
|
41
|
+
*/
|
|
42
|
+
export function getInput<TSchema extends FormSchema>(
|
|
43
|
+
form: BaseFormStore<TSchema>,
|
|
44
|
+
): PartialValues<v.InferInput<TSchema>>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Retrieves the current input value of a specific field or the entire form.
|
|
48
|
+
* Returns a partial object as not all fields may have been set.
|
|
49
|
+
*
|
|
50
|
+
* @param form The form store to retrieve input from.
|
|
51
|
+
* @param config The get input configuration.
|
|
52
|
+
*
|
|
53
|
+
* @returns The partial input values of the form or the specified field.
|
|
54
|
+
*/
|
|
55
|
+
export function getInput<
|
|
56
|
+
TSchema extends FormSchema,
|
|
57
|
+
TFieldPath extends RequiredPath | undefined = undefined,
|
|
58
|
+
>(
|
|
59
|
+
form: BaseFormStore<TSchema>,
|
|
60
|
+
config: TFieldPath extends RequiredPath
|
|
61
|
+
? GetFieldInputConfig<TSchema, TFieldPath>
|
|
62
|
+
: GetFormInputConfig,
|
|
63
|
+
): PartialValues<
|
|
64
|
+
TFieldPath extends RequiredPath
|
|
65
|
+
? PathValue<v.InferInput<TSchema>, TFieldPath>
|
|
66
|
+
: v.InferInput<TSchema>
|
|
67
|
+
>;
|
|
68
|
+
|
|
69
|
+
// @__NO_SIDE_EFFECTS__
|
|
70
|
+
export function getInput(
|
|
71
|
+
form: BaseFormStore,
|
|
72
|
+
config?: GetFormInputConfig | GetFieldInputConfig<FormSchema, RequiredPath>,
|
|
73
|
+
): unknown {
|
|
74
|
+
return getFieldInput(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]);
|
|
75
|
+
}
|