@aerogel/core 0.0.0-next.f86b4b09f066c4aef21796a37dbc8417b7dce3cd → 0.0.0-next.f8c757d83e1e0d001a2836fa45aba318ec17b9b9
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 +1973 -1688
- package/dist/aerogel-core.js +3234 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +30 -37
- package/src/bootstrap/bootstrap.test.ts +4 -7
- package/src/bootstrap/index.ts +14 -15
- package/src/bootstrap/options.ts +1 -1
- package/src/components/{AGAppLayout.vue → AppLayout.vue} +4 -4
- package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
- package/src/components/{AGAppOverlays.vue → AppOverlays.vue} +5 -10
- 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 +18 -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 +30 -0
- package/src/components/contracts/Select.ts +44 -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 +51 -0
- package/src/components/headless/HeadlessInput.vue +59 -0
- package/src/components/headless/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +7 -8
- package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
- package/src/components/headless/{forms/AGHeadlessInputInput.vue → HeadlessInputInput.vue} +16 -25
- package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
- package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +10 -13
- package/src/components/headless/{modals/AGHeadlessModal.vue → HeadlessModal.vue} +18 -18
- package/src/components/headless/HeadlessModalContent.vue +24 -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 +113 -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 +37 -0
- package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
- package/src/components/headless/HeadlessSelectValue.vue +18 -0
- package/src/components/headless/HeadlessToast.vue +18 -0
- package/src/components/headless/HeadlessToastAction.vue +13 -0
- package/src/components/headless/index.ts +19 -3
- package/src/components/index.ts +6 -11
- package/src/components/ui/AdvancedOptions.vue +18 -0
- package/src/components/ui/AlertModal.vue +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 +27 -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 +62 -0
- package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +34 -27
- package/src/components/ui/ErrorReportModalTitle.vue +24 -0
- package/src/components/{forms/AGForm.vue → ui/Form.vue} +4 -5
- package/src/components/ui/Input.vue +56 -0
- package/src/components/ui/Link.vue +12 -0
- package/src/components/ui/LoadingModal.vue +32 -0
- package/src/components/ui/Markdown.vue +69 -0
- package/src/components/ui/Modal.vue +75 -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 +25 -0
- package/src/components/ui/SelectLabel.vue +17 -0
- package/src/components/ui/SelectOption.vue +29 -0
- package/src/components/ui/SelectOptions.vue +30 -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 +42 -0
- package/src/components/ui/index.ts +30 -0
- package/src/directives/index.ts +9 -5
- package/src/directives/measure.ts +1 -1
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +17 -18
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +9 -6
- package/src/errors/utils.ts +1 -1
- package/src/forms/{Form.test.ts → FormController.test.ts} +5 -4
- package/src/forms/{Form.ts → FormController.ts} +22 -19
- package/src/forms/composition.ts +4 -4
- package/src/forms/index.ts +2 -2
- package/src/forms/utils.ts +2 -2
- package/src/index.css +46 -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 +7 -4
- package/src/lang/Lang.state.ts +1 -1
- package/src/lang/Lang.ts +1 -1
- 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 +1 -1
- package/src/plugins/index.ts +10 -7
- package/src/services/App.state.ts +21 -5
- package/src/services/App.ts +7 -4
- package/src/services/Cache.ts +1 -1
- package/src/services/Events.ts +15 -5
- package/src/services/Service.ts +116 -53
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +14 -5
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +4 -3
- package/src/testing/setup.ts +5 -13
- package/src/ui/UI.state.ts +12 -7
- package/src/ui/UI.ts +126 -84
- package/src/ui/index.ts +18 -18
- package/src/utils/classes.ts +49 -0
- package/src/utils/composition/events.ts +2 -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.ts +11 -2
- package/src/utils/index.ts +4 -1
- package/src/utils/markdown.test.ts +50 -0
- package/src/utils/markdown.ts +19 -6
- package/src/utils/vue.ts +28 -136
- package/dist/aerogel-core.cjs.js +0 -2
- package/dist/aerogel-core.cjs.js.map +0 -1
- package/dist/aerogel-core.esm.js +0 -2
- package/dist/aerogel-core.esm.js.map +0 -1
- package/histoire.config.ts +0 -7
- package/noeldemartin.config.js +0 -5
- package/postcss.config.js +0 -6
- package/src/assets/histoire.css +0 -3
- package/src/components/AGAppSnackbars.vue +0 -13
- package/src/components/composition.ts +0 -23
- package/src/components/constants.ts +0 -8
- package/src/components/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -41
- package/src/components/forms/AGInput.vue +0 -40
- package/src/components/forms/AGSelect.story.vue +0 -46
- package/src/components/forms/AGSelect.vue +0 -60
- package/src/components/forms/index.ts +0 -5
- package/src/components/headless/forms/AGHeadlessButton.ts +0 -3
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -62
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -34
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -70
- package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
- package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
- package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
- package/src/components/headless/forms/composition.ts +0 -10
- package/src/components/headless/forms/index.ts +0 -18
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -34
- 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/interfaces.ts +0 -24
- package/src/components/lib/AGErrorMessage.vue +0 -16
- package/src/components/lib/AGLink.vue +0 -9
- package/src/components/lib/AGMarkdown.vue +0 -41
- package/src/components/lib/AGMeasured.vue +0 -16
- 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 -35
- 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 -36
- 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/main.histoire.ts +0 -1
- package/src/utils/tailwindcss.test.ts +0 -26
- package/src/utils/tailwindcss.ts +0 -7
- package/tailwind.config.js +0 -4
- package/tsconfig.json +0 -11
- package/vite.config.ts +0 -17
- /package/src/{main.ts → index.ts} +0 -0
package/src/ui/UI.state.ts
CHANGED
|
@@ -1,10 +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
6
|
|
|
7
|
-
export interface
|
|
7
|
+
export interface UIModal<T = unknown> {
|
|
8
8
|
id: string;
|
|
9
9
|
properties: Record<string, unknown>;
|
|
10
10
|
component: Component;
|
|
@@ -12,14 +12,19 @@ export interface Modal<T = unknown> {
|
|
|
12
12
|
afterClose: Promise<T | undefined>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface UIModalContext {
|
|
16
|
+
modal: UIModal;
|
|
17
|
+
childIndex?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
export interface ModalComponent<
|
|
16
21
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
17
|
-
Properties extends
|
|
22
|
+
Properties extends object = object,
|
|
18
23
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
|
-
Result = unknown
|
|
24
|
+
Result = unknown,
|
|
20
25
|
> {}
|
|
21
26
|
|
|
22
|
-
export interface
|
|
27
|
+
export interface UIToast {
|
|
23
28
|
id: string;
|
|
24
29
|
component: Component;
|
|
25
30
|
properties: Record<string, unknown>;
|
|
@@ -28,8 +33,8 @@ export interface Snackbar {
|
|
|
28
33
|
export default defineServiceState({
|
|
29
34
|
name: 'ui',
|
|
30
35
|
initialState: {
|
|
31
|
-
modals: [] as
|
|
32
|
-
|
|
36
|
+
modals: [] as UIModal[],
|
|
37
|
+
toasts: [] as UIToast[],
|
|
33
38
|
layout: getCurrentLayout(),
|
|
34
39
|
},
|
|
35
40
|
computed: {
|
package/src/ui/UI.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
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
3
|
import type { Component } from 'vue';
|
|
4
4
|
import type { ObjectValues } from '@noeldemartin/utils';
|
|
5
5
|
|
|
6
|
-
import App from '
|
|
7
|
-
import Events from '
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
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';
|
|
11
15
|
|
|
12
16
|
import Service from './UI.state';
|
|
13
17
|
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
14
|
-
import type {
|
|
18
|
+
import type { ModalComponent, UIModal, UIToast } from './UI.state';
|
|
15
19
|
|
|
16
20
|
interface ModalCallbacks<T = unknown> {
|
|
17
21
|
willClose(result: T | undefined): void;
|
|
@@ -19,9 +23,8 @@ interface ModalCallbacks<T = unknown> {
|
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
type ModalProperties<TComponent> = TComponent extends ModalComponent<infer TProperties, unknown> ? TProperties : never;
|
|
22
|
-
type ModalResult<TComponent> =
|
|
23
|
-
? TResult
|
|
24
|
-
: never;
|
|
26
|
+
type ModalResult<TComponent> =
|
|
27
|
+
TComponent extends ModalComponent<Record<string, unknown>, infer TResult> ? TResult : never;
|
|
25
28
|
|
|
26
29
|
export const UIComponents = {
|
|
27
30
|
AlertModal: 'alert-modal',
|
|
@@ -29,40 +32,47 @@ export const UIComponents = {
|
|
|
29
32
|
ErrorReportModal: 'error-report-modal',
|
|
30
33
|
LoadingModal: 'loading-modal',
|
|
31
34
|
PromptModal: 'prompt-modal',
|
|
32
|
-
|
|
35
|
+
Toast: 'toast',
|
|
33
36
|
StartupCrash: 'startup-crash',
|
|
34
37
|
} as const;
|
|
35
38
|
|
|
36
39
|
export type UIComponent = ObjectValues<typeof UIComponents>;
|
|
37
40
|
|
|
38
|
-
export type
|
|
39
|
-
|
|
40
|
-
export interface ConfirmOptions {
|
|
41
|
+
export type ConfirmOptions = AcceptRefs<{
|
|
41
42
|
acceptText?: string;
|
|
42
|
-
|
|
43
|
+
acceptVariant?: ButtonVariant;
|
|
43
44
|
cancelText?: string;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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 {
|
|
48
58
|
checkboxes?: T;
|
|
49
59
|
}
|
|
50
60
|
|
|
51
|
-
export
|
|
61
|
+
export type PromptOptions = AcceptRefs<{
|
|
52
62
|
label?: string;
|
|
53
63
|
defaultValue?: string;
|
|
54
64
|
placeholder?: string;
|
|
55
65
|
acceptText?: string;
|
|
56
|
-
|
|
66
|
+
acceptVariant?: ButtonVariant;
|
|
57
67
|
cancelText?: string;
|
|
58
|
-
|
|
68
|
+
cancelVariant?: ButtonVariant;
|
|
59
69
|
trim?: boolean;
|
|
60
|
-
}
|
|
70
|
+
}>;
|
|
61
71
|
|
|
62
|
-
export interface
|
|
72
|
+
export interface ToastOptions {
|
|
63
73
|
component?: Component;
|
|
64
|
-
|
|
65
|
-
actions?:
|
|
74
|
+
variant?: ToastVariant;
|
|
75
|
+
actions?: ToastAction[];
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
export class UIService extends Service {
|
|
@@ -77,7 +87,7 @@ export class UIService extends Service {
|
|
|
77
87
|
public alert(message: string): void;
|
|
78
88
|
public alert(title: string, message: string): void;
|
|
79
89
|
public alert(messageOrTitle: string, message?: string): void {
|
|
80
|
-
const getProperties = ():
|
|
90
|
+
const getProperties = (): AlertModalProps => {
|
|
81
91
|
if (typeof message !== 'string') {
|
|
82
92
|
return { message: messageOrTitle };
|
|
83
93
|
}
|
|
@@ -88,14 +98,17 @@ export class UIService extends Service {
|
|
|
88
98
|
};
|
|
89
99
|
};
|
|
90
100
|
|
|
91
|
-
this.openModal(
|
|
101
|
+
this.openModal<ModalComponent<AlertModalProps>>(
|
|
102
|
+
this.requireComponent(UIComponents.AlertModal),
|
|
103
|
+
getProperties(),
|
|
104
|
+
);
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
/* eslint-disable max-len */
|
|
95
108
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
96
109
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
97
|
-
public async confirm<T extends
|
|
98
|
-
public async confirm<T extends
|
|
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
|
|
99
112
|
/* eslint-enable max-len */
|
|
100
113
|
|
|
101
114
|
public async confirm(
|
|
@@ -103,27 +116,36 @@ export class UIService extends Service {
|
|
|
103
116
|
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
104
117
|
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
105
118
|
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
106
|
-
const getProperties = ():
|
|
119
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
107
120
|
if (typeof messageOrOptions !== 'string') {
|
|
108
121
|
return {
|
|
109
|
-
message: messageOrTitle,
|
|
110
122
|
...(messageOrOptions ?? {}),
|
|
123
|
+
message: messageOrTitle,
|
|
124
|
+
required: !!messageOrOptions?.required,
|
|
111
125
|
};
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
return {
|
|
129
|
+
...(options ?? {}),
|
|
115
130
|
title: messageOrTitle,
|
|
116
131
|
message: messageOrOptions,
|
|
117
|
-
|
|
132
|
+
required: !!options?.required,
|
|
118
133
|
};
|
|
119
134
|
};
|
|
135
|
+
|
|
136
|
+
type ConfirmModalComponent = ModalComponent<
|
|
137
|
+
AcceptRefs<ConfirmModalProps>,
|
|
138
|
+
boolean | [boolean, Record<string, boolean>]
|
|
139
|
+
>;
|
|
140
|
+
|
|
120
141
|
const properties = getProperties();
|
|
121
|
-
const modal = await this.openModal<
|
|
122
|
-
|
|
123
|
-
|
|
142
|
+
const modal = await this.openModal<ConfirmModalComponent>(
|
|
143
|
+
this.requireComponent(UIComponents.ConfirmModal),
|
|
144
|
+
properties,
|
|
145
|
+
);
|
|
124
146
|
const result = await modal.beforeClose;
|
|
125
147
|
|
|
126
|
-
const confirmed = typeof result === 'object' ? result[0] : result ?? false;
|
|
148
|
+
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
127
149
|
const checkboxes =
|
|
128
150
|
typeof result === 'object'
|
|
129
151
|
? result[1]
|
|
@@ -140,7 +162,7 @@ export class UIService extends Service {
|
|
|
140
162
|
continue;
|
|
141
163
|
}
|
|
142
164
|
|
|
143
|
-
if (confirmed &&
|
|
165
|
+
if (confirmed && isDevelopment()) {
|
|
144
166
|
// eslint-disable-next-line no-console
|
|
145
167
|
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
146
168
|
}
|
|
@@ -159,22 +181,22 @@ export class UIService extends Service {
|
|
|
159
181
|
options?: PromptOptions,
|
|
160
182
|
): Promise<string | null> {
|
|
161
183
|
const trim = options?.trim ?? true;
|
|
162
|
-
const getProperties = ():
|
|
184
|
+
const getProperties = (): PromptModalProps => {
|
|
163
185
|
if (typeof messageOrOptions !== 'string') {
|
|
164
186
|
return {
|
|
165
187
|
message: messageOrTitle,
|
|
166
188
|
...(messageOrOptions ?? {}),
|
|
167
|
-
};
|
|
189
|
+
} as PromptModalProps;
|
|
168
190
|
}
|
|
169
191
|
|
|
170
192
|
return {
|
|
171
193
|
title: messageOrTitle,
|
|
172
194
|
message: messageOrOptions,
|
|
173
195
|
...(options ?? {}),
|
|
174
|
-
};
|
|
196
|
+
} as PromptModalProps;
|
|
175
197
|
};
|
|
176
198
|
|
|
177
|
-
const modal = await this.openModal<ModalComponent<
|
|
199
|
+
const modal = await this.openModal<ModalComponent<PromptModalProps, string | null>>(
|
|
178
200
|
this.requireComponent(UIComponents.PromptModal),
|
|
179
201
|
getProperties(),
|
|
180
202
|
);
|
|
@@ -186,25 +208,37 @@ export class UIService extends Service {
|
|
|
186
208
|
|
|
187
209
|
public async loading<T>(operation: Promise<T> | (() => T)): Promise<T>;
|
|
188
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>;
|
|
189
212
|
public async loading<T>(
|
|
190
|
-
|
|
213
|
+
operationOrMessageOrOptions: string | LoadingOptions | Promise<T> | (() => T),
|
|
191
214
|
operation?: Promise<T> | (() => T),
|
|
192
215
|
): Promise<T> {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
};
|
|
196
223
|
}
|
|
197
224
|
|
|
198
|
-
|
|
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
|
+
};
|
|
199
233
|
};
|
|
200
234
|
|
|
201
|
-
const
|
|
235
|
+
const { operationPromise, props } = processArgs();
|
|
236
|
+
const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), props);
|
|
202
237
|
|
|
203
238
|
try {
|
|
204
|
-
|
|
205
|
-
operation = typeof operation === 'function' ? Promise.resolve(operation()) : operation;
|
|
239
|
+
const result = await operationPromise;
|
|
206
240
|
|
|
207
|
-
|
|
241
|
+
await after({ ms: 500 });
|
|
208
242
|
|
|
209
243
|
return result;
|
|
210
244
|
} finally {
|
|
@@ -212,23 +246,15 @@ export class UIService extends Service {
|
|
|
212
246
|
}
|
|
213
247
|
}
|
|
214
248
|
|
|
215
|
-
public
|
|
216
|
-
const
|
|
249
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
250
|
+
const { component, ...otherOptions } = options;
|
|
251
|
+
const toast: UIToast = {
|
|
217
252
|
id: uuid(),
|
|
218
|
-
properties: { message, ...
|
|
219
|
-
component: markRaw(
|
|
253
|
+
properties: { message, ...otherOptions },
|
|
254
|
+
component: markRaw(component ?? this.requireComponent(UIComponents.Toast)),
|
|
220
255
|
};
|
|
221
256
|
|
|
222
|
-
this.setState('
|
|
223
|
-
|
|
224
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
public hideSnackbar(id: string): void {
|
|
228
|
-
this.setState(
|
|
229
|
-
'snackbars',
|
|
230
|
-
this.snackbars.filter((snackbar) => snackbar.id !== id),
|
|
231
|
-
);
|
|
257
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
232
258
|
}
|
|
233
259
|
|
|
234
260
|
public registerComponent(name: UIComponent, component: Component): void {
|
|
@@ -238,10 +264,10 @@ export class UIService extends Service {
|
|
|
238
264
|
public async openModal<TModalComponent extends ModalComponent>(
|
|
239
265
|
component: TModalComponent,
|
|
240
266
|
properties?: ModalProperties<TModalComponent>,
|
|
241
|
-
): Promise<
|
|
267
|
+
): Promise<UIModal<ModalResult<TModalComponent>>> {
|
|
242
268
|
const id = uuid();
|
|
243
269
|
const callbacks: Partial<ModalCallbacks<ModalResult<TModalComponent>>> = {};
|
|
244
|
-
const modal:
|
|
270
|
+
const modal: UIModal<ModalResult<TModalComponent>> = {
|
|
245
271
|
id,
|
|
246
272
|
properties: properties ?? {},
|
|
247
273
|
component: markRaw(component),
|
|
@@ -266,15 +292,42 @@ export class UIService extends Service {
|
|
|
266
292
|
}
|
|
267
293
|
|
|
268
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
|
+
|
|
269
301
|
await Events.emit('close-modal', { id, result });
|
|
270
302
|
}
|
|
271
303
|
|
|
272
|
-
|
|
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> {
|
|
273
311
|
this.watchModalEvents();
|
|
274
312
|
this.watchMountedEvent();
|
|
275
313
|
this.watchViewportBreakpoints();
|
|
276
314
|
}
|
|
277
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 }));
|
|
329
|
+
}
|
|
330
|
+
|
|
278
331
|
private watchModalEvents(): void {
|
|
279
332
|
Events.on('modal-will-close', ({ modal, result }) => {
|
|
280
333
|
this.modalCallbacks[modal.id]?.willClose?.(result);
|
|
@@ -284,19 +337,8 @@ export class UIService extends Service {
|
|
|
284
337
|
}
|
|
285
338
|
});
|
|
286
339
|
|
|
287
|
-
Events.on('modal-closed', async ({ modal, result }) => {
|
|
288
|
-
this.
|
|
289
|
-
'modals',
|
|
290
|
-
this.modals.filter((m) => m.id !== modal.id),
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
this.modalCallbacks[modal.id]?.closed?.(result);
|
|
294
|
-
|
|
295
|
-
delete this.modalCallbacks[modal.id];
|
|
296
|
-
|
|
297
|
-
const activeModal = this.modals.at(-1);
|
|
298
|
-
|
|
299
|
-
await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
|
|
340
|
+
Events.on('modal-closed', async ({ modal: { id }, result }) => {
|
|
341
|
+
await this.removeModal(id, result);
|
|
300
342
|
});
|
|
301
343
|
}
|
|
302
344
|
|
|
@@ -336,13 +378,13 @@ export class UIService extends Service {
|
|
|
336
378
|
|
|
337
379
|
export default facade(UIService);
|
|
338
380
|
|
|
339
|
-
declare module '
|
|
381
|
+
declare module '@aerogel/core/services/Events' {
|
|
340
382
|
export interface EventsPayload {
|
|
341
383
|
'close-modal': { id: string; result?: unknown };
|
|
342
384
|
'hide-modal': { id: string };
|
|
343
385
|
'hide-overlays-backdrop': void;
|
|
344
|
-
'modal-closed': { modal:
|
|
345
|
-
'modal-will-close': { modal:
|
|
386
|
+
'modal-closed': { modal: UIModal; result?: unknown };
|
|
387
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
346
388
|
'show-modal': { id: string };
|
|
347
389
|
'show-overlays-backdrop': void;
|
|
348
390
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
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 AGPromptModal from '../components/modals/AGPromptModal.vue';
|
|
12
|
-
import AGSnackbar from '../components/snackbars/AGSnackbar.vue';
|
|
13
|
-
import AGStartupCrash from '../components/lib/AGStartupCrash.vue';
|
|
14
14
|
import type { UIComponent } from './UI';
|
|
15
15
|
|
|
16
16
|
const services = { $ui: UI };
|
|
@@ -24,13 +24,13 @@ export type UIServices = typeof services;
|
|
|
24
24
|
export default definePlugin({
|
|
25
25
|
async install(app, options) {
|
|
26
26
|
const defaultComponents = {
|
|
27
|
-
[UIComponents.AlertModal]:
|
|
28
|
-
[UIComponents.ConfirmModal]:
|
|
29
|
-
[UIComponents.ErrorReportModal]:
|
|
30
|
-
[UIComponents.LoadingModal]:
|
|
31
|
-
[UIComponents.PromptModal]:
|
|
32
|
-
[UIComponents.
|
|
33
|
-
[UIComponents.StartupCrash]:
|
|
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,
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
Object.entries({
|
|
@@ -42,12 +42,12 @@ export default definePlugin({
|
|
|
42
42
|
},
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
declare module '
|
|
45
|
+
declare module '@aerogel/core/bootstrap/options' {
|
|
46
46
|
export interface AerogelOptions {
|
|
47
47
|
components?: Partial<Record<UIComponent, Component>>;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
declare module '
|
|
51
|
+
declare module '@aerogel/core/services' {
|
|
52
52
|
export interface Services extends UIServices {}
|
|
53
53
|
}
|
|
@@ -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,13 +1,13 @@
|
|
|
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>(
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { objectWithout } from '@noeldemartin/utils';
|
|
2
|
-
import { computed, useAttrs } from 'vue';
|
|
2
|
+
import { computed, inject, onUnmounted, useAttrs } from 'vue';
|
|
3
|
+
import type { ClassValue } from 'clsx';
|
|
3
4
|
import type { ComputedRef } from 'vue';
|
|
5
|
+
import type { FormController } from '@aerogel/core/forms';
|
|
6
|
+
import type { Nullable } from '@noeldemartin/utils';
|
|
4
7
|
|
|
5
|
-
export function
|
|
8
|
+
export function onFormFocus(input: { name: Nullable<string> }, listener: () => unknown): void {
|
|
9
|
+
const form = inject<FormController | null>('form', null);
|
|
10
|
+
const stop = form?.on('focus', (name) => input.name === name && listener());
|
|
11
|
+
|
|
12
|
+
onUnmounted(() => stop?.());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useInputAttrs(): [ComputedRef<{}>, ComputedRef<ClassValue>] {
|
|
6
16
|
const attrs = useAttrs();
|
|
7
|
-
const
|
|
17
|
+
const classes = computed(() => attrs.class);
|
|
8
18
|
const inputAttrs = computed(() => objectWithout(attrs, 'class'));
|
|
9
19
|
|
|
10
|
-
return [inputAttrs,
|
|
20
|
+
return [inputAttrs, classes as ComputedRef<ClassValue>];
|
|
11
21
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
import { Storage } from '@noeldemartin/utils';
|
|
4
|
+
|
|
5
|
+
import { persistent } from './persistent';
|
|
6
|
+
|
|
7
|
+
describe('Vue persistent helper', () => {
|
|
8
|
+
|
|
9
|
+
it('serializes to localStorage', async () => {
|
|
10
|
+
// Arrange
|
|
11
|
+
const store = persistent<{ foo?: string }>('foobar', {});
|
|
12
|
+
|
|
13
|
+
// Act
|
|
14
|
+
store.foo = 'bar';
|
|
15
|
+
|
|
16
|
+
await nextTick();
|
|
17
|
+
|
|
18
|
+
// Assert
|
|
19
|
+
expect(Storage.get('foobar')).toEqual({ foo: 'bar' });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('reads from localStorage', async () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
Storage.set('foobar', { foo: 'bar' });
|
|
25
|
+
|
|
26
|
+
// Act
|
|
27
|
+
const store = persistent<{ foo?: string }>('foobar', {});
|
|
28
|
+
|
|
29
|
+
// Assert
|
|
30
|
+
expect(store.foo).toEqual('bar');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { reactive, toRaw, watch } from 'vue';
|
|
2
|
+
import { Storage } from '@noeldemartin/utils';
|
|
3
|
+
import type { UnwrapNestedRefs } from 'vue';
|
|
4
|
+
|
|
5
|
+
export function persistent<T extends object>(name: string, defaults: T): UnwrapNestedRefs<T> {
|
|
6
|
+
const store = reactive<T>(Storage.get<T>(name) ?? defaults);
|
|
7
|
+
|
|
8
|
+
watch(store, () => Storage.set(name, toRaw(store)));
|
|
9
|
+
|
|
10
|
+
return store;
|
|
11
|
+
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { debounce } from '@noeldemartin/utils';
|
|
2
|
-
import { ref, watchEffect } from 'vue';
|
|
3
|
-
import type { ComputedGetter, ComputedRef } from '
|
|
2
|
+
import { computed, ref, watch, watchEffect } from 'vue';
|
|
3
|
+
import type { ComputedGetter, ComputedRef, Ref } from 'vue';
|
|
4
4
|
|
|
5
5
|
export interface ComputedDebounceOptions<T> {
|
|
6
6
|
initial?: T;
|
|
7
7
|
delay?: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export function computedAsync<T>(getter: () => Promise<T>): Ref<T | undefined> {
|
|
11
|
+
const result = ref<T>();
|
|
12
|
+
const asyncValue = computed(getter);
|
|
13
|
+
|
|
14
|
+
watch(asyncValue, async () => (result.value = await asyncValue.value), { immediate: true });
|
|
15
|
+
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
export function computedDebounce<T>(options: ComputedDebounceOptions<T>, getter: ComputedGetter<T>): ComputedRef<T>;
|
|
11
20
|
export function computedDebounce<T>(getter: ComputedGetter<T>): ComputedRef<T | null>;
|
|
12
21
|
export function computedDebounce<T>(
|