@aerogel/core 0.0.0-next.fcfbfdc3428c34c4d1c0e781b61d244f13232fc9 → 0.1.0-next.c4b24f52d8b652bd5c14c2d12e1b38b779ab7682
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aerogel-core.css +1 -0
- package/dist/aerogel-core.d.ts +661 -919
- package/dist/aerogel-core.js +1983 -1440
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +7 -5
- package/src/components/AppLayout.vue +1 -3
- package/src/components/AppOverlays.vue +0 -27
- package/src/components/contracts/AlertModal.ts +15 -0
- package/src/components/contracts/ConfirmModal.ts +12 -5
- package/src/components/contracts/DropdownMenu.ts +8 -3
- package/src/components/contracts/ErrorReportModal.ts +8 -4
- package/src/components/contracts/Input.ts +7 -7
- package/src/components/contracts/LoadingModal.ts +6 -2
- package/src/components/contracts/Modal.ts +4 -4
- package/src/components/contracts/PromptModal.ts +5 -1
- package/src/components/contracts/Select.ts +9 -8
- package/src/components/contracts/Toast.ts +4 -2
- package/src/components/headless/HeadlessButton.vue +2 -2
- package/src/components/headless/HeadlessInputInput.vue +16 -5
- package/src/components/headless/HeadlessModal.vue +8 -43
- package/src/components/headless/HeadlessModalContent.vue +2 -2
- package/src/components/headless/HeadlessSelect.vue +10 -8
- package/src/components/headless/HeadlessSelectOptions.vue +8 -3
- package/src/components/headless/HeadlessSwitch.vue +96 -0
- package/src/components/headless/index.ts +1 -0
- package/src/components/ui/AdvancedOptions.vue +1 -1
- package/src/components/ui/AlertModal.vue +7 -3
- package/src/components/ui/Button.vue +27 -10
- package/src/components/ui/ConfirmModal.vue +11 -3
- package/src/components/ui/DropdownMenuOption.vue +12 -4
- package/src/components/ui/DropdownMenuOptions.vue +18 -1
- package/src/components/ui/ErrorLogs.vue +19 -0
- package/src/components/ui/ErrorLogsModal.vue +48 -0
- package/src/components/ui/ErrorReportModal.vue +18 -7
- package/src/components/ui/Input.vue +2 -2
- package/src/components/ui/LoadingModal.vue +3 -1
- package/src/components/ui/Markdown.vue +29 -1
- package/src/components/ui/Modal.vue +61 -21
- package/src/components/ui/ModalContext.vue +2 -1
- package/src/components/ui/PromptModal.vue +5 -2
- package/src/components/ui/Select.vue +5 -3
- package/src/components/ui/SelectLabel.vue +5 -1
- package/src/components/ui/SelectOptions.vue +6 -1
- package/src/components/ui/SelectTrigger.vue +1 -1
- package/src/components/ui/Setting.vue +31 -0
- package/src/components/ui/StartupCrash.vue +51 -6
- package/src/components/ui/Switch.vue +11 -0
- package/src/components/ui/TextArea.vue +56 -0
- package/src/components/ui/Toast.vue +19 -15
- package/src/components/ui/index.ts +5 -0
- package/src/directives/measure.ts +11 -5
- package/src/errors/Errors.state.ts +1 -0
- package/src/errors/Errors.ts +45 -21
- package/src/errors/index.ts +6 -2
- package/src/errors/settings/Debug.vue +14 -0
- package/src/errors/settings/index.ts +10 -0
- package/src/forms/FormController.test.ts +35 -9
- package/src/forms/FormController.ts +34 -24
- package/src/forms/index.ts +0 -1
- package/src/forms/utils.ts +58 -33
- package/src/forms/validation.ts +31 -0
- package/src/index.css +34 -12
- package/src/lang/index.ts +1 -1
- package/src/lang/settings/Language.vue +1 -1
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +2 -8
- package/src/services/Service.ts +11 -6
- package/src/services/index.ts +2 -2
- package/src/testing/index.ts +4 -0
- package/src/ui/UI.state.ts +3 -13
- package/src/ui/UI.ts +103 -84
- package/src/ui/index.ts +16 -17
- package/src/utils/app.ts +7 -0
- package/src/utils/classes.ts +9 -17
- package/src/utils/composition/events.ts +2 -4
- package/src/utils/composition/forms.ts +7 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/markdown.ts +35 -1
- package/src/utils/vue.ts +6 -1
- package/src/forms/composition.ts +0 -6
package/src/ui/UI.state.ts
CHANGED
|
@@ -8,22 +8,11 @@ export interface UIModal<T = unknown> {
|
|
|
8
8
|
id: string;
|
|
9
9
|
properties: Record<string, unknown>;
|
|
10
10
|
component: Component;
|
|
11
|
+
closing: boolean;
|
|
11
12
|
beforeClose: Promise<T | undefined>;
|
|
12
13
|
afterClose: Promise<T | undefined>;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
export interface UIModalContext {
|
|
16
|
-
modal: UIModal;
|
|
17
|
-
childIndex?: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ModalComponent<
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
22
|
-
Properties extends object = object,
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
24
|
-
Result = unknown,
|
|
25
|
-
> {}
|
|
26
|
-
|
|
27
16
|
export interface UIToast {
|
|
28
17
|
id: string;
|
|
29
18
|
component: Component;
|
|
@@ -38,7 +27,8 @@ export default defineServiceState({
|
|
|
38
27
|
layout: getCurrentLayout(),
|
|
39
28
|
},
|
|
40
29
|
computed: {
|
|
41
|
-
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
42
30
|
desktop: ({ layout }) => layout === Layouts.Desktop,
|
|
31
|
+
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
32
|
+
openModals: ({ modals }) => modals.filter(({ closing }) => !closing),
|
|
43
33
|
},
|
|
44
34
|
});
|
package/src/ui/UI.ts
CHANGED
|
@@ -1,43 +1,55 @@
|
|
|
1
1
|
import { after, facade, fail, isDevelopment, required, uuid } from '@noeldemartin/utils';
|
|
2
|
-
import { markRaw, nextTick } from 'vue';
|
|
2
|
+
import { markRaw, nextTick, unref } from 'vue';
|
|
3
|
+
import type { ComponentExposed, ComponentProps } from 'vue-component-type-helpers';
|
|
3
4
|
import type { Component } from 'vue';
|
|
4
|
-
import type {
|
|
5
|
+
import type { ClosureArgs } from '@noeldemartin/utils';
|
|
5
6
|
|
|
6
7
|
import App from '@aerogel/core/services/App';
|
|
7
8
|
import Events from '@aerogel/core/services/Events';
|
|
9
|
+
import type {
|
|
10
|
+
ConfirmModalCheckboxes,
|
|
11
|
+
ConfirmModalExpose,
|
|
12
|
+
ConfirmModalProps,
|
|
13
|
+
} from '@aerogel/core/components/contracts/ConfirmModal';
|
|
14
|
+
import type {
|
|
15
|
+
ErrorReportModalExpose,
|
|
16
|
+
ErrorReportModalProps,
|
|
17
|
+
} from '@aerogel/core/components/contracts/ErrorReportModal';
|
|
8
18
|
import type { AcceptRefs } from '@aerogel/core/utils';
|
|
9
|
-
import type { AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
|
|
19
|
+
import type { AlertModalExpose, AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
|
|
10
20
|
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
11
|
-
import type {
|
|
12
|
-
import type {
|
|
13
|
-
import type {
|
|
14
|
-
import type { ToastAction, ToastVariant } from '@aerogel/core/components/contracts/Toast';
|
|
21
|
+
import type { LoadingModalExpose, LoadingModalProps } from '@aerogel/core/components/contracts/LoadingModal';
|
|
22
|
+
import type { PromptModalExpose, PromptModalProps } from '@aerogel/core/components/contracts/PromptModal';
|
|
23
|
+
import type { ToastAction, ToastExpose, ToastProps, ToastVariant } from '@aerogel/core/components/contracts/Toast';
|
|
15
24
|
|
|
16
25
|
import Service from './UI.state';
|
|
17
26
|
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
18
|
-
import type {
|
|
27
|
+
import type { UIModal, UIToast } from './UI.state';
|
|
19
28
|
|
|
20
29
|
interface ModalCallbacks<T = unknown> {
|
|
21
30
|
willClose(result: T | undefined): void;
|
|
22
|
-
|
|
31
|
+
hasClosed(result: T | undefined): void;
|
|
23
32
|
}
|
|
24
33
|
|
|
25
|
-
type
|
|
26
|
-
type
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
34
|
+
export type ModalResult<T> = ModalExposeResult<ComponentExposed<T>>;
|
|
35
|
+
export type ModalExposeResult<T> = T extends { close(result?: infer Result): Promise<void> } ? Result : unknown;
|
|
36
|
+
export type UIComponent<Props = {}, Exposed = {}> = { new (...args: ClosureArgs): Exposed & { $props: Props } };
|
|
37
|
+
|
|
38
|
+
export interface UIComponents {
|
|
39
|
+
'alert-modal': UIComponent<AlertModalProps, AlertModalExpose>;
|
|
40
|
+
'confirm-modal': UIComponent<ConfirmModalProps, ConfirmModalExpose>;
|
|
41
|
+
'error-report-modal': UIComponent<ErrorReportModalProps, ErrorReportModalExpose>;
|
|
42
|
+
'loading-modal': UIComponent<LoadingModalProps, LoadingModalExpose>;
|
|
43
|
+
'prompt-modal': UIComponent<PromptModalProps, PromptModalExpose>;
|
|
44
|
+
'router-link': UIComponent;
|
|
45
|
+
'startup-crash': UIComponent;
|
|
46
|
+
toast: UIComponent<ToastProps, ToastExpose>;
|
|
47
|
+
}
|
|
39
48
|
|
|
40
|
-
export
|
|
49
|
+
export interface UIModalContext {
|
|
50
|
+
modal: UIModal;
|
|
51
|
+
childIndex?: number;
|
|
52
|
+
}
|
|
41
53
|
|
|
42
54
|
export type ConfirmOptions = AcceptRefs<{
|
|
43
55
|
acceptText?: string;
|
|
@@ -52,6 +64,7 @@ export type LoadingOptions = AcceptRefs<{
|
|
|
52
64
|
title?: string;
|
|
53
65
|
message?: string;
|
|
54
66
|
progress?: number;
|
|
67
|
+
delay?: number;
|
|
55
68
|
}>;
|
|
56
69
|
|
|
57
70
|
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
@@ -79,13 +92,17 @@ export interface ToastOptions {
|
|
|
79
92
|
export class UIService extends Service {
|
|
80
93
|
|
|
81
94
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
82
|
-
private components: Partial<
|
|
95
|
+
private components: Partial<UIComponents> = {};
|
|
83
96
|
|
|
84
|
-
public
|
|
97
|
+
public registerComponent<T extends keyof UIComponents>(name: T, component: UIComponents[T]): void {
|
|
98
|
+
this.components[name] = component;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public resolveComponent<T extends keyof UIComponents>(name: T): UIComponents[T] | null {
|
|
85
102
|
return this.components[name] ?? null;
|
|
86
103
|
}
|
|
87
104
|
|
|
88
|
-
public requireComponent(name:
|
|
105
|
+
public requireComponent<T extends keyof UIComponents>(name: T): UIComponents[T] {
|
|
89
106
|
return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
|
|
90
107
|
}
|
|
91
108
|
|
|
@@ -103,10 +120,7 @@ export class UIService extends Service {
|
|
|
103
120
|
};
|
|
104
121
|
};
|
|
105
122
|
|
|
106
|
-
this.
|
|
107
|
-
this.requireComponent(UIComponents.AlertModal),
|
|
108
|
-
getProperties(),
|
|
109
|
-
);
|
|
123
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
110
124
|
}
|
|
111
125
|
|
|
112
126
|
/* eslint-disable max-len */
|
|
@@ -138,18 +152,8 @@ export class UIService extends Service {
|
|
|
138
152
|
};
|
|
139
153
|
};
|
|
140
154
|
|
|
141
|
-
type ConfirmModalComponent = ModalComponent<
|
|
142
|
-
AcceptRefs<ConfirmModalProps>,
|
|
143
|
-
boolean | [boolean, Record<string, boolean>]
|
|
144
|
-
>;
|
|
145
|
-
|
|
146
155
|
const properties = getProperties();
|
|
147
|
-
const
|
|
148
|
-
this.requireComponent(UIComponents.ConfirmModal),
|
|
149
|
-
properties,
|
|
150
|
-
);
|
|
151
|
-
const result = await modal.beforeClose;
|
|
152
|
-
|
|
156
|
+
const result = await this.modalForm(this.requireComponent('confirm-modal'), properties);
|
|
153
157
|
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
154
158
|
const checkboxes =
|
|
155
159
|
typeof result === 'object'
|
|
@@ -201,11 +205,7 @@ export class UIService extends Service {
|
|
|
201
205
|
} as PromptModalProps;
|
|
202
206
|
};
|
|
203
207
|
|
|
204
|
-
const
|
|
205
|
-
this.requireComponent(UIComponents.PromptModal),
|
|
206
|
-
getProperties(),
|
|
207
|
-
);
|
|
208
|
-
const rawResult = await modal.beforeClose;
|
|
208
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
209
209
|
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
210
210
|
|
|
211
211
|
return result ?? null;
|
|
@@ -219,7 +219,11 @@ export class UIService extends Service {
|
|
|
219
219
|
operation?: Promise<T> | (() => T),
|
|
220
220
|
): Promise<T> {
|
|
221
221
|
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
222
|
-
const processArgs = (): {
|
|
222
|
+
const processArgs = (): {
|
|
223
|
+
operationPromise: Promise<T>;
|
|
224
|
+
props?: AcceptRefs<LoadingModalProps>;
|
|
225
|
+
delay?: number;
|
|
226
|
+
} => {
|
|
223
227
|
if (typeof operationOrMessageOrOptions === 'string') {
|
|
224
228
|
return {
|
|
225
229
|
props: { message: operationOrMessageOrOptions },
|
|
@@ -231,14 +235,25 @@ export class UIService extends Service {
|
|
|
231
235
|
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
232
236
|
}
|
|
233
237
|
|
|
238
|
+
const { delay, ...props } = operationOrMessageOrOptions;
|
|
239
|
+
|
|
234
240
|
return {
|
|
235
|
-
props
|
|
241
|
+
props,
|
|
242
|
+
delay: unref(delay),
|
|
236
243
|
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
237
244
|
};
|
|
238
245
|
};
|
|
239
246
|
|
|
240
|
-
|
|
241
|
-
const
|
|
247
|
+
let delayed = false;
|
|
248
|
+
const { operationPromise, props, delay } = processArgs();
|
|
249
|
+
|
|
250
|
+
delay && (await Promise.race([after({ ms: delay }).then(() => (delayed = true)), operationPromise]));
|
|
251
|
+
|
|
252
|
+
if (delay && !delayed) {
|
|
253
|
+
return operationPromise;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const modal = await this.modal(this.requireComponent('loading-modal'), props);
|
|
242
257
|
|
|
243
258
|
try {
|
|
244
259
|
const result = await operationPromise;
|
|
@@ -256,30 +271,29 @@ export class UIService extends Service {
|
|
|
256
271
|
const toast: UIToast = {
|
|
257
272
|
id: uuid(),
|
|
258
273
|
properties: { message, ...otherOptions },
|
|
259
|
-
component: markRaw(component ?? this.requireComponent(
|
|
274
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
260
275
|
};
|
|
261
276
|
|
|
262
277
|
this.setState('toasts', this.toasts.concat(toast));
|
|
263
278
|
}
|
|
264
279
|
|
|
265
|
-
public
|
|
266
|
-
|
|
267
|
-
|
|
280
|
+
public modal<T extends Component>(
|
|
281
|
+
...args: {} extends ComponentProps<T>
|
|
282
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
283
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
284
|
+
): Promise<UIModal<ModalResult<T>>>;
|
|
268
285
|
|
|
269
|
-
public async
|
|
270
|
-
component: TModalComponent,
|
|
271
|
-
properties?: ModalProperties<TModalComponent>,
|
|
272
|
-
): Promise<UIModal<ModalResult<TModalComponent>>> {
|
|
286
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
273
287
|
const id = uuid();
|
|
274
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
275
|
-
const modal: UIModal<ModalResult<
|
|
288
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
289
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
276
290
|
id,
|
|
277
|
-
|
|
291
|
+
closing: false,
|
|
292
|
+
properties: props ?? {},
|
|
278
293
|
component: markRaw(component),
|
|
279
294
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
280
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
295
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
281
296
|
};
|
|
282
|
-
const activeModal = this.modals.at(-1);
|
|
283
297
|
const modals = this.modals.concat(modal);
|
|
284
298
|
|
|
285
299
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -287,15 +301,26 @@ export class UIService extends Service {
|
|
|
287
301
|
this.setState({ modals });
|
|
288
302
|
|
|
289
303
|
await nextTick();
|
|
290
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
291
|
-
await Promise.all([
|
|
292
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
293
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
294
|
-
]);
|
|
295
304
|
|
|
296
305
|
return modal;
|
|
297
306
|
}
|
|
298
307
|
|
|
308
|
+
public modalForm<T extends Component>(
|
|
309
|
+
...args: {} extends ComponentProps<T>
|
|
310
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
311
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
312
|
+
): Promise<ModalResult<T> | undefined>;
|
|
313
|
+
|
|
314
|
+
public async modalForm<T extends Component>(
|
|
315
|
+
component: T,
|
|
316
|
+
props?: ComponentProps<T>,
|
|
317
|
+
): Promise<ModalResult<T> | undefined> {
|
|
318
|
+
const modal = await this.modal<T>(component, props as ComponentProps<T>);
|
|
319
|
+
const result = await modal.beforeClose;
|
|
320
|
+
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
|
|
299
324
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
300
325
|
if (!App.isMounted()) {
|
|
301
326
|
await this.removeModal(id, result);
|
|
@@ -324,25 +349,23 @@ export class UIService extends Service {
|
|
|
324
349
|
this.modals.filter((m) => m.id !== id),
|
|
325
350
|
);
|
|
326
351
|
|
|
327
|
-
this.modalCallbacks[id]?.
|
|
352
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
328
353
|
|
|
329
354
|
delete this.modalCallbacks[id];
|
|
330
|
-
|
|
331
|
-
const activeModal = this.modals.at(-1);
|
|
332
|
-
|
|
333
|
-
await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
|
|
334
355
|
}
|
|
335
356
|
|
|
336
357
|
private watchModalEvents(): void {
|
|
337
|
-
Events.on('modal-will-close', ({ modal, result }) => {
|
|
338
|
-
this.
|
|
358
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
359
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
339
360
|
|
|
340
|
-
if (
|
|
341
|
-
|
|
361
|
+
if (modal) {
|
|
362
|
+
modal.closing = true;
|
|
342
363
|
}
|
|
364
|
+
|
|
365
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
343
366
|
});
|
|
344
367
|
|
|
345
|
-
Events.on('modal-closed', async ({ modal: { id }, result }) => {
|
|
368
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
346
369
|
await this.removeModal(id, result);
|
|
347
370
|
});
|
|
348
371
|
}
|
|
@@ -386,11 +409,7 @@ export default facade(UIService);
|
|
|
386
409
|
declare module '@aerogel/core/services/Events' {
|
|
387
410
|
export interface EventsPayload {
|
|
388
411
|
'close-modal': { id: string; result?: unknown };
|
|
389
|
-
'hide-modal': { id: string };
|
|
390
|
-
'hide-overlays-backdrop': void;
|
|
391
|
-
'modal-closed': { modal: UIModal; result?: unknown };
|
|
392
412
|
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
393
|
-
'
|
|
394
|
-
'show-overlays-backdrop': void;
|
|
413
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
395
414
|
}
|
|
396
415
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { Component } from 'vue';
|
|
2
|
-
|
|
3
1
|
import AlertModal from '@aerogel/core/components/ui/AlertModal.vue';
|
|
4
2
|
import ConfirmModal from '@aerogel/core/components/ui/ConfirmModal.vue';
|
|
5
3
|
import ErrorReportModal from '@aerogel/core/components/ui/ErrorReportModal.vue';
|
|
@@ -10,8 +8,9 @@ import Toast from '@aerogel/core/components/ui/Toast.vue';
|
|
|
10
8
|
import { bootServices } from '@aerogel/core/services';
|
|
11
9
|
import { definePlugin } from '@aerogel/core/plugins';
|
|
12
10
|
|
|
13
|
-
import UI
|
|
14
|
-
import type {
|
|
11
|
+
import UI from './UI';
|
|
12
|
+
import type { UIComponents } from './UI';
|
|
13
|
+
import type { Component } from 'vue';
|
|
15
14
|
|
|
16
15
|
const services = { $ui: UI };
|
|
17
16
|
|
|
@@ -23,20 +22,20 @@ export type UIServices = typeof services;
|
|
|
23
22
|
|
|
24
23
|
export default definePlugin({
|
|
25
24
|
async install(app, options) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
const components: Partial<Record<keyof UIComponents, Component>> = {
|
|
26
|
+
'alert-modal': AlertModal,
|
|
27
|
+
'confirm-modal': ConfirmModal,
|
|
28
|
+
'error-report-modal': ErrorReportModal,
|
|
29
|
+
'loading-modal': LoadingModal,
|
|
30
|
+
'prompt-modal': PromptModal,
|
|
31
|
+
'startup-crash': StartupCrash,
|
|
32
|
+
'toast': Toast,
|
|
33
|
+
...options.components,
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
Object.entries({
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}).forEach(([name, component]) => UI.registerComponent(name as UIComponent, component));
|
|
36
|
+
for (const [name, component] of Object.entries(components)) {
|
|
37
|
+
UI.registerComponent(name as keyof UIComponents, component as UIComponents[keyof UIComponents]);
|
|
38
|
+
}
|
|
40
39
|
|
|
41
40
|
await bootServices(app, services);
|
|
42
41
|
},
|
|
@@ -44,7 +43,7 @@ export default definePlugin({
|
|
|
44
43
|
|
|
45
44
|
declare module '@aerogel/core/bootstrap/options' {
|
|
46
45
|
export interface AerogelOptions {
|
|
47
|
-
components?: Partial<
|
|
46
|
+
components?: Partial<Partial<UIComponents>>;
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
|
package/src/utils/app.ts
ADDED
package/src/utils/classes.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import {
|
|
2
|
+
import { unref } from 'vue';
|
|
3
3
|
import { cva } from 'class-variance-authority';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
5
|
import type { ClassValue } from 'clsx';
|
|
6
|
-
import type {
|
|
6
|
+
import type { PropType } from 'vue';
|
|
7
7
|
import type { GetClosureArgs, GetClosureResult } from '@noeldemartin/utils';
|
|
8
8
|
|
|
9
9
|
export type CVAConfig<T> = NonNullable<GetClosureArgs<typeof cva<T>>[1]>;
|
|
10
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
11
|
export type Variants<T extends Record<string, string | boolean>> = Required<{
|
|
13
12
|
[K in keyof T]: Exclude<T[K], undefined> extends string
|
|
14
13
|
? { [key in Exclude<T[K], undefined>]: string | null }
|
|
@@ -26,22 +25,15 @@ export type PickComponentProps<TValues, TDefinitions> = {
|
|
|
26
25
|
[K in keyof TValues]: K extends keyof TDefinitions ? TValues[K] : never;
|
|
27
26
|
};
|
|
28
27
|
|
|
29
|
-
export function
|
|
30
|
-
value:
|
|
28
|
+
export function variantClasses<T>(
|
|
29
|
+
value: { baseClasses?: string } & CVAProps<T>,
|
|
31
30
|
config: { baseClasses?: string } & CVAConfig<T>,
|
|
32
|
-
):
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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);
|
|
31
|
+
): string {
|
|
32
|
+
const { baseClasses: valueBaseClasses, ...values } = value;
|
|
33
|
+
const { baseClasses: configBaseClasses, ...configs } = config;
|
|
34
|
+
const variants = cva(configBaseClasses, configs as CVAConfig<T>);
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
}, {} as CVAProps<T>);
|
|
42
|
-
|
|
43
|
-
return classes(variants(values), unref(valueBaseClasses));
|
|
44
|
-
});
|
|
36
|
+
return classes(variants(values as CVAProps<T>), unref(valueBaseClasses));
|
|
45
37
|
}
|
|
46
38
|
|
|
47
39
|
export function classes(...inputs: ClassValue[]): string {
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
EventWithPayload,
|
|
7
7
|
EventWithoutPayload,
|
|
8
8
|
EventsPayload,
|
|
9
|
-
UnknownEvent,
|
|
10
9
|
} from '@aerogel/core/services/Events';
|
|
11
10
|
|
|
12
11
|
export function useEvent<Event extends EventWithoutPayload>(event: Event, listener: () => unknown): void;
|
|
@@ -14,11 +13,10 @@ export function useEvent<Event extends EventWithPayload>(
|
|
|
14
13
|
event: Event,
|
|
15
14
|
listener: EventListener<EventsPayload[Event]>
|
|
16
15
|
): void;
|
|
17
|
-
export function useEvent<Payload>(event: string, listener: (payload: Payload) => unknown): void;
|
|
18
|
-
export function useEvent<Event extends string>(event: UnknownEvent<Event>, listener: EventListener): void;
|
|
19
16
|
|
|
20
17
|
export function useEvent(event: string, listener: EventListener): void {
|
|
21
|
-
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
const unsubscribe = Events.on(event as any, listener);
|
|
22
20
|
|
|
23
21
|
onUnmounted(() => unsubscribe());
|
|
24
22
|
}
|
|
@@ -2,9 +2,11 @@ import { objectWithout } from '@noeldemartin/utils';
|
|
|
2
2
|
import { computed, inject, onUnmounted, useAttrs } from 'vue';
|
|
3
3
|
import type { ClassValue } from 'clsx';
|
|
4
4
|
import type { ComputedRef } from 'vue';
|
|
5
|
-
import type { FormController } from '@aerogel/core/forms';
|
|
6
5
|
import type { Nullable } from '@noeldemartin/utils';
|
|
7
6
|
|
|
7
|
+
import FormController from '@aerogel/core/forms/FormController';
|
|
8
|
+
import type { FormData, FormFieldDefinitions } from '@aerogel/core/forms/FormController';
|
|
9
|
+
|
|
8
10
|
export function onFormFocus(input: { name: Nullable<string> }, listener: () => unknown): void {
|
|
9
11
|
const form = inject<FormController | null>('form', null);
|
|
10
12
|
const stop = form?.on('focus', (name) => input.name === name && listener());
|
|
@@ -12,6 +14,10 @@ export function onFormFocus(input: { name: Nullable<string> }, listener: () => u
|
|
|
12
14
|
onUnmounted(() => stop?.());
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
export function useForm<const T extends FormFieldDefinitions>(fields: T): FormController<T> & FormData<T> {
|
|
18
|
+
return new FormController(fields) as FormController<T> & FormData<T>;
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
export function useInputAttrs(): [ComputedRef<{}>, ComputedRef<ClassValue>] {
|
|
16
22
|
const attrs = useAttrs();
|
|
17
23
|
const classes = computed(() => attrs.class);
|
package/src/utils/index.ts
CHANGED
package/src/utils/markdown.ts
CHANGED
|
@@ -2,10 +2,18 @@ import DOMPurify from 'dompurify';
|
|
|
2
2
|
import { stringMatchAll, tap } from '@noeldemartin/utils';
|
|
3
3
|
import { Renderer, marked } from 'marked';
|
|
4
4
|
|
|
5
|
+
let router: MarkdownRouter | null = null;
|
|
6
|
+
|
|
5
7
|
function makeRenderer(): Renderer {
|
|
6
8
|
return tap(new Renderer(), (renderer) => {
|
|
7
9
|
renderer.link = function(link) {
|
|
8
|
-
|
|
10
|
+
const defaultLink = Renderer.prototype.link.apply(this, [link]);
|
|
11
|
+
|
|
12
|
+
if (!link.href.startsWith('#')) {
|
|
13
|
+
return defaultLink.replace('<a', '<a target="_blank"');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return defaultLink;
|
|
9
17
|
};
|
|
10
18
|
});
|
|
11
19
|
}
|
|
@@ -20,11 +28,37 @@ function renderActionLinks(html: string): string {
|
|
|
20
28
|
return html;
|
|
21
29
|
}
|
|
22
30
|
|
|
31
|
+
function renderRouteLinks(html: string): string {
|
|
32
|
+
const matches = stringMatchAll<3>(html, /<a[^>]*href="#route:([^"]+)"[^>]*>([^<]+)<\/a>/g);
|
|
33
|
+
|
|
34
|
+
for (const [link, route, text] of matches) {
|
|
35
|
+
const url = router?.resolve(route) ?? route;
|
|
36
|
+
|
|
37
|
+
html = html.replace(link, `<a data-markdown-route="${route}" href="${url}">${text}</a>`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return html;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MarkdownRouter {
|
|
44
|
+
resolve(route: string): string;
|
|
45
|
+
visit(route: string): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getMarkdownRouter(): MarkdownRouter | null {
|
|
49
|
+
return router;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function setMarkdownRouter(markdownRouter: MarkdownRouter): void {
|
|
53
|
+
router = markdownRouter;
|
|
54
|
+
}
|
|
55
|
+
|
|
23
56
|
export function renderMarkdown(markdown: string): string {
|
|
24
57
|
let html = marked(markdown, { renderer: makeRenderer(), async: false });
|
|
25
58
|
|
|
26
59
|
html = safeHtml(html);
|
|
27
60
|
html = renderActionLinks(html);
|
|
61
|
+
html = renderRouteLinks(html);
|
|
28
62
|
|
|
29
63
|
return html;
|
|
30
64
|
}
|
package/src/utils/vue.ts
CHANGED
|
@@ -12,7 +12,12 @@ function renderVNodeAttrs(node: VNode): string {
|
|
|
12
12
|
}, '');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
export function defineDirective<TValue = any, TModifiers extends string = string>(
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
directive: Directive<any, TValue, TModifiers>,
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
): Directive<any, TValue, TModifiers> {
|
|
16
21
|
return directive;
|
|
17
22
|
}
|
|
18
23
|
|
package/src/forms/composition.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import FormController from '@aerogel/core/forms/FormController';
|
|
2
|
-
import type { FormData, FormFieldDefinitions } from '@aerogel/core/forms/FormController';
|
|
3
|
-
|
|
4
|
-
export function useForm<const T extends FormFieldDefinitions>(fields: T): FormController<T> & FormData<T> {
|
|
5
|
-
return new FormController(fields) as FormController<T> & FormData<T>;
|
|
6
|
-
}
|