@aerogel/core 0.0.0-next.c8f032a868370824898e171969aec1bb6827688e → 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.
Files changed (115) hide show
  1. package/dist/aerogel-core.cjs.js +1 -1
  2. package/dist/aerogel-core.cjs.js.map +1 -1
  3. package/dist/aerogel-core.d.ts +1511 -236
  4. package/dist/aerogel-core.esm.js +1 -1
  5. package/dist/aerogel-core.esm.js.map +1 -1
  6. package/histoire.config.ts +7 -0
  7. package/noeldemartin.config.js +4 -1
  8. package/package.json +14 -4
  9. package/postcss.config.js +6 -0
  10. package/src/assets/histoire.css +3 -0
  11. package/src/bootstrap/bootstrap.test.ts +4 -3
  12. package/src/bootstrap/index.ts +27 -4
  13. package/src/bootstrap/options.ts +3 -0
  14. package/src/components/AGAppLayout.vue +7 -2
  15. package/src/components/AGAppModals.vue +15 -0
  16. package/src/components/AGAppOverlays.vue +10 -8
  17. package/src/components/AGAppSnackbars.vue +13 -0
  18. package/src/components/constants.ts +8 -0
  19. package/src/components/forms/AGButton.vue +33 -10
  20. package/src/components/forms/AGCheckbox.vue +41 -0
  21. package/src/components/forms/AGInput.vue +15 -9
  22. package/src/components/forms/AGSelect.story.vue +46 -0
  23. package/src/components/forms/AGSelect.vue +60 -0
  24. package/src/components/forms/index.ts +5 -5
  25. package/src/components/headless/forms/AGHeadlessButton.vue +12 -12
  26. package/src/components/headless/forms/AGHeadlessInput.ts +23 -3
  27. package/src/components/headless/forms/AGHeadlessInput.vue +11 -8
  28. package/src/components/headless/forms/AGHeadlessInputError.vue +1 -1
  29. package/src/components/headless/forms/AGHeadlessInputInput.vue +17 -3
  30. package/src/components/headless/forms/AGHeadlessInputLabel.vue +8 -2
  31. package/src/components/headless/forms/AGHeadlessSelect.ts +42 -0
  32. package/src/components/headless/forms/AGHeadlessSelect.vue +77 -0
  33. package/src/components/headless/forms/AGHeadlessSelectButton.vue +24 -0
  34. package/src/components/headless/forms/AGHeadlessSelectError.vue +26 -0
  35. package/src/components/headless/forms/AGHeadlessSelectLabel.vue +24 -0
  36. package/src/components/headless/forms/AGHeadlessSelectOption.ts +4 -0
  37. package/src/components/headless/forms/AGHeadlessSelectOption.vue +39 -0
  38. package/src/components/headless/forms/AGHeadlessSelectOptions.ts +3 -0
  39. package/src/components/headless/forms/index.ts +9 -1
  40. package/src/components/headless/index.ts +1 -0
  41. package/src/components/headless/modals/AGHeadlessModal.ts +27 -0
  42. package/src/components/headless/modals/AGHeadlessModal.vue +3 -5
  43. package/src/components/headless/modals/AGHeadlessModalPanel.vue +5 -1
  44. package/src/components/headless/modals/index.ts +4 -6
  45. package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +10 -0
  46. package/src/components/headless/snackbars/index.ts +40 -0
  47. package/src/components/index.ts +3 -1
  48. package/src/components/lib/AGErrorMessage.vue +16 -0
  49. package/src/components/lib/AGLink.vue +9 -0
  50. package/src/components/lib/AGMarkdown.vue +36 -0
  51. package/src/components/lib/AGMeasured.vue +15 -0
  52. package/src/components/lib/AGStartupCrash.vue +31 -0
  53. package/src/components/lib/index.ts +5 -0
  54. package/src/components/modals/AGAlertModal.ts +15 -0
  55. package/src/components/modals/AGAlertModal.vue +4 -16
  56. package/src/components/modals/AGConfirmModal.ts +27 -0
  57. package/src/components/modals/AGConfirmModal.vue +8 -12
  58. package/src/components/modals/AGErrorReportModal.ts +46 -0
  59. package/src/components/modals/AGErrorReportModal.vue +54 -0
  60. package/src/components/modals/AGErrorReportModalButtons.vue +111 -0
  61. package/src/components/modals/AGErrorReportModalTitle.vue +25 -0
  62. package/src/components/modals/AGLoadingModal.ts +23 -0
  63. package/src/components/modals/AGLoadingModal.vue +15 -0
  64. package/src/components/modals/AGModal.ts +1 -1
  65. package/src/components/modals/AGModal.vue +26 -5
  66. package/src/components/modals/AGModalTitle.vue +9 -0
  67. package/src/components/modals/AGPromptModal.ts +30 -0
  68. package/src/components/modals/AGPromptModal.vue +34 -0
  69. package/src/components/modals/index.ts +16 -6
  70. package/src/components/snackbars/AGSnackbar.vue +36 -0
  71. package/src/components/snackbars/index.ts +3 -0
  72. package/src/components/utils.ts +10 -0
  73. package/src/directives/index.ts +20 -3
  74. package/src/directives/measure.ts +21 -0
  75. package/src/errors/Errors.state.ts +31 -0
  76. package/src/errors/Errors.ts +185 -0
  77. package/src/errors/index.ts +46 -0
  78. package/src/errors/utils.ts +19 -0
  79. package/src/forms/Form.test.ts +21 -0
  80. package/src/forms/Form.ts +34 -15
  81. package/src/forms/utils.ts +17 -0
  82. package/src/jobs/Job.ts +5 -0
  83. package/src/jobs/index.ts +7 -0
  84. package/src/lang/Lang.ts +11 -15
  85. package/src/lang/index.ts +3 -5
  86. package/src/lang/utils.ts +4 -0
  87. package/src/main.histoire.ts +1 -0
  88. package/src/main.ts +4 -2
  89. package/src/plugins/Plugin.ts +1 -0
  90. package/src/plugins/index.ts +19 -0
  91. package/src/services/App.state.ts +9 -2
  92. package/src/services/App.ts +43 -3
  93. package/src/services/Events.test.ts +39 -0
  94. package/src/services/Events.ts +100 -30
  95. package/src/services/Service.ts +174 -53
  96. package/src/services/index.ts +22 -4
  97. package/src/services/store.ts +30 -0
  98. package/src/testing/index.ts +25 -0
  99. package/src/ui/UI.state.ts +11 -1
  100. package/src/ui/UI.ts +169 -20
  101. package/src/ui/index.ts +15 -4
  102. package/src/utils/composition/events.ts +1 -0
  103. package/src/utils/composition/forms.ts +11 -0
  104. package/src/utils/index.ts +2 -0
  105. package/src/utils/markdown.ts +11 -2
  106. package/src/utils/tailwindcss.test.ts +26 -0
  107. package/src/utils/tailwindcss.ts +7 -0
  108. package/src/utils/vue.ts +15 -4
  109. package/tailwind.config.js +4 -0
  110. package/tsconfig.json +1 -0
  111. package/vite.config.ts +2 -1
  112. package/.eslintrc.js +0 -3
  113. package/src/components/basic/AGMarkdown.vue +0 -35
  114. package/src/components/basic/index.ts +0 -3
  115. package/src/globals.ts +0 -6
