@aerogel/core 0.1.1-next.bad775a386a94fedbee1d575ee1f4fb99d1f5bab → 0.1.1-next.ce10b6d6ba4cd98145cf14595fb4f292b979e2dc
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 +363 -117
- package/dist/aerogel-core.js +1606 -1259
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +1 -1
- package/src/bootstrap/index.ts +2 -1
- package/src/components/AppLayout.vue +1 -1
- package/src/components/contracts/Combobox.ts +5 -0
- package/src/components/contracts/Select.ts +98 -4
- package/src/components/contracts/index.ts +1 -0
- package/src/components/headless/HeadlessInputInput.vue +6 -3
- package/src/components/headless/HeadlessSelect.vue +10 -91
- package/src/components/headless/HeadlessSelectOption.vue +1 -5
- package/src/components/index.ts +1 -0
- package/src/components/ui/Combobox.vue +94 -0
- package/src/components/ui/ComboboxLabel.vue +29 -0
- package/src/components/ui/ComboboxOption.vue +46 -0
- package/src/components/ui/ComboboxOptions.vue +71 -0
- package/src/components/ui/ComboboxTrigger.vue +67 -0
- package/src/components/ui/Details.vue +16 -6
- package/src/components/ui/ProgressBar.vue +1 -1
- package/src/components/ui/Select.vue +2 -0
- package/src/components/ui/SelectTrigger.vue +13 -2
- package/src/components/ui/index.ts +5 -0
- package/src/components/vue/Provide.vue +11 -0
- package/src/components/vue/index.ts +1 -0
- package/src/forms/FormController.test.ts +4 -4
- package/src/forms/FormController.ts +3 -3
- package/src/forms/index.ts +11 -0
- package/src/forms/utils.ts +36 -17
- package/src/forms/validation.ts +5 -1
- package/src/index.css +1 -0
- package/src/jobs/Job.ts +1 -1
- package/src/services/index.ts +2 -0
- package/src/utils/time.ts +6 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<details class="group">
|
|
3
|
-
<summary
|
|
4
|
-
class="-ml-2 flex w-[max-content] items-center rounded-lg py-2 pr-3 pl-1 hover:bg-gray-100 focus-visible:outline focus-visible:outline-gray-700"
|
|
5
|
-
>
|
|
3
|
+
<summary :class="renderedSummaryClasses">
|
|
6
4
|
<IconCheveronRight class="size-6 transition-transform group-open:rotate-90" />
|
|
7
|
-
<
|
|
5
|
+
<slot name="label">
|
|
6
|
+
<span>{{ label ?? $td('ui.details', 'Details') }}</span>
|
|
7
|
+
</slot>
|
|
8
8
|
</summary>
|
|
9
9
|
|
|
10
10
|
<div :class="renderedContentClasses">
|
|
@@ -14,10 +14,20 @@
|
|
|
14
14
|
</template>
|
|
15
15
|
|
|
16
16
|
<script setup lang="ts">
|
|
17
|
+
import IconCheveronRight from '~icons/zondicons/cheveron-right';
|
|
17
18
|
import { classes } from '@aerogel/core/utils';
|
|
18
19
|
import { type HTMLAttributes, computed } from 'vue';
|
|
19
|
-
import IconCheveronRight from '~icons/zondicons/cheveron-right';
|
|
20
20
|
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
label = undefined,
|
|
23
|
+
contentClass,
|
|
24
|
+
summaryClass,
|
|
25
|
+
} = defineProps<{ label?: string; contentClass?: HTMLAttributes['class']; summaryClass?: HTMLAttributes['class'] }>();
|
|
22
26
|
const renderedContentClasses = computed(() => classes('pt-2 pl-4', contentClass));
|
|
27
|
+
const renderedSummaryClasses = computed(() =>
|
|
28
|
+
classes(
|
|
29
|
+
'-ml-2 flex w-[max-content] items-center rounded-lg py-2 pr-3 pl-1 max-w-full',
|
|
30
|
+
'hover:bg-gray-100 focus-visible:outline focus-visible:outline-gray-700',
|
|
31
|
+
summaryClass,
|
|
32
|
+
));
|
|
23
33
|
</script>
|
|
@@ -36,7 +36,7 @@ const filledClasses = computed(() =>
|
|
|
36
36
|
classes('size-full transition-transform duration-500 rounded-r-full ease-linear bg-primary-600', filledClass));
|
|
37
37
|
const overflowClasses = computed(() =>
|
|
38
38
|
classes(
|
|
39
|
-
'absolute inset-y-0 right-0 size-full rounded-r-full
|
|
39
|
+
'absolute inset-y-0 right-0 size-full rounded-r-full',
|
|
40
40
|
'bg-primary-900 transition-[width] duration-500 ease-linear',
|
|
41
41
|
overflowClass,
|
|
42
42
|
));
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<SelectLabel />
|
|
4
4
|
<slot>
|
|
5
5
|
<SelectTrigger />
|
|
6
|
+
<HeadlessSelectError class="mt-2 text-sm text-red-600" />
|
|
6
7
|
<SelectOptions />
|
|
7
8
|
</slot>
|
|
8
9
|
</HeadlessSelect>
|
|
@@ -19,6 +20,7 @@ import type { FormFieldValue } from '@aerogel/core/forms';
|
|
|
19
20
|
import SelectLabel from './SelectLabel.vue';
|
|
20
21
|
import SelectOptions from './SelectOptions.vue';
|
|
21
22
|
import SelectTrigger from './SelectTrigger.vue';
|
|
23
|
+
import HeadlessSelectError from '../headless/HeadlessSelectError.vue';
|
|
22
24
|
|
|
23
25
|
defineProps<SelectProps<T>>();
|
|
24
26
|
defineEmits<SelectEmits<T>>();
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
<HeadlessSelectTrigger :class="renderedClasses">
|
|
3
3
|
<HeadlessSelectValue class="col-start-1 row-start-1 truncate pr-6" />
|
|
4
4
|
<IconCheveronDown class="col-start-1 row-start-1 size-5 self-center justify-self-end text-gray-500 sm:size-4" />
|
|
5
|
+
<div v-if="select?.errors" class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
|
|
6
|
+
<IconExclamationSolid class="size-5 text-red-500" />
|
|
7
|
+
</div>
|
|
5
8
|
</HeadlessSelectTrigger>
|
|
6
9
|
</template>
|
|
7
10
|
|
|
8
11
|
<script setup lang="ts">
|
|
9
12
|
import IconCheveronDown from '~icons/zondicons/cheveron-down';
|
|
13
|
+
import IconExclamationSolid from '~icons/zondicons/exclamation-solid';
|
|
10
14
|
|
|
11
15
|
import { computed } from 'vue';
|
|
12
16
|
import type { HTMLAttributes } from 'vue';
|
|
@@ -22,8 +26,15 @@ const select = injectReactiveOrFail<SelectExpose>('select', '<SelectTrigger> mus
|
|
|
22
26
|
const renderedClasses = computed(() =>
|
|
23
27
|
classes(
|
|
24
28
|
// eslint-disable-next-line vue/max-len
|
|
25
|
-
'
|
|
26
|
-
|
|
29
|
+
'relative grid w-full cursor-default grid-cols-1 rounded-md bg-white py-1.5 pr-2 pl-3 text-left text-gray-900 outline-1 -outline-offset-1',
|
|
30
|
+
'focus:outline-2 focus:-outline-offset-2 sm:text-sm/6',
|
|
31
|
+
{
|
|
32
|
+
'mt-1': select.label,
|
|
33
|
+
'outline-gray-300 focus:outline-primary-600 data-[state=open]:outline-primary-600': !select.errors,
|
|
34
|
+
'text-gray-900 shadow-2xs ring-gray-900/10 placeholder:text-gray-400': !select.errors,
|
|
35
|
+
'outline-red-900/10 pr-10 text-red-900 placeholder:text-red-300': select.errors,
|
|
36
|
+
'focus:outline-red-500 data-[state=open]:outline-red-500': select.errors,
|
|
37
|
+
},
|
|
27
38
|
rootClasses,
|
|
28
39
|
));
|
|
29
40
|
</script>
|
|
@@ -2,6 +2,11 @@ export { default as AdvancedOptions } from './AdvancedOptions.vue';
|
|
|
2
2
|
export { default as AlertModal } from './AlertModal.vue';
|
|
3
3
|
export { default as Button } from './Button.vue';
|
|
4
4
|
export { default as Checkbox } from './Checkbox.vue';
|
|
5
|
+
export { default as Combobox } from './Combobox.vue';
|
|
6
|
+
export { default as ComboboxLabel } from './ComboboxLabel.vue';
|
|
7
|
+
export { default as ComboboxOption } from './ComboboxOption.vue';
|
|
8
|
+
export { default as ComboboxOptions } from './ComboboxOptions.vue';
|
|
9
|
+
export { default as ComboboxTrigger } from './ComboboxTrigger.vue';
|
|
5
10
|
export { default as ConfirmModal } from './ConfirmModal.vue';
|
|
6
11
|
export { default as Details } from './Details.vue';
|
|
7
12
|
export { default as DropdownMenu } from './DropdownMenu.vue';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Provide } from './Provide.vue';
|
|
@@ -30,7 +30,7 @@ describe('FormController', () => {
|
|
|
30
30
|
const form = useForm({
|
|
31
31
|
name: {
|
|
32
32
|
type: 'string',
|
|
33
|
-
rules: 'required',
|
|
33
|
+
rules: ['required'],
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -48,7 +48,7 @@ describe('FormController', () => {
|
|
|
48
48
|
const form = useForm({
|
|
49
49
|
name: {
|
|
50
50
|
type: 'string',
|
|
51
|
-
rules: 'required',
|
|
51
|
+
rules: ['required'],
|
|
52
52
|
},
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -69,11 +69,11 @@ describe('FormController', () => {
|
|
|
69
69
|
const form = useForm({
|
|
70
70
|
trimmed: {
|
|
71
71
|
type: 'string',
|
|
72
|
-
rules: 'required',
|
|
72
|
+
rules: ['required'],
|
|
73
73
|
},
|
|
74
74
|
untrimmed: {
|
|
75
75
|
type: 'string',
|
|
76
|
-
rules: 'required',
|
|
76
|
+
rules: ['required'],
|
|
77
77
|
trim: false,
|
|
78
78
|
},
|
|
79
79
|
});
|
|
@@ -14,7 +14,7 @@ export interface FormFieldDefinition<
|
|
|
14
14
|
type: TType;
|
|
15
15
|
trim?: boolean;
|
|
16
16
|
default?: GetFormFieldValue<TType>;
|
|
17
|
-
rules?: TRules;
|
|
17
|
+
rules?: TRules[];
|
|
18
18
|
values?: readonly TValueType[];
|
|
19
19
|
[__valueType]?: TValueType;
|
|
20
20
|
}
|
|
@@ -109,7 +109,7 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
public getFieldRules<T extends keyof Fields>(field: T): string[] {
|
|
112
|
-
return this._fields[field]?.rules
|
|
112
|
+
return this._fields[field]?.rules ?? [];
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
public setFieldErrors<T extends keyof Fields>(field: T, errors: string[] | null): void {
|
|
@@ -201,7 +201,7 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
201
201
|
private getFieldErrors(name: keyof Fields, definition: FormFieldDefinition): string[] | null {
|
|
202
202
|
const errors = [];
|
|
203
203
|
const value = this._data[name];
|
|
204
|
-
const rules = definition.rules
|
|
204
|
+
const rules = definition.rules ?? [];
|
|
205
205
|
|
|
206
206
|
errors.push(...validateType(value, definition));
|
|
207
207
|
|
package/src/forms/index.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import { registerFormValidationRule } from '@aerogel/core/forms/validation';
|
|
2
|
+
import { definePlugin } from '@aerogel/core/plugins';
|
|
3
|
+
|
|
1
4
|
export * from './FormController';
|
|
2
5
|
export * from './utils';
|
|
3
6
|
export * from './validation';
|
|
4
7
|
export { default as FormController } from './FormController';
|
|
8
|
+
|
|
9
|
+
export default definePlugin({
|
|
10
|
+
async install(_, { formValidationRules }) {
|
|
11
|
+
for (const [rule, validator] of Object.entries(formValidationRules ?? {})) {
|
|
12
|
+
registerFormValidationRule(rule, validator);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
});
|
package/src/forms/utils.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { FormFieldDefinition } from './FormController';
|
|
2
2
|
|
|
3
|
-
export function booleanInput(
|
|
3
|
+
export function booleanInput(
|
|
4
|
+
defaultValue?: boolean,
|
|
5
|
+
options: { rules?: string[] } = {},
|
|
6
|
+
): FormFieldDefinition<'boolean'> {
|
|
4
7
|
return {
|
|
5
8
|
default: defaultValue,
|
|
6
9
|
type: 'boolean',
|
|
@@ -8,7 +11,7 @@ export function booleanInput(defaultValue?: boolean, options: { rules?: string }
|
|
|
8
11
|
};
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
export function dateInput(defaultValue?: Date, options: { rules?: string } = {}): FormFieldDefinition<'date'> {
|
|
14
|
+
export function dateInput(defaultValue?: Date, options: { rules?: string[] } = {}): FormFieldDefinition<'date'> {
|
|
12
15
|
return {
|
|
13
16
|
default: defaultValue,
|
|
14
17
|
type: 'date',
|
|
@@ -19,7 +22,7 @@ export function dateInput(defaultValue?: Date, options: { rules?: string } = {})
|
|
|
19
22
|
export function enumInput<const T extends string>(
|
|
20
23
|
values: readonly T[],
|
|
21
24
|
defaultValue?: T,
|
|
22
|
-
options: { rules?: string } = {},
|
|
25
|
+
options: { rules?: string[] } = {},
|
|
23
26
|
): FormFieldDefinition<'enum', string, T> {
|
|
24
27
|
return {
|
|
25
28
|
default: defaultValue,
|
|
@@ -29,59 +32,75 @@ export function enumInput<const T extends string>(
|
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
export function requiredBooleanInput(
|
|
35
|
+
export function requiredBooleanInput(
|
|
36
|
+
defaultValue?: boolean,
|
|
37
|
+
options: { rules?: string[] } = {},
|
|
38
|
+
): FormFieldDefinition<'boolean', 'required'> {
|
|
33
39
|
return {
|
|
34
40
|
default: defaultValue,
|
|
35
41
|
type: 'boolean',
|
|
36
|
-
rules: 'required',
|
|
42
|
+
rules: ['required', ...((options.rules as 'required'[]) ?? [])],
|
|
37
43
|
};
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
export function requiredDateInput(
|
|
46
|
+
export function requiredDateInput(
|
|
47
|
+
defaultValue?: Date,
|
|
48
|
+
options: { rules?: string[] } = {},
|
|
49
|
+
): FormFieldDefinition<'date', 'required'> {
|
|
41
50
|
return {
|
|
42
51
|
default: defaultValue,
|
|
43
52
|
type: 'date',
|
|
44
|
-
rules: 'required',
|
|
53
|
+
rules: ['required', ...((options.rules as 'required'[]) ?? [])],
|
|
45
54
|
};
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
export function requiredEnumInput<const T extends string>(
|
|
49
58
|
values: readonly T[],
|
|
50
59
|
defaultValue?: T,
|
|
60
|
+
options: { rules?: string[] } = {},
|
|
51
61
|
): FormFieldDefinition<'enum', 'required', T> {
|
|
52
62
|
return {
|
|
53
63
|
default: defaultValue,
|
|
54
64
|
type: 'enum',
|
|
55
|
-
rules: 'required',
|
|
65
|
+
rules: ['required', ...((options.rules as 'required'[]) ?? [])],
|
|
56
66
|
values,
|
|
57
67
|
};
|
|
58
68
|
}
|
|
59
69
|
|
|
60
|
-
export function requiredNumberInput(
|
|
70
|
+
export function requiredNumberInput(
|
|
71
|
+
defaultValue?: number,
|
|
72
|
+
options: { rules?: string[] } = {},
|
|
73
|
+
): FormFieldDefinition<'number', 'required'> {
|
|
61
74
|
return {
|
|
62
75
|
default: defaultValue,
|
|
63
76
|
type: 'number',
|
|
64
|
-
rules: 'required',
|
|
77
|
+
rules: ['required', ...((options.rules as 'required'[]) ?? [])],
|
|
65
78
|
};
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
export function requiredObjectInput<T extends object>(
|
|
81
|
+
export function requiredObjectInput<T extends object>(
|
|
82
|
+
defaultValue?: T,
|
|
83
|
+
options: { rules?: string[] } = {},
|
|
84
|
+
): FormFieldDefinition<'object', 'required', T> {
|
|
69
85
|
return {
|
|
70
86
|
default: defaultValue,
|
|
71
87
|
type: 'object',
|
|
72
|
-
rules: 'required',
|
|
88
|
+
rules: ['required', ...((options.rules as 'required'[]) ?? [])],
|
|
73
89
|
};
|
|
74
90
|
}
|
|
75
91
|
|
|
76
|
-
export function requiredStringInput(
|
|
92
|
+
export function requiredStringInput(
|
|
93
|
+
defaultValue?: string,
|
|
94
|
+
options: { rules?: string[] } = {},
|
|
95
|
+
): FormFieldDefinition<'string', 'required'> {
|
|
77
96
|
return {
|
|
78
97
|
default: defaultValue,
|
|
79
98
|
type: 'string',
|
|
80
|
-
rules: 'required',
|
|
99
|
+
rules: ['required', ...((options.rules as 'required'[]) ?? [])],
|
|
81
100
|
};
|
|
82
101
|
}
|
|
83
102
|
|
|
84
|
-
export function numberInput(defaultValue?: number, options: { rules?: string } = {}): FormFieldDefinition<'number'> {
|
|
103
|
+
export function numberInput(defaultValue?: number, options: { rules?: string[] } = {}): FormFieldDefinition<'number'> {
|
|
85
104
|
return {
|
|
86
105
|
default: defaultValue,
|
|
87
106
|
type: 'number',
|
|
@@ -91,7 +110,7 @@ export function numberInput(defaultValue?: number, options: { rules?: string } =
|
|
|
91
110
|
|
|
92
111
|
export function objectInput<T extends object>(
|
|
93
112
|
defaultValue?: T,
|
|
94
|
-
options: { rules?: string } = {},
|
|
113
|
+
options: { rules?: string[] } = {},
|
|
95
114
|
): FormFieldDefinition<'object', string, T> {
|
|
96
115
|
return {
|
|
97
116
|
default: defaultValue,
|
|
@@ -100,7 +119,7 @@ export function objectInput<T extends object>(
|
|
|
100
119
|
};
|
|
101
120
|
}
|
|
102
121
|
|
|
103
|
-
export function stringInput(defaultValue?: string, options: { rules?: string } = {}): FormFieldDefinition<'string'> {
|
|
122
|
+
export function stringInput(defaultValue?: string, options: { rules?: string[] } = {}): FormFieldDefinition<'string'> {
|
|
104
123
|
return {
|
|
105
124
|
default: defaultValue,
|
|
106
125
|
type: 'string',
|
package/src/forms/validation.ts
CHANGED
|
@@ -31,10 +31,14 @@ export type FormFieldValidator<T = unknown> = (value: T) => string | string[] |
|
|
|
31
31
|
|
|
32
32
|
export const validators: Record<string, FormFieldValidator> = { ...builtInRules };
|
|
33
33
|
|
|
34
|
-
export function
|
|
34
|
+
export function registerFormValidationRule<T>(rule: string, validator: FormFieldValidator<T>): void {
|
|
35
35
|
validators[rule] = validator as FormFieldValidator;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export function defineFormValidationRules<T extends Record<string, FormFieldValidator>>(rules: T): T {
|
|
39
|
+
return rules;
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
export function validateType(value: unknown, definition: FormFieldDefinition): string[] {
|
|
39
43
|
if (isValidType(value, definition)) {
|
|
40
44
|
return [];
|
package/src/index.css
CHANGED
package/src/jobs/Job.ts
CHANGED
package/src/services/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import Service from './Service';
|
|
|
10
10
|
import Storage from './Storage';
|
|
11
11
|
import { getPiniaStore } from './store';
|
|
12
12
|
import type { AppSetting } from './App.state';
|
|
13
|
+
import type { FormFieldValidator } from '@aerogel/core/forms';
|
|
13
14
|
|
|
14
15
|
export * from './App';
|
|
15
16
|
export * from './Cache';
|
|
@@ -68,6 +69,7 @@ declare module '@aerogel/core/bootstrap/options' {
|
|
|
68
69
|
export interface AerogelOptions {
|
|
69
70
|
services?: Record<string, Service>;
|
|
70
71
|
settings?: AppSetting[];
|
|
72
|
+
formValidationRules?: Record<string, FormFieldValidator>;
|
|
71
73
|
settingsFullscreenOnMobile?: boolean;
|
|
72
74
|
}
|
|
73
75
|
}
|
package/src/utils/time.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
import type { Nullable } from '@noeldemartin/utils';
|
|
2
|
+
|
|
1
3
|
export const MINUTE_MILLISECONDS = 60000;
|
|
2
|
-
|
|
4
|
+
|
|
5
|
+
export function getLocalTimezoneOffset(date?: Nullable<Date>): number {
|
|
6
|
+
return -(date ?? new Date()).getTimezoneOffset() * -MINUTE_MILLISECONDS;
|
|
7
|
+
}
|