@aerogel/core 0.0.0-next.b85327579d32f21c6a9fa21142f0165cdd320d7e → 0.0.0-next.d547095ca85c86c1e41f5c7fa170d0ba856c3f4d
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 +1371 -257
- package/dist/aerogel-core.esm.js +1 -1
- package/dist/aerogel-core.esm.js.map +1 -1
- package/histoire.config.ts +7 -0
- package/noeldemartin.config.js +4 -1
- package/package.json +13 -4
- package/postcss.config.js +6 -0
- package/src/assets/histoire.css +3 -0
- package/src/bootstrap/bootstrap.test.ts +4 -3
- package/src/bootstrap/index.ts +25 -5
- package/src/bootstrap/options.ts +3 -0
- package/src/components/AGAppLayout.vue +7 -2
- package/src/components/AGAppModals.vue +15 -0
- package/src/components/AGAppOverlays.vue +10 -8
- package/src/components/AGAppSnackbars.vue +13 -0
- package/src/components/constants.ts +8 -0
- package/src/components/forms/AGButton.vue +25 -15
- package/src/components/forms/AGCheckbox.vue +7 -1
- package/src/components/forms/AGInput.vue +8 -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.vue +9 -8
- package/src/components/headless/forms/AGHeadlessInput.ts +21 -1
- package/src/components/headless/forms/AGHeadlessInput.vue +8 -5
- package/src/components/headless/forms/AGHeadlessInputInput.vue +2 -0
- package/src/components/headless/forms/AGHeadlessInputLabel.vue +8 -2
- 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/index.ts +9 -1
- package/src/components/headless/index.ts +1 -0
- 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/AGHeadlessSnackbar.vue +10 -0
- package/src/components/headless/snackbars/index.ts +40 -0
- package/src/components/index.ts +3 -1
- package/src/components/lib/AGErrorMessage.vue +16 -0
- package/src/components/lib/AGLink.vue +9 -0
- package/src/components/{basic → lib}/AGMarkdown.vue +7 -6
- package/src/components/lib/AGMeasured.vue +15 -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 -16
- package/src/components/modals/AGConfirmModal.ts +27 -0
- package/src/components/modals/AGConfirmModal.vue +8 -12
- package/src/components/modals/AGErrorReportModal.ts +46 -0
- package/src/components/modals/AGErrorReportModal.vue +54 -0
- package/src/components/modals/AGErrorReportModalButtons.vue +111 -0
- package/src/components/modals/AGErrorReportModalTitle.vue +25 -0
- 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 +16 -13
- package/src/components/modals/AGModalTitle.vue +9 -0
- package/src/components/modals/AGPromptModal.ts +30 -0
- package/src/components/modals/AGPromptModal.vue +34 -0
- package/src/components/modals/index.ts +16 -7
- package/src/components/snackbars/AGSnackbar.vue +36 -0
- package/src/components/snackbars/index.ts +3 -0
- package/src/components/utils.ts +10 -0
- package/src/directives/index.ts +20 -3
- package/src/directives/measure.ts +21 -0
- package/src/errors/Errors.ts +65 -12
- package/src/errors/index.ts +26 -1
- package/src/errors/utils.ts +19 -0
- package/src/forms/Form.ts +15 -6
- package/src/jobs/Job.ts +5 -0
- package/src/jobs/index.ts +7 -0
- package/src/lang/Lang.ts +12 -24
- package/src/main.histoire.ts +1 -0
- package/src/main.ts +3 -2
- package/src/plugins/Plugin.ts +1 -0
- package/src/plugins/index.ts +19 -0
- package/src/services/App.state.ts +8 -4
- package/src/services/App.ts +35 -5
- package/src/services/Events.test.ts +39 -0
- package/src/services/Events.ts +100 -30
- package/src/services/Service.ts +55 -16
- package/src/services/index.ts +8 -4
- package/src/services/store.ts +8 -5
- package/src/testing/index.ts +25 -0
- package/src/ui/UI.state.ts +10 -1
- package/src/ui/UI.ts +160 -26
- package/src/ui/index.ts +12 -3
- package/src/utils/composition/events.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/markdown.ts +11 -2
- package/src/utils/tailwindcss.test.ts +26 -0
- package/src/utils/tailwindcss.ts +7 -0
- package/src/utils/vue.ts +15 -4
- package/tailwind.config.js +4 -0
- package/tsconfig.json +1 -0
- package/vite.config.ts +2 -1
- package/.eslintrc.js +0 -3
- package/src/components/basic/index.ts +0 -3
- package/src/globals.ts +0 -6
package/src/ui/UI.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
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 { SnackbarAction, SnackbarColor } from '@/components/headless/snackbars';
|
|
8
|
+
import type { AGAlertModalProps, AGConfirmModalProps, AGLoadingModalProps, AGPromptModalProps } from '@/components';
|
|
7
9
|
|
|
8
10
|
import Service from './UI.state';
|
|
9
|
-
import type { Modal, ModalComponent } from './UI.state';
|
|
11
|
+
import type { Modal, ModalComponent, Snackbar } from './UI.state';
|
|
10
12
|
|
|
11
13
|
interface ModalCallbacks<T = unknown> {
|
|
12
14
|
willClose(result: T | undefined): void;
|
|
@@ -21,49 +23,163 @@ type ModalResult<TComponent> = TComponent extends ModalComponent<Record<string,
|
|
|
21
23
|
export const UIComponents = {
|
|
22
24
|
AlertModal: 'alert-modal',
|
|
23
25
|
ConfirmModal: 'confirm-modal',
|
|
26
|
+
ErrorReportModal: 'error-report-modal',
|
|
24
27
|
LoadingModal: 'loading-modal',
|
|
28
|
+
PromptModal: 'prompt-modal',
|
|
29
|
+
Snackbar: 'snackbar',
|
|
30
|
+
StartupCrash: 'startup-crash',
|
|
25
31
|
} as const;
|
|
26
32
|
|
|
27
33
|
export type UIComponent = ObjectValues<typeof UIComponents>;
|
|
28
34
|
|
|
35
|
+
export interface ConfirmOptions {
|
|
36
|
+
acceptText?: string;
|
|
37
|
+
cancelText?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PromptOptions {
|
|
41
|
+
label?: string;
|
|
42
|
+
defaultValue?: string;
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
acceptText?: string;
|
|
45
|
+
cancelText?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ShowSnackbarOptions {
|
|
49
|
+
component?: Component;
|
|
50
|
+
color?: SnackbarColor;
|
|
51
|
+
actions?: SnackbarAction[];
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
export class UIService extends Service {
|
|
30
55
|
|
|
31
56
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
32
57
|
private components: Partial<Record<UIComponent, Component>> = {};
|
|
33
58
|
|
|
59
|
+
public requireComponent(name: UIComponent): Component {
|
|
60
|
+
return this.components[name] ?? fail(`UI Component '${name}' is not defined!`);
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
public alert(message: string): void;
|
|
35
64
|
public alert(title: string, message: string): void;
|
|
36
65
|
public alert(messageOrTitle: string, message?: string): void {
|
|
37
|
-
const
|
|
66
|
+
const getProperties = (): AGAlertModalProps => {
|
|
67
|
+
if (typeof message !== 'string') {
|
|
68
|
+
return { message: messageOrTitle };
|
|
69
|
+
}
|
|
38
70
|
|
|
39
|
-
|
|
71
|
+
return {
|
|
72
|
+
title: messageOrTitle,
|
|
73
|
+
message,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
|
|
40
78
|
}
|
|
41
79
|
|
|
42
|
-
public async confirm(message: string): Promise<boolean>;
|
|
43
|
-
public async confirm(title: string, message: string): Promise<boolean>;
|
|
44
|
-
public async confirm(
|
|
45
|
-
|
|
46
|
-
|
|
80
|
+
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
81
|
+
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
82
|
+
public async confirm(
|
|
83
|
+
messageOrTitle: string,
|
|
84
|
+
messageOrOptions?: string | ConfirmOptions,
|
|
85
|
+
options?: ConfirmOptions,
|
|
86
|
+
): Promise<boolean> {
|
|
87
|
+
const getProperties = (): AGConfirmModalProps => {
|
|
88
|
+
if (typeof messageOrOptions !== 'string') {
|
|
89
|
+
return {
|
|
90
|
+
message: messageOrTitle,
|
|
91
|
+
...(messageOrOptions ?? {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
title: messageOrTitle,
|
|
97
|
+
message: messageOrOptions,
|
|
98
|
+
...(options ?? {}),
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const modal = await this.openModal<ModalComponent<AGConfirmModalProps, boolean>>(
|
|
47
103
|
this.requireComponent(UIComponents.ConfirmModal),
|
|
48
|
-
|
|
104
|
+
getProperties(),
|
|
49
105
|
);
|
|
50
106
|
const result = await modal.beforeClose;
|
|
51
107
|
|
|
52
108
|
return result ?? false;
|
|
53
109
|
}
|
|
54
110
|
|
|
111
|
+
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
112
|
+
public async prompt(title: string, message: string, options?: PromptOptions): Promise<string | null>;
|
|
113
|
+
public async prompt(
|
|
114
|
+
messageOrTitle: string,
|
|
115
|
+
messageOrOptions?: string | PromptOptions,
|
|
116
|
+
options?: PromptOptions,
|
|
117
|
+
): Promise<string | null> {
|
|
118
|
+
const getProperties = (): AGPromptModalProps => {
|
|
119
|
+
if (typeof messageOrOptions !== 'string') {
|
|
120
|
+
return {
|
|
121
|
+
message: messageOrTitle,
|
|
122
|
+
...(messageOrOptions ?? {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
title: messageOrTitle,
|
|
128
|
+
message: messageOrOptions,
|
|
129
|
+
...(options ?? {}),
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const modal = await this.openModal<ModalComponent<AGPromptModalProps, string | null>>(
|
|
134
|
+
this.requireComponent(UIComponents.PromptModal),
|
|
135
|
+
getProperties(),
|
|
136
|
+
);
|
|
137
|
+
const result = await modal.beforeClose;
|
|
138
|
+
|
|
139
|
+
return result ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
55
142
|
public async loading<T>(operation: Promise<T>): Promise<T>;
|
|
56
143
|
public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
|
|
57
144
|
public async loading<T>(messageOrOperation: string | Promise<T>, operation?: Promise<T>): Promise<T> {
|
|
58
|
-
|
|
145
|
+
const getProperties = (): AGLoadingModalProps => {
|
|
146
|
+
if (typeof messageOrOperation !== 'string') {
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { message: messageOrOperation };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), getProperties());
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
operation = typeof messageOrOperation === 'string' ? (operation as Promise<T>) : messageOrOperation;
|
|
157
|
+
|
|
158
|
+
const [result] = await Promise.all([operation, after({ seconds: 1 })]);
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
} finally {
|
|
162
|
+
await this.closeModal(modal.id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public showSnackbar(message: string, options: ShowSnackbarOptions = {}): void {
|
|
167
|
+
const snackbar: Snackbar = {
|
|
168
|
+
id: uuid(),
|
|
169
|
+
properties: { message, ...options },
|
|
170
|
+
component: markRaw(options.component ?? this.requireComponent(UIComponents.Snackbar)),
|
|
171
|
+
};
|
|
59
172
|
|
|
60
|
-
|
|
61
|
-
const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), { message });
|
|
62
|
-
const result = await operation;
|
|
173
|
+
this.setState('snackbars', this.snackbars.concat(snackbar));
|
|
63
174
|
|
|
64
|
-
|
|
175
|
+
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
176
|
+
}
|
|
65
177
|
|
|
66
|
-
|
|
178
|
+
public hideSnackbar(id: string): void {
|
|
179
|
+
this.setState(
|
|
180
|
+
'snackbars',
|
|
181
|
+
this.snackbars.filter((snackbar) => snackbar.id !== id),
|
|
182
|
+
);
|
|
67
183
|
}
|
|
68
184
|
|
|
69
185
|
public registerComponent(name: UIComponent, component: Component): void {
|
|
@@ -105,13 +221,8 @@ export class UIService extends Service {
|
|
|
105
221
|
}
|
|
106
222
|
|
|
107
223
|
protected async boot(): Promise<void> {
|
|
108
|
-
await super.boot();
|
|
109
|
-
|
|
110
224
|
this.watchModalEvents();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
private requireComponent(name: UIComponent): Component {
|
|
114
|
-
return this.components[name] ?? fail(`UI Component '${name}' is not defined!`);
|
|
225
|
+
this.watchMountedEvent();
|
|
115
226
|
}
|
|
116
227
|
|
|
117
228
|
private watchModalEvents(): void {
|
|
@@ -124,7 +235,10 @@ export class UIService extends Service {
|
|
|
124
235
|
});
|
|
125
236
|
|
|
126
237
|
Events.on('modal-closed', async ({ modal, result }) => {
|
|
127
|
-
this.setState(
|
|
238
|
+
this.setState(
|
|
239
|
+
'modals',
|
|
240
|
+
this.modals.filter((m) => m.id !== modal.id),
|
|
241
|
+
);
|
|
128
242
|
|
|
129
243
|
this.modalCallbacks[modal.id]?.closed?.(result);
|
|
130
244
|
|
|
@@ -136,16 +250,36 @@ export class UIService extends Service {
|
|
|
136
250
|
});
|
|
137
251
|
}
|
|
138
252
|
|
|
253
|
+
private watchMountedEvent(): void {
|
|
254
|
+
Events.once('application-mounted', async () => {
|
|
255
|
+
const splash = document.getElementById('splash');
|
|
256
|
+
|
|
257
|
+
if (!splash) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (window.getComputedStyle(splash).opacity !== '0') {
|
|
262
|
+
splash.style.opacity = '0';
|
|
263
|
+
|
|
264
|
+
await after({ ms: 600 });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
splash.remove();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
139
271
|
}
|
|
140
272
|
|
|
141
|
-
export default facade(
|
|
273
|
+
export default facade(UIService);
|
|
142
274
|
|
|
143
275
|
declare module '@/services/Events' {
|
|
144
276
|
export interface EventsPayload {
|
|
145
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
146
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
147
277
|
'close-modal': { id: string; result?: unknown };
|
|
148
278
|
'hide-modal': { id: string };
|
|
279
|
+
'hide-overlays-backdrop': void;
|
|
280
|
+
'modal-closed': { modal: Modal; result?: unknown };
|
|
281
|
+
'modal-will-close': { modal: Modal; result?: unknown };
|
|
149
282
|
'show-modal': { id: string };
|
|
283
|
+
'show-overlays-backdrop': void;
|
|
150
284
|
}
|
|
151
285
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -6,13 +6,18 @@ import { definePlugin } from '@/plugins';
|
|
|
6
6
|
import UI, { UIComponents } from './UI';
|
|
7
7
|
import AGAlertModal from '../components/modals/AGAlertModal.vue';
|
|
8
8
|
import AGConfirmModal from '../components/modals/AGConfirmModal.vue';
|
|
9
|
+
import AGErrorReportModal from '../components/modals/AGErrorReportModal.vue';
|
|
9
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';
|
|
10
14
|
import type { UIComponent } from './UI';
|
|
11
15
|
|
|
12
|
-
export { UI, UIComponents, UIComponent };
|
|
13
|
-
|
|
14
16
|
const services = { $ui: UI };
|
|
15
17
|
|
|
18
|
+
export * from './UI';
|
|
19
|
+
export { default as UI } from './UI';
|
|
20
|
+
|
|
16
21
|
export type UIServices = typeof services;
|
|
17
22
|
|
|
18
23
|
export default definePlugin({
|
|
@@ -20,7 +25,11 @@ export default definePlugin({
|
|
|
20
25
|
const defaultComponents = {
|
|
21
26
|
[UIComponents.AlertModal]: AGAlertModal,
|
|
22
27
|
[UIComponents.ConfirmModal]: AGConfirmModal,
|
|
28
|
+
[UIComponents.ErrorReportModal]: AGErrorReportModal,
|
|
23
29
|
[UIComponents.LoadingModal]: AGLoadingModal,
|
|
30
|
+
[UIComponents.PromptModal]: AGPromptModal,
|
|
31
|
+
[UIComponents.Snackbar]: AGSnackbar,
|
|
32
|
+
[UIComponents.StartupCrash]: AGStartupCrash,
|
|
24
33
|
};
|
|
25
34
|
|
|
26
35
|
Object.entries({
|
|
@@ -33,7 +42,7 @@ export default definePlugin({
|
|
|
33
42
|
});
|
|
34
43
|
|
|
35
44
|
declare module '@/bootstrap/options' {
|
|
36
|
-
interface AerogelOptions {
|
|
45
|
+
export interface AerogelOptions {
|
|
37
46
|
components?: Partial<Record<UIComponent, Component>>;
|
|
38
47
|
}
|
|
39
48
|
}
|
|
@@ -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
package/src/utils/markdown.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import { tap } from '@noeldemartin/utils';
|
|
1
2
|
import DOMPurify from 'dompurify';
|
|
2
|
-
import { marked } from 'marked';
|
|
3
|
+
import { Renderer, marked } from 'marked';
|
|
4
|
+
|
|
5
|
+
function makeRenderer(): Renderer {
|
|
6
|
+
return tap(new Renderer(), (renderer) => {
|
|
7
|
+
renderer.link = function(href, title, text) {
|
|
8
|
+
return Renderer.prototype.link.apply(this, [href, title, text]).replace('<a', '<a target="_blank"');
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
}
|
|
3
12
|
|
|
4
13
|
export function renderMarkdown(markdown: string): string {
|
|
5
|
-
return safeHtml(marked(markdown, { mangle: false, headerIds: false }));
|
|
14
|
+
return safeHtml(marked(markdown, { mangle: false, headerIds: false, renderer: makeRenderer() }));
|
|
6
15
|
}
|
|
7
16
|
|
|
8
17
|
export function safeHtml(html: string): string {
|
|
@@ -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,15 +1,17 @@
|
|
|
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
|
|
|
10
10
|
type RequiredProp<T> = BaseProp<T> & { required: true };
|
|
11
11
|
type OptionalProp<T> = BaseProp<T> & { default: T | (() => T) | null };
|
|
12
12
|
|
|
13
|
+
export type ComponentProps = Record<string, unknown>;
|
|
14
|
+
|
|
13
15
|
export function arrayProp<T>(defaultValue?: () => T[]): OptionalProp<T[]> {
|
|
14
16
|
return {
|
|
15
17
|
type: Array as PropType<T[]>,
|
|
@@ -28,6 +30,15 @@ export function componentRef<T>(): Ref<UnwrapNestedRefs<T> | undefined> {
|
|
|
28
30
|
return ref<UnwrapNestedRefs<T>>();
|
|
29
31
|
}
|
|
30
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
|
+
|
|
31
42
|
export function defineDirective(directive: Directive): Directive {
|
|
32
43
|
return directive;
|
|
33
44
|
}
|
|
@@ -62,7 +73,7 @@ export function injectOrFail<T>(key: InjectionKey<T> | string, errorMessage?: st
|
|
|
62
73
|
return inject(key) ?? fail(errorMessage ?? `Could not resolve '${key}' injection key`);
|
|
63
74
|
}
|
|
64
75
|
|
|
65
|
-
export function mixedProp<T>(type
|
|
76
|
+
export function mixedProp<T>(type?: PropType<T>): OptionalProp<T | null> {
|
|
66
77
|
return {
|
|
67
78
|
type,
|
|
68
79
|
default: null,
|
|
@@ -106,7 +117,7 @@ export function requiredEnumProp<Enum extends Record<string, unknown>>(
|
|
|
106
117
|
};
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
export function requiredMixedProp<T>(type
|
|
120
|
+
export function requiredMixedProp<T>(type?: PropType<T>): RequiredProp<T> {
|
|
110
121
|
return {
|
|
111
122
|
type,
|
|
112
123
|
required: true,
|
package/tsconfig.json
CHANGED
package/vite.config.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import Aerogel from '@aerogel/vite';
|
|
2
|
+
import Icons from 'unplugin-icons/vite';
|
|
2
3
|
import { defineConfig } from 'vitest/config';
|
|
3
4
|
import { resolve } from 'path';
|
|
4
5
|
|
|
5
6
|
export default defineConfig({
|
|
6
7
|
test: { clearMocks: true },
|
|
7
|
-
plugins: [Aerogel()],
|
|
8
|
+
plugins: [Aerogel(), Icons()],
|
|
8
9
|
resolve: {
|
|
9
10
|
alias: {
|
|
10
11
|
'@': resolve(__dirname, './src'),
|
package/.eslintrc.js
DELETED