@@ -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
+ }
@@ -17,6 +17,16 @@ export interface ModalComponent<
17
17
  Result = unknown
18
18
  > {}
19
19
 
20
+ export interface Snackbar {
21
+ id: string;
22
+ component: Component;
23
+ properties: Record<string, unknown>;
24
+ }
25
+
20
26
  export default defineServiceState({
21
- initialState: { modals: [] as Modal[] },
27
+ name: 'ui',
28
+ initialState: {
29
+ modals: [] as Modal[],
30
+ snackbars: [] as Snackbar[],
31
+ },
22
32
  });
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,36 +23,165 @@ 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',
27
+ LoadingModal: 'loading-modal',
28
+ PromptModal: 'prompt-modal',
29
+ Snackbar: 'snackbar',
30
+ StartupCrash: 'startup-crash',
24
31
  } as const;
25
32
 
26
33
  export type UIComponent = ObjectValues<typeof UIComponents>;
27
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
+
28
54
  export class UIService extends Service {
29
55
 
30
56
  private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
31
57
  private components: Partial<Record<UIComponent, Component>> = {};
32
58
 
59
+ public requireComponent(name: UIComponent): Component {
60
+ return this.components[name] ?? fail(`UI Component '${name}' is not defined!`);
61
+ }
62
+
33
63
  public alert(message: string): void;
34
64
  public alert(title: string, message: string): void;
35
65
  public alert(messageOrTitle: string, message?: string): void {
36
- const options = typeof message === 'string' ? { title: messageOrTitle, message } : { message: messageOrTitle };
66
+ const getProperties = (): AGAlertModalProps => {
67
+ if (typeof message !== 'string') {
68
+ return { message: messageOrTitle };
69
+ }
70
+
71
+ return {
72
+ title: messageOrTitle,
73
+ message,
74
+ };
75
+ };
37
76
 
38
- this.openModal(this.requireComponent(UIComponents.AlertModal), options);
77
+ this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
39
78
  }
40
79
 
41
- public async confirm(message: string): Promise<boolean>;
42
- public async confirm(title: string, message: string): Promise<boolean>;
43
- public async confirm(messageOrTitle: string, message?: string): Promise<boolean> {
44
- const options = typeof message === 'string' ? { title: messageOrTitle, message } : { message: messageOrTitle };
45
- const modal = await this.openModal<ModalComponent<{ message: string }, boolean>>(
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>>(
46
103
  this.requireComponent(UIComponents.ConfirmModal),
47
- options,
104
+ getProperties(),
48
105
  );
49
106
  const result = await modal.beforeClose;
50
107
 
51
108
  return result ?? false;
52
109
  }
53
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
+
142
+ public async loading<T>(operation: Promise<T>): Promise<T>;
143
+ public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
144
+ public async loading<T>(messageOrOperation: string | Promise<T>, operation?: Promise<T>): Promise<T> {
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
+ };
172
+
173
+ this.setState('snackbars', this.snackbars.concat(snackbar));
174
+
175
+ setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
176
+ }
177
+
178
+ public hideSnackbar(id: string): void {
179
+ this.setState(
180
+ 'snackbars',
181
+ this.snackbars.filter((snackbar) => snackbar.id !== id),
182
+ );
183
+ }
184
+
54
185
  public registerComponent(name: UIComponent, component: Component): void {
55
186
  this.components[name] = component;
56
187
  }
@@ -90,13 +221,8 @@ export class UIService extends Service {
90
221
  }
91
222
 
92
223
  protected async boot(): Promise<void> {
93
- await super.boot();
94
-
95
224
  this.watchModalEvents();
96
- }
97
-
98
- private requireComponent(name: UIComponent): Component {
99
- return this.components[name] ?? fail(`UI Component '${name}' is not defined!`);
225
+ this.watchMountedEvent();
100
226
  }
101
227
 
102
228
  private watchModalEvents(): void {
@@ -109,7 +235,10 @@ export class UIService extends Service {
109
235
  });
110
236
 
111
237
  Events.on('modal-closed', async ({ modal, result }) => {
112
- this.setState({ modals: this.modals.filter((m) => m.id !== modal.id) });
238
+ this.setState(
239
+ 'modals',
240
+ this.modals.filter((m) => m.id !== modal.id),
241
+ );
113
242
 
114
243
  this.modalCallbacks[modal.id]?.closed?.(result);
115
244
 
@@ -121,16 +250,36 @@ export class UIService extends Service {
121
250
  });
122
251
  }
123
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
+
124
271
  }
