@aerogel/core 0.0.0-next.e4c0d5bd2801fbe93545477ea515af53df69b522 → 0.0.0-next.eb6fcafb87cdccbc72933f616799ca3124d1eca8
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 +257 -83
- package/dist/aerogel-core.js +1607 -1230
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +2 -1
- package/src/components/contracts/AlertModal.ts +1 -1
- package/src/components/contracts/DropdownMenu.ts +2 -2
- package/src/components/contracts/Select.ts +2 -2
- package/src/components/headless/HeadlessInputInput.vue +16 -5
- package/src/components/headless/HeadlessSwitch.vue +96 -0
- package/src/components/headless/index.ts +1 -0
- package/src/components/ui/Button.vue +15 -0
- package/src/components/ui/Input.vue +2 -2
- package/src/components/ui/Modal.vue +12 -4
- package/src/components/ui/SelectLabel.vue +5 -1
- package/src/components/ui/Setting.vue +31 -0
- package/src/components/ui/StartupCrash.vue +51 -6
- package/src/components/ui/Switch.vue +11 -0
- package/src/components/ui/TextArea.vue +56 -0
- package/src/components/ui/index.ts +3 -0
- package/src/errors/Errors.state.ts +1 -0
- package/src/errors/Errors.ts +27 -6
- package/src/errors/index.ts +6 -2
- package/src/errors/settings/Debug.vue +14 -0
- package/src/errors/settings/index.ts +10 -0
- package/src/forms/FormController.test.ts +3 -0
- package/src/forms/FormController.ts +25 -16
- package/src/forms/utils.ts +25 -0
- package/src/forms/validation.ts +31 -0
- package/src/index.css +3 -0
- package/src/lang/index.ts +1 -1
- package/src/lang/settings/Language.vue +1 -1
- package/src/services/Service.ts +11 -6
- package/src/services/index.ts +2 -2
- package/src/testing/index.ts +4 -0
- package/src/ui/UI.ts +20 -4
- package/src/utils/app.ts +7 -0
- package/src/utils/index.ts +1 -0
|
@@ -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?: readonly 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
|
|
|
@@ -109,6 +112,10 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
109
112
|
return this._fields[field]?.rules?.split('|') ?? [];
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
public getFieldType<T extends keyof Fields>(field: T): FormFieldType | null {
|
|
116
|
+
return this._fields[field]?.type ?? null;
|
|
117
|
+
}
|
|
118
|
+
|
|
112
119
|
public data(): FormData<Fields> {
|
|
113
120
|
return { ...this._data };
|
|
114
121
|
}
|
|
@@ -192,6 +199,8 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
|
|
|
192
199
|
const value = this._data[name];
|
|
193
200
|
const rules = definition.rules?.split('|') ?? [];
|
|
194
201
|
|
|
202
|
+
errors.push(...validateType(value, definition));
|
|
203
|
+
|
|
195
204
|
for (const rule of rules) {
|
|
196
205
|
if (rule !== 'required' && (value === null || value === undefined)) {
|
|
197
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: readonly 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<const T extends string>(
|
|
49
|
+
values: readonly 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
|
|
package/src/index.css
CHANGED
package/src/lang/index.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { definePlugin } from '@aerogel/core/plugins';
|
|
|
4
4
|
|
|
5
5
|
import Lang from './Lang';
|
|
6
6
|
import settings from './settings';
|
|
7
|
-
import type { LangProvider } from './Lang';
|
|
8
7
|
import { translate, translateWithDefault } from './utils';
|
|
8
|
+
import type { LangProvider } from './Lang';
|
|
9
9
|
|
|
10
10
|
export { Lang, translate, translateWithDefault };
|
|
11
11
|
export type { LangProvider };
|
package/src/services/Service.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { Constructor, Nullable } from '@noeldemartin/utils';
|
|
|
12
12
|
import type { Store } from 'pinia';
|
|
13
13
|
|
|
14
14
|
import ServiceBootError from '@aerogel/core/errors/ServiceBootError';
|
|
15
|
+
import { appNamespace } from '@aerogel/core/utils/app';
|
|
15
16
|
import { defineServiceStore } from '@aerogel/core/services/store';
|
|
16
17
|
import type { Unref } from '@aerogel/core/utils/vue';
|
|
17
18
|
|
|
@@ -164,7 +165,7 @@ export default class Service<
|
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
public hasPersistedState(): boolean {
|
|
167
|
-
return Storage.has(this.
|
|
168
|
+
return Storage.has(this.storageKey);
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
public hasState<P extends keyof State>(property: P): boolean {
|
|
@@ -231,6 +232,10 @@ export default class Service<
|
|
|
231
232
|
this.setState({ [property]: value } as Partial<State>);
|
|
232
233
|
}
|
|
233
234
|
|
|
235
|
+
protected get storageKey(): string {
|
|
236
|
+
return `${appNamespace()}:${this._name}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
234
239
|
protected onStateUpdated(update: Partial<State>, old: Partial<State>): void {
|
|
235
240
|
const persisted = objectOnly(update, this.static('persist'));
|
|
236
241
|
|
|
@@ -250,13 +255,13 @@ export default class Service<
|
|
|
250
255
|
}
|
|
251
256
|
|
|
252
257
|
protected onPersistentStateUpdated(persisted: Partial<State>): void {
|
|
253
|
-
const storage = Storage.get<ServiceStorage>(this.
|
|
258
|
+
const storage = Storage.get<ServiceStorage>(this.storageKey);
|
|
254
259
|
|
|
255
260
|
if (!storage) {
|
|
256
261
|
return;
|
|
257
262
|
}
|
|
258
263
|
|
|
259
|
-
Storage.set(this.
|
|
264
|
+
Storage.set(this.storageKey, {
|
|
260
265
|
...storage,
|
|
261
266
|
...this.serializePersistedState(objectDeepClone(persisted) as Partial<State>),
|
|
262
267
|
});
|
|
@@ -303,14 +308,14 @@ export default class Service<
|
|
|
303
308
|
return;
|
|
304
309
|
}
|
|
305
310
|
|
|
306
|
-
if (Storage.has(this.
|
|
307
|
-
const persisted = Storage.require<ServiceStorage>(this.
|
|
311
|
+
if (Storage.has(this.storageKey)) {
|
|
312
|
+
const persisted = Storage.require<ServiceStorage>(this.storageKey);
|
|
308
313
|
this.setState(this.deserializePersistedState(persisted));
|
|
309
314
|
|
|
310
315
|
return;
|
|
311
316
|
}
|
|
312
317
|
|
|
313
|
-
Storage.set(this.
|
|
318
|
+
Storage.set(this.storageKey, objectOnly(this.getState(), this.static('persist')));
|
|
314
319
|
}
|
|
315
320
|
|
|
316
321
|
protected requireStore(): Store<string, State, ComputedState, {}> {
|
package/src/services/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { App as
|
|
1
|
+
import type { App as AppInstance } from 'vue';
|
|
2
2
|
|
|
3
3
|
import { definePlugin } from '@aerogel/core/plugins';
|
|
4
4
|
import { isDevelopment, isTesting } from '@noeldemartin/utils';
|
|
@@ -30,7 +30,7 @@ export type DefaultServices = typeof defaultServices;
|
|
|
30
30
|
|
|
31
31
|
export interface Services extends DefaultServices {}
|
|
32
32
|
|
|
33
|
-
export async function bootServices(app:
|
|
33
|
+
export async function bootServices(app: AppInstance, services: Record<string, Service>): Promise<void> {
|
|
34
34
|
await Promise.all(
|
|
35
35
|
Object.entries(services).map(async ([name, service]) => {
|
|
36
36
|
await service
|
package/src/testing/index.ts
CHANGED
|
@@ -2,10 +2,13 @@ import { isTesting } from '@noeldemartin/utils';
|
|
|
2
2
|
import type { GetClosureArgs } from '@noeldemartin/utils';
|
|
3
3
|
|
|
4
4
|
import Events from '@aerogel/core/services/Events';
|
|
5
|
+
import { App } from '@aerogel/core/services';
|
|
5
6
|
import { definePlugin } from '@aerogel/core/plugins';
|
|
7
|
+
import type { Services } from '@aerogel/core/services';
|
|
6
8
|
|
|
7
9
|
export interface AerogelTestingRuntime {
|
|
8
10
|
on: (typeof Events)['on'];
|
|
11
|
+
service<T extends keyof Services>(name: T): Services[T] | null;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
export default definePlugin({
|
|
@@ -16,6 +19,7 @@ export default definePlugin({
|
|
|
16
19
|
|
|
17
20
|
globalThis.testingRuntime = {
|
|
18
21
|
on: ((...args: GetClosureArgs<(typeof Events)['on']>) => Events.on(...args)) as (typeof Events)['on'],
|
|
22
|
+
service: (name) => App.service(name),
|
|
19
23
|
};
|
|
20
24
|
},
|
|
21
25
|
});
|
package/src/ui/UI.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { after, facade, fail, isDevelopment, required, uuid } from '@noeldemartin/utils';
|
|
2
|
-
import { markRaw, nextTick } from 'vue';
|
|
2
|
+
import { markRaw, nextTick, unref } from 'vue';
|
|
3
3
|
import type { ComponentExposed, ComponentProps } from 'vue-component-type-helpers';
|
|
4
4
|
import type { Component } from 'vue';
|
|
5
5
|
import type { ClosureArgs } from '@noeldemartin/utils';
|
|
@@ -64,6 +64,7 @@ export type LoadingOptions = AcceptRefs<{
|
|
|
64
64
|
title?: string;
|
|
65
65
|
message?: string;
|
|
66
66
|
progress?: number;
|
|
67
|
+
delay?: number;
|
|
67
68
|
}>;
|
|
68
69
|
|
|
69
70
|
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
@@ -218,7 +219,11 @@ export class UIService extends Service {
|
|
|
218
219
|
operation?: Promise<T> | (() => T),
|
|
219
220
|
): Promise<T> {
|
|
220
221
|
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
221
|
-
const processArgs = (): {
|
|
222
|
+
const processArgs = (): {
|
|
223
|
+
operationPromise: Promise<T>;
|
|
224
|
+
props?: AcceptRefs<LoadingModalProps>;
|
|
225
|
+
delay?: number;
|
|
226
|
+
} => {
|
|
222
227
|
if (typeof operationOrMessageOrOptions === 'string') {
|
|
223
228
|
return {
|
|
224
229
|
props: { message: operationOrMessageOrOptions },
|
|
@@ -230,13 +235,24 @@ export class UIService extends Service {
|
|
|
230
235
|
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
231
236
|
}
|
|
232
237
|
|
|
238
|
+
const { delay, ...props } = operationOrMessageOrOptions;
|
|
239
|
+
|
|
233
240
|
return {
|
|
234
|
-
props
|
|
241
|
+
props,
|
|
242
|
+
delay: unref(delay),
|
|
235
243
|
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
236
244
|
};
|
|
237
245
|
};
|
|
238
246
|
|
|
239
|
-
|
|
247
|
+
let delayed = false;
|
|
248
|
+
const { operationPromise, props, delay } = processArgs();
|
|
249
|
+
|
|
250
|
+
delay && (await Promise.race([after({ ms: delay }).then(() => (delayed = true)), operationPromise]));
|
|
251
|
+
|
|
252
|
+
if (delay && !delayed) {
|
|
253
|
+
return operationPromise;
|
|
254
|
+
}
|
|
255
|
+
|
|
240
256
|
const modal = await this.modal(this.requireComponent('loading-modal'), props);
|
|
241
257
|
|
|
242
258
|
try {
|
package/src/utils/app.ts
ADDED
package/src/utils/index.ts
CHANGED