@aerogel/core 0.0.0-next.c3236837f7f8fc319a4a56022accb32757b3db89 → 0.0.0-next.c38a10cd5e81c5f04469ef79e59a53e6bfd3df55
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/aerogel-core.d.ts +87 -78
- package/dist/aerogel-core.js +392 -349
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/Input.vue +2 -2
- package/src/components/ui/TextArea.vue +2 -2
- package/src/forms/FormController.test.ts +3 -0
- package/src/forms/FormController.ts +21 -16
- package/src/forms/utils.ts +25 -0
- package/src/forms/validation.ts +31 -0
- package/src/index.css +1 -0
package/package.json
CHANGED
|
@@ -48,8 +48,8 @@ const renderedInputClasses = computed(() =>
|
|
|
48
48
|
'block w-full rounded-md border-0 py-1.5 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6',
|
|
49
49
|
{
|
|
50
50
|
'focus:ring-primary-600': !$input.value?.errors,
|
|
51
|
-
'text-gray-900 shadow-2xs ring-gray-
|
|
52
|
-
'pr-10 text-red-900 ring-red-
|
|
51
|
+
'text-gray-900 shadow-2xs ring-gray-900/10 placeholder:text-gray-400': !$input.value?.errors,
|
|
52
|
+
'pr-10 text-red-900 ring-red-900/10 placeholder:text-red-300 focus:ring-red-500': $input.value?.errors,
|
|
53
53
|
},
|
|
54
54
|
inputClass,
|
|
55
55
|
));
|
|
@@ -48,8 +48,8 @@ const renderedInputClasses = computed(() =>
|
|
|
48
48
|
'block w-full rounded-md border-0 py-1.5 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6',
|
|
49
49
|
{
|
|
50
50
|
'focus:ring-primary-600': !$input.value?.errors,
|
|
51
|
-
'text-gray-900 shadow-2xs ring-gray-
|
|
52
|
-
'pr-10 text-red-900 ring-red-
|
|
51
|
+
'text-gray-900 shadow-2xs ring-gray-900/10 placeholder:text-gray-400': !$input.value?.errors,
|
|
52
|
+
'pr-10 text-red-900 ring-red-900/10 placeholder:text-red-300 focus:ring-red-500': $input.value?.errors,
|
|
53
53
|
},
|
|
54
54
|
inputClass,
|
|
55
55
|
));
|
|
@@ -4,6 +4,7 @@ import type { Equals } from '@noeldemartin/utils';
|
|
|
4
4
|
import type { Expect } from '@noeldemartin/testing';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
enumInput,
|
|
7
8
|
numberInput,
|
|
8
9
|
objectInput,
|
|
9
10
|
requiredObjectInput,
|
|
@@ -97,6 +98,7 @@ describe('FormController', () => {
|
|
|
97
98
|
two: requiredStringInput(),
|
|
98
99
|
three: objectInput(),
|
|
99
100
|
four: requiredObjectInput<{ foo: string; bar?: number }>(),
|
|
101
|
+
five: enumInput(['foo', 'bar']),
|
|
100
102
|
});
|
|
101
103
|
|
|
102
104
|
tt<
|
|
@@ -104,6 +106,7 @@ describe('FormController', () => {
|
|
|
104
106
|
| Expect<Equals<typeof form.two, string>>
|
|
105
107
|
| Expect<Equals<typeof form.three, object | null>>
|
|
106
108
|
| Expect<Equals<typeof form.four, { foo: string; bar?: number }>>
|
|
109
|
+
| Expect<Equals<typeof form.five, 'foo' | 'bar' | null>>
|
|
107
110
|
>();
|
|
108
111
|
});
|
|
109
112
|
|
|
@@ -2,31 +2,32 @@ import { computed, nextTick, reactive, readonly, ref } from 'vue';
|
|
|
2
2
|
import { MagicObject, arrayRemove, fail, toString } from '@noeldemartin/utils';
|
|
3
3
|
import type { ComputedRef, DeepReadonly, Ref, UnwrapNestedRefs } from 'vue';
|
|
4
4
|
|
|
5
|
-
import { validate } from './validation';
|
|
5
|
+
import { validate, validateType } from './validation';
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const __valueType: unique symbol = Symbol();
|
|
8
8
|
|
|
9
9
|
export interface FormFieldDefinition<
|
|
10
10
|
TType extends FormFieldType = FormFieldType,
|
|
11
11
|
TRules extends string = string,
|
|
12
|
-
|
|
12
|
+
TValueType = unknown,
|
|
13
13
|
> {
|
|
14
14
|
type: TType;
|
|
15
15
|
trim?: boolean;
|
|
16
16
|
default?: GetFormFieldValue<TType>;
|
|
17
17
|
rules?: TRules;
|
|
18
|
-
[
|
|
18
|
+
values?: TValueType[];
|
|
19
|
+
[__valueType]?: TValueType;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export type FormFieldType = 'string' | 'number' | 'boolean' | 'object' | 'date';
|
|
22
|
+
export type FormFieldType = 'string' | 'enum' | 'number' | 'boolean' | 'object' | 'date';
|
|
22
23
|
export type FormFieldValue = GetFormFieldValue<FormFieldType>;
|
|
23
24
|
export type FormFieldDefinitions = Record<string, FormFieldDefinition>;
|
|
24
25
|
|
|
25
26
|
export type FormData<T> = {
|
|
26
|
-
-readonly [k in keyof T]: T[k] extends FormFieldDefinition<infer TType, infer TRules, infer
|
|
27
|
+
-readonly [k in keyof T]: T[k] extends FormFieldDefinition<infer TType, infer TRules, infer TValueType>
|
|
27
28
|
? TRules extends 'required'
|
|
28
|
-
? GetFormFieldValue<TType,
|
|
29
|
-
: GetFormFieldValue<TType,
|
|
29
|
+
? GetFormFieldValue<TType, TValueType>
|
|
30
|
+
: GetFormFieldValue<TType, TValueType> | null
|
|
30
31
|
: never;
|
|
31
32
|
};
|
|
32
33
|
|
|
@@ -34,19 +35,21 @@ export type FormErrors<T> = {
|
|
|
34
35
|
[k in keyof T]: string[] | null;
|
|
35
36
|
};
|
|
36
37
|
|
|
37
|
-
export type GetFormFieldValue<TType,
|
|
38
|
+
export type GetFormFieldValue<TType, TValueType = unknown> = TType extends 'string'
|
|
38
39
|
? string
|
|
39
40
|
: TType extends 'number'
|
|
40
41
|
? number
|
|
41
42
|
: TType extends 'boolean'
|
|
42
43
|
? boolean
|
|
43
|
-
: TType extends '
|
|
44
|
-
?
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
:
|
|
44
|
+
: TType extends 'enum'
|
|
45
|
+
? TValueType
|
|
46
|
+
: TType extends 'object'
|
|
47
|
+
? TValueType extends object
|
|
48
|
+
? TValueType
|
|
49
|
+
: object
|
|
50
|
+
: TType extends 'date'
|
|
51
|
+
? Date
|
|
52
|
+
: never;
|
|
50
53
|
|
|
51
54
|
const validForms: WeakMap<FormController, ComputedRef<boolean>> = new WeakMap();
|
|
52
55
|
|
|
@@ -196,6 +199,8 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
196
199
|
const value = this._data[name];
|
|
197
200
|
const rules = definition.rules?.split('|') ?? [];
|
|
198
201
|
|
|
202
|
+
errors.push(...validateType(value, definition));
|
|
203
|
+
|
|
199
204
|
for (const rule of rules) {
|
|
200
205
|
if (rule !== 'required' && (value === null || value === undefined)) {
|
|
201
206
|
continue;
|
package/src/forms/utils.ts
CHANGED
|
@@ -16,6 +16,19 @@ export function dateInput(defaultValue?: Date, options: { rules?: string } = {})
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export function enumInput<const T extends string>(
|
|
20
|
+
values: T[],
|
|
21
|
+
defaultValue?: T,
|
|
22
|
+
options: { rules?: string } = {},
|
|
23
|
+
): FormFieldDefinition<'enum', string, T> {
|
|
24
|
+
return {
|
|
25
|
+
default: defaultValue,
|
|
26
|
+
type: 'enum',
|
|
27
|
+
rules: options.rules,
|
|
28
|
+
values,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
19
32
|
export function requiredBooleanInput(defaultValue?: boolean): FormFieldDefinition<'boolean', 'required'> {
|
|
20
33
|
return {
|
|
21
34
|
default: defaultValue,
|
|
@@ -32,6 +45,18 @@ export function requiredDateInput(defaultValue?: Date): FormFieldDefinition<'dat
|
|
|
32
45
|
};
|
|
33
46
|
}
|
|
34
47
|
|
|
48
|
+
export function requiredEnumInput<T extends string>(
|
|
49
|
+
values: T[],
|
|
50
|
+
defaultValue?: T,
|
|
51
|
+
): FormFieldDefinition<'enum', 'required', T> {
|
|
52
|
+
return {
|
|
53
|
+
default: defaultValue,
|
|
54
|
+
type: 'enum',
|
|
55
|
+
rules: 'required',
|
|
56
|
+
values,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
35
60
|
export function requiredNumberInput(defaultValue?: number): FormFieldDefinition<'number', 'required'> {
|
|
36
61
|
return {
|
|
37
62
|
default: defaultValue,
|
package/src/forms/validation.ts
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import { arrayFrom } from '@noeldemartin/utils';
|
|
2
2
|
|
|
3
|
+
import type { FormFieldDefinition } from '@aerogel/core/forms/FormController';
|
|
4
|
+
|
|
3
5
|
const builtInRules: Record<string, FormFieldValidator> = {
|
|
4
6
|
required: (value) => (value ? undefined : 'required'),
|
|
5
7
|
};
|
|
6
8
|
|
|
9
|
+
function isValidType(value: unknown, definition: FormFieldDefinition): boolean {
|
|
10
|
+
if (value === undefined || value === null) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
switch (definition.type) {
|
|
15
|
+
case 'string':
|
|
16
|
+
return typeof value === 'string';
|
|
17
|
+
case 'enum':
|
|
18
|
+
return !!definition.values?.includes(value);
|
|
19
|
+
case 'number':
|
|
20
|
+
return typeof value === 'number';
|
|
21
|
+
case 'boolean':
|
|
22
|
+
return typeof value === 'boolean';
|
|
23
|
+
case 'date':
|
|
24
|
+
return value instanceof Date;
|
|
25
|
+
case 'object':
|
|
26
|
+
return typeof value === 'object';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
7
30
|
export type FormFieldValidator<T = unknown> = (value: T) => string | string[] | undefined;
|
|
8
31
|
|
|
9
32
|
export const validators: Record<string, FormFieldValidator> = { ...builtInRules };
|
|
@@ -12,6 +35,14 @@ export function defineFormValidationRule<T>(rule: string, validator: FormFieldVa
|
|
|
12
35
|
validators[rule] = validator as FormFieldValidator;
|
|
13
36
|
}
|
|
14
37
|
|
|
38
|
+
export function validateType(value: unknown, definition: FormFieldDefinition): string[] {
|
|
39
|
+
if (isValidType(value, definition)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return ['invalid_value'];
|
|
44
|
+
}
|
|
45
|
+
|
|
15
46
|
export function validate(value: unknown, rule: string): string[] {
|
|
16
47
|
const errors = validators[rule]?.(value);
|
|
17
48
|
|