@aerogel/core 0.0.0-next.c4825c5cbe0fe3257e478c2a7ec8df27d5a72305 → 0.0.0-next.d197d66a9d339318d752a1d8a96b2919faba3003
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 +1873 -1949
- package/dist/aerogel-core.js +3268 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +31 -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 +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/contracts/ErrorReportModal.ts +32 -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/{forms/AGHeadlessInputInput.vue → HeadlessInputInput.vue} +16 -25
- 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/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 +6 -11
- 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} +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 +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} +8 -8
- package/src/components/ui/Toast.vue +44 -0
- package/src/components/ui/index.ts +30 -0
- package/src/directives/index.ts +9 -5
- package/src/directives/measure.ts +1 -1
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +14 -23
- package/src/errors/index.ts +9 -6
- 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} +42 -38
- package/src/forms/index.ts +2 -3
- package/src/forms/utils.ts +35 -35
- package/src/index.css +72 -0
- package/src/jobs/Job.ts +2 -2
- package/src/jobs/listeners.ts +1 -1
- 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 +8 -4
- package/src/services/utils.ts +2 -2
- package/src/testing/index.ts +4 -3
- package/src/testing/setup.ts +3 -19
- package/src/ui/UI.state.ts +8 -13
- package/src/ui/UI.ts +124 -111
- package/src/ui/index.ts +27 -28
- package/src/utils/classes.ts +49 -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 +4 -1
- package/src/utils/markdown.ts +3 -5
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +25 -136
- 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/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 -30
- 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';
|
|
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
|
-
|
|
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
|
}>;
|
|
@@ -53,7 +66,8 @@ export type LoadingOptions = AcceptRefs<{
|
|
|
53
66
|
progress?: number;
|
|
54
67
|
}>;
|
|
55
68
|
|
|
56
|
-
export interface ConfirmOptionsWithCheckboxes<T extends
|
|
69
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
70
|
+
extends ConfirmOptions {
|
|
57
71
|
checkboxes?: T;
|
|
58
72
|
}
|
|
59
73
|
|
|
@@ -62,31 +76,39 @@ export type PromptOptions = AcceptRefs<{
|
|
|
62
76
|
defaultValue?: string;
|
|
63
77
|
placeholder?: string;
|
|
64
78
|
acceptText?: string;
|
|
65
|
-
|
|
79
|
+
acceptVariant?: ButtonVariant;
|
|
66
80
|
cancelText?: string;
|
|
67
|
-
|
|
81
|
+
cancelVariant?: ButtonVariant;
|
|
68
82
|
trim?: boolean;
|
|
69
83
|
}>;
|
|
70
84
|
|
|
71
|
-
export interface
|
|
85
|
+
export interface ToastOptions {
|
|
72
86
|
component?: Component;
|
|
73
|
-
|
|
74
|
-
actions?:
|
|
87
|
+
variant?: ToastVariant;
|
|
88
|
+
actions?: ToastAction[];
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
export class UIService extends Service {
|
|
78
92
|
|
|
79
93
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
80
|
-
private components: Partial<
|
|
94
|
+
private components: Partial<UIComponents> = {};
|
|
95
|
+
|
|
96
|
+
public registerComponent<T extends keyof UIComponents>(name: T, component: UIComponents[T]): void {
|
|
97
|
+
this.components[name] = component;
|
|
98
|
+
}
|
|
81
99
|
|
|
82
|
-
public
|
|
83
|
-
return this.components[name] ??
|
|
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!`);
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
public alert(message: string): void;
|
|
87
109
|
public alert(title: string, message: string): void;
|
|
88
110
|
public alert(messageOrTitle: string, message?: string): void {
|
|
89
|
-
const getProperties = ():
|
|
111
|
+
const getProperties = (): AlertModalProps => {
|
|
90
112
|
if (typeof message !== 'string') {
|
|
91
113
|
return { message: messageOrTitle };
|
|
92
114
|
}
|
|
@@ -97,14 +119,14 @@ export class UIService extends Service {
|
|
|
97
119
|
};
|
|
98
120
|
};
|
|
99
121
|
|
|
100
|
-
this.
|
|
122
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
101
123
|
}
|
|
102
124
|
|
|
103
125
|
/* eslint-disable max-len */
|
|
104
126
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
105
127
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
106
|
-
public async confirm<T extends
|
|
107
|
-
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
|
|
108
130
|
/* eslint-enable max-len */
|
|
109
131
|
|
|
110
132
|
public async confirm(
|
|
@@ -112,7 +134,7 @@ export class UIService extends Service {
|
|
|
112
134
|
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
113
135
|
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
114
136
|
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
115
|
-
const getProperties = ():
|
|
137
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
116
138
|
if (typeof messageOrOptions !== 'string') {
|
|
117
139
|
return {
|
|
118
140
|
...(messageOrOptions ?? {}),
|
|
@@ -128,13 +150,10 @@ export class UIService extends Service {
|
|
|
128
150
|
required: !!options?.required,
|
|
129
151
|
};
|
|
130
152
|
};
|
|
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
153
|
|
|
137
|
-
const
|
|
154
|
+
const properties = getProperties();
|
|
155
|
+
const result = await this.modalForm(this.requireComponent('confirm-modal'), properties);
|
|
156
|
+
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
138
157
|
const checkboxes =
|
|
139
158
|
typeof result === 'object'
|
|
140
159
|
? result[1]
|
|
@@ -151,7 +170,7 @@ export class UIService extends Service {
|
|
|
151
170
|
continue;
|
|
152
171
|
}
|
|
153
172
|
|
|
154
|
-
if (confirmed &&
|
|
173
|
+
if (confirmed && isDevelopment()) {
|
|
155
174
|
// eslint-disable-next-line no-console
|
|
156
175
|
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
157
176
|
}
|
|
@@ -170,26 +189,22 @@ export class UIService extends Service {
|
|
|
170
189
|
options?: PromptOptions,
|
|
171
190
|
): Promise<string | null> {
|
|
172
191
|
const trim = options?.trim ?? true;
|
|
173
|
-
const getProperties = ():
|
|
192
|
+
const getProperties = (): PromptModalProps => {
|
|
174
193
|
if (typeof messageOrOptions !== 'string') {
|
|
175
194
|
return {
|
|
176
195
|
message: messageOrTitle,
|
|
177
196
|
...(messageOrOptions ?? {}),
|
|
178
|
-
} as
|
|
197
|
+
} as PromptModalProps;
|
|
179
198
|
}
|
|
180
199
|
|
|
181
200
|
return {
|
|
182
201
|
title: messageOrTitle,
|
|
183
202
|
message: messageOrOptions,
|
|
184
203
|
...(options ?? {}),
|
|
185
|
-
} as
|
|
204
|
+
} as PromptModalProps;
|
|
186
205
|
};
|
|
187
206
|
|
|
188
|
-
const
|
|
189
|
-
this.requireComponent(UIComponents.PromptModal),
|
|
190
|
-
getProperties(),
|
|
191
|
-
);
|
|
192
|
-
const rawResult = await modal.beforeClose;
|
|
207
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
193
208
|
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
194
209
|
|
|
195
210
|
return result ?? null;
|
|
@@ -203,7 +218,7 @@ export class UIService extends Service {
|
|
|
203
218
|
operation?: Promise<T> | (() => T),
|
|
204
219
|
): Promise<T> {
|
|
205
220
|
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
206
|
-
const processArgs = (): { operationPromise: Promise<T>; props?:
|
|
221
|
+
const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
|
|
207
222
|
if (typeof operationOrMessageOrOptions === 'string') {
|
|
208
223
|
return {
|
|
209
224
|
props: { message: operationOrMessageOrOptions },
|
|
@@ -222,10 +237,12 @@ export class UIService extends Service {
|
|
|
222
237
|
};
|
|
223
238
|
|
|
224
239
|
const { operationPromise, props } = processArgs();
|
|
225
|
-
const modal = await this.
|
|
240
|
+
const modal = await this.modal(this.requireComponent('loading-modal'), props);
|
|
226
241
|
|
|
227
242
|
try {
|
|
228
|
-
const
|
|
243
|
+
const result = await operationPromise;
|
|
244
|
+
|
|
245
|
+
await after({ ms: 500 });
|
|
229
246
|
|
|
230
247
|
return result;
|
|
231
248
|
} finally {
|
|
@@ -233,43 +250,34 @@ export class UIService extends Service {
|
|
|
233
250
|
}
|
|
234
251
|
}
|
|
235
252
|
|
|
236
|
-
public
|
|
237
|
-
const
|
|
253
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
254
|
+
const { component, ...otherOptions } = options;
|
|
255
|
+
const toast: UIToast = {
|
|
238
256
|
id: uuid(),
|
|
239
|
-
properties: { message, ...
|
|
240
|
-
component: markRaw(
|
|
257
|
+
properties: { message, ...otherOptions },
|
|
258
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
241
259
|
};
|
|
242
260
|
|
|
243
|
-
this.setState('
|
|
244
|
-
|
|
245
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
261
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
246
262
|
}
|
|
247
263
|
|
|
248
|
-
public
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
public registerComponent(name: UIComponent, component: Component): void {
|
|
256
|
-
this.components[name] = component;
|
|
257
|
-
}
|
|
264
|
+
public modal<T extends Component>(
|
|
265
|
+
...args: {} extends ComponentProps<T>
|
|
266
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
267
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
268
|
+
): Promise<UIModal<ModalResult<T>>>;
|
|
258
269
|
|
|
259
|
-
public async
|
|
260
|
-
component: TModalComponent,
|
|
261
|
-
properties?: ModalProperties<TModalComponent>,
|
|
262
|
-
): Promise<Modal<ModalResult<TModalComponent>>> {
|
|
270
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
263
271
|
const id = uuid();
|
|
264
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
265
|
-
const modal:
|
|
272
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
273
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
266
274
|
id,
|
|
267
|
-
|
|
275
|
+
closing: false,
|
|
276
|
+
properties: props ?? {},
|
|
268
277
|
component: markRaw(component),
|
|
269
278
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
270
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
279
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
271
280
|
};
|
|
272
|
-
const activeModal = this.modals.at(-1);
|
|
273
281
|
const modals = this.modals.concat(modal);
|
|
274
282
|
|
|
275
283
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -277,15 +285,26 @@ export class UIService extends Service {
|
|
|
277
285
|
this.setState({ modals });
|
|
278
286
|
|
|
279
287
|
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
288
|
|
|
286
289
|
return modal;
|
|
287
290
|
}
|
|
288
291
|
|
|
292
|
+
public modalForm<T extends Component>(
|
|
293
|
+
...args: {} extends ComponentProps<T>
|
|
294
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
295
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
296
|
+
): Promise<ModalResult<T> | undefined>;
|
|
297
|
+
|
|
298
|
+
public async modalForm<T extends Component>(
|
|
299
|
+
component: T,
|
|
300
|
+
props?: ComponentProps<T>,
|
|
301
|
+
): Promise<ModalResult<T> | undefined> {
|
|
302
|
+
const modal = await this.modal<T>(component, props as ComponentProps<T>);
|
|
303
|
+
const result = await modal.beforeClose;
|
|
304
|
+
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
|
|
289
308
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
290
309
|
if (!App.isMounted()) {
|
|
291
310
|
await this.removeModal(id, result);
|
|
@@ -302,7 +321,7 @@ export class UIService extends Service {
|
|
|
302
321
|
}
|
|
303
322
|
}
|
|
304
323
|
|
|
305
|
-
protected async boot(): Promise<void> {
|
|
324
|
+
protected override async boot(): Promise<void> {
|
|
306
325
|
this.watchModalEvents();
|
|
307
326
|
this.watchMountedEvent();
|
|
308
327
|
this.watchViewportBreakpoints();
|
|
@@ -314,25 +333,23 @@ export class UIService extends Service {
|
|
|
314
333
|
this.modals.filter((m) => m.id !== id),
|
|
315
334
|
);
|
|
316
335
|
|
|
317
|
-
this.modalCallbacks[id]?.
|
|
336
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
318
337
|
|
|
319
338
|
delete this.modalCallbacks[id];
|
|
320
|
-
|
|
321
|
-
const activeModal = this.modals.at(-1);
|
|
322
|
-
|
|
323
|
-
await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
|
|
324
339
|
}
|
|
325
340
|
|
|
326
341
|
private watchModalEvents(): void {
|
|
327
|
-
Events.on('modal-will-close', ({ modal, result }) => {
|
|
328
|
-
this.
|
|
342
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
343
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
329
344
|
|
|
330
|
-
if (
|
|
331
|
-
|
|
345
|
+
if (modal) {
|
|
346
|
+
modal.closing = true;
|
|
332
347
|
}
|
|
348
|
+
|
|
349
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
333
350
|
});
|
|
334
351
|
|
|
335
|
-
Events.on('modal-closed', async ({ modal: { id }, result }) => {
|
|
352
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
336
353
|
await this.removeModal(id, result);
|
|
337
354
|
});
|
|
338
355
|
}
|
|
@@ -373,14 +390,10 @@ export class UIService extends Service {
|
|
|
373
390
|
|
|
374
391
|
export default facade(UIService);
|
|
375
392
|
|
|
376
|
-
declare module '
|
|
393
|
+
declare module '@aerogel/core/services/Events' {
|
|
377
394
|
export interface EventsPayload {
|
|
378
395
|
'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;
|
|
396
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
397
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
385
398
|
}
|
|
386
399
|
}
|
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,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
|
+
}
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|