@aerogel/core 0.0.0-next.b58141fee5d2fe7d25debdbca6b1d2bf1c13e48e → 0.0.0-next.b656a964404fbde17d9cce7668722596098e47fd
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 +2082 -813
- package/dist/aerogel-core.js +3119 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +36 -34
- package/src/bootstrap/bootstrap.test.ts +7 -10
- package/src/bootstrap/index.ts +43 -14
- package/src/bootstrap/options.ts +4 -1
- package/src/components/AppLayout.vue +16 -0
- package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
- package/src/components/{AGAppOverlays.vue → AppOverlays.vue} +5 -6
- package/src/components/AppToasts.vue +16 -0
- package/src/components/contracts/AlertModal.ts +4 -0
- package/src/components/contracts/Button.ts +16 -0
- package/src/components/contracts/ConfirmModal.ts +41 -0
- package/src/components/contracts/DropdownMenu.ts +11 -0
- package/src/components/contracts/ErrorReportModal.ts +29 -0
- package/src/components/contracts/Input.ts +26 -0
- package/src/components/contracts/LoadingModal.ts +18 -0
- package/src/components/contracts/Modal.ts +13 -0
- package/src/components/contracts/PromptModal.ts +28 -0
- package/src/components/contracts/Select.ts +36 -0
- package/src/components/contracts/Toast.ts +13 -0
- package/src/components/contracts/index.ts +9 -0
- package/src/components/contracts/shared.ts +9 -0
- package/src/components/headless/HeadlessButton.vue +50 -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/HeadlessInputLabel.vue +18 -0
- package/src/components/headless/HeadlessInputTextArea.vue +40 -0
- package/src/components/headless/{modals/AGHeadlessModal.vue → HeadlessModal.vue} +17 -19
- package/src/components/headless/HeadlessModalContent.vue +24 -0
- package/src/components/headless/HeadlessModalOverlay.vue +12 -0
- package/src/components/headless/HeadlessModalTitle.vue +12 -0
- package/src/components/headless/HeadlessSelect.vue +104 -0
- package/src/components/headless/HeadlessSelectError.vue +25 -0
- package/src/components/headless/HeadlessSelectLabel.vue +25 -0
- package/src/components/headless/HeadlessSelectOption.vue +34 -0
- package/src/components/headless/HeadlessSelectOptions.vue +30 -0
- package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
- package/src/components/headless/HeadlessSelectValue.vue +15 -0
- package/src/components/headless/HeadlessToast.vue +18 -0
- package/src/components/headless/HeadlessToastAction.vue +13 -0
- package/src/components/headless/index.ts +18 -3
- package/src/components/index.ts +6 -9
- package/src/components/ui/AdvancedOptions.vue +18 -0
- package/src/components/ui/AlertModal.vue +13 -0
- package/src/components/ui/Button.vue +98 -0
- package/src/components/ui/Checkbox.vue +56 -0
- package/src/components/ui/ConfirmModal.vue +42 -0
- package/src/components/ui/DropdownMenu.vue +33 -0
- package/src/components/ui/EditableContent.vue +82 -0
- package/src/components/ui/ErrorMessage.vue +15 -0
- package/src/components/ui/ErrorReportModal.vue +62 -0
- package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +38 -29
- 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 +32 -0
- package/src/components/ui/Markdown.vue +69 -0
- package/src/components/ui/Modal.vue +70 -0
- package/src/components/ui/ModalContext.vue +30 -0
- package/src/components/ui/ProgressBar.vue +50 -0
- package/src/components/ui/PromptModal.vue +35 -0
- package/src/components/ui/Select.vue +21 -0
- package/src/components/ui/SelectLabel.vue +10 -0
- package/src/components/ui/SelectOptions.vue +31 -0
- package/src/components/ui/SelectTrigger.vue +29 -0
- package/src/components/ui/SettingsModal.vue +15 -0
- package/src/components/ui/StartupCrash.vue +31 -0
- package/src/components/ui/Toast.vue +42 -0
- package/src/components/ui/index.ts +27 -0
- package/src/directives/index.ts +13 -5
- package/src/directives/measure.ts +40 -0
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +35 -34
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +19 -29
- package/src/errors/utils.ts +35 -0
- package/src/forms/{Form.test.ts → FormController.test.ts} +33 -4
- package/src/forms/{Form.ts → FormController.ts} +94 -25
- package/src/forms/composition.ts +4 -4
- package/src/forms/index.ts +3 -1
- package/src/forms/utils.ts +36 -5
- package/src/forms/validation.ts +19 -0
- package/src/index.css +46 -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 +11 -6
- package/src/lang/settings/Language.vue +48 -0
- package/src/lang/settings/index.ts +10 -0
- package/src/plugins/Plugin.ts +2 -1
- package/src/plugins/index.ts +22 -0
- package/src/services/App.state.ts +40 -6
- package/src/services/App.ts +48 -5
- package/src/services/Cache.ts +43 -0
- package/src/services/Events.test.ts +39 -0
- package/src/services/Events.ts +112 -32
- package/src/services/Service.ts +154 -49
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +18 -6
- package/src/services/store.ts +8 -5
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +26 -0
- package/src/testing/setup.ts +11 -0
- package/src/ui/UI.state.ts +19 -7
- package/src/ui/UI.ts +268 -57
- package/src/ui/index.ts +23 -17
- package/src/ui/utils.ts +16 -0
- package/src/utils/classes.ts +49 -0
- package/src/utils/composition/events.ts +3 -2
- package/src/utils/composition/forms.ts +14 -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 +4 -0
- package/src/utils/markdown.test.ts +50 -0
- package/src/utils/markdown.ts +19 -6
- package/src/utils/vue.ts +28 -118
- package/.eslintrc.js +0 -3
- 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/dist/virtual.d.ts +0 -11
- package/noeldemartin.config.js +0 -5
- package/src/components/AGAppLayout.vue +0 -11
- package/src/components/AGAppSnackbars.vue +0 -13
- package/src/components/basic/AGErrorMessage.vue +0 -16
- package/src/components/basic/AGLink.vue +0 -9
- package/src/components/basic/AGMarkdown.vue +0 -36
- package/src/components/basic/index.ts +0 -5
- 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/index.ts +0 -6
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -51
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -8
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -54
- package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -45
- package/src/components/headless/forms/AGHeadlessInputLabel.vue +0 -16
- package/src/components/headless/forms/index.ts +0 -6
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -7
- 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 -6
- package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
- package/src/components/headless/snackbars/index.ts +0 -25
- package/src/components/modals/AGAlertModal.vue +0 -25
- package/src/components/modals/AGConfirmModal.vue +0 -30
- package/src/components/modals/AGErrorReportModal.ts +0 -20
- package/src/components/modals/AGErrorReportModal.vue +0 -62
- package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
- package/src/components/modals/AGLoadingModal.vue +0 -19
- package/src/components/modals/AGModal.ts +0 -10
- package/src/components/modals/AGModal.vue +0 -37
- 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 -23
- package/src/components/snackbars/AGSnackbar.vue +0 -42
- package/src/components/snackbars/index.ts +0 -3
- package/src/directives/initial-focus.ts +0 -11
- package/src/types/virtual.d.ts +0 -11
- package/tsconfig.json +0 -11
- package/vite.config.ts +0 -14
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isTesting } from '@noeldemartin/utils';
|
|
2
|
+
import type { GetClosureArgs } from '@noeldemartin/utils';
|
|
3
|
+
|
|
4
|
+
import Events from '@aerogel/core/services/Events';
|
|
5
|
+
import { definePlugin } from '@aerogel/core/plugins';
|
|
6
|
+
|
|
7
|
+
export interface AerogelTestingRuntime {
|
|
8
|
+
on: (typeof Events)['on'];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default definePlugin({
|
|
12
|
+
async install() {
|
|
13
|
+
if (!isTesting()) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
globalThis.testingRuntime = {
|
|
18
|
+
on: ((...args: GetClosureArgs<(typeof Events)['on']>) => Events.on(...args)) as (typeof Events)['on'],
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
declare global {
|
|
24
|
+
// eslint-disable-next-line no-var
|
|
25
|
+
var testingRuntime: AerogelTestingRuntime | undefined;
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FakeLocalStorage } from '@noeldemartin/testing';
|
|
2
|
+
import { beforeEach, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
vi.mock('dompurify', async () => {
|
|
5
|
+
return { default: { sanitize: (html: string) => html } };
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
FakeLocalStorage.reset();
|
|
10
|
+
FakeLocalStorage.patchGlobal();
|
|
11
|
+
});
|
package/src/ui/UI.state.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Component } from 'vue';
|
|
2
2
|
|
|
3
|
-
import { defineServiceState } from '
|
|
3
|
+
import { defineServiceState } from '@aerogel/core/services/Service';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { Layouts, getCurrentLayout } from './utils';
|
|
6
|
+
|
|
7
|
+
export interface UIModal<T = unknown> {
|
|
6
8
|
id: string;
|
|
7
9
|
properties: Record<string, unknown>;
|
|
8
10
|
component: Component;
|
|
@@ -10,14 +12,19 @@ export interface Modal<T = unknown> {
|
|
|
10
12
|
afterClose: Promise<T | undefined>;
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
export interface UIModalContext {
|
|
16
|
+
modal: UIModal;
|
|
17
|
+
childIndex?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export interface ModalComponent<
|
|
14
21
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
-
Properties extends
|
|
22
|
+
Properties extends object = object,
|
|
16
23
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
17
|
-
Result = unknown
|
|
24
|
+
Result = unknown,
|
|
18
25
|
> {}
|
|
19
26
|
|
|
20
|
-
export interface
|
|
27
|
+
export interface UIToast {
|
|
21
28
|
id: string;
|
|
22
29
|
component: Component;
|
|
23
30
|
properties: Record<string, unknown>;
|
|
@@ -26,7 +33,12 @@ export interface Snackbar {
|
|
|
26
33
|
export default defineServiceState({
|
|
27
34
|
name: 'ui',
|
|
28
35
|
initialState: {
|
|
29
|
-
modals: [] as
|
|
30
|
-
|
|
36
|
+
modals: [] as UIModal[],
|
|
37
|
+
toasts: [] as UIToast[],
|
|
38
|
+
layout: getCurrentLayout(),
|
|
39
|
+
},
|
|
40
|
+
computed: {
|
|
41
|
+
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
42
|
+
desktop: ({ layout }) => layout === Layouts.Desktop,
|
|
31
43
|
},
|
|
32
44
|
});
|
package/src/ui/UI.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { 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
3
|
import type { Component } from 'vue';
|
|
4
4
|
import type { ObjectValues } from '@noeldemartin/utils';
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import
|
|
6
|
+
import App from '@aerogel/core/services/App';
|
|
7
|
+
import Events from '@aerogel/core/services/Events';
|
|
8
|
+
import type { AcceptRefs } from '@aerogel/core/utils';
|
|
9
|
+
import type { AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
|
|
10
|
+
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
11
|
+
import type { ConfirmModalCheckboxes, ConfirmModalProps } from '@aerogel/core/components/contracts/ConfirmModal';
|
|
12
|
+
import type { LoadingModalProps } from '@aerogel/core/components/contracts/LoadingModal';
|
|
13
|
+
import type { PromptModalProps } from '@aerogel/core/components/contracts/PromptModal';
|
|
14
|
+
import type { ToastAction, ToastVariant } from '@aerogel/core/components/contracts/Toast';
|
|
8
15
|
|
|
9
16
|
import Service from './UI.state';
|
|
10
|
-
import
|
|
17
|
+
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
18
|
+
import type { ModalComponent, UIModal, UIToast } from './UI.state';
|
|
11
19
|
|
|
12
20
|
interface ModalCallbacks<T = unknown> {
|
|
13
21
|
willClose(result: T | undefined): void;
|
|
@@ -15,24 +23,56 @@ interface ModalCallbacks<T = unknown> {
|
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
type ModalProperties<TComponent> = TComponent extends ModalComponent<infer TProperties, unknown> ? TProperties : never;
|
|
18
|
-
type ModalResult<TComponent> =
|
|
19
|
-
? TResult
|
|
20
|
-
: never;
|
|
26
|
+
type ModalResult<TComponent> =
|
|
27
|
+
TComponent extends ModalComponent<Record<string, unknown>, infer TResult> ? TResult : never;
|
|
21
28
|
|
|
22
29
|
export const UIComponents = {
|
|
23
30
|
AlertModal: 'alert-modal',
|
|
24
31
|
ConfirmModal: 'confirm-modal',
|
|
25
32
|
ErrorReportModal: 'error-report-modal',
|
|
26
33
|
LoadingModal: 'loading-modal',
|
|
27
|
-
|
|
34
|
+
PromptModal: 'prompt-modal',
|
|
35
|
+
Toast: 'toast',
|
|
36
|
+
StartupCrash: 'startup-crash',
|
|
28
37
|
} as const;
|
|
29
38
|
|
|
30
39
|
export type UIComponent = ObjectValues<typeof UIComponents>;
|
|
31
40
|
|
|
32
|
-
export
|
|
41
|
+
export type ConfirmOptions = AcceptRefs<{
|
|
42
|
+
acceptText?: string;
|
|
43
|
+
acceptVariant?: ButtonVariant;
|
|
44
|
+
cancelText?: string;
|
|
45
|
+
cancelVariant?: ButtonVariant;
|
|
46
|
+
actions?: Record<string, () => unknown>;
|
|
47
|
+
required?: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
|
|
50
|
+
export type LoadingOptions = AcceptRefs<{
|
|
51
|
+
title?: string;
|
|
52
|
+
message?: string;
|
|
53
|
+
progress?: number;
|
|
54
|
+
}>;
|
|
55
|
+
|
|
56
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
57
|
+
extends ConfirmOptions {
|
|
58
|
+
checkboxes?: T;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type PromptOptions = AcceptRefs<{
|
|
62
|
+
label?: string;
|
|
63
|
+
defaultValue?: string;
|
|
64
|
+
placeholder?: string;
|
|
65
|
+
acceptText?: string;
|
|
66
|
+
acceptVariant?: ButtonVariant;
|
|
67
|
+
cancelText?: string;
|
|
68
|
+
cancelVariant?: ButtonVariant;
|
|
69
|
+
trim?: boolean;
|
|
70
|
+
}>;
|
|
71
|
+
|
|
72
|
+
export interface ToastOptions {
|
|
33
73
|
component?: Component;
|
|
34
|
-
|
|
35
|
-
actions?:
|
|
74
|
+
variant?: ToastVariant;
|
|
75
|
+
actions?: ToastAction[];
|
|
36
76
|
}
|
|
37
77
|
|
|
38
78
|
export class UIService extends Service {
|
|
@@ -47,55 +87,174 @@ export class UIService extends Service {
|
|
|
47
87
|
public alert(message: string): void;
|
|
48
88
|
public alert(title: string, message: string): void;
|
|
49
89
|
public alert(messageOrTitle: string, message?: string): void {
|
|
50
|
-
const
|
|
90
|
+
const getProperties = (): AlertModalProps => {
|
|
91
|
+
if (typeof message !== 'string') {
|
|
92
|
+
return { message: messageOrTitle };
|
|
93
|
+
}
|
|
51
94
|
|
|
52
|
-
|
|
95
|
+
return {
|
|
96
|
+
title: messageOrTitle,
|
|
97
|
+
message,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
this.openModal<ModalComponent<AlertModalProps>>(
|
|
102
|
+
this.requireComponent(UIComponents.AlertModal),
|
|
103
|
+
getProperties(),
|
|
104
|
+
);
|
|
53
105
|
}
|
|
54
106
|
|
|
55
|
-
|
|
56
|
-
public async confirm(
|
|
57
|
-
public async confirm(
|
|
58
|
-
|
|
59
|
-
|
|
107
|
+
/* eslint-disable max-len */
|
|
108
|
+
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
109
|
+
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
110
|
+
public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
111
|
+
public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
112
|
+
/* eslint-enable max-len */
|
|
113
|
+
|
|
114
|
+
public async confirm(
|
|
115
|
+
messageOrTitle: string,
|
|
116
|
+
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
117
|
+
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
118
|
+
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
119
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
120
|
+
if (typeof messageOrOptions !== 'string') {
|
|
121
|
+
return {
|
|
122
|
+
...(messageOrOptions ?? {}),
|
|
123
|
+
message: messageOrTitle,
|
|
124
|
+
required: !!messageOrOptions?.required,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...(options ?? {}),
|
|
130
|
+
title: messageOrTitle,
|
|
131
|
+
message: messageOrOptions,
|
|
132
|
+
required: !!options?.required,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type ConfirmModalComponent = ModalComponent<
|
|
137
|
+
AcceptRefs<ConfirmModalProps>,
|
|
138
|
+
boolean | [boolean, Record<string, boolean>]
|
|
139
|
+
>;
|
|
140
|
+
|
|
141
|
+
const properties = getProperties();
|
|
142
|
+
const modal = await this.openModal<ConfirmModalComponent>(
|
|
60
143
|
this.requireComponent(UIComponents.ConfirmModal),
|
|
61
|
-
|
|
144
|
+
properties,
|
|
62
145
|
);
|
|
63
146
|
const result = await modal.beforeClose;
|
|
64
147
|
|
|
65
|
-
|
|
148
|
+
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
149
|
+
const checkboxes =
|
|
150
|
+
typeof result === 'object'
|
|
151
|
+
? result[1]
|
|
152
|
+
: Object.entries(properties.checkboxes ?? {}).reduce(
|
|
153
|
+
(values, [checkbox, { default: defaultValue }]) => ({
|
|
154
|
+
[checkbox]: defaultValue ?? false,
|
|
155
|
+
...values,
|
|
156
|
+
}),
|
|
157
|
+
{} as Record<string, boolean>,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
for (const [name, checkbox] of Object.entries(properties.checkboxes ?? {})) {
|
|
161
|
+
if (!checkbox.required || checkboxes[name]) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (confirmed && isDevelopment()) {
|
|
166
|
+
// eslint-disable-next-line no-console
|
|
167
|
+
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return [false, checkboxes];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return 'checkboxes' in properties ? [confirmed, checkboxes] : confirmed;
|
|
66
174
|
}
|
|
67
175
|
|
|
68
|
-
public async
|
|
69
|
-
public async
|
|
70
|
-
public async
|
|
71
|
-
|
|
176
|
+
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
177
|
+
public async prompt(title: string, message: string, options?: PromptOptions): Promise<string | null>;
|
|
178
|
+
public async prompt(
|
|
179
|
+
messageOrTitle: string,
|
|
180
|
+
messageOrOptions?: string | PromptOptions,
|
|
181
|
+
options?: PromptOptions,
|
|
182
|
+
): Promise<string | null> {
|
|
183
|
+
const trim = options?.trim ?? true;
|
|
184
|
+
const getProperties = (): PromptModalProps => {
|
|
185
|
+
if (typeof messageOrOptions !== 'string') {
|
|
186
|
+
return {
|
|
187
|
+
message: messageOrTitle,
|
|
188
|
+
...(messageOrOptions ?? {}),
|
|
189
|
+
} as PromptModalProps;
|
|
190
|
+
}
|
|
72
191
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
192
|
+
return {
|
|
193
|
+
title: messageOrTitle,
|
|
194
|
+
message: messageOrOptions,
|
|
195
|
+
...(options ?? {}),
|
|
196
|
+
} as PromptModalProps;
|
|
197
|
+
};
|
|
76
198
|
|
|
77
|
-
await this.
|
|
199
|
+
const modal = await this.openModal<ModalComponent<PromptModalProps, string | null>>(
|
|
200
|
+
this.requireComponent(UIComponents.PromptModal),
|
|
201
|
+
getProperties(),
|
|
202
|
+
);
|
|
203
|
+
const rawResult = await modal.beforeClose;
|
|
204
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
78
205
|
|
|
79
|
-
return result;
|
|
206
|
+
return result ?? null;
|
|
80
207
|
}
|
|
81
208
|
|
|
82
|
-
public
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
209
|
+
public async loading<T>(operation: Promise<T> | (() => T)): Promise<T>;
|
|
210
|
+
public async loading<T>(message: string, operation: Promise<T> | (() => T)): Promise<T>;
|
|
211
|
+
public async loading<T>(options: LoadingOptions, operation: Promise<T> | (() => T)): Promise<T>;
|
|
212
|
+
public async loading<T>(
|
|
213
|
+
operationOrMessageOrOptions: string | LoadingOptions | Promise<T> | (() => T),
|
|
214
|
+
operation?: Promise<T> | (() => T),
|
|
215
|
+
): Promise<T> {
|
|
216
|
+
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
217
|
+
const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
|
|
218
|
+
if (typeof operationOrMessageOrOptions === 'string') {
|
|
219
|
+
return {
|
|
220
|
+
props: { message: operationOrMessageOrOptions },
|
|
221
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (typeof operationOrMessageOrOptions === 'function' || operationOrMessageOrOptions instanceof Promise) {
|
|
226
|
+
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
props: operationOrMessageOrOptions,
|
|
231
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
232
|
+
};
|
|
87
233
|
};
|
|
88
234
|
|
|
89
|
-
|
|
235
|
+
const { operationPromise, props } = processArgs();
|
|
236
|
+
const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), props);
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const result = await operationPromise;
|
|
90
240
|
|
|
91
|
-
|
|
241
|
+
await after({ ms: 500 });
|
|
242
|
+
|
|
243
|
+
return result;
|
|
244
|
+
} finally {
|
|
245
|
+
await this.closeModal(modal.id);
|
|
246
|
+
}
|
|
92
247
|
}
|
|
93
248
|
|
|
94
|
-
public
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
249
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
250
|
+
const { component, ...otherOptions } = options;
|
|
251
|
+
const toast: UIToast = {
|
|
252
|
+
id: uuid(),
|
|
253
|
+
properties: { message, ...otherOptions },
|
|
254
|
+
component: markRaw(component ?? this.requireComponent(UIComponents.Toast)),
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
99
258
|
}
|
|
100
259
|
|
|
101
260
|
public registerComponent(name: UIComponent, component: Component): void {
|
|
@@ -105,10 +264,10 @@ export class UIService extends Service {
|
|
|
105
264
|
public async openModal<TModalComponent extends ModalComponent>(
|
|
106
265
|
component: TModalComponent,
|
|
107
266
|
properties?: ModalProperties<TModalComponent>,
|
|
108
|
-
): Promise<
|
|
267
|
+
): Promise<UIModal<ModalResult<TModalComponent>>> {
|
|
109
268
|
const id = uuid();
|
|
110
269
|
const callbacks: Partial<ModalCallbacks<ModalResult<TModalComponent>>> = {};
|
|
111
|
-
const modal:
|
|
270
|
+
const modal: UIModal<ModalResult<TModalComponent>> = {
|
|
112
271
|
id,
|
|
113
272
|
properties: properties ?? {},
|
|
114
273
|
component: markRaw(component),
|
|
@@ -133,11 +292,40 @@ export class UIService extends Service {
|
|
|
133
292
|
}
|
|
134
293
|
|
|
135
294
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
295
|
+
if (!App.isMounted()) {
|
|
296
|
+
await this.removeModal(id, result);
|
|
297
|
+
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
136
301
|
await Events.emit('close-modal', { id, result });
|
|
137
302
|
}
|
|
138
303
|
|
|
139
|
-
|
|
304
|
+
public async closeAllModals(): Promise<void> {
|
|
305
|
+
while (this.modals.length > 0) {
|
|
306
|
+
await this.closeModal(required(this.modals[this.modals.length - 1]).id);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
protected override async boot(): Promise<void> {
|
|
140
311
|
this.watchModalEvents();
|
|
312
|
+
this.watchMountedEvent();
|
|
313
|
+
this.watchViewportBreakpoints();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private async removeModal(id: string, result?: unknown): Promise<void> {
|
|
317
|
+
this.setState(
|
|
318
|
+
'modals',
|
|
319
|
+
this.modals.filter((m) => m.id !== id),
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
this.modalCallbacks[id]?.closed?.(result);
|
|
323
|
+
|
|
324
|
+
delete this.modalCallbacks[id];
|
|
325
|
+
|
|
326
|
+
const activeModal = this.modals.at(-1);
|
|
327
|
+
|
|
328
|
+
await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
|
|
141
329
|
}
|
|
142
330
|
|
|
143
331
|
private watchModalEvents(): void {
|
|
@@ -149,32 +337,55 @@ export class UIService extends Service {
|
|
|
149
337
|
}
|
|
150
338
|
});
|
|
151
339
|
|
|
152
|
-
Events.on('modal-closed', async ({ modal, result }) => {
|
|
153
|
-
this.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
);
|
|
340
|
+
Events.on('modal-closed', async ({ modal: { id }, result }) => {
|
|
341
|
+
await this.removeModal(id, result);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
157
344
|
|
|
158
|
-
|
|
345
|
+
private watchMountedEvent(): void {
|
|
346
|
+
Events.once('application-mounted', async () => {
|
|
347
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const splash = globalThis.document.getElementById('splash');
|
|
159
352
|
|
|
160
|
-
|
|
353
|
+
if (!splash) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
161
356
|
|
|
162
|
-
|
|
357
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
358
|
+
splash.style.opacity = '0';
|
|
163
359
|
|
|
164
|
-
|
|
360
|
+
await after({ ms: 600 });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
splash.remove();
|
|
165
364
|
});
|
|
166
365
|
}
|
|
167
366
|
|
|
367
|
+
private watchViewportBreakpoints(): void {
|
|
368
|
+
if (!globalThis.matchMedia) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const media = globalThis.matchMedia(`(min-width: ${MOBILE_BREAKPOINT}px)`);
|
|
373
|
+
|
|
374
|
+
media.addEventListener('change', () => this.setState({ layout: getCurrentLayout() }));
|
|
375
|
+
}
|
|
376
|
+
|
|
168
377
|
}
|
|
169
378
|
|
|
170
|
-
export default facade(
|
|
379
|
+
export default facade(UIService);
|
|
171
380
|
|
|
172
|
-
declare module '
|
|
381
|
+
declare module '@aerogel/core/services/Events' {
|
|
173
382
|
export interface EventsPayload {
|
|
174
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
175
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
176
383
|
'close-modal': { id: string; result?: unknown };
|
|
177
384
|
'hide-modal': { id: string };
|
|
385
|
+
'hide-overlays-backdrop': void;
|
|
386
|
+
'modal-closed': { modal: UIModal; result?: unknown };
|
|
387
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
178
388
|
'show-modal': { id: string };
|
|
389
|
+
'show-overlays-backdrop': void;
|
|
179
390
|
}
|
|
180
391
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
import type { Component } from 'vue';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import AlertModal from '@aerogel/core/components/ui/AlertModal.vue';
|
|
4
|
+
import ConfirmModal from '@aerogel/core/components/ui/ConfirmModal.vue';
|
|
5
|
+
import ErrorReportModal from '@aerogel/core/components/ui/ErrorReportModal.vue';
|
|
6
|
+
import LoadingModal from '@aerogel/core/components/ui/LoadingModal.vue';
|
|
7
|
+
import PromptModal from '@aerogel/core/components/ui/PromptModal.vue';
|
|
8
|
+
import StartupCrash from '@aerogel/core/components/ui/StartupCrash.vue';
|
|
9
|
+
import Toast from '@aerogel/core/components/ui/Toast.vue';
|
|
10
|
+
import { bootServices } from '@aerogel/core/services';
|
|
11
|
+
import { definePlugin } from '@aerogel/core/plugins';
|
|
5
12
|
|
|
6
13
|
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
14
|
import type { UIComponent } from './UI';
|
|
13
15
|
|
|
14
|
-
export { UI, UIComponents, UIComponent };
|
|
15
|
-
|
|
16
16
|
const services = { $ui: UI };
|
|
17
17
|
|
|
18
|
+
export * from './UI';
|
|
19
|
+
export * from './utils';
|
|
20
|
+
export { default as UI } from './UI';
|
|
21
|
+
|
|
18
22
|
export type UIServices = typeof services;
|
|
19
23
|
|
|
20
24
|
export default definePlugin({
|
|
21
25
|
async install(app, options) {
|
|
22
26
|
const defaultComponents = {
|
|
23
|
-
[UIComponents.AlertModal]:
|
|
24
|
-
[UIComponents.ConfirmModal]:
|
|
25
|
-
[UIComponents.ErrorReportModal]:
|
|
26
|
-
[UIComponents.LoadingModal]:
|
|
27
|
-
[UIComponents.
|
|
27
|
+
[UIComponents.AlertModal]: AlertModal,
|
|
28
|
+
[UIComponents.ConfirmModal]: ConfirmModal,
|
|
29
|
+
[UIComponents.ErrorReportModal]: ErrorReportModal,
|
|
30
|
+
[UIComponents.LoadingModal]: LoadingModal,
|
|
31
|
+
[UIComponents.PromptModal]: PromptModal,
|
|
32
|
+
[UIComponents.Toast]: Toast,
|
|
33
|
+
[UIComponents.StartupCrash]: StartupCrash,
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
Object.entries({
|
|
@@ -36,12 +42,12 @@ export default definePlugin({
|
|
|
36
42
|
},
|
|
37
43
|
});
|
|
38
44
|
|
|
39
|
-
declare module '
|
|
40
|
-
interface AerogelOptions {
|
|
45
|
+
declare module '@aerogel/core/bootstrap/options' {
|
|
46
|
+
export interface AerogelOptions {
|
|
41
47
|
components?: Partial<Record<UIComponent, Component>>;
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
declare module '
|
|
51
|
+
declare module '@aerogel/core/services' {
|
|
46
52
|
export interface Services extends UIServices {}
|
|
47
53
|
}
|
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
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
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
9
|
UnknownEvent,
|
|
10
|
-
} from '
|
|
10
|
+
} from '@aerogel/core/services/Events';
|
|
11
11
|
|
|
12
12
|
export function useEvent<Event extends EventWithoutPayload>(event: Event, listener: () => unknown): void;
|
|
13
13
|
export function useEvent<Event extends EventWithPayload>(
|
|
14
14
|
event: Event,
|
|
15
15
|
listener: EventListener<EventsPayload[Event]>
|
|
16
16
|
): void;
|
|
17
|
+
export function useEvent<Payload>(event: string, listener: (payload: Payload) => unknown): void;
|
|
17
18
|
export function useEvent<Event extends string>(event: UnknownEvent<Event>, listener: EventListener): void;
|
|
18
19
|
|
|
19
20
|
export function useEvent(event: string, listener: EventListener): void {
|