@aerogel/core 0.0.0-next.7035064d9ec6a82a936ee8dfcc4b58ed2e25a399 → 0.0.0-next.73a6df428477c011a1aebe0a932ebef0aa5e1b16
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 +2078 -1925
- package/dist/aerogel-core.js +3809 -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/ui/StartupCrash.vue +76 -0
- 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 +2 -1
- package/src/errors/Errors.ts +56 -33
- package/src/errors/index.ts +15 -8
- package/src/errors/settings/Debug.vue +14 -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} +37 -10
- package/src/forms/{Form.ts → FormController.ts} +51 -38
- package/src/forms/index.ts +2 -3
- package/src/forms/utils.ts +59 -34
- package/src/forms/validation.ts +31 -0
- package/src/index.css +76 -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 +32 -27
- 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 +148 -116
- package/src/ui/index.ts +27 -28
- package/src/utils/app.ts +7 -0
- 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 +5 -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 -30
- package/src/components/lib/AGStartupCrash.vue +0 -31
- 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 -41
- 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.ts
CHANGED
|
@@ -1,58 +1,74 @@
|
|
|
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>;
|
|
60
|
+
required?: boolean;
|
|
47
61
|
}>;
|
|
48
62
|
|
|
49
63
|
export type LoadingOptions = AcceptRefs<{
|
|
50
64
|
title?: string;
|
|
51
65
|
message?: string;
|
|
52
66
|
progress?: number;
|
|
67
|
+
delay?: number;
|
|
53
68
|
}>;
|
|
54
69
|
|
|
55
|
-
export interface ConfirmOptionsWithCheckboxes<T extends
|
|
70
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
71
|
+
extends ConfirmOptions {
|
|
56
72
|
checkboxes?: T;
|
|
57
73
|
}
|
|
58
74
|
|
|
@@ -61,31 +77,39 @@ export type PromptOptions = AcceptRefs<{
|
|
|
61
77
|
defaultValue?: string;
|
|
62
78
|
placeholder?: string;
|
|
63
79
|
acceptText?: string;
|
|
64
|
-
|
|
80
|
+
acceptVariant?: ButtonVariant;
|
|
65
81
|
cancelText?: string;
|
|
66
|
-
|
|
82
|
+
cancelVariant?: ButtonVariant;
|
|
67
83
|
trim?: boolean;
|
|
68
84
|
}>;
|
|
69
85
|
|
|
70
|
-
export interface
|
|
86
|
+
export interface ToastOptions {
|
|
71
87
|
component?: Component;
|
|
72
|
-
|
|
73
|
-
actions?:
|
|
88
|
+
variant?: ToastVariant;
|
|
89
|
+
actions?: ToastAction[];
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
export class UIService extends Service {
|
|
77
93
|
|
|
78
94
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
79
|
-
private components: Partial<
|
|
95
|
+
private components: Partial<UIComponents> = {};
|
|
96
|
+
|
|
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
|
+
}
|
|
80
104
|
|
|
81
|
-
public requireComponent(name:
|
|
82
|
-
return this.
|
|
105
|
+
public requireComponent<T extends keyof UIComponents>(name: T): UIComponents[T] {
|
|
106
|
+
return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
public alert(message: string): void;
|
|
86
110
|
public alert(title: string, message: string): void;
|
|
87
111
|
public alert(messageOrTitle: string, message?: string): void {
|
|
88
|
-
const getProperties = ():
|
|
112
|
+
const getProperties = (): AlertModalProps => {
|
|
89
113
|
if (typeof message !== 'string') {
|
|
90
114
|
return { message: messageOrTitle };
|
|
91
115
|
}
|
|
@@ -96,14 +120,14 @@ export class UIService extends Service {
|
|
|
96
120
|
};
|
|
97
121
|
};
|
|
98
122
|
|
|
99
|
-
this.
|
|
123
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
/* eslint-disable max-len */
|
|
103
127
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
104
128
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
105
|
-
public async confirm<T extends
|
|
106
|
-
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
|
|
107
131
|
/* eslint-enable max-len */
|
|
108
132
|
|
|
109
133
|
public async confirm(
|
|
@@ -111,27 +135,26 @@ export class UIService extends Service {
|
|
|
111
135
|
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
112
136
|
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
113
137
|
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
114
|
-
const getProperties = ():
|
|
138
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
115
139
|
if (typeof messageOrOptions !== 'string') {
|
|
116
140
|
return {
|
|
117
|
-
message: messageOrTitle,
|
|
118
141
|
...(messageOrOptions ?? {}),
|
|
142
|
+
message: messageOrTitle,
|
|
143
|
+
required: !!messageOrOptions?.required,
|
|
119
144
|
};
|
|
120
145
|
}
|
|
121
146
|
|
|
122
147
|
return {
|
|
148
|
+
...(options ?? {}),
|
|
123
149
|
title: messageOrTitle,
|
|
124
150
|
message: messageOrOptions,
|
|
125
|
-
|
|
151
|
+
required: !!options?.required,
|
|
126
152
|
};
|
|
127
153
|
};
|
|
128
|
-
const properties = getProperties();
|
|
129
|
-
const modal = await this.openModal<
|
|
130
|
-
ModalComponent<AGConfirmModalProps, boolean | [boolean, Record<string, boolean>]>
|
|
131
|
-
>(this.requireComponent(UIComponents.ConfirmModal), properties);
|
|
132
|
-
const result = await modal.beforeClose;
|
|
133
154
|
|
|
134
|
-
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);
|
|
135
158
|
const checkboxes =
|
|
136
159
|
typeof result === 'object'
|
|
137
160
|
? result[1]
|
|
@@ -148,7 +171,7 @@ export class UIService extends Service {
|
|
|
148
171
|
continue;
|
|
149
172
|
}
|
|
150
173
|
|
|
151
|
-
if (confirmed &&
|
|
174
|
+
if (confirmed && isDevelopment()) {
|
|
152
175
|
// eslint-disable-next-line no-console
|
|
153
176
|
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
154
177
|
}
|
|
@@ -167,26 +190,22 @@ export class UIService extends Service {
|
|
|
167
190
|
options?: PromptOptions,
|
|
168
191
|
): Promise<string | null> {
|
|
169
192
|
const trim = options?.trim ?? true;
|
|
170
|
-
const getProperties = ():
|
|
193
|
+
const getProperties = (): PromptModalProps => {
|
|
171
194
|
if (typeof messageOrOptions !== 'string') {
|
|
172
195
|
return {
|
|
173
196
|
message: messageOrTitle,
|
|
174
197
|
...(messageOrOptions ?? {}),
|
|
175
|
-
} as
|
|
198
|
+
} as PromptModalProps;
|
|
176
199
|
}
|
|
177
200
|
|
|
178
201
|
return {
|
|
179
202
|
title: messageOrTitle,
|
|
180
203
|
message: messageOrOptions,
|
|
181
204
|
...(options ?? {}),
|
|
182
|
-
} as
|
|
205
|
+
} as PromptModalProps;
|
|
183
206
|
};
|
|
184
207
|
|
|
185
|
-
const
|
|
186
|
-
this.requireComponent(UIComponents.PromptModal),
|
|
187
|
-
getProperties(),
|
|
188
|
-
);
|
|
189
|
-
const rawResult = await modal.beforeClose;
|
|
208
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
190
209
|
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
191
210
|
|
|
192
211
|
return result ?? null;
|
|
@@ -200,7 +219,11 @@ export class UIService extends Service {
|
|
|
200
219
|
operation?: Promise<T> | (() => T),
|
|
201
220
|
): Promise<T> {
|
|
202
221
|
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
203
|
-
const processArgs = (): {
|
|
222
|
+
const processArgs = (): {
|
|
223
|
+
operationPromise: Promise<T>;
|
|
224
|
+
props?: AcceptRefs<LoadingModalProps>;
|
|
225
|
+
delay?: number;
|
|
226
|
+
} => {
|
|
204
227
|
if (typeof operationOrMessageOrOptions === 'string') {
|
|
205
228
|
return {
|
|
206
229
|
props: { message: operationOrMessageOrOptions },
|
|
@@ -212,17 +235,30 @@ export class UIService extends Service {
|
|
|
212
235
|
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
213
236
|
}
|
|
214
237
|
|
|
238
|
+
const { delay, ...props } = operationOrMessageOrOptions;
|
|
239
|
+
|
|
215
240
|
return {
|
|
216
|
-
props
|
|
241
|
+
props,
|
|
242
|
+
delay: unref(delay),
|
|
217
243
|
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
218
244
|
};
|
|
219
245
|
};
|
|
220
246
|
|
|
221
|
-
|
|
222
|
-
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);
|
|
223
257
|
|
|
224
258
|
try {
|
|
225
|
-
const
|
|
259
|
+
const result = await operationPromise;
|
|
260
|
+
|
|
261
|
+
await after({ ms: 500 });
|
|
226
262
|
|
|
227
263
|
return result;
|
|
228
264
|
} finally {
|
|
@@ -230,43 +266,34 @@ export class UIService extends Service {
|
|
|
230
266
|
}
|
|
231
267
|
}
|
|
232
268
|
|
|
233
|
-
public
|
|
234
|
-
const
|
|
269
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
270
|
+
const { component, ...otherOptions } = options;
|
|
271
|
+
const toast: UIToast = {
|
|
235
272
|
id: uuid(),
|
|
236
|
-
properties: { message, ...
|
|
237
|
-
component: markRaw(
|
|
273
|
+
properties: { message, ...otherOptions },
|
|
274
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
238
275
|
};
|
|
239
276
|
|
|
240
|
-
this.setState('
|
|
241
|
-
|
|
242
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
277
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
243
278
|
}
|
|
244
279
|
|
|
245
|
-
public
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
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>>>;
|
|
251
285
|
|
|
252
|
-
public
|
|
253
|
-
this.components[name] = component;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
public async openModal<TModalComponent extends ModalComponent>(
|
|
257
|
-
component: TModalComponent,
|
|
258
|
-
properties?: ModalProperties<TModalComponent>,
|
|
259
|
-
): Promise<Modal<ModalResult<TModalComponent>>> {
|
|
286
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
260
287
|
const id = uuid();
|
|
261
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
262
|
-
const modal:
|
|
288
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
289
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
263
290
|
id,
|
|
264
|
-
|
|
291
|
+
closing: false,
|
|
292
|
+
properties: props ?? {},
|
|
265
293
|
component: markRaw(component),
|
|
266
294
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
267
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
295
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
268
296
|
};
|
|
269
|
-
const activeModal = this.modals.at(-1);
|
|
270
297
|
const modals = this.modals.concat(modal);
|
|
271
298
|
|
|
272
299
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -274,15 +301,26 @@ export class UIService extends Service {
|
|
|
274
301
|
this.setState({ modals });
|
|
275
302
|
|
|
276
303
|
await nextTick();
|
|
277
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
278
|
-
await Promise.all([
|
|
279
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
280
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
281
|
-
]);
|
|
282
304
|
|
|
283
305
|
return modal;
|
|
284
306
|
}
|
|
285
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
|
+
|
|
286
324
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
287
325
|
if (!App.isMounted()) {
|
|
288
326
|
await this.removeModal(id, result);
|
|
@@ -299,7 +337,7 @@ export class UIService extends Service {
|
|
|
299
337
|
}
|
|
300
338
|
}
|
|
301
339
|
|
|
302
|
-
protected async boot(): Promise<void> {
|
|
340
|
+
protected override async boot(): Promise<void> {
|
|
303
341
|
this.watchModalEvents();
|
|
304
342
|
this.watchMountedEvent();
|
|
305
343
|
this.watchViewportBreakpoints();
|
|
@@ -311,25 +349,23 @@ export class UIService extends Service {
|
|
|
311
349
|
this.modals.filter((m) => m.id !== id),
|
|
312
350
|
);
|
|
313
351
|
|
|
314
|
-
this.modalCallbacks[id]?.
|
|
352
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
315
353
|
|
|
316
354
|
delete this.modalCallbacks[id];
|
|
317
|
-
|
|
318
|
-
const activeModal = this.modals.at(-1);
|
|
319
|
-
|
|
320
|
-
await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
|
|
321
355
|
}
|
|
322
356
|
|
|
323
357
|
private watchModalEvents(): void {
|
|
324
|
-
Events.on('modal-will-close', ({ modal, result }) => {
|
|
325
|
-
this.
|
|
358
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
359
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
326
360
|
|
|
327
|
-
if (
|
|
328
|
-
|
|
361
|
+
if (modal) {
|
|
362
|
+
modal.closing = true;
|
|
329
363
|
}
|
|
364
|
+
|
|
365
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
330
366
|
});
|
|
331
367
|
|
|
332
|
-
Events.on('modal-closed', async ({ modal: { id }, result }) => {
|
|
368
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
333
369
|
await this.removeModal(id, result);
|
|
334
370
|
});
|
|
335
371
|
}
|
|
@@ -370,14 +406,10 @@ export class UIService extends Service {
|
|
|
370
406
|
|
|
371
407
|
export default facade(UIService);
|
|
372
408
|
|
|
373
|
-
declare module '
|
|
409
|
+
declare module '@aerogel/core/services/Events' {
|
|
374
410
|
export interface EventsPayload {
|
|
375
411
|
'close-modal': { id: string; result?: unknown };
|
|
376
|
-
'
|
|
377
|
-
'
|
|
378
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
379
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
380
|
-
'show-modal': { id: string };
|
|
381
|
-
'show-overlays-backdrop': void;
|
|
412
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
413
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
382
414
|
}
|
|
383
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
|
}
|
package/src/utils/app.ts
ADDED
|
@@ -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
|
}
|
|
@@ -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
|
}
|