@aerogel/core 0.0.0-next.c29ffcd25bffdbed37ecce3aac1ba14cde3e9d39 → 0.0.0-next.c3236837f7f8fc319a4a56022accb32757b3db89
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 -0
- package/dist/aerogel-core.d.ts +2061 -1962
- package/dist/aerogel-core.js +3669 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +32 -37
- package/src/bootstrap/bootstrap.test.ts +4 -7
- package/src/bootstrap/index.ts +14 -15
- package/src/bootstrap/options.ts +1 -1
- package/src/components/AppLayout.vue +14 -0
- package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
- package/src/components/AppOverlays.vue +9 -0
- package/src/components/AppToasts.vue +16 -0
- package/src/components/contracts/AlertModal.ts +19 -0
- package/src/components/contracts/Button.ts +16 -0
- package/src/components/contracts/ConfirmModal.ts +48 -0
- package/src/components/contracts/DropdownMenu.ts +25 -0
- package/src/components/contracts/ErrorReportModal.ts +33 -0
- package/src/components/contracts/Input.ts +26 -0
- package/src/components/contracts/LoadingModal.ts +26 -0
- package/src/components/contracts/Modal.ts +21 -0
- 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 +11 -0
- 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} +7 -8
- package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
- package/src/components/headless/HeadlessInputInput.vue +86 -0
- package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
- package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +10 -13
- 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} +5 -6
- 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/HeadlessSwitch.vue +96 -0
- package/src/components/headless/HeadlessToast.vue +18 -0
- package/src/components/headless/HeadlessToastAction.vue +13 -0
- package/src/components/headless/index.ts +20 -3
- package/src/components/index.ts +6 -11
- package/src/components/ui/AdvancedOptions.vue +18 -0
- package/src/components/ui/AlertModal.vue +17 -0
- package/src/components/ui/Button.vue +115 -0
- package/src/components/ui/Checkbox.vue +56 -0
- package/src/components/ui/ConfirmModal.vue +50 -0
- package/src/components/ui/DropdownMenu.vue +32 -0
- package/src/components/ui/DropdownMenuOption.vue +22 -0
- package/src/components/ui/DropdownMenuOptions.vue +44 -0
- package/src/components/ui/EditableContent.vue +82 -0
- package/src/components/ui/ErrorLogs.vue +19 -0
- package/src/components/ui/ErrorLogsModal.vue +48 -0
- package/src/components/ui/ErrorMessage.vue +15 -0
- package/src/components/ui/ErrorReportModal.vue +73 -0
- package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +34 -27
- 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 +97 -0
- package/src/components/ui/Modal.vue +131 -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 +21 -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/Setting.vue +31 -0
- package/src/components/ui/SettingsModal.vue +15 -0
- package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +8 -8
- package/src/components/ui/Switch.vue +11 -0
- package/src/components/ui/TextArea.vue +56 -0
- package/src/components/ui/Toast.vue +46 -0
- package/src/components/ui/index.ts +35 -0
- package/src/directives/index.ts +9 -5
- package/src/directives/measure.ts +12 -6
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +29 -27
- package/src/errors/index.ts +15 -8
- package/src/errors/settings/Debug.vue +32 -0
- package/src/errors/settings/index.ts +10 -0
- package/src/errors/utils.ts +1 -1
- package/src/forms/{Form.test.ts → FormController.test.ts} +32 -8
- package/src/forms/{Form.ts → FormController.ts} +46 -38
- package/src/forms/index.ts +2 -3
- package/src/forms/utils.ts +35 -35
- package/src/index.css +75 -0
- package/src/jobs/Job.ts +2 -2
- package/src/lang/DefaultLangProvider.ts +7 -4
- package/src/lang/Lang.state.ts +1 -1
- package/src/lang/Lang.ts +1 -1
- package/src/lang/index.ts +12 -6
- package/src/lang/settings/Language.vue +48 -0
- package/src/lang/settings/index.ts +10 -0
- package/src/plugins/Plugin.ts +1 -1
- package/src/plugins/index.ts +10 -7
- package/src/services/App.state.ts +15 -4
- package/src/services/App.ts +12 -4
- package/src/services/Cache.ts +1 -1
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +4 -10
- package/src/services/Service.ts +21 -21
- package/src/services/Storage.ts +3 -3
- package/src/services/index.ts +10 -6
- package/src/services/utils.ts +2 -2
- package/src/testing/index.ts +8 -3
- package/src/testing/setup.ts +3 -19
- package/src/ui/UI.state.ts +8 -13
- package/src/ui/UI.ts +143 -114
- package/src/ui/index.ts +27 -28
- package/src/utils/classes.ts +41 -0
- package/src/utils/composition/events.ts +4 -6
- 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/markdown.ts +37 -5
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +31 -137
- package/dist/aerogel-core.cjs.js +0 -2
- package/dist/aerogel-core.cjs.js.map +0 -1
- package/dist/aerogel-core.esm.js +0 -2
- package/dist/aerogel-core.esm.js.map +0 -1
- package/histoire.config.ts +0 -7
- package/noeldemartin.config.js +0 -5
- package/postcss.config.js +0 -6
- package/src/assets/histoire.css +0 -3
- 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/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -41
- package/src/components/forms/AGInput.vue +0 -40
- package/src/components/forms/AGSelect.story.vue +0 -46
- package/src/components/forms/AGSelect.vue +0 -60
- 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 -34
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -70
- package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -84
- package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
- package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
- package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
- package/src/components/headless/forms/composition.ts +0 -10
- package/src/components/headless/forms/index.ts +0 -18
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -36
- package/src/components/headless/modals/AGHeadlessModal.vue +0 -92
- package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -32
- package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -23
- package/src/components/headless/modals/index.ts +0 -4
- package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
- package/src/components/headless/snackbars/index.ts +0 -40
- package/src/components/interfaces.ts +0 -24
- 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 -45
- 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 -26
- package/src/components/modals/AGErrorReportModal.ts +0 -49
- 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.ts +0 -11
- package/src/components/modals/AGModal.vue +0 -39
- 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 -34
- package/src/components/modals/index.ts +0 -17
- package/src/components/snackbars/AGSnackbar.vue +0 -36
- package/src/components/snackbars/index.ts +0 -3
- package/src/components/utils.ts +0 -10
- package/src/directives/initial-focus.ts +0 -11
- package/src/forms/composition.ts +0 -6
- package/src/main.histoire.ts +0 -1
- package/src/utils/tailwindcss.test.ts +0 -26
- package/src/utils/tailwindcss.ts +0 -7
- package/tailwind.config.js +0 -4
- package/tsconfig.json +0 -11
- package/vite.config.ts +0 -17
- /package/src/{main.ts → index.ts} +0 -0
package/src/ui/UI.state.ts
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
import type { Component } from 'vue';
|
|
2
2
|
|
|
3
|
-
import { defineServiceState } from '
|
|
3
|
+
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,48 +1,61 @@
|
|
|
1
|
-
import { after, facade, fail, required, uuid } from '@noeldemartin/utils';
|
|
2
|
-
import { markRaw, nextTick } from 'vue';
|
|
1
|
+
import { after, facade, fail, isDevelopment, required, uuid } from '@noeldemartin/utils';
|
|
2
|
+
import { markRaw, nextTick, unref } from 'vue';
|
|
3
|
+
import type { ComponentExposed, ComponentProps } from 'vue-component-type-helpers';
|
|
3
4
|
import type { Component } from 'vue';
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
import App from '
|
|
7
|
-
import Events from '
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
import type { ClosureArgs } from '@noeldemartin/utils';
|
|
6
|
+
|
|
7
|
+
import App from '@aerogel/core/services/App';
|
|
8
|
+
import Events from '@aerogel/core/services/Events';
|
|
9
|
+
import type {
|
|
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';
|
|
12
24
|
|
|
13
25
|
import Service from './UI.state';
|
|
14
26
|
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
15
|
-
import type {
|
|
27
|
+
import type { UIModal, UIToast } from './UI.state';
|
|
16
28
|
|
|
17
29
|
interface ModalCallbacks<T = unknown> {
|
|
18
30
|
willClose(result: T | undefined): void;
|
|
19
|
-
|
|
31
|
+
hasClosed(result: T | undefined): void;
|
|
20
32
|
}
|
|
21
33
|
|
|
22
|
-
type
|
|
23
|
-
type
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
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
|
+
}
|
|
38
48
|
|
|
39
|
-
export
|
|
49
|
+
export interface UIModalContext {
|
|
50
|
+
modal: UIModal;
|
|
51
|
+
childIndex?: number;
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
export type ConfirmOptions = AcceptRefs<{
|
|
42
55
|
acceptText?: string;
|
|
43
|
-
|
|
56
|
+
acceptVariant?: ButtonVariant;
|
|
44
57
|
cancelText?: string;
|
|
45
|
-
|
|
58
|
+
cancelVariant?: ButtonVariant;
|
|
46
59
|
actions?: Record<string, () => unknown>;
|
|
47
60
|
required?: boolean;
|
|
48
61
|
}>;
|
|
@@ -51,9 +64,11 @@ export type LoadingOptions = AcceptRefs<{
|
|
|
51
64
|
title?: string;
|
|
52
65
|
message?: string;
|
|
53
66
|
progress?: number;
|
|
67
|
+
delay?: number;
|
|
54
68
|
}>;
|
|
55
69
|
|
|
56
|
-
export interface ConfirmOptionsWithCheckboxes<T extends
|
|
70
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
71
|
+
extends ConfirmOptions {
|
|
57
72
|
checkboxes?: T;
|
|
58
73
|
}
|
|
59
74
|
|
|
@@ -62,31 +77,39 @@ export type PromptOptions = AcceptRefs<{
|
|
|
62
77
|
defaultValue?: string;
|
|
63
78
|
placeholder?: string;
|
|
64
79
|
acceptText?: string;
|
|
65
|
-
|
|
80
|
+
acceptVariant?: ButtonVariant;
|
|
66
81
|
cancelText?: string;
|
|
67
|
-
|
|
82
|
+
cancelVariant?: ButtonVariant;
|
|
68
83
|
trim?: boolean;
|
|
69
84
|
}>;
|
|
70
85
|
|
|
71
|
-
export interface
|
|
86
|
+
export interface ToastOptions {
|
|
72
87
|
component?: Component;
|
|
73
|
-
|
|
74
|
-
actions?:
|
|
88
|
+
variant?: ToastVariant;
|
|
89
|
+
actions?: ToastAction[];
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
export class UIService extends Service {
|
|
78
93
|
|
|
79
94
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
80
|
-
private components: Partial<
|
|
95
|
+
private components: Partial<UIComponents> = {};
|
|
81
96
|
|
|
82
|
-
public
|
|
83
|
-
|
|
97
|
+
public registerComponent<T extends keyof UIComponents>(name: T, component: UIComponents[T]): void {
|
|
98
|
+
this.components[name] = component;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public resolveComponent<T extends keyof UIComponents>(name: T): UIComponents[T] | null {
|
|
102
|
+
return this.components[name] ?? null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public requireComponent<T extends keyof UIComponents>(name: T): UIComponents[T] {
|
|
106
|
+
return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
public alert(message: string): void;
|
|
87
110
|
public alert(title: string, message: string): void;
|
|
88
111
|
public alert(messageOrTitle: string, message?: string): void {
|
|
89
|
-
const getProperties = ():
|
|
112
|
+
const getProperties = (): AlertModalProps => {
|
|
90
113
|
if (typeof message !== 'string') {
|
|
91
114
|
return { message: messageOrTitle };
|
|
92
115
|
}
|
|
@@ -97,14 +120,14 @@ export class UIService extends Service {
|
|
|
97
120
|
};
|
|
98
121
|
};
|
|
99
122
|
|
|
100
|
-
this.
|
|
123
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
101
124
|
}
|
|
102
125
|
|
|
103
126
|
/* eslint-disable max-len */
|
|
104
127
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
105
128
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
106
|
-
public async confirm<T extends
|
|
107
|
-
public async confirm<T extends
|
|
129
|
+
public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
130
|
+
public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
108
131
|
/* eslint-enable max-len */
|
|
109
132
|
|
|
110
133
|
public async confirm(
|
|
@@ -112,7 +135,7 @@ export class UIService extends Service {
|
|
|
112
135
|
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
113
136
|
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
114
137
|
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
115
|
-
const getProperties = ():
|
|
138
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
116
139
|
if (typeof messageOrOptions !== 'string') {
|
|
117
140
|
return {
|
|
118
141
|
...(messageOrOptions ?? {}),
|
|
@@ -128,13 +151,10 @@ export class UIService extends Service {
|
|
|
128
151
|
required: !!options?.required,
|
|
129
152
|
};
|
|
130
153
|
};
|
|
131
|
-
const properties = getProperties();
|
|
132
|
-
const modal = await this.openModal<
|
|
133
|
-
ModalComponent<AGConfirmModalProps, boolean | [boolean, Record<string, boolean>]>
|
|
134
|
-
>(this.requireComponent(UIComponents.ConfirmModal), properties);
|
|
135
|
-
const result = await modal.beforeClose;
|
|
136
154
|
|
|
137
|
-
const
|
|
155
|
+
const properties = getProperties();
|
|
156
|
+
const result = await this.modalForm(this.requireComponent('confirm-modal'), properties);
|
|
157
|
+
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
138
158
|
const checkboxes =
|
|
139
159
|
typeof result === 'object'
|
|
140
160
|
? result[1]
|
|
@@ -151,7 +171,7 @@ export class UIService extends Service {
|
|
|
151
171
|
continue;
|
|
152
172
|
}
|
|
153
173
|
|
|
154
|
-
if (confirmed &&
|
|
174
|
+
if (confirmed && isDevelopment()) {
|
|
155
175
|
// eslint-disable-next-line no-console
|
|
156
176
|
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
157
177
|
}
|
|
@@ -170,26 +190,22 @@ export class UIService extends Service {
|
|
|
170
190
|
options?: PromptOptions,
|
|
171
191
|
): Promise<string | null> {
|
|
172
192
|
const trim = options?.trim ?? true;
|
|
173
|
-
const getProperties = ():
|
|
193
|
+
const getProperties = (): PromptModalProps => {
|
|
174
194
|
if (typeof messageOrOptions !== 'string') {
|
|
175
195
|
return {
|
|
176
196
|
message: messageOrTitle,
|
|
177
197
|
...(messageOrOptions ?? {}),
|
|
178
|
-
} as
|
|
198
|
+
} as PromptModalProps;
|
|
179
199
|
}
|
|
180
200
|
|
|
181
201
|
return {
|
|
182
202
|
title: messageOrTitle,
|
|
183
203
|
message: messageOrOptions,
|
|
184
204
|
...(options ?? {}),
|
|
185
|
-
} as
|
|
205
|
+
} as PromptModalProps;
|
|
186
206
|
};
|
|
187
207
|
|
|
188
|
-
const
|
|
189
|
-
this.requireComponent(UIComponents.PromptModal),
|
|
190
|
-
getProperties(),
|
|
191
|
-
);
|
|
192
|
-
const rawResult = await modal.beforeClose;
|
|
208
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
193
209
|
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
194
210
|
|
|
195
211
|
return result ?? null;
|
|
@@ -203,7 +219,11 @@ export class UIService extends Service {
|
|
|
203
219
|
operation?: Promise<T> | (() => T),
|
|
204
220
|
): Promise<T> {
|
|
205
221
|
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
206
|
-
const processArgs = (): {
|
|
222
|
+
const processArgs = (): {
|
|
223
|
+
operationPromise: Promise<T>;
|
|
224
|
+
props?: AcceptRefs<LoadingModalProps>;
|
|
225
|
+
delay?: number;
|
|
226
|
+
} => {
|
|
207
227
|
if (typeof operationOrMessageOrOptions === 'string') {
|
|
208
228
|
return {
|
|
209
229
|
props: { message: operationOrMessageOrOptions },
|
|
@@ -215,17 +235,30 @@ export class UIService extends Service {
|
|
|
215
235
|
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
216
236
|
}
|
|
217
237
|
|
|
238
|
+
const { delay, ...props } = operationOrMessageOrOptions;
|
|
239
|
+
|
|
218
240
|
return {
|
|
219
|
-
props
|
|
241
|
+
props,
|
|
242
|
+
delay: unref(delay),
|
|
220
243
|
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
221
244
|
};
|
|
222
245
|
};
|
|
223
246
|
|
|
224
|
-
|
|
225
|
-
const
|
|
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
|
+
|
|
256
|
+
const modal = await this.modal(this.requireComponent('loading-modal'), props);
|
|
226
257
|
|
|
227
258
|
try {
|
|
228
|
-
const
|
|
259
|
+
const result = await operationPromise;
|
|
260
|
+
|
|
261
|
+
await after({ ms: 500 });
|
|
229
262
|
|
|
230
263
|
return result;
|
|
231
264
|
} finally {
|
|
@@ -233,43 +266,34 @@ export class UIService extends Service {
|
|
|
233
266
|
}
|
|
234
267
|
}
|
|
235
268
|
|
|
236
|
-
public
|
|
237
|
-
const
|
|
269
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
270
|
+
const { component, ...otherOptions } = options;
|
|
271
|
+
const toast: UIToast = {
|
|
238
272
|
id: uuid(),
|
|
239
|
-
properties: { message, ...
|
|
240
|
-
component: markRaw(
|
|
273
|
+
properties: { message, ...otherOptions },
|
|
274
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
241
275
|
};
|
|
242
276
|
|
|
243
|
-
this.setState('
|
|
244
|
-
|
|
245
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
277
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
246
278
|
}
|
|
247
279
|
|
|
248
|
-
public
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
280
|
+
public modal<T extends Component>(
|
|
281
|
+
...args: {} extends ComponentProps<T>
|
|
282
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
283
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
284
|
+
): Promise<UIModal<ModalResult<T>>>;
|
|
254
285
|
|
|
255
|
-
public
|
|
256
|
-
this.components[name] = component;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
public async openModal<TModalComponent extends ModalComponent>(
|
|
260
|
-
component: TModalComponent,
|
|
261
|
-
properties?: ModalProperties<TModalComponent>,
|
|
262
|
-
): Promise<Modal<ModalResult<TModalComponent>>> {
|
|
286
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
263
287
|
const id = uuid();
|
|
264
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
265
|
-
const modal:
|
|
288
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
289
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
266
290
|
id,
|
|
267
|
-
|
|
291
|
+
closing: false,
|
|
292
|
+
properties: props ?? {},
|
|
268
293
|
component: markRaw(component),
|
|
269
294
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
270
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
295
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
271
296
|
};
|
|
272
|
-
const activeModal = this.modals.at(-1);
|
|
273
297
|
const modals = this.modals.concat(modal);
|
|
274
298
|
|
|
275
299
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -277,15 +301,26 @@ export class UIService extends Service {
|
|
|
277
301
|
this.setState({ modals });
|
|
278
302
|
|
|
279
303
|
await nextTick();
|
|
280
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
281
|
-
await Promise.all([
|
|
282
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
283
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
284
|
-
]);
|
|
285
304
|
|
|
286
305
|
return modal;
|
|
287
306
|
}
|
|
288
307
|
|
|
308
|
+
public modalForm<T extends Component>(
|
|
309
|
+
...args: {} extends ComponentProps<T>
|
|
310
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
311
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
312
|
+
): Promise<ModalResult<T> | undefined>;
|
|
313
|
+
|
|
314
|
+
public async modalForm<T extends Component>(
|
|
315
|
+
component: T,
|
|
316
|
+
props?: ComponentProps<T>,
|
|
317
|
+
): Promise<ModalResult<T> | undefined> {
|
|
318
|
+
const modal = await this.modal<T>(component, props as ComponentProps<T>);
|
|
319
|
+
const result = await modal.beforeClose;
|
|
320
|
+
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
|
|
289
324
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
290
325
|
if (!App.isMounted()) {
|
|
291
326
|
await this.removeModal(id, result);
|
|
@@ -302,7 +337,7 @@ export class UIService extends Service {
|
|
|
302
337
|
}
|
|
303
338
|
}
|
|
304
339
|
|
|
305
|
-
protected async boot(): Promise<void> {
|
|
340
|
+
protected override async boot(): Promise<void> {
|
|
306
341
|
this.watchModalEvents();
|
|
307
342
|
this.watchMountedEvent();
|
|
308
343
|
this.watchViewportBreakpoints();
|
|
@@ -314,25 +349,23 @@ export class UIService extends Service {
|
|
|
314
349
|
this.modals.filter((m) => m.id !== id),
|
|
315
350
|
);
|
|
316
351
|
|
|
317
|
-
this.modalCallbacks[id]?.
|
|
352
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
318
353
|
|
|
319
354
|
delete this.modalCallbacks[id];
|
|
320
|
-
|
|
321
|
-
const activeModal = this.modals.at(-1);
|
|
322
|
-
|
|
323
|
-
await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
|
|
324
355
|
}
|
|
325
356
|
|
|
326
357
|
private watchModalEvents(): void {
|
|
327
|
-
Events.on('modal-will-close', ({ modal, result }) => {
|
|
328
|
-
this.
|
|
358
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
359
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
329
360
|
|
|
330
|
-
if (
|
|
331
|
-
|
|
361
|
+
if (modal) {
|
|
362
|
+
modal.closing = true;
|
|
332
363
|
}
|
|
364
|
+
|
|
365
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
333
366
|
});
|
|
334
367
|
|
|
335
|
-
Events.on('modal-closed', async ({ modal: { id }, result }) => {
|
|
368
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
336
369
|
await this.removeModal(id, result);
|
|
337
370
|
});
|
|
338
371
|
}
|
|
@@ -373,14 +406,10 @@ export class UIService extends Service {
|
|
|
373
406
|
|
|
374
407
|
export default facade(UIService);
|
|
375
408
|
|
|
376
|
-
declare module '
|
|
409
|
+
declare module '@aerogel/core/services/Events' {
|
|
377
410
|
export interface EventsPayload {
|
|
378
411
|
'close-modal': { id: string; result?: unknown };
|
|
379
|
-
'
|
|
380
|
-
'
|
|
381
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
382
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
383
|
-
'show-modal': { id: string };
|
|
384
|
-
'show-overlays-backdrop': void;
|
|
412
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
413
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
385
414
|
}
|
|
386
415
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
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';
|
|
8
|
+
import { bootServices } from '@aerogel/core/services';
|
|
9
|
+
import { definePlugin } from '@aerogel/core/plugins';
|
|
10
|
+
|
|
11
|
+
import UI from './UI';
|
|
12
|
+
import type { UIComponents } from './UI';
|
|
1
13
|
import type { Component } from 'vue';
|
|
2
14
|
|
|
3
|
-
import { bootServices } from '@/services';
|
|
4
|
-
import { definePlugin } from '@/plugins';
|
|
5
|
-
|
|
6
|
-
import UI, { UIComponents } from './UI';
|
|
7
|
-
import AGAlertModal from '../components/modals/AGAlertModal.vue';
|
|
8
|
-
import AGConfirmModal from '../components/modals/AGConfirmModal.vue';
|
|
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';
|
|
15
|
-
|
|
16
15
|
const services = { $ui: UI };
|
|
17
16
|
|
|
18
17
|
export * from './UI';
|
|
@@ -23,31 +22,31 @@ 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
|
},
|
|
43
42
|
});
|
|
44
43
|
|
|
45
|
-
declare module '
|
|
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
|
|
|
51
|
-
declare module '
|
|
50
|
+
declare module '@aerogel/core/services' {
|
|
52
51
|
export interface Services extends UIServices {}
|
|
53
52
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { 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 { PropType } 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 Variants<T extends Record<string, string | boolean>> = Required<{
|
|
12
|
+
[K in keyof T]: Exclude<T[K], undefined> extends string
|
|
13
|
+
? { [key in Exclude<T[K], undefined>]: string | null }
|
|
14
|
+
: { true: string | null; false: string | null };
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
export type ComponentPropDefinitions<T> = {
|
|
18
|
+
[K in keyof T]: {
|
|
19
|
+
type?: PropType<T[K]>;
|
|
20
|
+
default: T[K] | (() => T[K]) | null;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type PickComponentProps<TValues, TDefinitions> = {
|
|
25
|
+
[K in keyof TValues]: K extends keyof TDefinitions ? TValues[K] : never;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function variantClasses<T>(
|
|
29
|
+
value: { baseClasses?: string } & CVAProps<T>,
|
|
30
|
+
config: { baseClasses?: string } & CVAConfig<T>,
|
|
31
|
+
): string {
|
|
32
|
+
const { baseClasses: valueBaseClasses, ...values } = value;
|
|
33
|
+
const { baseClasses: configBaseClasses, ...configs } = config;
|
|
34
|
+
const variants = cva(configBaseClasses, configs as CVAConfig<T>);
|
|
35
|
+
|
|
36
|
+
return classes(variants(values as CVAProps<T>), unref(valueBaseClasses));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function classes(...inputs: ClassValue[]): string {
|
|
40
|
+
return twMerge(clsx(inputs));
|
|
41
|
+
}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import { onUnmounted } from 'vue';
|
|
2
2
|
|
|
3
|
-
import Events from '
|
|
3
|
+
import Events from '@aerogel/core/services/Events';
|
|
4
4
|
import type {
|
|
5
5
|
EventListener,
|
|
6
6
|
EventWithPayload,
|
|
7
7
|
EventWithoutPayload,
|
|
8
8
|
EventsPayload,
|
|
9
|
-
|
|
10
|
-
} from '@/services/Events';
|
|
9
|
+
} from '@aerogel/core/services/Events';
|
|
11
10
|
|
|
12
11
|
export function useEvent<Event extends EventWithoutPayload>(event: Event, listener: () => unknown): void;
|
|
13
12
|
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
|
}
|