125
272
 
126
- export default facade(new UIService());
273
+ export default facade(UIService);
127
274
 
128
275
  declare module '@/services/Events' {
129
276
  export interface EventsPayload {
130
- 'modal-will-close': { modal: Modal; result?: unknown };
131
- 'modal-closed': { modal: Modal; result?: unknown };
132
277
  'close-modal': { id: string; result?: unknown };
133
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 };
134
282
  'show-modal': { id: string };
283
+ 'show-overlays-backdrop': void;
135
284
  }
136
285
  }
package/src/ui/index.ts CHANGED
@@ -6,12 +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';
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';
9
14
  import type { UIComponent } from './UI';
10
15
 
11
- export { UI, UIComponents, UIComponent };
12
-
13
16
  const services = { $ui: UI };
14
17
 
18
+ export * from './UI';
19
+ export { default as UI } from './UI';
20
+
15
21
  export type UIServices = typeof services;
16
22
 
17
23
  export default definePlugin({
@@ -19,6 +25,11 @@ export default definePlugin({
19
25
  const defaultComponents = {
20
26
  [UIComponents.AlertModal]: AGAlertModal,
21
27
  [UIComponents.ConfirmModal]: AGConfirmModal,
28
+ [UIComponents.ErrorReportModal]: AGErrorReportModal,
29
+ [UIComponents.LoadingModal]: AGLoadingModal,
30
+ [UIComponents.PromptModal]: AGPromptModal,
31
+ [UIComponents.Snackbar]: AGSnackbar,
32
+ [UIComponents.StartupCrash]: AGStartupCrash,
22
33
  };
23
34
 
24
35
  Object.entries({
@@ -31,11 +42,11 @@ export default definePlugin({
31
42
  });
32
43
 
33
44
  declare module '@/bootstrap/options' {
34
- interface AerogelOptions {
45
+ export interface AerogelOptions {
35
46
  components?: Partial<Record<UIComponent, Component>>;
36
47
  }
37
48
  }
38
49
 
39
50
  declare module '@/services' {
40
- interface Services extends UIServices {}
51
+ export interface Services extends UIServices {}
41
52
  }
@@ -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 {
@@ -0,0 +1,11 @@
1
+ import { objectWithout } from '@noeldemartin/utils';
2
+ import { computed, useAttrs } from 'vue';
3
+ import type { ComputedRef } from 'vue';
4
+
5
+ export function useInputAttrs(): [ComputedRef<{}>, ComputedRef<unknown>] {
6
+ const attrs = useAttrs();
7
+ const className = computed(() => attrs.class);
8
+ const inputAttrs = computed(() => objectWithout(attrs, 'class'));
9
+
10
+ return [inputAttrs, className];
11
+ }
@@ -1,3 +1,5 @@
1
1
  export * from './composition/events';
2
+ export * from './composition/forms';
2
3
  export * from './composition/hooks';
4
+ export * from './tailwindcss';
3
5
  export * from './vue';
@@ -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
+ });
@@ -0,0 +1,7 @@
1
+ export function removeInteractiveClasses(classes: string): string {
2
+ return classes
3
+ .split(/\s+/)
4
+ .filter((className) => !/^(hover|focus|focus-visible):/.test(className))
5
+ .join(' ')
6
+ .trim();
7
+ }
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: PropType<T>;
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: PropType<T>): OptionalProp<T | null> {
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: PropType<T>): RequiredProp<T> {
120
+ export function requiredMixedProp<T>(type?: PropType<T>): RequiredProp<T> {
110
121
  return {
111
122
  type,
112
123
  required: true,
@@ -0,0 +1,4 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: ['./src/**/*.{vue,ts}'],
4
+ };
package/tsconfig.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "../../tsconfig.json",
3
3
  "compilerOptions": {
4
+ "types": ["unplugin-icons/types/vue3", "@aerogel/vite/dist/virtual"],
4
5
  "baseUrl": ".",
5
6
  "paths": {
6
7
  "@/*": ["./src/*"]
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
@@ -1,3 +0,0 @@
1
- module.exports = {
2
- extends: ['@noeldemartin/eslint-config-vue'],
3
- };
@@ -1,35 +0,0 @@
1
- <template>
2
- <root />
3
- </template>
4
-
5
- <script setup lang="ts">
6
- import { computed, h } from 'vue';
7
-
8
- import { renderMarkdown } from '@/utils/markdown';
9
- import { booleanProp, stringProp } from '@/utils/vue';
10
- import { translate } from '@/lang';
11
-
12
- const props = defineProps({
13
- as: stringProp('div'),
14
- langKey: stringProp(),
15
- text: stringProp(),
16
- inline: booleanProp(),
17
- raw: booleanProp(),
18
- });
19
-
20
- const markdown = computed(() => props.text ?? (props.langKey && translate(props.langKey)));
21
- const html = computed(() => {
22
- if (!markdown.value) {
23
- return null;
24
- }
25
-
26
- let html = renderMarkdown(markdown.value);
27
-
28
- if (props.inline) {
29
- html = html.replace('<p>', '<span>').replace('</p>', '</span>');
30
- }
31
-
32
- return html;
33
- });
34
- const root = () => h(props.as, { class: props.raw ? '' : 'prose', innerHTML: html.value });
35
- </script>
@@ -1,3 +0,0 @@
1
- import AGMarkdown from './AGMarkdown.vue';
2
-
3
- export { AGMarkdown };
package/src/globals.ts DELETED
@@ -1,6 +0,0 @@
1
- export {};
2
-
3
- declare global {
4
- export const __AG_BASE_PATH: string | undefined;
5
- export const __AG_ENV: 'production' | 'development' | 'testing';
6
- }