@aerogel/core 0.0.0-next.9a1c5ba39a454b316eba36ec7bdf579fed3d95d2 → 0.0.0-next.9aa7c279868edbedbcee075aef52212597d803fb
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 +1283 -1588
- package/dist/aerogel-core.js +2496 -1994
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +10 -2
- package/src/components/AppLayout.vue +14 -0
- package/src/components/{AGAppModals.vue → AppModals.vue} +2 -3
- package/src/components/AppOverlays.vue +9 -0
- package/src/components/AppToasts.vue +16 -0
- package/src/components/contracts/AlertModal.ts +8 -0
- package/src/components/contracts/Button.ts +16 -0
- package/src/components/contracts/ConfirmModal.ts +46 -0
- package/src/components/contracts/DropdownMenu.ts +20 -0
- package/src/components/{modals/AGErrorReportModal.ts → contracts/ErrorReportModal.ts} +5 -22
- package/src/components/contracts/Input.ts +26 -0
- package/src/components/contracts/LoadingModal.ts +26 -0
- package/src/components/contracts/Modal.ts +15 -10
- package/src/components/contracts/PromptModal.ts +34 -0
- package/src/components/contracts/Select.ts +45 -0
- package/src/components/contracts/Toast.ts +15 -0
- package/src/components/contracts/index.ts +10 -1
- package/src/components/headless/HeadlessButton.vue +51 -0
- package/src/components/headless/HeadlessInput.vue +59 -0
- package/src/components/headless/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +6 -7
- package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +2 -6
- package/src/components/headless/{forms/AGHeadlessInputInput.vue → HeadlessInputInput.vue} +16 -25
- package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +2 -6
- package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +9 -12
- package/src/components/headless/HeadlessModal.vue +57 -0
- package/src/components/headless/HeadlessModalContent.vue +30 -0
- package/src/components/headless/HeadlessModalDescription.vue +12 -0
- package/src/components/headless/HeadlessModalOverlay.vue +12 -0
- package/src/components/headless/HeadlessModalTitle.vue +12 -0
- package/src/components/headless/HeadlessSelect.vue +120 -0
- package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +3 -4
- package/src/components/headless/HeadlessSelectLabel.vue +25 -0
- package/src/components/headless/HeadlessSelectOption.vue +34 -0
- package/src/components/headless/HeadlessSelectOptions.vue +42 -0
- package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
- package/src/components/headless/HeadlessSelectValue.vue +18 -0
- package/src/components/headless/HeadlessToast.vue +18 -0
- package/src/components/headless/HeadlessToastAction.vue +13 -0
- package/src/components/headless/index.ts +19 -3
- package/src/components/index.ts +5 -10
- package/src/components/ui/AdvancedOptions.vue +18 -0
- package/src/components/ui/AlertModal.vue +14 -0
- package/src/components/ui/Button.vue +98 -0
- package/src/components/ui/Checkbox.vue +56 -0
- package/src/components/ui/ConfirmModal.vue +45 -0
- package/src/components/ui/DropdownMenu.vue +32 -0
- package/src/components/ui/DropdownMenuOption.vue +14 -0
- package/src/components/ui/DropdownMenuOptions.vue +27 -0
- package/src/components/ui/EditableContent.vue +82 -0
- package/src/components/ui/ErrorMessage.vue +15 -0
- package/src/components/ui/ErrorReportModal.vue +67 -0
- package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +28 -21
- package/src/components/ui/ErrorReportModalTitle.vue +24 -0
- package/src/components/{forms/AGForm.vue → ui/Form.vue} +4 -5
- package/src/components/ui/Input.vue +56 -0
- package/src/components/ui/Link.vue +12 -0
- package/src/components/ui/LoadingModal.vue +34 -0
- package/src/components/ui/Markdown.vue +69 -0
- package/src/components/ui/Modal.vue +122 -0
- package/src/components/{modals/AGModalContext.vue → ui/ModalContext.vue} +8 -9
- package/src/components/ui/ProgressBar.vue +51 -0
- package/src/components/ui/PromptModal.vue +38 -0
- package/src/components/ui/Select.vue +27 -0
- package/src/components/ui/SelectLabel.vue +17 -0
- package/src/components/ui/SelectOption.vue +29 -0
- package/src/components/ui/SelectOptions.vue +35 -0
- package/src/components/ui/SelectTrigger.vue +29 -0
- package/src/components/ui/SettingsModal.vue +15 -0
- package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +7 -7
- package/src/components/ui/Toast.vue +44 -0
- package/src/components/ui/index.ts +30 -0
- package/src/errors/Errors.ts +7 -16
- package/src/forms/{Form.test.ts → FormController.test.ts} +32 -9
- package/src/forms/{Form.ts → FormController.ts} +28 -27
- package/src/forms/index.ts +2 -3
- package/src/forms/utils.ts +35 -35
- package/src/index.css +72 -0
- package/src/lang/index.ts +4 -0
- package/src/lang/settings/Language.vue +48 -0
- package/src/lang/settings/index.ts +10 -0
- package/src/services/App.state.ts +11 -1
- package/src/services/App.ts +9 -1
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +2 -8
- package/src/services/index.ts +3 -0
- package/src/ui/UI.state.ts +7 -12
- package/src/ui/UI.ts +102 -103
- package/src/ui/index.ts +23 -24
- package/src/utils/classes.ts +49 -0
- package/src/utils/composition/events.ts +2 -4
- package/src/utils/composition/forms.ts +20 -4
- package/src/utils/composition/state.ts +11 -2
- package/src/utils/index.ts +3 -1
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +22 -128
- package/src/components/AGAppLayout.vue +0 -16
- package/src/components/AGAppOverlays.vue +0 -41
- package/src/components/AGAppSnackbars.vue +0 -13
- package/src/components/composition.ts +0 -23
- package/src/components/constants.ts +0 -8
- package/src/components/contracts/shared.ts +0 -9
- package/src/components/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -42
- package/src/components/forms/AGInput.vue +0 -42
- package/src/components/forms/AGSelect.story.vue +0 -46
- package/src/components/forms/AGSelect.vue +0 -54
- package/src/components/forms/index.ts +0 -5
- package/src/components/headless/forms/AGHeadlessButton.ts +0 -3
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -62
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -41
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -70
- package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -31
- package/src/components/headless/forms/AGHeadlessSelectOptions.vue +0 -19
- package/src/components/headless/forms/AGHeadlessSelectTrigger.vue +0 -25
- package/src/components/headless/forms/composition.ts +0 -10
- package/src/components/headless/forms/index.ts +0 -17
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -33
- package/src/components/headless/modals/AGHeadlessModal.vue +0 -88
- package/src/components/headless/modals/AGHeadlessModalContent.vue +0 -25
- package/src/components/headless/modals/index.ts +0 -5
- package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
- package/src/components/headless/snackbars/index.ts +0 -40
- package/src/components/lib/AGErrorMessage.vue +0 -16
- package/src/components/lib/AGLink.vue +0 -9
- package/src/components/lib/AGMarkdown.vue +0 -54
- package/src/components/lib/AGMeasured.vue +0 -16
- package/src/components/lib/AGProgressBar.vue +0 -55
- package/src/components/lib/index.ts +0 -6
- package/src/components/modals/AGAlertModal.ts +0 -18
- package/src/components/modals/AGAlertModal.vue +0 -14
- package/src/components/modals/AGConfirmModal.ts +0 -42
- package/src/components/modals/AGConfirmModal.vue +0 -27
- package/src/components/modals/AGErrorReportModal.vue +0 -54
- package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
- package/src/components/modals/AGLoadingModal.ts +0 -29
- package/src/components/modals/AGLoadingModal.vue +0 -15
- package/src/components/modals/AGModal.vue +0 -40
- package/src/components/modals/AGModalContext.ts +0 -8
- package/src/components/modals/AGModalTitle.vue +0 -9
- package/src/components/modals/AGPromptModal.ts +0 -41
- package/src/components/modals/AGPromptModal.vue +0 -35
- package/src/components/modals/index.ts +0 -16
- package/src/components/snackbars/AGSnackbar.vue +0 -36
- package/src/components/snackbars/index.ts +0 -3
- package/src/components/utils.ts +0 -63
- package/src/forms/composition.ts +0 -6
- package/src/utils/tailwindcss.test.ts +0 -26
- package/src/utils/tailwindcss.ts +0 -7
package/src/services/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import Events from './Events';
|
|
|
9
9
|
import Service from './Service';
|
|
10
10
|
import Storage from './Storage';
|
|
11
11
|
import { getPiniaStore } from './store';
|
|
12
|
+
import type { AppSetting } from './App.state';
|
|
12
13
|
|
|
13
14
|
export * from './App';
|
|
14
15
|
export * from './Cache';
|
|
@@ -53,6 +54,7 @@ export default definePlugin({
|
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
app.use(getPiniaStore());
|
|
57
|
+
options.settings?.forEach((setting) => App.addSetting(setting));
|
|
56
58
|
|
|
57
59
|
await bootServices(app, services);
|
|
58
60
|
},
|
|
@@ -61,6 +63,7 @@ export default definePlugin({
|
|
|
61
63
|
declare module '@aerogel/core/bootstrap/options' {
|
|
62
64
|
export interface AerogelOptions {
|
|
63
65
|
services?: Record<string, Service>;
|
|
66
|
+
settings?: AppSetting[];
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
|
package/src/ui/UI.state.ts
CHANGED
|
@@ -4,22 +4,16 @@ import { defineServiceState } from '@aerogel/core/services/Service';
|
|
|
4
4
|
|
|
5
5
|
import { Layouts, getCurrentLayout } from './utils';
|
|
6
6
|
|
|
7
|
-
export interface
|
|
7
|
+
export interface UIModal<T = unknown> {
|
|
8
8
|
id: string;
|
|
9
9
|
properties: Record<string, unknown>;
|
|
10
10
|
component: Component;
|
|
11
|
+
closing: boolean;
|
|
11
12
|
beforeClose: Promise<T | undefined>;
|
|
12
13
|
afterClose: Promise<T | undefined>;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
export interface
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
17
|
-
Properties extends Record<string, unknown> = Record<string, unknown>,
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
|
-
Result = unknown,
|
|
20
|
-
> {}
|
|
21
|
-
|
|
22
|
-
export interface Snackbar {
|
|
16
|
+
export interface UIToast {
|
|
23
17
|
id: string;
|
|
24
18
|
component: Component;
|
|
25
19
|
properties: Record<string, unknown>;
|
|
@@ -28,12 +22,13 @@ export interface Snackbar {
|
|
|
28
22
|
export default defineServiceState({
|
|
29
23
|
name: 'ui',
|
|
30
24
|
initialState: {
|
|
31
|
-
modals: [] as
|
|
32
|
-
|
|
25
|
+
modals: [] as UIModal[],
|
|
26
|
+
toasts: [] as UIToast[],
|
|
33
27
|
layout: getCurrentLayout(),
|
|
34
28
|
},
|
|
35
29
|
computed: {
|
|
36
|
-
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
37
30
|
desktop: ({ layout }) => layout === Layouts.Desktop,
|
|
31
|
+
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
32
|
+
openModals: ({ modals }) => modals.filter(({ closing }) => !closing),
|
|
38
33
|
},
|
|
39
34
|
});
|
package/src/ui/UI.ts
CHANGED
|
@@ -1,52 +1,61 @@
|
|
|
1
1
|
import { after, facade, fail, isDevelopment, required, uuid } from '@noeldemartin/utils';
|
|
2
2
|
import { markRaw, nextTick } from 'vue';
|
|
3
|
+
import type { ComponentExposed, ComponentProps } from 'vue-component-type-helpers';
|
|
3
4
|
import type { Component } from 'vue';
|
|
4
|
-
import type {
|
|
5
|
+
import type { ClosureArgs } from '@noeldemartin/utils';
|
|
5
6
|
|
|
6
7
|
import App from '@aerogel/core/services/App';
|
|
7
8
|
import Events from '@aerogel/core/services/Events';
|
|
8
|
-
import type { AcceptRefs } from '@aerogel/core/utils';
|
|
9
|
-
import type { Color } from '@aerogel/core/components/constants';
|
|
10
|
-
import type { SnackbarAction, SnackbarColor } from '@aerogel/core/components/headless/snackbars';
|
|
11
9
|
import type {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
ConfirmModalCheckboxes,
|
|
11
|
+
ConfirmModalExpose,
|
|
12
|
+
ConfirmModalProps,
|
|
13
|
+
} from '@aerogel/core/components/contracts/ConfirmModal';
|
|
14
|
+
import type {
|
|
15
|
+
ErrorReportModalExpose,
|
|
16
|
+
ErrorReportModalProps,
|
|
17
|
+
} from '@aerogel/core/components/contracts/ErrorReportModal';
|
|
18
|
+
import type { AcceptRefs } from '@aerogel/core/utils';
|
|
19
|
+
import type { AlertModalExpose, AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
|
|
20
|
+
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
21
|
+
import type { LoadingModalExpose, LoadingModalProps } from '@aerogel/core/components/contracts/LoadingModal';
|
|
22
|
+
import type { PromptModalExpose, PromptModalProps } from '@aerogel/core/components/contracts/PromptModal';
|
|
23
|
+
import type { ToastAction, ToastExpose, ToastProps, ToastVariant } from '@aerogel/core/components/contracts/Toast';
|
|
17
24
|
|
|
18
25
|
import Service from './UI.state';
|
|
19
26
|
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
20
|
-
import type {
|
|
27
|
+
import type { UIModal, UIToast } from './UI.state';
|
|
21
28
|
|
|
22
29
|
interface ModalCallbacks<T = unknown> {
|
|
23
30
|
willClose(result: T | undefined): void;
|
|
24
|
-
|
|
31
|
+
hasClosed(result: T | undefined): void;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
type
|
|
28
|
-
type
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export type UIComponent = ObjectValues<typeof UIComponents>;
|
|
34
|
+
export type ModalResult<T> = ModalExposeResult<ComponentExposed<T>>;
|
|
35
|
+
export type ModalExposeResult<T> = T extends { close(result?: infer Result): Promise<void> } ? Result : unknown;
|
|
36
|
+
export type UIComponent<Props = {}, Exposed = {}> = { new (...args: ClosureArgs): Exposed & { $props: Props } };
|
|
37
|
+
|
|
38
|
+
export interface UIComponents {
|
|
39
|
+
'alert-modal': UIComponent<AlertModalProps, AlertModalExpose>;
|
|
40
|
+
'confirm-modal': UIComponent<ConfirmModalProps, ConfirmModalExpose>;
|
|
41
|
+
'error-report-modal': UIComponent<ErrorReportModalProps, ErrorReportModalExpose>;
|
|
42
|
+
'loading-modal': UIComponent<LoadingModalProps, LoadingModalExpose>;
|
|
43
|
+
'prompt-modal': UIComponent<PromptModalProps, PromptModalExpose>;
|
|
44
|
+
'router-link': UIComponent;
|
|
45
|
+
'startup-crash': UIComponent;
|
|
46
|
+
toast: UIComponent<ToastProps, ToastExpose>;
|
|
47
|
+
}
|
|
42
48
|
|
|
43
|
-
export
|
|
49
|
+
export interface UIModalContext {
|
|
50
|
+
modal: UIModal;
|
|
51
|
+
childIndex?: number;
|
|
52
|
+
}
|
|
44
53
|
|
|
45
54
|
export type ConfirmOptions = AcceptRefs<{
|
|
46
55
|
acceptText?: string;
|
|
47
|
-
|
|
56
|
+
acceptVariant?: ButtonVariant;
|
|
48
57
|
cancelText?: string;
|
|
49
|
-
|
|
58
|
+
cancelVariant?: ButtonVariant;
|
|
50
59
|
actions?: Record<string, () => unknown>;
|
|
51
60
|
required?: boolean;
|
|
52
61
|
}>;
|
|
@@ -57,7 +66,8 @@ export type LoadingOptions = AcceptRefs<{
|
|
|
57
66
|
progress?: number;
|
|
58
67
|
}>;
|
|
59
68
|
|
|
60
|
-
export interface ConfirmOptionsWithCheckboxes<T extends
|
|
69
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
70
|
+
extends ConfirmOptions {
|
|
61
71
|
checkboxes?: T;
|
|
62
72
|
}
|
|
63
73
|
|
|
@@ -66,31 +76,39 @@ export type PromptOptions = AcceptRefs<{
|
|
|
66
76
|
defaultValue?: string;
|
|
67
77
|
placeholder?: string;
|
|
68
78
|
acceptText?: string;
|
|
69
|
-
|
|
79
|
+
acceptVariant?: ButtonVariant;
|
|
70
80
|
cancelText?: string;
|
|
71
|
-
|
|
81
|
+
cancelVariant?: ButtonVariant;
|
|
72
82
|
trim?: boolean;
|
|
73
83
|
}>;
|
|
74
84
|
|
|
75
|
-
export interface
|
|
85
|
+
export interface ToastOptions {
|
|
76
86
|
component?: Component;
|
|
77
|
-
|
|
78
|
-
actions?:
|
|
87
|
+
variant?: ToastVariant;
|
|
88
|
+
actions?: ToastAction[];
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
export class UIService extends Service {
|
|
82
92
|
|
|
83
93
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
84
|
-
private components: Partial<
|
|
94
|
+
private components: Partial<UIComponents> = {};
|
|
85
95
|
|
|
86
|
-
public
|
|
87
|
-
|
|
96
|
+
public registerComponent<T extends keyof UIComponents>(name: T, component: UIComponents[T]): void {
|
|
97
|
+
this.components[name] = component;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public resolveComponent<T extends keyof UIComponents>(name: T): UIComponents[T] | null {
|
|
101
|
+
return this.components[name] ?? null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public requireComponent<T extends keyof UIComponents>(name: T): UIComponents[T] {
|
|
105
|
+
return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
|
|
88
106
|
}
|
|
89
107
|
|
|
90
108
|
public alert(message: string): void;
|
|
91
109
|
public alert(title: string, message: string): void;
|
|
92
110
|
public alert(messageOrTitle: string, message?: string): void {
|
|
93
|
-
const getProperties = ():
|
|
111
|
+
const getProperties = (): AlertModalProps => {
|
|
94
112
|
if (typeof message !== 'string') {
|
|
95
113
|
return { message: messageOrTitle };
|
|
96
114
|
}
|
|
@@ -101,14 +119,14 @@ export class UIService extends Service {
|
|
|
101
119
|
};
|
|
102
120
|
};
|
|
103
121
|
|
|
104
|
-
this.openModal(this.requireComponent(
|
|
122
|
+
this.openModal(this.requireComponent('alert-modal'), getProperties());
|
|
105
123
|
}
|
|
106
124
|
|
|
107
125
|
/* eslint-disable max-len */
|
|
108
126
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
109
127
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
110
|
-
public async confirm<T extends
|
|
111
|
-
public async confirm<T extends
|
|
128
|
+
public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
129
|
+
public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
112
130
|
/* eslint-enable max-len */
|
|
113
131
|
|
|
114
132
|
public async confirm(
|
|
@@ -116,7 +134,7 @@ export class UIService extends Service {
|
|
|
116
134
|
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
117
135
|
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
118
136
|
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
119
|
-
const getProperties = ():
|
|
137
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
120
138
|
if (typeof messageOrOptions !== 'string') {
|
|
121
139
|
return {
|
|
122
140
|
...(messageOrOptions ?? {}),
|
|
@@ -132,10 +150,9 @@ export class UIService extends Service {
|
|
|
132
150
|
required: !!options?.required,
|
|
133
151
|
};
|
|
134
152
|
};
|
|
153
|
+
|
|
135
154
|
const properties = getProperties();
|
|
136
|
-
const modal = await this.openModal
|
|
137
|
-
ModalComponent<AGConfirmModalProps, boolean | [boolean, Record<string, boolean>]>
|
|
138
|
-
>(this.requireComponent(UIComponents.ConfirmModal), properties);
|
|
155
|
+
const modal = await this.openModal(this.requireComponent('confirm-modal'), properties);
|
|
139
156
|
const result = await modal.beforeClose;
|
|
140
157
|
|
|
141
158
|
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
@@ -174,25 +191,22 @@ export class UIService extends Service {
|
|
|
174
191
|
options?: PromptOptions,
|
|
175
192
|
): Promise<string | null> {
|
|
176
193
|
const trim = options?.trim ?? true;
|
|
177
|
-
const getProperties = ():
|
|
194
|
+
const getProperties = (): PromptModalProps => {
|
|
178
195
|
if (typeof messageOrOptions !== 'string') {
|
|
179
196
|
return {
|
|
180
197
|
message: messageOrTitle,
|
|
181
198
|
...(messageOrOptions ?? {}),
|
|
182
|
-
} as
|
|
199
|
+
} as PromptModalProps;
|
|
183
200
|
}
|
|
184
201
|
|
|
185
202
|
return {
|
|
186
203
|
title: messageOrTitle,
|
|
187
204
|
message: messageOrOptions,
|
|
188
205
|
...(options ?? {}),
|
|
189
|
-
} as
|
|
206
|
+
} as PromptModalProps;
|
|
190
207
|
};
|
|
191
208
|
|
|
192
|
-
const modal = await this.openModal
|
|
193
|
-
this.requireComponent(UIComponents.PromptModal),
|
|
194
|
-
getProperties(),
|
|
195
|
-
);
|
|
209
|
+
const modal = await this.openModal(this.requireComponent('prompt-modal'), getProperties());
|
|
196
210
|
const rawResult = await modal.beforeClose;
|
|
197
211
|
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
198
212
|
|
|
@@ -207,7 +221,7 @@ export class UIService extends Service {
|
|
|
207
221
|
operation?: Promise<T> | (() => T),
|
|
208
222
|
): Promise<T> {
|
|
209
223
|
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
210
|
-
const processArgs = (): { operationPromise: Promise<T>; props?:
|
|
224
|
+
const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
|
|
211
225
|
if (typeof operationOrMessageOrOptions === 'string') {
|
|
212
226
|
return {
|
|
213
227
|
props: { message: operationOrMessageOrOptions },
|
|
@@ -226,10 +240,12 @@ export class UIService extends Service {
|
|
|
226
240
|
};
|
|
227
241
|
|
|
228
242
|
const { operationPromise, props } = processArgs();
|
|
229
|
-
const modal = await this.openModal(this.requireComponent(
|
|
243
|
+
const modal = await this.openModal(this.requireComponent('loading-modal'), props);
|
|
230
244
|
|
|
231
245
|
try {
|
|
232
|
-
const
|
|
246
|
+
const result = await operationPromise;
|
|
247
|
+
|
|
248
|
+
await after({ ms: 500 });
|
|
233
249
|
|
|
234
250
|
return result;
|
|
235
251
|
} finally {
|
|
@@ -237,43 +253,37 @@ export class UIService extends Service {
|
|
|
237
253
|
}
|
|
238
254
|
}
|
|
239
255
|
|
|
240
|
-
public
|
|
241
|
-
const
|
|
256
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
257
|
+
const { component, ...otherOptions } = options;
|
|
258
|
+
const toast: UIToast = {
|
|
242
259
|
id: uuid(),
|
|
243
|
-
properties: { message, ...
|
|
244
|
-
component: markRaw(
|
|
260
|
+
properties: { message, ...otherOptions },
|
|
261
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
245
262
|
};
|
|
246
263
|
|
|
247
|
-
this.setState('
|
|
248
|
-
|
|
249
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
public hideSnackbar(id: string): void {
|
|
253
|
-
this.setState(
|
|
254
|
-
'snackbars',
|
|
255
|
-
this.snackbars.filter((snackbar) => snackbar.id !== id),
|
|
256
|
-
);
|
|
264
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
257
265
|
}
|
|
258
266
|
|
|
259
|
-
public
|
|
260
|
-
|
|
261
|
-
|
|
267
|
+
public openModal<T extends Component>(
|
|
268
|
+
...args: {} extends ComponentProps<T>
|
|
269
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
270
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
271
|
+
): Promise<UIModal<ModalResult<T>>>;
|
|
262
272
|
|
|
263
|
-
public async openModal<
|
|
264
|
-
component:
|
|
265
|
-
|
|
266
|
-
): Promise<
|
|
273
|
+
public async openModal<T extends Component>(
|
|
274
|
+
component: T,
|
|
275
|
+
props?: ComponentProps<T>,
|
|
276
|
+
): Promise<UIModal<ModalResult<T>>> {
|
|
267
277
|
const id = uuid();
|
|
268
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
269
|
-
const modal:
|
|
278
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
279
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
270
280
|
id,
|
|
271
|
-
|
|
281
|
+
closing: false,
|
|
282
|
+
properties: props ?? {},
|
|
272
283
|
component: markRaw(component),
|
|
273
284
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
274
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
285
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
275
286
|
};
|
|
276
|
-
const activeModal = this.modals.at(-1);
|
|
277
287
|
const modals = this.modals.concat(modal);
|
|
278
288
|
|
|
279
289
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -281,11 +291,6 @@ export class UIService extends Service {
|
|
|
281
291
|
this.setState({ modals });
|
|
282
292
|
|
|
283
293
|
await nextTick();
|
|
284
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
285
|
-
await Promise.all([
|
|
286
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
287
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
288
|
-
]);
|
|
289
294
|
|
|
290
295
|
return modal;
|
|
291
296
|
}
|
|
@@ -318,25 +323,23 @@ export class UIService extends Service {
|
|
|
318
323
|
this.modals.filter((m) => m.id !== id),
|
|
319
324
|
);
|
|
320
325
|
|
|
321
|
-
this.modalCallbacks[id]?.
|
|
326
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
322
327
|
|
|
323
328
|
delete this.modalCallbacks[id];
|
|
324
|
-
|
|
325
|
-
const activeModal = this.modals.at(-1);
|
|
326
|
-
|
|
327
|
-
await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
private watchModalEvents(): void {
|
|
331
|
-
Events.on('modal-will-close', ({ modal, result }) => {
|
|
332
|
-
this.
|
|
332
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
333
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
333
334
|
|
|
334
|
-
if (
|
|
335
|
-
|
|
335
|
+
if (modal) {
|
|
336
|
+
modal.closing = true;
|
|
336
337
|
}
|
|
338
|
+
|
|
339
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
337
340
|
});
|
|
338
341
|
|
|
339
|
-
Events.on('modal-closed', async ({ modal: { id }, result }) => {
|
|
342
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
340
343
|
await this.removeModal(id, result);
|
|
341
344
|
});
|
|
342
345
|
}
|
|
@@ -380,11 +383,7 @@ export default facade(UIService);
|
|
|
380
383
|
declare module '@aerogel/core/services/Events' {
|
|
381
384
|
export interface EventsPayload {
|
|
382
385
|
'close-modal': { id: string; result?: unknown };
|
|
383
|
-
'
|
|
384
|
-
'
|
|
385
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
386
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
387
|
-
'show-modal': { id: string };
|
|
388
|
-
'show-overlays-backdrop': void;
|
|
386
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
387
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
389
388
|
}
|
|
390
389
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import AlertModal from '@aerogel/core/components/ui/AlertModal.vue';
|
|
2
|
+
import ConfirmModal from '@aerogel/core/components/ui/ConfirmModal.vue';
|
|
3
|
+
import ErrorReportModal from '@aerogel/core/components/ui/ErrorReportModal.vue';
|
|
4
|
+
import LoadingModal from '@aerogel/core/components/ui/LoadingModal.vue';
|
|
5
|
+
import PromptModal from '@aerogel/core/components/ui/PromptModal.vue';
|
|
6
|
+
import StartupCrash from '@aerogel/core/components/ui/StartupCrash.vue';
|
|
7
|
+
import Toast from '@aerogel/core/components/ui/Toast.vue';
|
|
3
8
|
import { bootServices } from '@aerogel/core/services';
|
|
4
9
|
import { definePlugin } from '@aerogel/core/plugins';
|
|
5
10
|
|
|
6
|
-
import UI
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import AGErrorReportModal from '../components/modals/AGErrorReportModal.vue';
|
|
10
|
-
import AGLoadingModal from '../components/modals/AGLoadingModal.vue';
|
|
11
|
-
import AGPromptModal from '../components/modals/AGPromptModal.vue';
|
|
12
|
-
import AGSnackbar from '../components/snackbars/AGSnackbar.vue';
|
|
13
|
-
import AGStartupCrash from '../components/lib/AGStartupCrash.vue';
|
|
14
|
-
import type { UIComponent } from './UI';
|
|
11
|
+
import UI from './UI';
|
|
12
|
+
import type { UIComponents } from './UI';
|
|
13
|
+
import type { Component } from 'vue';
|
|
15
14
|
|
|
16
15
|
const services = { $ui: UI };
|
|
17
16
|
|
|
@@ -23,20 +22,20 @@ export type UIServices = typeof services;
|
|
|
23
22
|
|
|
24
23
|
export default definePlugin({
|
|
25
24
|
async install(app, options) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
const components: Partial<Record<keyof UIComponents, Component>> = {
|
|
26
|
+
'alert-modal': AlertModal,
|
|
27
|
+
'confirm-modal': ConfirmModal,
|
|
28
|
+
'error-report-modal': ErrorReportModal,
|
|
29
|
+
'loading-modal': LoadingModal,
|
|
30
|
+
'prompt-modal': PromptModal,
|
|
31
|
+
'startup-crash': StartupCrash,
|
|
32
|
+
'toast': Toast,
|
|
33
|
+
...options.components,
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
Object.entries({
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}).forEach(([name, component]) => UI.registerComponent(name as UIComponent, component));
|
|
36
|
+
for (const [name, component] of Object.entries(components)) {
|
|
37
|
+
UI.registerComponent(name as keyof UIComponents, component as UIComponents[keyof UIComponents]);
|
|
38
|
+
}
|
|
40
39
|
|
|
41
40
|
await bootServices(app, services);
|
|
42
41
|
},
|
|
@@ -44,7 +43,7 @@ export default definePlugin({
|
|
|
44
43
|
|
|
45
44
|
declare module '@aerogel/core/bootstrap/options' {
|
|
46
45
|
export interface AerogelOptions {
|
|
47
|
-
components?: Partial<
|
|
46
|
+
components?: Partial<Partial<UIComponents>>;
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { computed, unref } from 'vue';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import type { ClassValue } from 'clsx';
|
|
6
|
+
import type { ComputedRef, PropType, Ref } from 'vue';
|
|
7
|
+
import type { GetClosureArgs, GetClosureResult } from '@noeldemartin/utils';
|
|
8
|
+
|
|
9
|
+
export type CVAConfig<T> = NonNullable<GetClosureArgs<typeof cva<T>>[1]>;
|
|
10
|
+
export type CVAProps<T> = NonNullable<GetClosureArgs<GetClosureResult<typeof cva<T>>>[0]>;
|
|
11
|
+
export type RefsObject<T> = { [K in keyof T]: Ref<T[K]> | T[K] };
|
|
12
|
+
export type Variants<T extends Record<string, string | boolean>> = Required<{
|
|
13
|
+
[K in keyof T]: Exclude<T[K], undefined> extends string
|
|
14
|
+
? { [key in Exclude<T[K], undefined>]: string | null }
|
|
15
|
+
: { true: string | null; false: string | null };
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export type ComponentPropDefinitions<T> = {
|
|
19
|
+
[K in keyof T]: {
|
|
20
|
+
type?: PropType<T[K]>;
|
|
21
|
+
default: T[K] | (() => T[K]) | null;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type PickComponentProps<TValues, TDefinitions> = {
|
|
26
|
+
[K in keyof TValues]: K extends keyof TDefinitions ? TValues[K] : never;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function computedVariantClasses<T>(
|
|
30
|
+
value: RefsObject<{ baseClasses?: string } & CVAProps<T>>,
|
|
31
|
+
config: { baseClasses?: string } & CVAConfig<T>,
|
|
32
|
+
): ComputedRef<string> {
|
|
33
|
+
return computed(() => {
|
|
34
|
+
const { baseClasses: valueBaseClasses, ...valueRefs } = value;
|
|
35
|
+
const { baseClasses: configBaseClasses, ...configs } = config;
|
|
36
|
+
const variants = cva(configBaseClasses, configs as CVAConfig<T>);
|
|
37
|
+
const values = Object.entries(valueRefs).reduce((extractedValues, [name, valueRef]) => {
|
|
38
|
+
extractedValues[name as keyof CVAProps<T>] = unref(valueRef);
|
|
39
|
+
|
|
40
|
+
return extractedValues;
|
|
41
|
+
}, {} as CVAProps<T>);
|
|
42
|
+
|
|
43
|
+
return classes(variants(values), unref(valueBaseClasses));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function classes(...inputs: ClassValue[]): string {
|
|
48
|
+
return twMerge(clsx(inputs));
|
|
49
|
+
}
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
EventWithPayload,
|
|
7
7
|
EventWithoutPayload,
|
|
8
8
|
EventsPayload,
|
|
9
|
-
UnknownEvent,
|
|
10
9
|
} from '@aerogel/core/services/Events';
|
|
11
10
|
|
|
12
11
|
export function useEvent<Event extends EventWithoutPayload>(event: Event, listener: () => unknown): void;
|
|
@@ -14,11 +13,10 @@ export function useEvent<Event extends EventWithPayload>(
|
|
|
14
13
|
event: Event,
|
|
15
14
|
listener: EventListener<EventsPayload[Event]>
|
|
16
15
|
): void;
|
|
17
|
-
export function useEvent<Payload>(event: string, listener: (payload: Payload) => unknown): void;
|
|
18
|
-
export function useEvent<Event extends string>(event: UnknownEvent<Event>, listener: EventListener): void;
|
|
19
16
|
|
|
20
17
|
export function useEvent(event: string, listener: EventListener): void {
|
|
21
|
-
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
const unsubscribe = Events.on(event as any, listener);
|
|
22
20
|
|
|
23
21
|
onUnmounted(() => unsubscribe());
|
|
24
22
|
}
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { objectWithout } from '@noeldemartin/utils';
|
|
2
|
-
import { computed, useAttrs } from 'vue';
|
|
2
|
+
import { computed, inject, onUnmounted, useAttrs } from 'vue';
|
|
3
|
+
import type { ClassValue } from 'clsx';
|
|
3
4
|
import type { ComputedRef } from 'vue';
|
|
5
|
+
import type { Nullable } from '@noeldemartin/utils';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
import FormController from '@aerogel/core/forms/FormController';
|
|
8
|
+
import type { FormData, FormFieldDefinitions } from '@aerogel/core/forms/FormController';
|
|
9
|
+
|
|
10
|
+
export function onFormFocus(input: { name: Nullable<string> }, listener: () => unknown): void {
|
|
11
|
+
const form = inject<FormController | null>('form', null);
|
|
12
|
+
const stop = form?.on('focus', (name) => input.name === name && listener());
|
|
13
|
+
|
|
14
|
+
onUnmounted(() => stop?.());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useForm<const T extends FormFieldDefinitions>(fields: T): FormController<T> & FormData<T> {
|
|
18
|
+
return new FormController(fields) as FormController<T> & FormData<T>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useInputAttrs(): [ComputedRef<{}>, ComputedRef<ClassValue>] {
|
|
6
22
|
const attrs = useAttrs();
|
|
7
|
-
const
|
|
23
|
+
const classes = computed(() => attrs.class);
|
|
8
24
|
const inputAttrs = computed(() => objectWithout(attrs, 'class'));
|
|
9
25
|
|
|
10
|
-
return [inputAttrs,
|
|
26
|
+
return [inputAttrs, classes as ComputedRef<ClassValue>];
|
|
11
27
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { debounce } from '@noeldemartin/utils';
|
|
2
|
-
import { ref, watchEffect } from 'vue';
|
|
3
|
-
import type { ComputedGetter, ComputedRef } from '
|
|
2
|
+
import { computed, ref, watch, watchEffect } from 'vue';
|
|
3
|
+
import type { ComputedGetter, ComputedRef, Ref } from 'vue';
|
|
4
4
|
|
|
5
5
|
export interface ComputedDebounceOptions<T> {
|
|
6
6
|
initial?: T;
|
|
7
7
|
delay?: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export function computedAsync<T>(getter: () => Promise<T>): Ref<T | undefined> {
|
|
11
|
+
const result = ref<T>();
|
|
12
|
+
const asyncValue = computed(getter);
|
|
13
|
+
|
|
14
|
+
watch(asyncValue, async () => (result.value = await asyncValue.value), { immediate: true });
|
|
15
|
+
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
export function computedDebounce<T>(options: ComputedDebounceOptions<T>, getter: ComputedGetter<T>): ComputedRef<T>;
|
|
11
20
|
export function computedDebounce<T>(getter: ComputedGetter<T>): ComputedRef<T | null>;
|
|
12
21
|
export function computedDebounce<T>(
|
package/src/utils/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export * from './classes';
|
|
1
2
|
export * from './composition/events';
|
|
2
3
|
export * from './composition/forms';
|
|
3
4
|
export * from './composition/hooks';
|
|
4
5
|
export * from './composition/persistent';
|
|
6
|
+
export * from './composition/state';
|
|
5
7
|
export * from './markdown';
|
|
6
|
-
export * from './
|
|
8
|
+
export * from './types';
|
|
7
9
|
export * from './vue';
|