@octanejs/formisch 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/UPSTREAM.md +95 -0
  4. package/package.json +65 -0
  5. package/src/components/Field/Field.tsrx +22 -0
  6. package/src/components/Field/Field.tsrx.d.ts +17 -0
  7. package/src/components/Field/index.ts +1 -0
  8. package/src/components/FieldArray/FieldArray.tsrx +22 -0
  9. package/src/components/FieldArray/FieldArray.tsrx.d.ts +18 -0
  10. package/src/components/FieldArray/index.ts +1 -0
  11. package/src/components/Form/Form.tsrx +28 -0
  12. package/src/components/Form/Form.tsrx.d.ts +15 -0
  13. package/src/components/Form/index.ts +1 -0
  14. package/src/components/index.ts +3 -0
  15. package/src/core/array/copyItemState/copyItemState.ts +101 -0
  16. package/src/core/array/copyItemState/index.ts +1 -0
  17. package/src/core/array/index.ts +3 -0
  18. package/src/core/array/resetItemState/index.ts +1 -0
  19. package/src/core/array/resetItemState/resetItemState.ts +172 -0
  20. package/src/core/array/swapItemState/index.ts +1 -0
  21. package/src/core/array/swapItemState/swapItemState.ts +138 -0
  22. package/src/core/field/focusFieldElement/focusFieldElement.ts +32 -0
  23. package/src/core/field/focusFieldElement/index.ts +1 -0
  24. package/src/core/field/getDirtyFieldInput/getDirtyFieldInput.ts +66 -0
  25. package/src/core/field/getDirtyFieldInput/index.ts +1 -0
  26. package/src/core/field/getElementInput/getElementInput.ts +78 -0
  27. package/src/core/field/getElementInput/index.ts +1 -0
  28. package/src/core/field/getFieldBool/getFieldBool.ts +22 -0
  29. package/src/core/field/getFieldBool/index.ts +1 -0
  30. package/src/core/field/getFieldInput/getFieldInput.ts +52 -0
  31. package/src/core/field/getFieldInput/index.ts +1 -0
  32. package/src/core/field/getFieldStore/getFieldStore.ts +34 -0
  33. package/src/core/field/getFieldStore/index.ts +1 -0
  34. package/src/core/field/index.ts +11 -0
  35. package/src/core/field/initializeFieldStore/index.ts +1 -0
  36. package/src/core/field/initializeFieldStore/initializeFieldStore.ts +325 -0
  37. package/src/core/field/setFieldBool/index.ts +1 -0
  38. package/src/core/field/setFieldBool/setFieldBool.ts +29 -0
  39. package/src/core/field/setFieldInput/index.ts +1 -0
  40. package/src/core/field/setFieldInput/setFieldInput.ts +180 -0
  41. package/src/core/field/setInitialFieldInput/index.ts +1 -0
  42. package/src/core/field/setInitialFieldInput/setInitialFieldInput.ts +99 -0
  43. package/src/core/field/walkFieldStore/index.ts +1 -0
  44. package/src/core/field/walkFieldStore/walkFieldStore.ts +49 -0
  45. package/src/core/form/createFormStore/createFormStore.ts +56 -0
  46. package/src/core/form/createFormStore/index.ts +1 -0
  47. package/src/core/form/decodeFormData/decodeFormData.ts +436 -0
  48. package/src/core/form/decodeFormData/index.ts +1 -0
  49. package/src/core/form/index.ts +4 -0
  50. package/src/core/form/validateFormInput/index.ts +1 -0
  51. package/src/core/form/validateFormInput/validateFormInput.ts +138 -0
  52. package/src/core/form/validateIfRequired/index.ts +1 -0
  53. package/src/core/form/validateIfRequired/validateIfRequired.ts +31 -0
  54. package/src/core/framework/index.ts +80 -0
  55. package/src/core/index.ts +6 -0
  56. package/src/core/types/field/field.ts +201 -0
  57. package/src/core/types/field/index.ts +1 -0
  58. package/src/core/types/form/form.ts +140 -0
  59. package/src/core/types/form/index.ts +1 -0
  60. package/src/core/types/index.ts +6 -0
  61. package/src/core/types/path/index.ts +10 -0
  62. package/src/core/types/path/path.ts +301 -0
  63. package/src/core/types/schema/index.ts +1 -0
  64. package/src/core/types/schema/schema.ts +18 -0
  65. package/src/core/types/signal/index.ts +1 -0
  66. package/src/core/types/signal/signal.ts +23 -0
  67. package/src/core/types/utils/index.ts +1 -0
  68. package/src/core/types/utils/utils.ts +46 -0
  69. package/src/core/values.ts +4 -0
  70. package/src/hooks/index.ts +3 -0
  71. package/src/hooks/useField/index.ts +1 -0
  72. package/src/hooks/useField/useField.ts +114 -0
  73. package/src/hooks/useFieldArray/index.ts +1 -0
  74. package/src/hooks/useFieldArray/useFieldArray.ts +63 -0
  75. package/src/hooks/useForm/index.ts +1 -0
  76. package/src/hooks/useForm/useForm.ts +68 -0
  77. package/src/hooks/useSignals/index.ts +1 -0
  78. package/src/hooks/useSignals/useSignals.ts +31 -0
  79. package/src/index.ts +19 -0
  80. package/src/internal.ts +32 -0
  81. package/src/methods/focus/focus.ts +35 -0
  82. package/src/methods/focus/index.ts +1 -0
  83. package/src/methods/getDeepErrorEntries/getDeepErrorEntries.ts +108 -0
  84. package/src/methods/getDeepErrorEntries/index.ts +1 -0
  85. package/src/methods/getDeepErrors/getDeepErrors.ts +90 -0
  86. package/src/methods/getDeepErrors/index.ts +1 -0
  87. package/src/methods/getDirtyInput/getDirtyInput.ts +87 -0
  88. package/src/methods/getDirtyInput/index.ts +1 -0
  89. package/src/methods/getDirtyPaths/getDirtyPaths.ts +123 -0
  90. package/src/methods/getDirtyPaths/index.ts +1 -0
  91. package/src/methods/getErrors/getErrors.ts +70 -0
  92. package/src/methods/getErrors/index.ts +1 -0
  93. package/src/methods/getInput/getInput.ts +75 -0
  94. package/src/methods/getInput/index.ts +1 -0
  95. package/src/methods/handleSubmit/handleSubmit.ts +83 -0
  96. package/src/methods/handleSubmit/index.ts +1 -0
  97. package/src/methods/index.ts +23 -0
  98. package/src/methods/insert/index.ts +1 -0
  99. package/src/methods/insert/insert.ts +134 -0
  100. package/src/methods/isDirty/index.ts +1 -0
  101. package/src/methods/isDirty/isDirty.ts +72 -0
  102. package/src/methods/isEdited/index.ts +1 -0
  103. package/src/methods/isEdited/isEdited.ts +72 -0
  104. package/src/methods/isTouched/index.ts +1 -0
  105. package/src/methods/isTouched/isTouched.ts +72 -0
  106. package/src/methods/isValid/index.ts +1 -0
  107. package/src/methods/isValid/isValid.ts +74 -0
  108. package/src/methods/move/index.ts +1 -0
  109. package/src/methods/move/move.ts +124 -0
  110. package/src/methods/pickDirty/index.ts +1 -0
  111. package/src/methods/pickDirty/pickDirty.ts +87 -0
  112. package/src/methods/remove/index.ts +1 -0
  113. package/src/methods/remove/remove.ts +76 -0
  114. package/src/methods/replace/index.ts +1 -0
  115. package/src/methods/replace/replace.ts +80 -0
  116. package/src/methods/reset/index.ts +1 -0
  117. package/src/methods/reset/reset.ts +216 -0
  118. package/src/methods/setErrors/index.ts +1 -0
  119. package/src/methods/setErrors/setErrors.ts +63 -0
  120. package/src/methods/setInput/index.ts +1 -0
  121. package/src/methods/setInput/setInput.ts +87 -0
  122. package/src/methods/submit/index.ts +1 -0
  123. package/src/methods/submit/submit.ts +11 -0
  124. package/src/methods/swap/index.ts +1 -0
  125. package/src/methods/swap/swap.ts +85 -0
  126. package/src/methods/validate/index.ts +1 -0
  127. package/src/methods/validate/validate.ts +34 -0
  128. package/src/types/field.ts +48 -0
  129. package/src/types/form.ts +12 -0
  130. package/src/types/index.ts +2 -0
@@ -0,0 +1,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';
@@ -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
+ }