@aerogel/core 0.0.0-next.9a02fcd3bcf698211dd7a71d4c48257c96dd7832 → 0.0.0-next.9aa7c279868edbedbcee075aef52212597d803fb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aerogel-core.d.ts +2080 -1616
- package/dist/aerogel-core.js +3265 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +31 -37
- package/src/bootstrap/bootstrap.test.ts +4 -8
- package/src/bootstrap/index.ts +25 -16
- 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/HeadlessInputDescription.vue +27 -0
- package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
- package/src/components/headless/HeadlessInputInput.vue +75 -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/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 -9
- 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/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 +69 -0
- package/src/components/ui/Modal.vue +122 -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 +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 +11 -5
- package/src/directives/measure.ts +34 -6
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +14 -23
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +9 -6
- package/src/errors/utils.ts +17 -1
- package/src/forms/FormController.test.ts +110 -0
- package/src/forms/FormController.ts +246 -0
- package/src/forms/index.ts +3 -2
- package/src/forms/utils.ts +51 -20
- package/src/forms/validation.ts +19 -0
- package/src/index.css +72 -0
- package/src/{main.ts → index.ts} +1 -0
- package/src/jobs/Job.ts +144 -2
- package/src/jobs/index.ts +4 -1
- 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 +48 -21
- 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 +36 -3
- package/src/services/App.ts +19 -3
- package/src/services/Cache.ts +43 -0
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +16 -12
- package/src/services/Service.ts +125 -54
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +16 -5
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +4 -3
- package/src/testing/setup.ts +11 -0
- package/src/ui/UI.state.ts +14 -12
- package/src/ui/UI.ts +224 -120
- package/src/ui/index.ts +28 -28
- package/src/ui/utils.ts +16 -0
- 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/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 +19 -6
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +28 -127
- 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 -41
- package/src/components/forms/AGForm.vue +0 -26
- package/src/components/forms/AGInput.vue +0 -38
- 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.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 -47
- 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/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/AGPromptModal.ts +0 -30
- 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/Form.test.ts +0 -58
- package/src/forms/Form.ts +0 -185
- 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,69 +1,114 @@
|
|
|
1
|
-
import { after, facade, fail, 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
|
|
7
|
-
import
|
|
8
|
-
import type {
|
|
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';
|
|
9
24
|
|
|
10
25
|
import Service from './UI.state';
|
|
11
|
-
import
|
|
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
|
-
|
|
31
|
-
}
|
|
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
|
+
}
|
|
32
48
|
|
|
33
|
-
export
|
|
49
|
+
export interface UIModalContext {
|
|
50
|
+
modal: UIModal;
|
|
51
|
+
childIndex?: number;
|
|
52
|
+
}
|
|
34
53
|
|
|
35
|
-
export
|
|
54
|
+
export type ConfirmOptions = AcceptRefs<{
|
|
36
55
|
acceptText?: string;
|
|
56
|
+
acceptVariant?: ButtonVariant;
|
|
37
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
|
+
}>;
|
|
68
|
+
|
|
69
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
70
|
+
extends ConfirmOptions {
|
|
71
|
+
checkboxes?: T;
|
|
38
72
|
}
|
|
39
73
|
|
|
40
|
-
export
|
|
74
|
+
export type PromptOptions = AcceptRefs<{
|
|
41
75
|
label?: string;
|
|
42
76
|
defaultValue?: string;
|
|
43
77
|
placeholder?: string;
|
|
44
78
|
acceptText?: string;
|
|
79
|
+
acceptVariant?: ButtonVariant;
|
|
45
80
|
cancelText?: string;
|
|
46
|
-
|
|
81
|
+
cancelVariant?: ButtonVariant;
|
|
82
|
+
trim?: boolean;
|
|
83
|
+
}>;
|
|
47
84
|
|
|
48
|
-
export interface
|
|
85
|
+
export interface ToastOptions {
|
|
49
86
|
component?: Component;
|
|
50
|
-
|
|
51
|
-
actions?:
|
|
87
|
+
variant?: ToastVariant;
|
|
88
|
+
actions?: ToastAction[];
|
|
52
89
|
}
|
|
53
90
|
|
|
54
91
|
export class UIService extends Service {
|
|
55
92
|
|
|
56
93
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
57
|
-
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
|
+
}
|
|
58
99
|
|
|
59
|
-
public
|
|
60
|
-
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!`);
|
|
61
106
|
}
|
|
62
107
|
|
|
63
108
|
public alert(message: string): void;
|
|
64
109
|
public alert(title: string, message: string): void;
|
|
65
110
|
public alert(messageOrTitle: string, message?: string): void {
|
|
66
|
-
const getProperties = ():
|
|
111
|
+
const getProperties = (): AlertModalProps => {
|
|
67
112
|
if (typeof message !== 'string') {
|
|
68
113
|
return { message: messageOrTitle };
|
|
69
114
|
}
|
|
@@ -74,38 +119,68 @@ export class UIService extends Service {
|
|
|
74
119
|
};
|
|
75
120
|
};
|
|
76
121
|
|
|
77
|
-
this.openModal(this.requireComponent(
|
|
122
|
+
this.openModal(this.requireComponent('alert-modal'), getProperties());
|
|
78
123
|
}
|
|
79
124
|
|
|
125
|
+
/* eslint-disable max-len */
|
|
80
126
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
81
127
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
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
|
|
130
|
+
/* eslint-enable max-len */
|
|
131
|
+
|
|
82
132
|
public async confirm(
|
|
83
133
|
messageOrTitle: string,
|
|
84
|
-
messageOrOptions?: string | ConfirmOptions,
|
|
85
|
-
options?: ConfirmOptions,
|
|
86
|
-
): Promise<boolean> {
|
|
87
|
-
const getProperties = ():
|
|
134
|
+
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
135
|
+
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
136
|
+
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
137
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
88
138
|
if (typeof messageOrOptions !== 'string') {
|
|
89
139
|
return {
|
|
90
|
-
message: messageOrTitle,
|
|
91
140
|
...(messageOrOptions ?? {}),
|
|
141
|
+
message: messageOrTitle,
|
|
142
|
+
required: !!messageOrOptions?.required,
|
|
92
143
|
};
|
|
93
144
|
}
|
|
94
145
|
|
|
95
146
|
return {
|
|
147
|
+
...(options ?? {}),
|
|
96
148
|
title: messageOrTitle,
|
|
97
149
|
message: messageOrOptions,
|
|
98
|
-
|
|
150
|
+
required: !!options?.required,
|
|
99
151
|
};
|
|
100
152
|
};
|
|
101
153
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
getProperties(),
|
|
105
|
-
);
|
|
154
|
+
const properties = getProperties();
|
|
155
|
+
const modal = await this.openModal(this.requireComponent('confirm-modal'), properties);
|
|
106
156
|
const result = await modal.beforeClose;
|
|
107
157
|
|
|
108
|
-
|
|
158
|
+
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
159
|
+
const checkboxes =
|
|
160
|
+
typeof result === 'object'
|
|
161
|
+
? result[1]
|
|
162
|
+
: Object.entries(properties.checkboxes ?? {}).reduce(
|
|
163
|
+
(values, [checkbox, { default: defaultValue }]) => ({
|
|
164
|
+
[checkbox]: defaultValue ?? false,
|
|
165
|
+
...values,
|
|
166
|
+
}),
|
|
167
|
+
{} as Record<string, boolean>,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
for (const [name, checkbox] of Object.entries(properties.checkboxes ?? {})) {
|
|
171
|
+
if (!checkbox.required || checkboxes[name]) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (confirmed && isDevelopment()) {
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return [false, checkboxes];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return 'checkboxes' in properties ? [confirmed, checkboxes] : confirmed;
|
|
109
184
|
}
|
|
110
185
|
|
|
111
186
|
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
@@ -115,47 +190,62 @@ export class UIService extends Service {
|
|
|
115
190
|
messageOrOptions?: string | PromptOptions,
|
|
116
191
|
options?: PromptOptions,
|
|
117
192
|
): Promise<string | null> {
|
|
118
|
-
const
|
|
193
|
+
const trim = options?.trim ?? true;
|
|
194
|
+
const getProperties = (): PromptModalProps => {
|
|
119
195
|
if (typeof messageOrOptions !== 'string') {
|
|
120
196
|
return {
|
|
121
197
|
message: messageOrTitle,
|
|
122
198
|
...(messageOrOptions ?? {}),
|
|
123
|
-
};
|
|
199
|
+
} as PromptModalProps;
|
|
124
200
|
}
|
|
125
201
|
|
|
126
202
|
return {
|
|
127
203
|
title: messageOrTitle,
|
|
128
204
|
message: messageOrOptions,
|
|
129
205
|
...(options ?? {}),
|
|
130
|
-
};
|
|
206
|
+
} as PromptModalProps;
|
|
131
207
|
};
|
|
132
208
|
|
|
133
|
-
const modal = await this.openModal
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
);
|
|
137
|
-
const result = await modal.beforeClose;
|
|
209
|
+
const modal = await this.openModal(this.requireComponent('prompt-modal'), getProperties());
|
|
210
|
+
const rawResult = await modal.beforeClose;
|
|
211
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
138
212
|
|
|
139
213
|
return result ?? null;
|
|
140
214
|
}
|
|
141
215
|
|
|
142
|
-
public async loading<T>(operation: Promise<T>): Promise<T>;
|
|
143
|
-
public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
|
|
144
|
-
public async loading<T>(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
216
|
+
public async loading<T>(operation: Promise<T> | (() => T)): Promise<T>;
|
|
217
|
+
public async loading<T>(message: string, operation: Promise<T> | (() => T)): Promise<T>;
|
|
218
|
+
public async loading<T>(options: LoadingOptions, operation: Promise<T> | (() => T)): Promise<T>;
|
|
219
|
+
public async loading<T>(
|
|
220
|
+
operationOrMessageOrOptions: string | LoadingOptions | Promise<T> | (() => T),
|
|
221
|
+
operation?: Promise<T> | (() => T),
|
|
222
|
+
): Promise<T> {
|
|
223
|
+
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
224
|
+
const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
|
|
225
|
+
if (typeof operationOrMessageOrOptions === 'string') {
|
|
226
|
+
return {
|
|
227
|
+
props: { message: operationOrMessageOrOptions },
|
|
228
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
229
|
+
};
|
|
148
230
|
}
|
|
149
231
|
|
|
150
|
-
|
|
232
|
+
if (typeof operationOrMessageOrOptions === 'function' || operationOrMessageOrOptions instanceof Promise) {
|
|
233
|
+
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
props: operationOrMessageOrOptions,
|
|
238
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
239
|
+
};
|
|
151
240
|
};
|
|
152
241
|
|
|
153
|
-
const
|
|
242
|
+
const { operationPromise, props } = processArgs();
|
|
243
|
+
const modal = await this.openModal(this.requireComponent('loading-modal'), props);
|
|
154
244
|
|
|
155
245
|
try {
|
|
156
|
-
|
|
246
|
+
const result = await operationPromise;
|
|
157
247
|
|
|
158
|
-
|
|
248
|
+
await after({ ms: 500 });
|
|
159
249
|
|
|
160
250
|
return result;
|
|
161
251
|
} finally {
|
|
@@ -163,43 +253,37 @@ export class UIService extends Service {
|
|
|
163
253
|
}
|
|
164
254
|
}
|
|
165
255
|
|
|
166
|
-
public
|
|
167
|
-
const
|
|
256
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
257
|
+
const { component, ...otherOptions } = options;
|
|
258
|
+
const toast: UIToast = {
|
|
168
259
|
id: uuid(),
|
|
169
|
-
properties: { message, ...
|
|
170
|
-
component: markRaw(
|
|
260
|
+
properties: { message, ...otherOptions },
|
|
261
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
171
262
|
};
|
|
172
263
|
|
|
173
|
-
this.setState('
|
|
174
|
-
|
|
175
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
public hideSnackbar(id: string): void {
|
|
179
|
-
this.setState(
|
|
180
|
-
'snackbars',
|
|
181
|
-
this.snackbars.filter((snackbar) => snackbar.id !== id),
|
|
182
|
-
);
|
|
264
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
183
265
|
}
|
|
184
266
|
|
|
185
|
-
public
|
|
186
|
-
|
|
187
|
-
|
|
267
|
+
public openModal<T extends Component>(
|
|
268
|
+
...args: {} extends ComponentProps<T>
|
|
269
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
270
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
271
|
+
): Promise<UIModal<ModalResult<T>>>;
|
|
188
272
|
|
|
189
|
-
public async openModal<
|
|
190
|
-
component:
|
|
191
|
-
|
|
192
|
-
): Promise<
|
|
273
|
+
public async openModal<T extends Component>(
|
|
274
|
+
component: T,
|
|
275
|
+
props?: ComponentProps<T>,
|
|
276
|
+
): Promise<UIModal<ModalResult<T>>> {
|
|
193
277
|
const id = uuid();
|
|
194
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
195
|
-
const modal:
|
|
278
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
279
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
196
280
|
id,
|
|
197
|
-
|
|
281
|
+
closing: false,
|
|
282
|
+
properties: props ?? {},
|
|
198
283
|
component: markRaw(component),
|
|
199
284
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
200
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
285
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
201
286
|
};
|
|
202
|
-
const activeModal = this.modals.at(-1);
|
|
203
287
|
const modals = this.modals.concat(modal);
|
|
204
288
|
|
|
205
289
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -207,58 +291,72 @@ export class UIService extends Service {
|
|
|
207
291
|
this.setState({ modals });
|
|
208
292
|
|
|
209
293
|
await nextTick();
|
|
210
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
211
|
-
await Promise.all([
|
|
212
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
213
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
214
|
-
]);
|
|
215
294
|
|
|
216
295
|
return modal;
|
|
217
296
|
}
|
|
218
297
|
|
|
219
298
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
299
|
+
if (!App.isMounted()) {
|
|
300
|
+
await this.removeModal(id, result);
|
|
301
|
+
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
220
305
|
await Events.emit('close-modal', { id, result });
|
|
221
306
|
}
|
|
222
307
|
|
|
223
|
-
|
|
308
|
+
public async closeAllModals(): Promise<void> {
|
|
309
|
+
while (this.modals.length > 0) {
|
|
310
|
+
await this.closeModal(required(this.modals[this.modals.length - 1]).id);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
protected override async boot(): Promise<void> {
|
|
224
315
|
this.watchModalEvents();
|
|
225
316
|
this.watchMountedEvent();
|
|
317
|
+
this.watchViewportBreakpoints();
|
|
226
318
|
}
|
|
227
319
|
|
|
228
|
-
private
|
|
229
|
-
|
|
230
|
-
|
|
320
|
+
private async removeModal(id: string, result?: unknown): Promise<void> {
|
|
321
|
+
this.setState(
|
|
322
|
+
'modals',
|
|
323
|
+
this.modals.filter((m) => m.id !== id),
|
|
324
|
+
);
|
|
231
325
|
|
|
232
|
-
|
|
233
|
-
Events.emit('hide-overlays-backdrop');
|
|
234
|
-
}
|
|
235
|
-
});
|
|
326
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
236
327
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
'modals',
|
|
240
|
-
this.modals.filter((m) => m.id !== modal.id),
|
|
241
|
-
);
|
|
328
|
+
delete this.modalCallbacks[id];
|
|
329
|
+
}
|
|
242
330
|
|
|
243
|
-
|
|
331
|
+
private watchModalEvents(): void {
|
|
332
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
333
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
244
334
|
|
|
245
|
-
|
|
335
|
+
if (modal) {
|
|
336
|
+
modal.closing = true;
|
|
337
|
+
}
|
|
246
338
|
|
|
247
|
-
|
|
339
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
340
|
+
});
|
|
248
341
|
|
|
249
|
-
|
|
342
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
343
|
+
await this.removeModal(id, result);
|
|
250
344
|
});
|
|
251
345
|
}
|
|
252
346
|
|
|
253
347
|
private watchMountedEvent(): void {
|
|
254
348
|
Events.once('application-mounted', async () => {
|
|
255
|
-
|
|
349
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const splash = globalThis.document.getElementById('splash');
|
|
256
354
|
|
|
257
355
|
if (!splash) {
|
|
258
356
|
return;
|
|
259
357
|
}
|
|
260
358
|
|
|
261
|
-
if (
|
|
359
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
262
360
|
splash.style.opacity = '0';
|
|
263
361
|
|
|
264
362
|
await after({ ms: 600 });
|
|
@@ -268,18 +366,24 @@ export class UIService extends Service {
|
|
|
268
366
|
});
|
|
269
367
|
}
|
|
270
368
|
|
|
369
|
+
private watchViewportBreakpoints(): void {
|
|
370
|
+
if (!globalThis.matchMedia) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const media = globalThis.matchMedia(`(min-width: ${MOBILE_BREAKPOINT}px)`);
|
|
375
|
+
|
|
376
|
+
media.addEventListener('change', () => this.setState({ layout: getCurrentLayout() }));
|
|
377
|
+
}
|
|
378
|
+
|
|
271
379
|
}
|
|
272
380
|
|
|
273
381
|
export default facade(UIService);
|
|
274
382
|
|
|
275
|
-
declare module '
|
|
383
|
+
declare module '@aerogel/core/services/Events' {
|
|
276
384
|
export interface EventsPayload {
|
|
277
385
|
'close-modal': { id: string; result?: unknown };
|
|
278
|
-
'
|
|
279
|
-
'
|
|
280
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
281
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
282
|
-
'show-modal': { id: string };
|
|
283
|
-
'show-overlays-backdrop': void;
|
|
386
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
387
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
284
388
|
}
|
|
285
389
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,52 +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 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';
|
|
18
|
+
export * from './utils';
|
|
19
19
|
export { default as UI } from './UI';
|
|
20
20
|
|
|
21
21
|
export type UIServices = typeof services;
|
|
22
22
|
|
|
23
23
|
export default definePlugin({
|
|
24
24
|
async install(app, options) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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,
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
Object.entries({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}).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
|
+
}
|
|
39
39
|
|
|
40
40
|
await bootServices(app, services);
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
declare module '
|
|
44
|
+
declare module '@aerogel/core/bootstrap/options' {
|
|
45
45
|
export interface AerogelOptions {
|
|
46
|
-
components?: Partial<
|
|
46
|
+
components?: Partial<Partial<UIComponents>>;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
declare module '
|
|
50
|
+
declare module '@aerogel/core/services' {
|
|
51
51
|
export interface Services extends UIServices {}
|
|
52
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,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
|
+
}
|