@aerogel/core 0.0.0-next.f8cdd39997c56dcd46e07c26af8a84d04d610fce → 0.0.0-next.f9394854509d71d644498ac087706a2f8f8eea1c
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.cjs.js +1 -1
- package/dist/aerogel-core.cjs.js.map +1 -1
- package/dist/aerogel-core.d.ts +1345 -231
- package/dist/aerogel-core.esm.js +1 -1
- package/dist/aerogel-core.esm.js.map +1 -1
- package/histoire.config.ts +7 -0
- package/package.json +14 -5
- package/postcss.config.js +6 -0
- package/src/assets/histoire.css +3 -0
- package/src/bootstrap/bootstrap.test.ts +3 -3
- package/src/bootstrap/index.ts +32 -5
- package/src/bootstrap/options.ts +3 -0
- package/src/components/AGAppLayout.vue +7 -2
- package/src/components/AGAppOverlays.vue +5 -1
- package/src/components/AGAppSnackbars.vue +2 -2
- package/src/components/composition.ts +23 -0
- package/src/components/forms/AGCheckbox.vue +7 -1
- package/src/components/forms/AGForm.vue +9 -10
- package/src/components/forms/AGInput.vue +10 -6
- package/src/components/forms/AGSelect.story.vue +46 -0
- package/src/components/forms/AGSelect.vue +60 -0
- package/src/components/forms/index.ts +5 -6
- package/src/components/headless/forms/AGHeadlessButton.ts +3 -0
- package/src/components/headless/forms/AGHeadlessButton.vue +23 -12
- package/src/components/headless/forms/AGHeadlessInput.ts +30 -4
- package/src/components/headless/forms/AGHeadlessInput.vue +23 -7
- package/src/components/headless/forms/AGHeadlessInputDescription.vue +28 -0
- package/src/components/headless/forms/AGHeadlessInputInput.vue +44 -5
- package/src/components/headless/forms/AGHeadlessInputLabel.vue +8 -2
- package/src/components/headless/forms/AGHeadlessInputTextArea.vue +43 -0
- package/src/components/headless/forms/AGHeadlessSelect.ts +42 -0
- package/src/components/headless/forms/AGHeadlessSelect.vue +77 -0
- package/src/components/headless/forms/AGHeadlessSelectButton.vue +24 -0
- package/src/components/headless/forms/AGHeadlessSelectError.vue +26 -0
- package/src/components/headless/forms/AGHeadlessSelectLabel.vue +24 -0
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +4 -0
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +39 -0
- package/src/components/headless/forms/AGHeadlessSelectOptions.ts +3 -0
- package/src/components/headless/forms/composition.ts +10 -0
- package/src/components/headless/forms/index.ts +13 -1
- package/src/components/headless/modals/AGHeadlessModal.ts +27 -0
- package/src/components/headless/modals/AGHeadlessModal.vue +3 -5
- package/src/components/headless/modals/index.ts +4 -6
- package/src/components/headless/snackbars/index.ts +23 -8
- package/src/components/index.ts +3 -1
- package/src/components/interfaces.ts +24 -0
- package/src/components/{basic → lib}/AGErrorMessage.vue +2 -2
- package/src/components/{basic → lib}/AGMarkdown.vue +9 -4
- package/src/components/lib/AGMeasured.vue +16 -0
- package/src/components/lib/AGStartupCrash.vue +31 -0
- package/src/components/lib/index.ts +5 -0
- package/src/components/modals/AGAlertModal.ts +15 -0
- package/src/components/modals/AGAlertModal.vue +4 -15
- package/src/components/modals/AGConfirmModal.ts +33 -0
- package/src/components/modals/AGConfirmModal.vue +9 -13
- package/src/components/modals/AGErrorReportModal.ts +27 -1
- package/src/components/modals/AGErrorReportModal.vue +8 -16
- package/src/components/modals/AGErrorReportModalButtons.vue +4 -2
- package/src/components/modals/AGErrorReportModalTitle.vue +1 -1
- package/src/components/modals/AGLoadingModal.ts +23 -0
- package/src/components/modals/AGLoadingModal.vue +4 -8
- package/src/components/modals/AGModal.ts +1 -1
- package/src/components/modals/AGModal.vue +15 -12
- package/src/components/modals/AGModalTitle.vue +9 -0
- package/src/components/modals/AGPromptModal.ts +36 -0
- package/src/components/modals/AGPromptModal.vue +34 -0
- package/src/components/modals/index.ts +13 -17
- package/src/components/snackbars/AGSnackbar.vue +3 -9
- package/src/components/utils.ts +10 -0
- package/src/directives/index.ts +5 -1
- package/src/directives/measure.ts +40 -0
- package/src/errors/Errors.ts +26 -24
- package/src/errors/index.ts +10 -23
- package/src/errors/utils.ts +35 -0
- package/src/forms/Form.test.ts +28 -0
- package/src/forms/Form.ts +80 -14
- package/src/forms/index.ts +3 -1
- package/src/forms/utils.ts +34 -3
- package/src/forms/validation.ts +19 -0
- package/src/jobs/Job.ts +5 -0
- package/src/jobs/index.ts +7 -0
- package/src/lang/DefaultLangProvider.ts +43 -0
- package/src/lang/Lang.state.ts +11 -0
- package/src/lang/Lang.ts +44 -29
- package/src/main.histoire.ts +1 -0
- package/src/main.ts +3 -0
- package/src/plugins/Plugin.ts +1 -0
- package/src/plugins/index.ts +19 -0
- package/src/services/App.state.ts +20 -5
- package/src/services/App.ts +38 -3
- package/src/services/Cache.ts +43 -0
- package/src/services/Events.test.ts +39 -0
- package/src/services/Events.ts +100 -30
- package/src/services/Service.ts +59 -17
- package/src/services/index.ts +5 -2
- package/src/services/store.ts +8 -5
- package/src/testing/index.ts +25 -0
- package/src/testing/setup.ts +19 -0
- package/src/ui/UI.state.ts +7 -0
- package/src/ui/UI.ts +155 -22
- package/src/ui/index.ts +9 -3
- package/src/ui/utils.ts +16 -0
- package/src/utils/composition/events.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/tailwindcss.test.ts +26 -0
- package/src/utils/tailwindcss.ts +7 -0
- package/src/utils/vue.ts +23 -5
- package/tailwind.config.js +4 -0
- package/tsconfig.json +1 -1
- package/vite.config.ts +4 -1
- package/.eslintrc.js +0 -3
- package/dist/virtual.d.ts +0 -11
- package/src/components/basic/index.ts +0 -5
- package/src/types/virtual.d.ts +0 -11
- /package/src/components/{basic → lib}/AGLink.vue +0 -0
package/src/services/index.ts
CHANGED
|
@@ -3,15 +3,18 @@ import type { App as VueApp } from 'vue';
|
|
|
3
3
|
import { definePlugin } from '@/plugins';
|
|
4
4
|
|
|
5
5
|
import App from './App';
|
|
6
|
+
import Cache from './Cache';
|
|
6
7
|
import Events from './Events';
|
|
7
8
|
import Service from './Service';
|
|
8
9
|
import { getPiniaStore } from './store';
|
|
9
10
|
|
|
10
11
|
export * from './App';
|
|
12
|
+
export * from './Cache';
|
|
11
13
|
export * from './Events';
|
|
12
14
|
export * from './Service';
|
|
15
|
+
export * from './store';
|
|
13
16
|
|
|
14
|
-
export { App, Events, Service };
|
|
17
|
+
export { App, Cache, Events, Service };
|
|
15
18
|
|
|
16
19
|
const defaultServices = {
|
|
17
20
|
$app: App,
|
|
@@ -50,7 +53,7 @@ export default definePlugin({
|
|
|
50
53
|
});
|
|
51
54
|
|
|
52
55
|
declare module '@/bootstrap/options' {
|
|
53
|
-
interface AerogelOptions {
|
|
56
|
+
export interface AerogelOptions {
|
|
54
57
|
services?: Record<string, Service>;
|
|
55
58
|
}
|
|
56
59
|
}
|
package/src/services/store.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { tap } from '@noeldemartin/utils';
|
|
1
2
|
import { createPinia, defineStore, setActivePinia } from 'pinia';
|
|
2
3
|
import type { DefineStoreOptions, Pinia, StateTree, Store, _GettersTree } from 'pinia';
|
|
3
4
|
|
|
4
5
|
let _store: Pinia | null = null;
|
|
5
6
|
|
|
6
7
|
function initializePiniaStore(): Pinia {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
return _store ?? resetPiniaStore();
|
|
9
|
+
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
export function resetPiniaStore(): Pinia {
|
|
12
|
+
return tap(createPinia(), (store) => {
|
|
13
|
+
_store = store;
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
setActivePinia(store);
|
|
16
|
+
});
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export function getPiniaStore(): Pinia {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { GetClosureArgs } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
import Events from '@/services/Events';
|
|
4
|
+
import { definePlugin } from '@/plugins';
|
|
5
|
+
|
|
6
|
+
export interface AerogelTestingRuntime {
|
|
7
|
+
on: (typeof Events)['on'];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default definePlugin({
|
|
11
|
+
async install() {
|
|
12
|
+
if (import.meta.env.MODE !== 'testing') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
globalThis.testingRuntime = {
|
|
17
|
+
on: ((...args: GetClosureArgs<(typeof Events)['on']>) => Events.on(...args)) as (typeof Events)['on'],
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
declare global {
|
|
23
|
+
// eslint-disable-next-line no-var
|
|
24
|
+
var testingRuntime: AerogelTestingRuntime | undefined;
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { mock, tap } from '@noeldemartin/utils';
|
|
2
|
+
import { beforeEach, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
tap(globalThis, (global: any) => {
|
|
6
|
+
global.jest = vi;
|
|
7
|
+
global.navigator = { languages: ['en'] };
|
|
8
|
+
global.localStorage = mock<Storage>({
|
|
9
|
+
getItem: () => null,
|
|
10
|
+
setItem: () => null,
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.stubGlobal('document', {
|
|
16
|
+
querySelector: () => null,
|
|
17
|
+
getElementById: () => null,
|
|
18
|
+
});
|
|
19
|
+
});
|
package/src/ui/UI.state.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { Component } from 'vue';
|
|
|
2
2
|
|
|
3
3
|
import { defineServiceState } from '@/services/Service';
|
|
4
4
|
|
|
5
|
+
import { Layouts, getCurrentLayout } from './utils';
|
|
6
|
+
|
|
5
7
|
export interface Modal<T = unknown> {
|
|
6
8
|
id: string;
|
|
7
9
|
properties: Record<string, unknown>;
|
|
@@ -28,5 +30,10 @@ export default defineServiceState({
|
|
|
28
30
|
initialState: {
|
|
29
31
|
modals: [] as Modal[],
|
|
30
32
|
snackbars: [] as Snackbar[],
|
|
33
|
+
layout: getCurrentLayout(),
|
|
34
|
+
},
|
|
35
|
+
computed: {
|
|
36
|
+
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
37
|
+
desktop: ({ layout }) => layout === Layouts.Desktop,
|
|
31
38
|
},
|
|
32
39
|
});
|
package/src/ui/UI.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { facade, fail, uuid } from '@noeldemartin/utils';
|
|
1
|
+
import { after, facade, fail, 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
6
|
import Events from '@/services/Events';
|
|
7
|
+
import type { Color } from '@/components/constants';
|
|
7
8
|
import type { SnackbarAction, SnackbarColor } from '@/components/headless/snackbars';
|
|
9
|
+
import type { AGAlertModalProps, AGConfirmModalProps, AGLoadingModalProps, AGPromptModalProps } from '@/components';
|
|
8
10
|
|
|
9
11
|
import Service from './UI.state';
|
|
12
|
+
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
10
13
|
import type { Modal, ModalComponent, Snackbar } from './UI.state';
|
|
11
14
|
|
|
12
15
|
interface ModalCallbacks<T = unknown> {
|
|
@@ -24,11 +27,31 @@ export const UIComponents = {
|
|
|
24
27
|
ConfirmModal: 'confirm-modal',
|
|
25
28
|
ErrorReportModal: 'error-report-modal',
|
|
26
29
|
LoadingModal: 'loading-modal',
|
|
30
|
+
PromptModal: 'prompt-modal',
|
|
27
31
|
Snackbar: 'snackbar',
|
|
32
|
+
StartupCrash: 'startup-crash',
|
|
28
33
|
} as const;
|
|
29
34
|
|
|
30
35
|
export type UIComponent = ObjectValues<typeof UIComponents>;
|
|
31
36
|
|
|
37
|
+
export interface ConfirmOptions {
|
|
38
|
+
acceptText?: string;
|
|
39
|
+
acceptColor?: Color;
|
|
40
|
+
cancelText?: string;
|
|
41
|
+
cancelColor?: Color;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PromptOptions {
|
|
45
|
+
label?: string;
|
|
46
|
+
defaultValue?: string;
|
|
47
|
+
placeholder?: string;
|
|
48
|
+
acceptText?: string;
|
|
49
|
+
acceptColor?: Color;
|
|
50
|
+
cancelText?: string;
|
|
51
|
+
cancelColor?: Color;
|
|
52
|
+
trim?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
export interface ShowSnackbarOptions {
|
|
33
56
|
component?: Component;
|
|
34
57
|
color?: SnackbarColor;
|
|
@@ -47,43 +70,117 @@ export class UIService extends Service {
|
|
|
47
70
|
public alert(message: string): void;
|
|
48
71
|
public alert(title: string, message: string): void;
|
|
49
72
|
public alert(messageOrTitle: string, message?: string): void {
|
|
50
|
-
const
|
|
73
|
+
const getProperties = (): AGAlertModalProps => {
|
|
74
|
+
if (typeof message !== 'string') {
|
|
75
|
+
return { message: messageOrTitle };
|
|
76
|
+
}
|
|
51
77
|
|
|
52
|
-
|
|
78
|
+
return {
|
|
79
|
+
title: messageOrTitle,
|
|
80
|
+
message,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
|
|
53
85
|
}
|
|
54
86
|
|
|
55
|
-
public async confirm(message: string): Promise<boolean>;
|
|
56
|
-
public async confirm(title: string, message: string): Promise<boolean>;
|
|
57
|
-
public async confirm(
|
|
58
|
-
|
|
59
|
-
|
|
87
|
+
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
88
|
+
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
89
|
+
public async confirm(
|
|
90
|
+
messageOrTitle: string,
|
|
91
|
+
messageOrOptions?: string | ConfirmOptions,
|
|
92
|
+
options?: ConfirmOptions,
|
|
93
|
+
): Promise<boolean> {
|
|
94
|
+
const getProperties = (): AGConfirmModalProps => {
|
|
95
|
+
if (typeof messageOrOptions !== 'string') {
|
|
96
|
+
return {
|
|
97
|
+
message: messageOrTitle,
|
|
98
|
+
...(messageOrOptions ?? {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
title: messageOrTitle,
|
|
104
|
+
message: messageOrOptions,
|
|
105
|
+
...(options ?? {}),
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const modal = await this.openModal<ModalComponent<AGConfirmModalProps, boolean>>(
|
|
60
110
|
this.requireComponent(UIComponents.ConfirmModal),
|
|
61
|
-
|
|
111
|
+
getProperties(),
|
|
62
112
|
);
|
|
63
113
|
const result = await modal.beforeClose;
|
|
64
114
|
|
|
65
115
|
return result ?? false;
|
|
66
116
|
}
|
|
67
117
|
|
|
68
|
-
public async
|
|
69
|
-
public async
|
|
70
|
-
public async
|
|
71
|
-
|
|
118
|
+
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
119
|
+
public async prompt(title: string, message: string, options?: PromptOptions): Promise<string | null>;
|
|
120
|
+
public async prompt(
|
|
121
|
+
messageOrTitle: string,
|
|
122
|
+
messageOrOptions?: string | PromptOptions,
|
|
123
|
+
options?: PromptOptions,
|
|
124
|
+
): Promise<string | null> {
|
|
125
|
+
const trim = options?.trim ?? true;
|
|
126
|
+
const getProperties = (): AGPromptModalProps => {
|
|
127
|
+
if (typeof messageOrOptions !== 'string') {
|
|
128
|
+
return {
|
|
129
|
+
message: messageOrTitle,
|
|
130
|
+
...(messageOrOptions ?? {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
title: messageOrTitle,
|
|
136
|
+
message: messageOrOptions,
|
|
137
|
+
...(options ?? {}),
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const modal = await this.openModal<ModalComponent<AGPromptModalProps, string | null>>(
|
|
142
|
+
this.requireComponent(UIComponents.PromptModal),
|
|
143
|
+
getProperties(),
|
|
144
|
+
);
|
|
145
|
+
const rawResult = await modal.beforeClose;
|
|
146
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
147
|
+
|
|
148
|
+
return result ?? null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public async loading<T>(operation: Promise<T> | (() => T)): Promise<T>;
|
|
152
|
+
public async loading<T>(message: string, operation: Promise<T> | (() => T)): Promise<T>;
|
|
153
|
+
public async loading<T>(
|
|
154
|
+
messageOrOperation: string | Promise<T> | (() => T),
|
|
155
|
+
operation?: Promise<T> | (() => T),
|
|
156
|
+
): Promise<T> {
|
|
157
|
+
const getProperties = (): AGLoadingModalProps => {
|
|
158
|
+
if (typeof messageOrOperation !== 'string') {
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { message: messageOrOperation };
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), getProperties());
|
|
72
166
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
167
|
+
try {
|
|
168
|
+
operation = typeof messageOrOperation === 'string' ? (operation as () => T) : messageOrOperation;
|
|
169
|
+
operation = typeof operation === 'function' ? Promise.resolve(operation()) : operation;
|
|
76
170
|
|
|
77
|
-
|
|
171
|
+
const [result] = await Promise.all([operation, after({ seconds: 1 })]);
|
|
78
172
|
|
|
79
|
-
|
|
173
|
+
return result;
|
|
174
|
+
} finally {
|
|
175
|
+
await this.closeModal(modal.id);
|
|
176
|
+
}
|
|
80
177
|
}
|
|
81
178
|
|
|
82
179
|
public showSnackbar(message: string, options: ShowSnackbarOptions = {}): void {
|
|
83
180
|
const snackbar: Snackbar = {
|
|
84
181
|
id: uuid(),
|
|
85
182
|
properties: { message, ...options },
|
|
86
|
-
component: options.component ??
|
|
183
|
+
component: markRaw(options.component ?? this.requireComponent(UIComponents.Snackbar)),
|
|
87
184
|
};
|
|
88
185
|
|
|
89
186
|
this.setState('snackbars', this.snackbars.concat(snackbar));
|
|
@@ -138,6 +235,8 @@ export class UIService extends Service {
|
|
|
138
235
|
|
|
139
236
|
protected async boot(): Promise<void> {
|
|
140
237
|
this.watchModalEvents();
|
|
238
|
+
this.watchMountedEvent();
|
|
239
|
+
this.watchViewportBreakpoints();
|
|
141
240
|
}
|
|
142
241
|
|
|
143
242
|
private watchModalEvents(): void {
|
|
@@ -165,16 +264,50 @@ export class UIService extends Service {
|
|
|
165
264
|
});
|
|
166
265
|
}
|
|
167
266
|
|
|
267
|
+
private watchMountedEvent(): void {
|
|
268
|
+
Events.once('application-mounted', async () => {
|
|
269
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const splash = globalThis.document.getElementById('splash');
|
|
274
|
+
|
|
275
|
+
if (!splash) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
280
|
+
splash.style.opacity = '0';
|
|
281
|
+
|
|
282
|
+
await after({ ms: 600 });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
splash.remove();
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private watchViewportBreakpoints(): void {
|
|
290
|
+
if (!globalThis.matchMedia) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const media = globalThis.matchMedia(`(min-width: ${MOBILE_BREAKPOINT}px)`);
|
|
295
|
+
|
|
296
|
+
media.addEventListener('change', () => this.setState({ layout: getCurrentLayout() }));
|
|
297
|
+
}
|
|
298
|
+
|
|
168
299
|
}
|
|
169
300
|
|
|
170
|
-
export default facade(
|
|
301
|
+
export default facade(UIService);
|
|
171
302
|
|
|
172
303
|
declare module '@/services/Events' {
|
|
173
304
|
export interface EventsPayload {
|
|
174
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
175
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
176
305
|
'close-modal': { id: string; result?: unknown };
|
|
177
306
|
'hide-modal': { id: string };
|
|
307
|
+
'hide-overlays-backdrop': void;
|
|
308
|
+
'modal-closed': { modal: Modal; result?: unknown };
|
|
309
|
+
'modal-will-close': { modal: Modal; result?: unknown };
|
|
178
310
|
'show-modal': { id: string };
|
|
311
|
+
'show-overlays-backdrop': void;
|
|
179
312
|
}
|
|
180
313
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -8,13 +8,17 @@ import AGAlertModal from '../components/modals/AGAlertModal.vue';
|
|
|
8
8
|
import AGConfirmModal from '../components/modals/AGConfirmModal.vue';
|
|
9
9
|
import AGErrorReportModal from '../components/modals/AGErrorReportModal.vue';
|
|
10
10
|
import AGLoadingModal from '../components/modals/AGLoadingModal.vue';
|
|
11
|
+
import AGPromptModal from '../components/modals/AGPromptModal.vue';
|
|
11
12
|
import AGSnackbar from '../components/snackbars/AGSnackbar.vue';
|
|
13
|
+
import AGStartupCrash from '../components/lib/AGStartupCrash.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({
|
|
@@ -24,7 +28,9 @@ export default definePlugin({
|
|
|
24
28
|
[UIComponents.ConfirmModal]: AGConfirmModal,
|
|
25
29
|
[UIComponents.ErrorReportModal]: AGErrorReportModal,
|
|
26
30
|
[UIComponents.LoadingModal]: AGLoadingModal,
|
|
31
|
+
[UIComponents.PromptModal]: AGPromptModal,
|
|
27
32
|
[UIComponents.Snackbar]: AGSnackbar,
|
|
33
|
+
[UIComponents.StartupCrash]: AGStartupCrash,
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
Object.entries({
|
|
@@ -37,7 +43,7 @@ export default definePlugin({
|
|
|
37
43
|
});
|
|
38
44
|
|
|
39
45
|
declare module '@/bootstrap/options' {
|
|
40
|
-
interface AerogelOptions {
|
|
46
|
+
export interface AerogelOptions {
|
|
41
47
|
components?: Partial<Record<UIComponent, Component>>;
|
|
42
48
|
}
|
|
43
49
|
}
|
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
|
+
}
|
|
@@ -14,6 +14,7 @@ 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 {
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { removeInteractiveClasses } from './tailwindcss';
|
|
4
|
+
|
|
5
|
+
describe('TailwindCSS utils', () => {
|
|
6
|
+
|
|
7
|
+
it('Removes interactive classes', () => {
|
|
8
|
+
const cases: [string, string][] = [
|
|
9
|
+
['text-red hover:text-green', 'text-red'],
|
|
10
|
+
['text-red hover:text-green text-lg', 'text-red text-lg'],
|
|
11
|
+
[
|
|
12
|
+
`
|
|
13
|
+
text-red text-lg
|
|
14
|
+
focus:text-yellow
|
|
15
|
+
hover:focus:text-black
|
|
16
|
+
`,
|
|
17
|
+
'text-red text-lg',
|
|
18
|
+
],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
cases.forEach(([original, expected]) => {
|
|
22
|
+
expect(removeInteractiveClasses(original)).toEqual(expected);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
});
|
package/src/utils/vue.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { fail } from '@noeldemartin/utils';
|
|
2
|
-
import { inject, reactive, ref } from 'vue';
|
|
2
|
+
import { computed, inject, reactive, ref, watch } from 'vue';
|
|
3
3
|
import type { Directive, InjectionKey, PropType, Ref, UnwrapNestedRefs } from 'vue';
|
|
4
4
|
|
|
5
5
|
type BaseProp<T> = {
|
|
6
|
-
type
|
|
6
|
+
type?: PropType<T>;
|
|
7
7
|
validator?(value: unknown): boolean;
|
|
8
8
|
};
|
|
9
9
|
|
|
@@ -30,6 +30,15 @@ export function componentRef<T>(): Ref<UnwrapNestedRefs<T> | undefined> {
|
|
|
30
30
|
return ref<UnwrapNestedRefs<T>>();
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export function computedAsync<T>(getter: () => Promise<T>): Ref<T | undefined> {
|
|
34
|
+
const result = ref<T>();
|
|
35
|
+
const asyncValue = computed(getter);
|
|
36
|
+
|
|
37
|
+
watch(asyncValue, async () => (result.value = await asyncValue.value), { immediate: true });
|
|
38
|
+
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
export function defineDirective(directive: Directive): Directive {
|
|
34
43
|
return directive;
|
|
35
44
|
}
|
|
@@ -64,13 +73,22 @@ export function injectOrFail<T>(key: InjectionKey<T> | string, errorMessage?: st
|
|
|
64
73
|
return inject(key) ?? fail(errorMessage ?? `Could not resolve '${key}' injection key`);
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
export function
|
|
76
|
+
export function listenerProp<T extends Function = Function>(): OptionalProp<T | null> {
|
|
68
77
|
return {
|
|
69
|
-
type
|
|
78
|
+
type: Function as PropType<T>,
|
|
70
79
|
default: null,
|
|
71
80
|
};
|
|
72
81
|
}
|
|
73
82
|
|
|
83
|
+
export function mixedProp<T>(type?: PropType<T>): OptionalProp<T | null>;
|
|
84
|
+
export function mixedProp<T>(type: PropType<T>, defaultValue: T): OptionalProp<T>;
|
|
85
|
+
export function mixedProp<T>(type?: PropType<T>, defaultValue?: T): OptionalProp<T | null> {
|
|
86
|
+
return {
|
|
87
|
+
type,
|
|
88
|
+
default: defaultValue ?? null,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
74
92
|
export function numberProp(): OptionalProp<number | null>;
|
|
75
93
|
export function numberProp(defaultValue: number): OptionalProp<number>;
|
|
76
94
|
export function numberProp(defaultValue: number | null = null): OptionalProp<number | null> {
|
|
@@ -108,7 +126,7 @@ export function requiredEnumProp<Enum extends Record<string, unknown>>(
|
|
|
108
126
|
};
|
|
109
127
|
}
|
|
110
128
|
|
|
111
|
-
export function requiredMixedProp<T>(type
|
|
129
|
+
export function requiredMixedProp<T>(type?: PropType<T>): RequiredProp<T> {
|
|
112
130
|
return {
|
|
113
131
|
type,
|
|
114
132
|
required: true,
|
package/tsconfig.json
CHANGED
package/vite.config.ts
CHANGED
|
@@ -4,7 +4,10 @@ import { defineConfig } from 'vitest/config';
|
|
|
4
4
|
import { resolve } from 'path';
|
|
5
5
|
|
|
6
6
|
export default defineConfig({
|
|
7
|
-
test: {
|
|
7
|
+
test: {
|
|
8
|
+
clearMocks: true,
|
|
9
|
+
setupFiles: ['./src/testing/setup.ts'],
|
|
10
|
+
},
|
|
8
11
|
plugins: [Aerogel(), Icons()],
|
|
9
12
|
resolve: {
|
|
10
13
|
alias: {
|
package/.eslintrc.js
DELETED
package/dist/virtual.d.ts
DELETED
package/src/types/virtual.d.ts
DELETED
|
File without changes
|