@teamnovu/kit-vue-forms 0.1.14 → 0.1.16
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/dist/components/FormFieldWrapper.vue.d.ts +1 -1
- package/dist/composables/useField.d.ts +7 -3
- package/dist/composables/useFieldRegistry.d.ts +13 -4
- package/dist/composables/useValidation.d.ts +1 -0
- package/dist/index.js +353 -278
- package/dist/types/form.d.ts +3 -1
- package/dist/utils/path.d.ts +4 -1
- package/docs/reference.md +0 -2
- package/package.json +1 -1
- package/src/components/FormFieldWrapper.vue +8 -0
- package/src/composables/useField.ts +23 -5
- package/src/composables/useFieldRegistry.ts +82 -15
- package/src/composables/useForm.ts +79 -32
- package/src/composables/useValidation.ts +17 -0
- package/src/types/form.ts +23 -8
- package/src/utils/path.ts +81 -46
- package/tests/subform.test.ts +17 -0
- package/tests/useField.test.ts +17 -0
- package/tests/useForm.test.ts +352 -200
- package/tests/useValidation.test.ts +97 -1
package/src/utils/path.ts
CHANGED
|
@@ -1,94 +1,129 @@
|
|
|
1
|
-
import { computed, isRef, unref, type MaybeRef } from
|
|
2
|
-
import type { Paths, PickProps, SplitPath } from
|
|
3
|
-
import type { ErrorBag, ValidationErrors } from
|
|
1
|
+
import { computed, isRef, unref, type MaybeRef } from "vue";
|
|
2
|
+
import type { Paths, PickProps, SplitPath } from "../types/util";
|
|
3
|
+
import type { ErrorBag, ValidationErrors } from "../types/validation";
|
|
4
|
+
import type { FormField } from "../types/form";
|
|
4
5
|
|
|
5
6
|
export function splitPath(path: string): string[] {
|
|
6
|
-
if (path ===
|
|
7
|
-
return []
|
|
7
|
+
if (path === "") {
|
|
8
|
+
return [];
|
|
8
9
|
}
|
|
9
|
-
return path.split(/\s*\.\s*/).filter(Boolean)
|
|
10
|
+
return path.split(/\s*\.\s*/).filter(Boolean);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export function
|
|
13
|
-
|
|
13
|
+
export function existsPath<T, K extends Paths<T>>(
|
|
14
|
+
obj: T,
|
|
15
|
+
path: K | SplitPath<K>,
|
|
16
|
+
): boolean {
|
|
17
|
+
const splittedPath = Array.isArray(path) ? path : splitPath(path) as SplitPath<K>;
|
|
18
|
+
return !!getNestedValue(obj, splittedPath.slice(0, -1) as SplitPath<K>);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function existsFieldPath<T, K extends Paths<T>>(field: FormField<T, K>) {
|
|
22
|
+
return existsPath(field.data.value, field.path.value)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getNestedValue<T, K extends Paths<T>>(
|
|
26
|
+
obj: T,
|
|
27
|
+
path: K | SplitPath<K>,
|
|
28
|
+
) {
|
|
29
|
+
const splittedPath = Array.isArray(path) ? path : splitPath(path);
|
|
14
30
|
return splittedPath.reduce(
|
|
15
31
|
(current, key) => current?.[key],
|
|
16
32
|
obj as Record<string, never>,
|
|
17
|
-
) as PickProps<T, K
|
|
33
|
+
) as PickProps<T, K>;
|
|
18
34
|
}
|
|
19
35
|
|
|
20
|
-
export function setNestedValue<T, K extends Paths<T>>(
|
|
21
|
-
|
|
36
|
+
export function setNestedValue<T, K extends Paths<T>>(
|
|
37
|
+
obj: MaybeRef<T>,
|
|
38
|
+
path: K | SplitPath<K>,
|
|
39
|
+
value: PickProps<T, K>,
|
|
40
|
+
): void {
|
|
41
|
+
const keys = Array.isArray(path) ? path : splitPath(path);
|
|
22
42
|
|
|
23
|
-
const lastKey = keys.at(-1)
|
|
43
|
+
const lastKey = keys.at(-1)!;
|
|
24
44
|
|
|
25
45
|
if (!lastKey) {
|
|
26
46
|
if (!isRef(obj)) {
|
|
27
47
|
// We cannot do anything here as we have nothing we can assign to
|
|
28
|
-
return
|
|
48
|
+
return;
|
|
29
49
|
}
|
|
30
50
|
|
|
31
|
-
obj.value = value
|
|
51
|
+
obj.value = value;
|
|
32
52
|
} else {
|
|
33
|
-
const target = keys
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
const target = keys.slice(0, -1).reduce(
|
|
54
|
+
(current, key) => {
|
|
55
|
+
if (current?.[key] === undefined) {
|
|
56
|
+
// Create the nested object if it doesn't exist
|
|
57
|
+
current[key] = {};
|
|
58
|
+
}
|
|
59
|
+
return current?.[key];
|
|
60
|
+
},
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
unref(obj) as Record<string, any>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
target[lastKey] = value;
|
|
42
66
|
}
|
|
43
67
|
}
|
|
44
68
|
|
|
45
|
-
export const getLens = <T, K extends Paths<T>>(
|
|
69
|
+
export const getLens = <T, K extends Paths<T>>(
|
|
70
|
+
data: MaybeRef<T>,
|
|
71
|
+
key: MaybeRef<K | SplitPath<K>>,
|
|
72
|
+
) => {
|
|
46
73
|
return computed({
|
|
47
74
|
get() {
|
|
48
|
-
return getNestedValue(unref(data), unref(key))
|
|
75
|
+
return getNestedValue(unref(data), unref(key));
|
|
49
76
|
},
|
|
50
77
|
set(value: PickProps<T, K>) {
|
|
51
|
-
setNestedValue(data, unref(key), value)
|
|
78
|
+
setNestedValue(data, unref(key), value);
|
|
52
79
|
},
|
|
53
|
-
})
|
|
54
|
-
}
|
|
80
|
+
});
|
|
81
|
+
};
|
|
55
82
|
|
|
56
|
-
type JoinPath<
|
|
57
|
-
|
|
83
|
+
type JoinPath<
|
|
84
|
+
Base extends string,
|
|
85
|
+
Sub extends string,
|
|
86
|
+
> = `${Base}${Base extends "" ? "" : Sub extends "" ? "" : "."}${Sub}`;
|
|
87
|
+
export function joinPath<Base extends string, Sub extends string>(
|
|
88
|
+
basePath: Base,
|
|
89
|
+
subPath: Sub,
|
|
90
|
+
): JoinPath<Base, Sub> {
|
|
58
91
|
if (!basePath && !subPath) {
|
|
59
|
-
return
|
|
92
|
+
return "" as JoinPath<Base, Sub>;
|
|
60
93
|
}
|
|
61
94
|
|
|
62
95
|
if (!basePath && subPath) {
|
|
63
|
-
return subPath as JoinPath<Base, Sub
|
|
96
|
+
return subPath as JoinPath<Base, Sub>;
|
|
64
97
|
}
|
|
65
98
|
|
|
66
99
|
if (!subPath && basePath) {
|
|
67
|
-
return basePath as JoinPath<Base, Sub
|
|
100
|
+
return basePath as JoinPath<Base, Sub>;
|
|
68
101
|
}
|
|
69
102
|
|
|
70
|
-
return `${basePath}.${subPath}` as JoinPath<Base, Sub
|
|
103
|
+
return `${basePath}.${subPath}` as JoinPath<Base, Sub>;
|
|
71
104
|
}
|
|
72
105
|
|
|
73
106
|
export function filterErrorsForPath(errors: ErrorBag, path: string): ErrorBag {
|
|
74
107
|
// Handle empty path - return all errors
|
|
75
108
|
if (!path) {
|
|
76
|
-
return errors
|
|
109
|
+
return errors;
|
|
77
110
|
}
|
|
78
111
|
|
|
79
|
-
const pathPrefix = `${path}
|
|
80
|
-
const filteredPropertyErrors: Record<string, ValidationErrors> =
|
|
81
|
-
Object.
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
([errorPath, errorMessages]) => [
|
|
87
|
-
|
|
88
|
-
|
|
112
|
+
const pathPrefix = `${path}.`;
|
|
113
|
+
const filteredPropertyErrors: Record<string, ValidationErrors> =
|
|
114
|
+
Object.fromEntries(
|
|
115
|
+
Object.entries(errors.propertyErrors)
|
|
116
|
+
.filter(([errorPath]) => {
|
|
117
|
+
return errorPath.startsWith(pathPrefix);
|
|
118
|
+
})
|
|
119
|
+
.map(([errorPath, errorMessages]) => [
|
|
120
|
+
errorPath.slice(pathPrefix.length),
|
|
121
|
+
errorMessages,
|
|
122
|
+
]),
|
|
123
|
+
);
|
|
89
124
|
|
|
90
125
|
return {
|
|
91
126
|
general: errors.general, // Keep general errors
|
|
92
127
|
propertyErrors: filteredPropertyErrors,
|
|
93
|
-
}
|
|
128
|
+
};
|
|
94
129
|
}
|
package/tests/subform.test.ts
CHANGED
|
@@ -1031,6 +1031,23 @@ describe('Subform Implementation', () => {
|
|
|
1031
1031
|
expect(result.isValid).toBe(false)
|
|
1032
1032
|
expect(result.errors.general).toContain('Data is undefined')
|
|
1033
1033
|
})
|
|
1034
|
+
|
|
1035
|
+
it('can create subforms on non-existent paths', async () => {
|
|
1036
|
+
const form = useForm<{
|
|
1037
|
+
test: string[]
|
|
1038
|
+
nonExistentPath?: { name: string }
|
|
1039
|
+
}>({
|
|
1040
|
+
initialData: { test: ['item1', 'item2'] },
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
const subform = form.getSubForm('nonExistentPath')
|
|
1044
|
+
|
|
1045
|
+
const nameField = subform.defineField({ path: 'name' })
|
|
1046
|
+
|
|
1047
|
+
nameField.setData('Test Name')
|
|
1048
|
+
|
|
1049
|
+
expect(nameField.data.value).toEqual('Test Name')
|
|
1050
|
+
})
|
|
1034
1051
|
})
|
|
1035
1052
|
|
|
1036
1053
|
describe('Path Edge Cases', () => {
|
package/tests/useField.test.ts
CHANGED
|
@@ -207,4 +207,21 @@ describe('useField', () => {
|
|
|
207
207
|
expect(field.initialValue.value).toBe('bar')
|
|
208
208
|
expect(field.data.value).toBe('modified')
|
|
209
209
|
})
|
|
210
|
+
|
|
211
|
+
it('it should reset errors on a field on blur', () => {
|
|
212
|
+
const errors = ref(['Initial error'])
|
|
213
|
+
|
|
214
|
+
const field = useField({
|
|
215
|
+
initialValue: 'foo',
|
|
216
|
+
value: 'foo',
|
|
217
|
+
path: 'name',
|
|
218
|
+
errors,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
expect(field.errors.value).toEqual(['Initial error'])
|
|
222
|
+
|
|
223
|
+
field.onBlur()
|
|
224
|
+
|
|
225
|
+
expect(field.errors.value).toEqual([])
|
|
226
|
+
})
|
|
210
227
|
})
|