@aerogel/core 0.1.1 → 0.1.2-next.faf07b8323db56fdfa807f3b9ffa8af276fdf288
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.css +1 -1
- package/dist/aerogel-core.d.ts +539 -317
- package/dist/aerogel-core.js +2216 -1824
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +9 -3
- package/src/bootstrap/index.ts +2 -1
- package/src/components/AppLayout.vue +1 -1
- package/src/components/AppOverlays.vue +3 -2
- package/src/components/contracts/AlertModal.ts +1 -1
- package/src/components/contracts/Button.ts +1 -1
- package/src/components/contracts/Combobox.ts +5 -0
- package/src/components/contracts/ConfirmModal.ts +5 -2
- package/src/components/contracts/Modal.ts +8 -3
- package/src/components/contracts/PromptModal.ts +6 -2
- package/src/components/contracts/Select.ts +98 -4
- package/src/components/contracts/Toast.ts +1 -1
- package/src/components/contracts/index.ts +1 -0
- package/src/components/headless/HeadlessInputInput.vue +27 -5
- package/src/components/headless/HeadlessModal.vue +6 -34
- package/src/components/headless/HeadlessModalContent.vue +5 -12
- package/src/components/headless/HeadlessSelect.vue +10 -91
- package/src/components/headless/HeadlessSelectOption.vue +1 -5
- package/src/components/index.ts +1 -1
- package/src/components/ui/AdvancedOptions.vue +4 -13
- package/src/components/ui/Button.vue +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/ConfirmModal.vue +7 -2
- package/src/components/ui/Details.vue +33 -0
- package/src/components/ui/Form.vue +1 -1
- package/src/components/ui/Input.vue +12 -4
- package/src/components/ui/LoadingModal.vue +1 -2
- package/src/components/ui/Modal.vue +59 -34
- package/src/components/ui/ProgressBar.vue +16 -2
- package/src/components/ui/PromptModal.vue +7 -2
- package/src/components/ui/Select.vue +2 -0
- package/src/components/ui/SelectTrigger.vue +13 -2
- package/src/components/ui/SettingsModal.vue +1 -1
- package/src/components/ui/Toast.vue +1 -0
- package/src/components/ui/index.ts +6 -1
- package/src/components/vue/Provide.vue +11 -0
- package/src/components/vue/index.ts +1 -0
- package/src/directives/index.ts +7 -7
- package/src/errors/Errors.ts +4 -0
- package/src/errors/index.ts +5 -0
- package/src/forms/FormController.test.ts +4 -4
- package/src/forms/FormController.ts +23 -13
- 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 +10 -0
- package/src/jobs/Job.ts +1 -1
- package/src/plugins/index.ts +1 -3
- package/src/services/App.state.ts +1 -0
- package/src/services/App.ts +4 -0
- package/src/services/index.ts +7 -0
- package/src/testing/index.ts +1 -2
- package/src/ui/UI.state.ts +0 -11
- package/src/ui/UI.ts +43 -125
- package/src/ui/index.ts +1 -0
- package/src/ui/modals.ts +36 -0
- package/src/utils/classes.ts +2 -3
- package/src/utils/composition/reactiveSet.test.ts +32 -0
- package/src/utils/composition/reactiveSet.ts +61 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/time.ts +7 -0
- package/src/components/AppModals.vue +0 -14
- package/src/components/ui/ModalContext.vue +0 -31
|
@@ -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>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Modal :title="$td('settings.title', 'Settings')">
|
|
2
|
+
<Modal :title="$td('settings.title', 'Settings')" :fullscreen-on-mobile="$app.settingsFullscreenOnMobile">
|
|
3
3
|
<component :is="setting.component" v-for="(setting, key) in settings" :key />
|
|
4
4
|
</Modal>
|
|
5
5
|
</template>
|
|
@@ -2,7 +2,13 @@ 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';
|
|
11
|
+
export { default as Details } from './Details.vue';
|
|
6
12
|
export { default as DropdownMenu } from './DropdownMenu.vue';
|
|
7
13
|
export { default as DropdownMenuOption } from './DropdownMenuOption.vue';
|
|
8
14
|
export { default as DropdownMenuOptions } from './DropdownMenuOptions.vue';
|
|
@@ -19,7 +25,6 @@ export { default as Link } from './Link.vue';
|
|
|
19
25
|
export { default as LoadingModal } from './LoadingModal.vue';
|
|
20
26
|
export { default as Markdown } from './Markdown.vue';
|
|
21
27
|
export { default as Modal } from './Modal.vue';
|
|
22
|
-
export { default as ModalContext } from './ModalContext.vue';
|
|
23
28
|
export { default as ProgressBar } from './ProgressBar.vue';
|
|
24
29
|
export { default as PromptModal } from './PromptModal.vue';
|
|
25
30
|
export { default as Select } from './Select.vue';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Provide } from './Provide.vue';
|
package/src/directives/index.ts
CHANGED
|
@@ -5,22 +5,24 @@ import { definePlugin } from '@aerogel/core/plugins';
|
|
|
5
5
|
import measure from './measure';
|
|
6
6
|
import safeHtml from './safe-html';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
export const aerogelDirectives = {
|
|
9
9
|
'measure': measure,
|
|
10
10
|
'safe-html': safeHtml,
|
|
11
|
-
}
|
|
11
|
+
} as const satisfies Record<string, Directive>;
|
|
12
|
+
|
|
13
|
+
export type AerogelDirectives = typeof aerogelDirectives;
|
|
12
14
|
|
|
13
15
|
export * from './measure';
|
|
14
16
|
|
|
15
17
|
export default definePlugin({
|
|
16
18
|
install(app, options) {
|
|
17
19
|
const directives = {
|
|
18
|
-
...
|
|
20
|
+
...aerogelDirectives,
|
|
19
21
|
...options.directives,
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
for (const [name, directive] of Object.entries(directives)) {
|
|
23
|
-
app.directive(name, directive);
|
|
25
|
+
app.directive(name, directive as Directive);
|
|
24
26
|
}
|
|
25
27
|
},
|
|
26
28
|
});
|
|
@@ -32,7 +34,5 @@ declare module '@aerogel/core/bootstrap/options' {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
declare module 'vue' {
|
|
35
|
-
interface ComponentCustomDirectives {
|
|
36
|
-
measure: Directive<string, string>;
|
|
37
|
-
}
|
|
37
|
+
interface ComponentCustomDirectives extends AerogelDirectives {}
|
|
38
38
|
}
|
package/src/errors/Errors.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { JSError, facade, isDevelopment, isObject, isTesting, objectWithoutEmpty, toString } from '@noeldemartin/utils';
|
|
2
2
|
import { watchEffect } from 'vue';
|
|
3
3
|
import type Eruda from 'eruda';
|
|
4
|
+
import type ErudaIndexedDB from 'eruda-indexeddb';
|
|
4
5
|
|
|
5
6
|
import App from '@aerogel/core/services/App';
|
|
6
7
|
import ServiceBootError from '@aerogel/core/errors/ServiceBootError';
|
|
@@ -16,6 +17,7 @@ export class ErrorsService extends Service {
|
|
|
16
17
|
public forceReporting: boolean = false;
|
|
17
18
|
private enabled: boolean = true;
|
|
18
19
|
private eruda: typeof Eruda | null = null;
|
|
20
|
+
private erudaPlugins: [typeof ErudaIndexedDB] | null = null;
|
|
19
21
|
|
|
20
22
|
public enable(): void {
|
|
21
23
|
this.enabled = true;
|
|
@@ -131,8 +133,10 @@ export class ErrorsService extends Service {
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
this.eruda ??= (await import('eruda')).default;
|
|
136
|
+
this.erudaPlugins ??= [(await import('eruda-indexeddb')).default];
|
|
134
137
|
|
|
135
138
|
this.eruda.init();
|
|
139
|
+
this.erudaPlugins.forEach((plugin) => this.eruda?.add(plugin));
|
|
136
140
|
});
|
|
137
141
|
}
|
|
138
142
|
|
package/src/errors/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { App as AppInstance } from 'vue';
|
|
|
3
3
|
import App from '@aerogel/core/services/App';
|
|
4
4
|
import { bootServices } from '@aerogel/core/services';
|
|
5
5
|
import { definePlugin } from '@aerogel/core/plugins';
|
|
6
|
+
import { getErrorMessage } from '@aerogel/core/errors/utils';
|
|
6
7
|
|
|
7
8
|
import Errors from './Errors';
|
|
8
9
|
import settings from './settings';
|
|
@@ -16,6 +17,10 @@ export type { ErrorSource, ErrorReport, ErrorReportLog };
|
|
|
16
17
|
|
|
17
18
|
const services = { $errors: Errors };
|
|
18
19
|
const frameworkHandler: ErrorHandler = (error) => {
|
|
20
|
+
if (getErrorMessage(error).includes('ResizeObserver loop completed with undelivered notifications.')) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
Errors.report(error);
|
|
20
25
|
|
|
21
26
|
return true;
|
|
@@ -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
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed, nextTick, reactive, readonly, ref } from 'vue';
|
|
2
|
-
import { MagicObject, arrayRemove, fail
|
|
2
|
+
import { MagicObject, arrayRemove, fail } from '@noeldemartin/utils';
|
|
3
3
|
import type { ComputedRef, DeepReadonly, Ref, UnwrapNestedRefs } from 'vue';
|
|
4
4
|
|
|
5
5
|
import { validate, validateType } from './validation';
|
|
@@ -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
|
}
|
|
@@ -91,17 +91,13 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
public setFieldValue<T extends keyof Fields>(field: T, value: FormData<Fields>[T]): void {
|
|
94
|
-
|
|
95
|
-
this._fields[field] ?? fail<FormFieldDefinition>(`Trying to set undefined '${toString(field)}' field`);
|
|
94
|
+
this._data[field] = value;
|
|
96
95
|
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
? (toString(value).trim() as FormData<Fields>[T])
|
|
100
|
-
: value;
|
|
101
|
-
|
|
102
|
-
if (this._submitted.value) {
|
|
103
|
-
this.validate();
|
|
96
|
+
if (!this._submitted.value) {
|
|
97
|
+
return;
|
|
104
98
|
}
|
|
99
|
+
|
|
100
|
+
this.validate();
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
public getFieldValue<T extends keyof Fields>(field: T): GetFormFieldValue<Fields[T]['type']> {
|
|
@@ -109,7 +105,11 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
109
105
|
}
|
|
110
106
|
|
|
111
107
|
public getFieldRules<T extends keyof Fields>(field: T): string[] {
|
|
112
|
-
return this._fields[field]?.rules
|
|
108
|
+
return this._fields[field]?.rules ?? [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public setFieldErrors<T extends keyof Fields>(field: T, errors: string[] | null): void {
|
|
112
|
+
this._errors[field] = errors;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
public getFieldType<T extends keyof Fields>(field: T): FormFieldType | null {
|
|
@@ -145,6 +145,16 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
145
145
|
public submit(): boolean {
|
|
146
146
|
this._submitted.value = true;
|
|
147
147
|
|
|
148
|
+
for (const [field, value] of Object.entries(this._data)) {
|
|
149
|
+
const definition = this._fields[field] ?? fail<FormFieldDefinition>();
|
|
150
|
+
|
|
151
|
+
if (typeof value !== 'string' || !(definition.trim ?? true)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this._data[field as keyof Fields] = value.trim() as FormData<Fields>[string];
|
|
156
|
+
}
|
|
157
|
+
|
|
148
158
|
const valid = this.validate();
|
|
149
159
|
|
|
150
160
|
valid && this._listeners['submit']?.forEach((listener) => listener());
|
|
@@ -197,7 +207,7 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
197
207
|
private getFieldErrors(name: keyof Fields, definition: FormFieldDefinition): string[] | null {
|
|
198
208
|
const errors = [];
|
|
199
209
|
const value = this._data[name];
|
|
200
|
-
const rules = definition.rules
|
|
210
|
+
const rules = definition.rules ?? [];
|
|
201
211
|
|
|
202
212
|
errors.push(...validateType(value, definition));
|
|
203
213
|
|
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
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
--color-primary-950: color-mix(in oklab, var(--color-primary-600) 50%, black);
|
|
21
21
|
|
|
22
22
|
--color-background: oklch(1 0 0);
|
|
23
|
+
--color-primary-text: var(--color-gray-900);
|
|
23
24
|
--color-links: var(--color-primary);
|
|
24
25
|
|
|
25
26
|
--breakpoint-content: var(--breakpoint-md);
|
|
@@ -66,6 +67,15 @@ button[data-markdown-action] {
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
@keyframes slide-in {
|
|
71
|
+
0% {
|
|
72
|
+
transform: translateY(100%);
|
|
73
|
+
}
|
|
74
|
+
100% {
|
|
75
|
+
transform: translateY(0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
@keyframes grow {
|
|
70
80
|
0% {
|
|
71
81
|
scale: 0;
|
package/src/jobs/Job.ts
CHANGED
package/src/plugins/index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { GetClosureArgs } from '@noeldemartin/utils';
|
|
2
|
-
|
|
3
1
|
import App from '@aerogel/core/services/App';
|
|
4
2
|
|
|
5
3
|
import type { Plugin } from './Plugin';
|
|
@@ -10,7 +8,7 @@ export function definePlugin<T extends Plugin>(plugin: T): T {
|
|
|
10
8
|
return plugin;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
export async function installPlugins(plugins: Plugin[], ...args:
|
|
11
|
+
export async function installPlugins(plugins: Plugin[], ...args: Parameters<Plugin['install']>): Promise<void> {
|
|
14
12
|
App.setState(
|
|
15
13
|
'plugins',
|
|
16
14
|
plugins.reduce(
|
package/src/services/App.ts
CHANGED
|
@@ -30,6 +30,10 @@ export class AppService extends Service {
|
|
|
30
30
|
this.settings.push(markRaw(setting));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
public setSettingsFullscreenOnMobile(fullscreenOnMobile: boolean): void {
|
|
34
|
+
this.settingsFullscreenOnMobile = fullscreenOnMobile;
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
public async whenReady<T>(callback: () => T): Promise<T> {
|
|
34
38
|
const result = await this.ready.then(callback);
|
|
35
39
|
|
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';
|
|
@@ -56,6 +57,10 @@ export default definePlugin({
|
|
|
56
57
|
app.use(getPiniaStore());
|
|
57
58
|
options.settings?.forEach((setting) => App.addSetting(setting));
|
|
58
59
|
|
|
60
|
+
if (options.settingsFullscreenOnMobile !== undefined) {
|
|
61
|
+
App.setSettingsFullscreenOnMobile(options.settingsFullscreenOnMobile);
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
await bootServices(app, services);
|
|
60
65
|
},
|
|
61
66
|
});
|
|
@@ -64,6 +69,8 @@ declare module '@aerogel/core/bootstrap/options' {
|
|
|
64
69
|
export interface AerogelOptions {
|
|
65
70
|
services?: Record<string, Service>;
|
|
66
71
|
settings?: AppSetting[];
|
|
72
|
+
formValidationRules?: Record<string, FormFieldValidator>;
|
|
73
|
+
settingsFullscreenOnMobile?: boolean;
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
|
package/src/testing/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isTesting } from '@noeldemartin/utils';
|
|
2
|
-
import type { GetClosureArgs } from '@noeldemartin/utils';
|
|
3
2
|
|
|
4
3
|
import Events from '@aerogel/core/services/Events';
|
|
5
4
|
import { App } from '@aerogel/core/services';
|
|
@@ -18,7 +17,7 @@ export default definePlugin({
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
globalThis.testingRuntime = {
|
|
21
|
-
on: ((...args:
|
|
20
|
+
on: ((...args: Parameters<(typeof Events)['on']>) => Events.on(...args)) as (typeof Events)['on'],
|
|
22
21
|
service: (name) => App.service(name),
|
|
23
22
|
};
|
|
24
23
|
},
|
package/src/ui/UI.state.ts
CHANGED
|
@@ -4,15 +4,6 @@ import { defineServiceState } from '@aerogel/core/services/Service';
|
|
|
4
4
|
|
|
5
5
|
import { Layouts, getCurrentLayout } from './utils';
|
|
6
6
|
|
|
7
|
-
export interface UIModal<T = unknown> {
|
|
8
|
-
id: string;
|
|
9
|
-
properties: Record<string, unknown>;
|
|
10
|
-
component: Component;
|
|
11
|
-
closing: boolean;
|
|
12
|
-
beforeClose: Promise<T | undefined>;
|
|
13
|
-
afterClose: Promise<T | undefined>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
7
|
export interface UIToast {
|
|
17
8
|
id: string;
|
|
18
9
|
component: Component;
|
|
@@ -22,13 +13,11 @@ export interface UIToast {
|
|
|
22
13
|
export default defineServiceState({
|
|
23
14
|
name: 'ui',
|
|
24
15
|
initialState: {
|
|
25
|
-
modals: [] as UIModal[],
|
|
26
16
|
toasts: [] as UIToast[],
|
|
27
17
|
layout: getCurrentLayout(),
|
|
28
18
|
},
|
|
29
19
|
computed: {
|
|
30
20
|
desktop: ({ layout }) => layout === Layouts.Desktop,
|
|
31
21
|
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
32
|
-
openModals: ({ modals }) => modals.filter(({ closing }) => !closing),
|
|
33
22
|
},
|
|
34
23
|
});
|