@aerogel/core 0.0.0-next.f1f5a990033d966dc0bb12d251110fbc9350dcc7 → 0.0.0-next.f7d6218c6788b7d93b1f58d848fc11cd0a351798
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 +2344 -1394
- package/dist/aerogel-core.js +3712 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +32 -37
- package/src/bootstrap/bootstrap.test.ts +4 -8
- package/src/bootstrap/index.ts +27 -14
- package/src/bootstrap/options.ts +4 -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/HeadlessInputDescription.vue +27 -0
- 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/HeadlessInputTextArea.vue +40 -0
- 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 -9
- 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/ui/Form.vue +24 -0
- 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/ui/ModalContext.vue +31 -0
- 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 +11 -5
- package/src/directives/measure.ts +41 -7
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +41 -42
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +16 -18
- package/src/errors/settings/Debug.vue +32 -0
- package/src/errors/settings/index.ts +10 -0
- package/src/errors/utils.ts +35 -0
- package/src/forms/FormController.test.ts +113 -0
- package/src/forms/FormController.ts +255 -0
- package/src/forms/index.ts +3 -2
- package/src/forms/utils.ts +76 -20
- package/src/forms/validation.ts +50 -0
- package/src/index.css +76 -0
- package/src/{main.ts → index.ts} +3 -0
- package/src/jobs/Job.ts +147 -0
- package/src/jobs/index.ts +10 -0
- package/src/jobs/listeners.ts +3 -0
- package/src/jobs/status.ts +4 -0
- package/src/lang/DefaultLangProvider.ts +46 -0
- package/src/lang/Lang.state.ts +11 -0
- package/src/lang/Lang.ts +44 -29
- 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 +37 -5
- package/src/services/App.ts +40 -6
- package/src/services/Cache.ts +43 -0
- package/src/services/Events.test.ts +39 -0
- package/src/services/Events.ts +110 -36
- package/src/services/Service.ts +150 -49
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +19 -7
- package/src/services/store.ts +8 -5
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +30 -0
- package/src/testing/setup.ts +11 -0
- package/src/ui/UI.state.ts +14 -12
- package/src/ui/UI.ts +282 -110
- package/src/ui/index.ts +28 -26
- package/src/ui/utils.ts +16 -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/persistent.test.ts +33 -0
- package/src/utils/composition/persistent.ts +11 -0
- package/src/utils/composition/state.test.ts +47 -0
- package/src/utils/composition/state.ts +33 -0
- package/src/utils/index.ts +5 -1
- package/src/utils/markdown.test.ts +50 -0
- package/src/utils/markdown.ts +53 -6
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +38 -132
- 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/constants.ts +0 -8
- package/src/components/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -35
- package/src/components/forms/AGForm.vue +0 -26
- package/src/components/forms/AGInput.vue +0 -36
- package/src/components/forms/AGSelect.story.vue +0 -28
- package/src/components/forms/AGSelect.vue +0 -53
- package/src/components/forms/index.ts +0 -5
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -51
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -28
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -57
- package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -45
- package/src/components/headless/forms/AGHeadlessSelect.ts +0 -39
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -76
- 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/index.ts +0 -14
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -34
- package/src/components/headless/modals/AGHeadlessModal.vue +0 -86
- package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -28
- package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -13
- 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/lib/AGErrorMessage.vue +0 -16
- package/src/components/lib/AGLink.vue +0 -9
- package/src/components/lib/AGMarkdown.vue +0 -36
- package/src/components/lib/AGMeasured.vue +0 -15
- package/src/components/lib/index.ts +0 -5
- package/src/components/modals/AGAlertModal.ts +0 -15
- package/src/components/modals/AGAlertModal.vue +0 -14
- package/src/components/modals/AGConfirmModal.ts +0 -27
- package/src/components/modals/AGConfirmModal.vue +0 -26
- package/src/components/modals/AGErrorReportModal.ts +0 -46
- package/src/components/modals/AGErrorReportModal.vue +0 -54
- package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
- package/src/components/modals/AGLoadingModal.ts +0 -23
- package/src/components/modals/AGLoadingModal.vue +0 -15
- package/src/components/modals/AGModal.ts +0 -10
- package/src/components/modals/AGModal.vue +0 -39
- package/src/components/modals/AGModalContext.ts +0 -8
- package/src/components/modals/AGModalContext.vue +0 -22
- package/src/components/modals/AGModalTitle.vue +0 -9
- package/src/components/modals/index.ts +0 -26
- 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/Form.test.ts +0 -58
- package/src/forms/Form.ts +0 -184
- 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 -14
package/src/ui/UI.ts
CHANGED
|
@@ -1,60 +1,115 @@
|
|
|
1
|
-
import { after, facade, fail, 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
|
|
7
|
-
import
|
|
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';
|
|
8
24
|
|
|
9
25
|
import Service from './UI.state';
|
|
10
|
-
import
|
|
11
|
-
import type {
|
|
26
|
+
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
27
|
+
import type { UIModal, UIToast } from './UI.state';
|
|
12
28
|
|
|
13
29
|
interface ModalCallbacks<T = unknown> {
|
|
14
30
|
willClose(result: T | undefined): void;
|
|
15
|
-
|
|
31
|
+
hasClosed(result: T | undefined): void;
|
|
16
32
|
}
|
|
17
33
|
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
+
}
|
|
31
48
|
|
|
32
|
-
export
|
|
49
|
+
export interface UIModalContext {
|
|
50
|
+
modal: UIModal;
|
|
51
|
+
childIndex?: number;
|
|
52
|
+
}
|
|
33
53
|
|
|
34
|
-
export
|
|
54
|
+
export type ConfirmOptions = AcceptRefs<{
|
|
35
55
|
acceptText?: string;
|
|
56
|
+
acceptVariant?: ButtonVariant;
|
|
36
57
|
cancelText?: string;
|
|
58
|
+
cancelVariant?: ButtonVariant;
|
|
59
|
+
actions?: Record<string, () => unknown>;
|
|
60
|
+
required?: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
|
|
63
|
+
export type LoadingOptions = AcceptRefs<{
|
|
64
|
+
title?: string;
|
|
65
|
+
message?: string;
|
|
66
|
+
progress?: number;
|
|
67
|
+
delay?: number;
|
|
68
|
+
}>;
|
|
69
|
+
|
|
70
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
71
|
+
extends ConfirmOptions {
|
|
72
|
+
checkboxes?: T;
|
|
37
73
|
}
|
|
38
74
|
|
|
39
|
-
export
|
|
75
|
+
export type PromptOptions = AcceptRefs<{
|
|
76
|
+
label?: string;
|
|
77
|
+
defaultValue?: string;
|
|
78
|
+
placeholder?: string;
|
|
79
|
+
acceptText?: string;
|
|
80
|
+
acceptVariant?: ButtonVariant;
|
|
81
|
+
cancelText?: string;
|
|
82
|
+
cancelVariant?: ButtonVariant;
|
|
83
|
+
trim?: boolean;
|
|
84
|
+
}>;
|
|
85
|
+
|
|
86
|
+
export interface ToastOptions {
|
|
40
87
|
component?: Component;
|
|
41
|
-
|
|
42
|
-
actions?:
|
|
88
|
+
variant?: ToastVariant;
|
|
89
|
+
actions?: ToastAction[];
|
|
43
90
|
}
|
|
44
91
|
|
|
45
92
|
export class UIService extends Service {
|
|
46
93
|
|
|
47
94
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
48
|
-
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
|
+
}
|
|
49
104
|
|
|
50
|
-
public requireComponent(name:
|
|
51
|
-
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!`);
|
|
52
107
|
}
|
|
53
108
|
|
|
54
109
|
public alert(message: string): void;
|
|
55
110
|
public alert(title: string, message: string): void;
|
|
56
111
|
public alert(messageOrTitle: string, message?: string): void {
|
|
57
|
-
const getProperties = ():
|
|
112
|
+
const getProperties = (): AlertModalProps => {
|
|
58
113
|
if (typeof message !== 'string') {
|
|
59
114
|
return { message: messageOrTitle };
|
|
60
115
|
}
|
|
@@ -65,57 +120,145 @@ export class UIService extends Service {
|
|
|
65
120
|
};
|
|
66
121
|
};
|
|
67
122
|
|
|
68
|
-
this.
|
|
123
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
69
124
|
}
|
|
70
125
|
|
|
126
|
+
/* eslint-disable max-len */
|
|
71
127
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
72
128
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
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
|
|
131
|
+
/* eslint-enable max-len */
|
|
132
|
+
|
|
73
133
|
public async confirm(
|
|
74
134
|
messageOrTitle: string,
|
|
75
|
-
messageOrOptions?: string | ConfirmOptions,
|
|
76
|
-
options?: ConfirmOptions,
|
|
77
|
-
): Promise<boolean> {
|
|
78
|
-
const getProperties = ():
|
|
135
|
+
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
136
|
+
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
137
|
+
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
138
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
79
139
|
if (typeof messageOrOptions !== 'string') {
|
|
80
140
|
return {
|
|
81
|
-
message: messageOrTitle,
|
|
82
141
|
...(messageOrOptions ?? {}),
|
|
142
|
+
message: messageOrTitle,
|
|
143
|
+
required: !!messageOrOptions?.required,
|
|
83
144
|
};
|
|
84
145
|
}
|
|
85
146
|
|
|
86
147
|
return {
|
|
148
|
+
...(options ?? {}),
|
|
87
149
|
title: messageOrTitle,
|
|
88
150
|
message: messageOrOptions,
|
|
89
|
-
|
|
151
|
+
required: !!options?.required,
|
|
90
152
|
};
|
|
91
153
|
};
|
|
92
154
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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);
|
|
158
|
+
const checkboxes =
|
|
159
|
+
typeof result === 'object'
|
|
160
|
+
? result[1]
|
|
161
|
+
: Object.entries(properties.checkboxes ?? {}).reduce(
|
|
162
|
+
(values, [checkbox, { default: defaultValue }]) => ({
|
|
163
|
+
[checkbox]: defaultValue ?? false,
|
|
164
|
+
...values,
|
|
165
|
+
}),
|
|
166
|
+
{} as Record<string, boolean>,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
for (const [name, checkbox] of Object.entries(properties.checkboxes ?? {})) {
|
|
170
|
+
if (!checkbox.required || checkboxes[name]) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (confirmed && isDevelopment()) {
|
|
175
|
+
// eslint-disable-next-line no-console
|
|
176
|
+
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
177
|
+
}
|
|
98
178
|
|
|
99
|
-
|
|
179
|
+
return [false, checkboxes];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return 'checkboxes' in properties ? [confirmed, checkboxes] : confirmed;
|
|
100
183
|
}
|
|
101
184
|
|
|
102
|
-
public async
|
|
103
|
-
public async
|
|
104
|
-
public async
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
185
|
+
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
186
|
+
public async prompt(title: string, message: string, options?: PromptOptions): Promise<string | null>;
|
|
187
|
+
public async prompt(
|
|
188
|
+
messageOrTitle: string,
|
|
189
|
+
messageOrOptions?: string | PromptOptions,
|
|
190
|
+
options?: PromptOptions,
|
|
191
|
+
): Promise<string | null> {
|
|
192
|
+
const trim = options?.trim ?? true;
|
|
193
|
+
const getProperties = (): PromptModalProps => {
|
|
194
|
+
if (typeof messageOrOptions !== 'string') {
|
|
195
|
+
return {
|
|
196
|
+
message: messageOrTitle,
|
|
197
|
+
...(messageOrOptions ?? {}),
|
|
198
|
+
} as PromptModalProps;
|
|
108
199
|
}
|
|
109
200
|
|
|
110
|
-
return {
|
|
201
|
+
return {
|
|
202
|
+
title: messageOrTitle,
|
|
203
|
+
message: messageOrOptions,
|
|
204
|
+
...(options ?? {}),
|
|
205
|
+
} as PromptModalProps;
|
|
111
206
|
};
|
|
112
207
|
|
|
113
|
-
const
|
|
208
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
209
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
210
|
+
|
|
211
|
+
return result ?? null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public async loading<T>(operation: Promise<T> | (() => T)): Promise<T>;
|
|
215
|
+
public async loading<T>(message: string, operation: Promise<T> | (() => T)): Promise<T>;
|
|
216
|
+
public async loading<T>(options: LoadingOptions, operation: Promise<T> | (() => T)): Promise<T>;
|
|
217
|
+
public async loading<T>(
|
|
218
|
+
operationOrMessageOrOptions: string | LoadingOptions | Promise<T> | (() => T),
|
|
219
|
+
operation?: Promise<T> | (() => T),
|
|
220
|
+
): Promise<T> {
|
|
221
|
+
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
222
|
+
const processArgs = (): {
|
|
223
|
+
operationPromise: Promise<T>;
|
|
224
|
+
props?: AcceptRefs<LoadingModalProps>;
|
|
225
|
+
delay?: number;
|
|
226
|
+
} => {
|
|
227
|
+
if (typeof operationOrMessageOrOptions === 'string') {
|
|
228
|
+
return {
|
|
229
|
+
props: { message: operationOrMessageOrOptions },
|
|
230
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (typeof operationOrMessageOrOptions === 'function' || operationOrMessageOrOptions instanceof Promise) {
|
|
235
|
+
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { delay, ...props } = operationOrMessageOrOptions;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
props,
|
|
242
|
+
delay: unref(delay),
|
|
243
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
|
|
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);
|
|
114
257
|
|
|
115
258
|
try {
|
|
116
|
-
|
|
259
|
+
const result = await operationPromise;
|
|
117
260
|
|
|
118
|
-
|
|
261
|
+
await after({ ms: 500 });
|
|
119
262
|
|
|
120
263
|
return result;
|
|
121
264
|
} finally {
|
|
@@ -123,43 +266,34 @@ export class UIService extends Service {
|
|
|
123
266
|
}
|
|
124
267
|
}
|
|
125
268
|
|
|
126
|
-
public
|
|
127
|
-
const
|
|
269
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
270
|
+
const { component, ...otherOptions } = options;
|
|
271
|
+
const toast: UIToast = {
|
|
128
272
|
id: uuid(),
|
|
129
|
-
properties: { message, ...
|
|
130
|
-
component:
|
|
273
|
+
properties: { message, ...otherOptions },
|
|
274
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
131
275
|
};
|
|
132
276
|
|
|
133
|
-
this.setState('
|
|
134
|
-
|
|
135
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
277
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
136
278
|
}
|
|
137
279
|
|
|
138
|
-
public
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
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>>>;
|
|
144
285
|
|
|
145
|
-
public
|
|
146
|
-
this.components[name] = component;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
public async openModal<TModalComponent extends ModalComponent>(
|
|
150
|
-
component: TModalComponent,
|
|
151
|
-
properties?: ModalProperties<TModalComponent>,
|
|
152
|
-
): Promise<Modal<ModalResult<TModalComponent>>> {
|
|
286
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
153
287
|
const id = uuid();
|
|
154
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
155
|
-
const modal:
|
|
288
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
289
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
156
290
|
id,
|
|
157
|
-
|
|
291
|
+
closing: false,
|
|
292
|
+
properties: props ?? {},
|
|
158
293
|
component: markRaw(component),
|
|
159
294
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
160
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
295
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
161
296
|
};
|
|
162
|
-
const activeModal = this.modals.at(-1);
|
|
163
297
|
const modals = this.modals.concat(modal);
|
|
164
298
|
|
|
165
299
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -167,58 +301,88 @@ export class UIService extends Service {
|
|
|
167
301
|
this.setState({ modals });
|
|
168
302
|
|
|
169
303
|
await nextTick();
|
|
170
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
171
|
-
await Promise.all([
|
|
172
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
173
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
174
|
-
]);
|
|
175
304
|
|
|
176
305
|
return modal;
|
|
177
306
|
}
|
|
178
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
|
+
|
|
179
324
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
325
|
+
if (!App.isMounted()) {
|
|
326
|
+
await this.removeModal(id, result);
|
|
327
|
+
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
180
331
|
await Events.emit('close-modal', { id, result });
|
|
181
332
|
}
|
|
182
333
|
|
|
183
|
-
|
|
334
|
+
public async closeAllModals(): Promise<void> {
|
|
335
|
+
while (this.modals.length > 0) {
|
|
336
|
+
await this.closeModal(required(this.modals[this.modals.length - 1]).id);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
protected override async boot(): Promise<void> {
|
|
184
341
|
this.watchModalEvents();
|
|
185
342
|
this.watchMountedEvent();
|
|
343
|
+
this.watchViewportBreakpoints();
|
|
186
344
|
}
|
|
187
345
|
|
|
188
|
-
private
|
|
189
|
-
|
|
190
|
-
|
|
346
|
+
private async removeModal(id: string, result?: unknown): Promise<void> {
|
|
347
|
+
this.setState(
|
|
348
|
+
'modals',
|
|
349
|
+
this.modals.filter((m) => m.id !== id),
|
|
350
|
+
);
|
|
191
351
|
|
|
192
|
-
|
|
193
|
-
Events.emit('hide-overlays-backdrop');
|
|
194
|
-
}
|
|
195
|
-
});
|
|
352
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
196
353
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
'modals',
|
|
200
|
-
this.modals.filter((m) => m.id !== modal.id),
|
|
201
|
-
);
|
|
354
|
+
delete this.modalCallbacks[id];
|
|
355
|
+
}
|
|
202
356
|
|
|
203
|
-
|
|
357
|
+
private watchModalEvents(): void {
|
|
358
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
359
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
204
360
|
|
|
205
|
-
|
|
361
|
+
if (modal) {
|
|
362
|
+
modal.closing = true;
|
|
363
|
+
}
|
|
206
364
|
|
|
207
|
-
|
|
365
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
366
|
+
});
|
|
208
367
|
|
|
209
|
-
|
|
368
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
369
|
+
await this.removeModal(id, result);
|
|
210
370
|
});
|
|
211
371
|
}
|
|
212
372
|
|
|
213
373
|
private watchMountedEvent(): void {
|
|
214
374
|
Events.once('application-mounted', async () => {
|
|
215
|
-
|
|
375
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const splash = globalThis.document.getElementById('splash');
|
|
216
380
|
|
|
217
381
|
if (!splash) {
|
|
218
382
|
return;
|
|
219
383
|
}
|
|
220
384
|
|
|
221
|
-
if (
|
|
385
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
222
386
|
splash.style.opacity = '0';
|
|
223
387
|
|
|
224
388
|
await after({ ms: 600 });
|
|
@@ -228,16 +392,24 @@ export class UIService extends Service {
|
|
|
228
392
|
});
|
|
229
393
|
}
|
|
230
394
|
|
|
395
|
+
private watchViewportBreakpoints(): void {
|
|
396
|
+
if (!globalThis.matchMedia) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const media = globalThis.matchMedia(`(min-width: ${MOBILE_BREAKPOINT}px)`);
|
|
401
|
+
|
|
402
|
+
media.addEventListener('change', () => this.setState({ layout: getCurrentLayout() }));
|
|
403
|
+
}
|
|
404
|
+
|
|
231
405
|
}
|
|
232
406
|
|
|
233
|
-
export default facade(
|
|
407
|
+
export default facade(UIService);
|
|
234
408
|
|
|
235
|
-
declare module '
|
|
409
|
+
declare module '@aerogel/core/services/Events' {
|
|
236
410
|
export interface EventsPayload {
|
|
237
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
238
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
239
411
|
'close-modal': { id: string; result?: unknown };
|
|
240
|
-
'
|
|
241
|
-
'
|
|
412
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
413
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
242
414
|
}
|
|
243
415
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,50 +1,52 @@
|
|
|
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 AGSnackbar from '../components/snackbars/AGSnackbar.vue';
|
|
12
|
-
import AGStartupCrash from '../components/lib/AGStartupCrash.vue';
|
|
13
|
-
import type { UIComponent } from './UI';
|
|
14
|
-
|
|
15
15
|
const services = { $ui: UI };
|
|
16
16
|
|
|
17
17
|
export * from './UI';
|
|
18
|
+
export * from './utils';
|
|
18
19
|
export { default as UI } from './UI';
|
|
19
20
|
|
|
20
21
|
export type UIServices = typeof services;
|
|
21
22
|
|
|
22
23
|
export default definePlugin({
|
|
23
24
|
async install(app, options) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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,
|
|
31
34
|
};
|
|
32
35
|
|
|
33
|
-
Object.entries({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}).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
|
+
}
|
|
37
39
|
|
|
38
40
|
await bootServices(app, services);
|
|
39
41
|
},
|
|
40
42
|
});
|
|
41
43
|
|
|
42
|
-
declare module '
|
|
44
|
+
declare module '@aerogel/core/bootstrap/options' {
|
|
43
45
|
export interface AerogelOptions {
|
|
44
|
-
components?: Partial<
|
|
46
|
+
components?: Partial<Partial<UIComponents>>;
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
declare module '
|
|
50
|
+
declare module '@aerogel/core/services' {
|
|
49
51
|
export interface Services extends UIServices {}
|
|
50
52
|
}
|
package/src/ui/utils.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const MOBILE_BREAKPOINT = 768;
|
|
2
|
+
|
|
3
|
+
export const Layouts = {
|
|
4
|
+
Mobile: 'mobile',
|
|
5
|
+
Desktop: 'desktop',
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export type Layout = (typeof Layouts)[keyof typeof Layouts];
|
|
9
|
+
|
|
10
|
+
export function getCurrentLayout(): Layout {
|
|
11
|
+
if (globalThis.innerWidth > MOBILE_BREAKPOINT) {
|
|
12
|
+
return Layouts.Desktop;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return Layouts.Mobile;
|
|
16
|
+
}
|
|
@@ -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
|
+
}
|