@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerogel/core",
3
- "version": "0.0.0-next.c3236837f7f8fc319a4a56022accb32757b3db89",
3
+ "version": "0.0.0-next.c38a10cd5e81c5f04469ef79e59a53e6bfd3df55",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -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-300 placeholder:text-gray-400': !$input.value?.errors,
52
- 'pr-10 text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500': $input.value?.errors,
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-300 placeholder:text-gray-400': !$input.value?.errors,
52
- 'pr-10 text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500': $input.value?.errors,
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 __objectType: unique symbol = Symbol();
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
- TObjectType = object,
12
+ TValueType = unknown,
13
13
  > {
14
14
  type: TType;
15
15
  trim?: boolean;
16
16
  default?: GetFormFieldValue<TType>;
17
17
  rules?: TRules;
18
- [__objectType]?: TObjectType;
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 TObjectType>
27
+ -readonly [k in keyof T]: T[k] extends FormFieldDefinition<infer TType, infer TRules, infer TValueType>
27
28
  ? TRules extends 'required'
28
- ? GetFormFieldValue<TType, TObjectType>
29
- : GetFormFieldValue<TType, TObjectType> | null
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, TObjectType = object> = TType extends 'string'
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 'object'
44
- ? TObjectType extends object
45
- ? TObjectType
46
- : object
47
- : TType extends 'date'
48
- ? Date
49
- : never;
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;
@@ -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,
@@ -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
 
package/src/index.css CHANGED
@@ -23,6 +23,7 @@
23
23
  --color-links: var(--color-primary);
24
24
 
25
25
  --breakpoint-content: var(--breakpoint-md);
26
+ --spacing-edge: 1rem;
26
27
  }
27
28
 
28
29
  .clickable {