@aerogel/core 0.0.0-next.b58141fee5d2fe7d25debdbca6b1d2bf1c13e48e → 0.0.0-next.bde642c4a8096c5fc3d5e676c2115da23f4bf1d8

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 (110) 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 +1297 -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/package.json +14 -5
  8. package/postcss.config.js +6 -0
  9. package/src/assets/histoire.css +3 -0
  10. package/src/bootstrap/bootstrap.test.ts +3 -3
  11. package/src/bootstrap/index.ts +25 -5
  12. package/src/bootstrap/options.ts +3 -0
  13. package/src/components/AGAppLayout.vue +7 -2
  14. package/src/components/AGAppOverlays.vue +5 -1
  15. package/src/components/AGAppSnackbars.vue +2 -2
  16. package/src/components/composition.ts +23 -0
  17. package/src/components/forms/AGCheckbox.vue +7 -1
  18. package/src/components/forms/AGForm.vue +9 -10
  19. package/src/components/forms/AGInput.vue +10 -6
  20. package/src/components/forms/AGSelect.story.vue +46 -0
  21. package/src/components/forms/AGSelect.vue +60 -0
  22. package/src/components/forms/index.ts +5 -6
  23. package/src/components/headless/forms/AGHeadlessButton.ts +3 -0
  24. package/src/components/headless/forms/AGHeadlessButton.vue +23 -12
  25. package/src/components/headless/forms/AGHeadlessInput.ts +29 -4
  26. package/src/components/headless/forms/AGHeadlessInput.vue +16 -7
  27. package/src/components/headless/forms/AGHeadlessInputDescription.vue +28 -0
  28. package/src/components/headless/forms/AGHeadlessInputInput.vue +43 -5
  29. package/src/components/headless/forms/AGHeadlessInputLabel.vue +8 -2
  30. package/src/components/headless/forms/AGHeadlessInputTextArea.vue +42 -0
  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/composition.ts +10 -0
  40. package/src/components/headless/forms/index.ts +13 -1
  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/index.ts +4 -6
  44. package/src/components/headless/snackbars/index.ts +23 -8
  45. package/src/components/index.ts +3 -1
  46. package/src/components/interfaces.ts +24 -0
  47. package/src/components/{basic → lib}/AGErrorMessage.vue +2 -2
  48. package/src/components/{basic → lib}/AGMarkdown.vue +9 -4
  49. package/src/components/lib/AGMeasured.vue +15 -0
  50. package/src/components/lib/AGStartupCrash.vue +31 -0
  51. package/src/components/lib/index.ts +5 -0
  52. package/src/components/modals/AGAlertModal.ts +15 -0
  53. package/src/components/modals/AGAlertModal.vue +4 -15
  54. package/src/components/modals/AGConfirmModal.ts +33 -0
  55. package/src/components/modals/AGConfirmModal.vue +9 -13
  56. package/src/components/modals/AGErrorReportModal.ts +27 -1
  57. package/src/components/modals/AGErrorReportModal.vue +8 -16
  58. package/src/components/modals/AGErrorReportModalButtons.vue +4 -2
  59. package/src/components/modals/AGErrorReportModalTitle.vue +1 -1
  60. package/src/components/modals/AGLoadingModal.ts +23 -0
  61. package/src/components/modals/AGLoadingModal.vue +4 -8
  62. package/src/components/modals/AGModal.ts +1 -1
  63. package/src/components/modals/AGModal.vue +14 -12
  64. package/src/components/modals/AGPromptModal.ts +36 -0
  65. package/src/components/modals/AGPromptModal.vue +34 -0
  66. package/src/components/modals/index.ts +13 -19
  67. package/src/components/snackbars/AGSnackbar.vue +3 -9
  68. package/src/components/utils.ts +10 -0
  69. package/src/directives/index.ts +5 -1
  70. package/src/directives/measure.ts +21 -0
  71. package/src/errors/Errors.ts +26 -24
  72. package/src/errors/index.ts +10 -23
  73. package/src/errors/utils.ts +19 -0
  74. package/src/forms/Form.ts +54 -6
  75. package/src/forms/index.ts +1 -0
  76. package/src/forms/utils.ts +15 -0
  77. package/src/jobs/Job.ts +5 -0
  78. package/src/jobs/index.ts +7 -0
  79. package/src/lang/DefaultLangProvider.ts +43 -0
  80. package/src/lang/Lang.state.ts +11 -0
  81. package/src/lang/Lang.ts +41 -30
  82. package/src/main.histoire.ts +1 -0
  83. package/src/main.ts +3 -0
  84. package/src/plugins/Plugin.ts +1 -0
  85. package/src/plugins/index.ts +19 -0
  86. package/src/services/App.state.ts +20 -5
  87. package/src/services/App.ts +38 -3
  88. package/src/services/Cache.ts +43 -0
  89. package/src/services/Events.test.ts +39 -0
  90. package/src/services/Events.ts +100 -30
  91. package/src/services/Service.ts +59 -17
  92. package/src/services/index.ts +5 -2
  93. package/src/services/store.ts +8 -5
  94. package/src/testing/index.ts +25 -0
  95. package/src/testing/setup.ts +19 -0
  96. package/src/ui/UI.ts +132 -19
  97. package/src/ui/index.ts +8 -3
  98. package/src/utils/composition/events.ts +1 -0
  99. package/src/utils/index.ts +1 -0
  100. package/src/utils/tailwindcss.test.ts +26 -0
  101. package/src/utils/tailwindcss.ts +7 -0
  102. package/src/utils/vue.ts +16 -5
  103. package/tailwind.config.js +4 -0
  104. package/tsconfig.json +1 -1
  105. package/vite.config.ts +4 -1
  106. package/.eslintrc.js +0 -3
  107. package/dist/virtual.d.ts +0 -11
  108. package/src/components/basic/index.ts +0 -5
  109. package/src/types/virtual.d.ts +0 -11
  110. /package/src/components/{basic → lib}/AGLink.vue +0 -0
package/src/ui/UI.ts CHANGED
@@ -1,10 +1,12 @@
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';
10
12
  import type { Modal, ModalComponent, Snackbar } from './UI.state';
@@ -24,11 +26,31 @@ export const UIComponents = {
24
26
  ConfirmModal: 'confirm-modal',
25
27
  ErrorReportModal: 'error-report-modal',
26
28
  LoadingModal: 'loading-modal',
29
+ PromptModal: 'prompt-modal',
27
30
  Snackbar: 'snackbar',
31
+ StartupCrash: 'startup-crash',
28
32
  } as const;
29
33
 
30
34
  export type UIComponent = ObjectValues<typeof UIComponents>;
31
35
 
36
+ export interface ConfirmOptions {
37
+ acceptText?: string;
38
+ acceptColor?: Color;
39
+ cancelText?: string;
40
+ cancelColor?: Color;
41
+ }
42
+
43
+ export interface PromptOptions {
44
+ label?: string;
45
+ defaultValue?: string;
46
+ placeholder?: string;
47
+ acceptText?: string;
48
+ acceptColor?: Color;
49
+ cancelText?: string;
50
+ cancelColor?: Color;
51
+ trim?: boolean;
52
+ }
53
+
32
54
  export interface ShowSnackbarOptions {
33
55
  component?: Component;
34
56
  color?: SnackbarColor;
@@ -47,43 +69,113 @@ export class UIService extends Service {
47
69
  public alert(message: string): void;
48
70
  public alert(title: string, message: string): void;
49
71
  public alert(messageOrTitle: string, message?: string): void {
50
- const options = typeof message === 'string' ? { title: messageOrTitle, message } : { message: messageOrTitle };
72
+ const getProperties = (): AGAlertModalProps => {
73
+ if (typeof message !== 'string') {
74
+ return { message: messageOrTitle };
75
+ }
51
76
 
52
- this.openModal(this.requireComponent(UIComponents.AlertModal), options);
77
+ return {
78
+ title: messageOrTitle,
79
+ message,
80
+ };
81
+ };
82
+
83
+ this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
53
84
  }
54
85
 
55
- public async confirm(message: string): Promise<boolean>;
56
- public async confirm(title: string, message: string): Promise<boolean>;
57
- public async confirm(messageOrTitle: string, message?: string): Promise<boolean> {
58
- const options = typeof message === 'string' ? { title: messageOrTitle, message } : { message: messageOrTitle };
59
- const modal = await this.openModal<ModalComponent<{ message: string }, boolean>>(
86
+ public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
87
+ public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
88
+ public async confirm(
89
+ messageOrTitle: string,
90
+ messageOrOptions?: string | ConfirmOptions,
91
+ options?: ConfirmOptions,
92
+ ): Promise<boolean> {
93
+ const getProperties = (): AGConfirmModalProps => {
94
+ if (typeof messageOrOptions !== 'string') {
95
+ return {
96
+ message: messageOrTitle,
97
+ ...(messageOrOptions ?? {}),
98
+ };
99
+ }
100
+
101
+ return {
102
+ title: messageOrTitle,
103
+ message: messageOrOptions,
104
+ ...(options ?? {}),
105
+ };
106
+ };
107
+
108
+ const modal = await this.openModal<ModalComponent<AGConfirmModalProps, boolean>>(
60
109
  this.requireComponent(UIComponents.ConfirmModal),
61
- options,
110
+ getProperties(),
62
111
  );
63
112
  const result = await modal.beforeClose;
64
113
 
65
114
  return result ?? false;
66
115
  }
67
116
 
117
+ public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
118
+ public async prompt(title: string, message: string, options?: PromptOptions): Promise<string | null>;
119
+ public async prompt(
120
+ messageOrTitle: string,
121
+ messageOrOptions?: string | PromptOptions,
122
+ options?: PromptOptions,
123
+ ): Promise<string | null> {
124
+ const trim = options?.trim ?? true;
125
+ const getProperties = (): AGPromptModalProps => {
126
+ if (typeof messageOrOptions !== 'string') {
127
+ return {
128
+ message: messageOrTitle,
129
+ ...(messageOrOptions ?? {}),
130
+ };
131
+ }
132
+
133
+ return {
134
+ title: messageOrTitle,
135
+ message: messageOrOptions,
136
+ ...(options ?? {}),
137
+ };
138
+ };
139
+
140
+ const modal = await this.openModal<ModalComponent<AGPromptModalProps, string | null>>(
141
+ this.requireComponent(UIComponents.PromptModal),
142
+ getProperties(),
143
+ );
144
+ const rawResult = await modal.beforeClose;
145
+ const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
146
+
147
+ return result ?? null;
148
+ }
149
+
68
150
  public async loading<T>(operation: Promise<T>): Promise<T>;
69
151
  public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
70
152
  public async loading<T>(messageOrOperation: string | Promise<T>, operation?: Promise<T>): Promise<T> {
71
- operation = typeof messageOrOperation === 'string' ? (operation as Promise<T>) : messageOrOperation;
153
+ const getProperties = (): AGLoadingModalProps => {
154
+ if (typeof messageOrOperation !== 'string') {
155
+ return {};
156
+ }
157
+
158
+ return { message: messageOrOperation };
159
+ };
160
+
161
+ const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), getProperties());
72
162
 
73
- const message = typeof messageOrOperation === 'string' ? messageOrOperation : undefined;
74
- const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), { message });
75
- const result = await operation;
163
+ try {
164
+ operation = typeof messageOrOperation === 'string' ? (operation as Promise<T>) : messageOrOperation;
76
165
 
77
- await this.closeModal(modal.id);
166
+ const [result] = await Promise.all([operation, after({ seconds: 1 })]);
78
167
 
79
- return result;
168
+ return result;
169
+ } finally {
170
+ await this.closeModal(modal.id);
171
+ }
80
172
  }
81
173
 
82
174
  public showSnackbar(message: string, options: ShowSnackbarOptions = {}): void {
83
175
  const snackbar: Snackbar = {
84
176
  id: uuid(),
85
177
  properties: { message, ...options },
86
- component: options.component ?? markRaw(this.requireComponent(UIComponents.Snackbar)),
178
+ component: markRaw(options.component ?? this.requireComponent(UIComponents.Snackbar)),
87
179
  };
88
180
 
89
181
  this.setState('snackbars', this.snackbars.concat(snackbar));
@@ -138,6 +230,7 @@ export class UIService extends Service {
138
230
 
139
231
  protected async boot(): Promise<void> {
140
232
  this.watchModalEvents();
233
+ this.watchMountedEvent();
141
234
  }
142
235
 
143
236
  private watchModalEvents(): void {
@@ -165,16 +258,36 @@ export class UIService extends Service {
165
258
  });
166
259
  }
167
260
 
261
+ private watchMountedEvent(): void {
262
+ Events.once('application-mounted', async () => {
263
+ const splash = document.getElementById('splash');
264
+
265
+ if (!splash) {
266
+ return;
267
+ }
268
+
269
+ if (window.getComputedStyle(splash).opacity !== '0') {
270
+ splash.style.opacity = '0';
271
+
272
+ await after({ ms: 600 });
273
+ }
274
+
275
+ splash.remove();
276
+ });
277
+ }
278
+
168
279
  }
169
280
 
170
- export default facade(new UIService());
281
+ export default facade(UIService);
171
282
 
172
283
  declare module '@/services/Events' {
173
284
  export interface EventsPayload {
174
- 'modal-will-close': { modal: Modal; result?: unknown };
175
- 'modal-closed': { modal: Modal; result?: unknown };
176
285
  'close-modal': { id: string; result?: unknown };
177
286
  'hide-modal': { id: string };
287
+ 'hide-overlays-backdrop': void;
288
+ 'modal-closed': { modal: Modal; result?: unknown };
289
+ 'modal-will-close': { modal: Modal; result?: unknown };
178
290
  'show-modal': { id: string };
291
+ 'show-overlays-backdrop': void;
179
292
  }
180
293
  }
package/src/ui/index.ts CHANGED
@@ -8,13 +8,16 @@ 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 { default as UI } from './UI';
20
+
18
21
  export type UIServices = typeof services;
19
22
 
20
23
  export default definePlugin({
@@ -24,7 +27,9 @@ export default definePlugin({
24
27
  [UIComponents.ConfirmModal]: AGConfirmModal,
25
28
  [UIComponents.ErrorReportModal]: AGErrorReportModal,
26
29
  [UIComponents.LoadingModal]: AGLoadingModal,
30
+ [UIComponents.PromptModal]: AGPromptModal,
27
31
  [UIComponents.Snackbar]: AGSnackbar,
32
+ [UIComponents.StartupCrash]: AGStartupCrash,
28
33
  };
29
34
 
30
35
  Object.entries({
@@ -37,7 +42,7 @@ export default definePlugin({
37
42
  });
38
43
 
39
44
  declare module '@/bootstrap/options' {
40
- interface AerogelOptions {
45
+ export interface AerogelOptions {
41
46
  components?: Partial<Record<UIComponent, Component>>;
42
47
  }
43
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 {
@@ -1,4 +1,5 @@
1
1
  export * from './composition/events';
2
2
  export * from './composition/forms';
3
3
  export * from './composition/hooks';
4
+ export * from './tailwindcss';
4
5
  export * from './vue';
@@ -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,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: PropType<T>;
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,10 +73,12 @@ 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 mixedProp<T>(type: PropType<T>): OptionalProp<T | null> {
76
+ export function mixedProp<T>(type?: PropType<T>): OptionalProp<T | null>;
77
+ export function mixedProp<T>(type: PropType<T>, defaultValue: T): OptionalProp<T>;
78
+ export function mixedProp<T>(type?: PropType<T>, defaultValue?: T): OptionalProp<T | null> {
68
79
  return {
69
80
  type,
70
- default: null,
81
+ default: defaultValue ?? null,
71
82
  };
72
83
  }
73
84
 
@@ -108,7 +119,7 @@ export function requiredEnumProp<Enum extends Record<string, unknown>>(
108
119
  };
109
120
  }
110
121
 
111
- export function requiredMixedProp<T>(type: PropType<T>): RequiredProp<T> {
122
+ export function requiredMixedProp<T>(type?: PropType<T>): RequiredProp<T> {
112
123
  return {
113
124
  type,
114
125
  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,7 +1,7 @@
1
1
  {
2
2
  "extends": "../../tsconfig.json",
3
3
  "compilerOptions": {
4
- "types": ["unplugin-icons/types/vue3"],
4
+ "types": ["unplugin-icons/types/vue3", "@aerogel/vite/dist/virtual"],
5
5
  "baseUrl": ".",
6
6
  "paths": {
7
7
  "@/*": ["./src/*"]
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: { clearMocks: true },
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
@@ -1,3 +0,0 @@
1
- module.exports = {
2
- extends: ['@noeldemartin/eslint-config-vue'],
3
- };
package/dist/virtual.d.ts DELETED
@@ -1,11 +0,0 @@
1
- declare module 'virtual:aerogel' {
2
- interface AerogelBuild {
3
- environment: 'production' | 'development' | 'testing';
4
- basePath?: string;
5
- sourceUrl?: string;
6
- }
7
-
8
- const build: AerogelBuild;
9
-
10
- export default build;
11
- }
@@ -1,5 +0,0 @@
1
- import AGErrorMessage from './AGErrorMessage.vue';
2
- import AGLink from './AGLink.vue';
3
- import AGMarkdown from './AGMarkdown.vue';
4
-
5
- export { AGErrorMessage, AGLink, AGMarkdown };
@@ -1,11 +0,0 @@
1
- declare module 'virtual:aerogel' {
2
- interface AerogelBuild {
3
- environment: 'production' | 'development' | 'testing';
4
- basePath?: string;
5
- sourceUrl?: string;
6
- }
7
-
8
- const build: AerogelBuild;
9
-
10
- export default build;
11
- }
File without changes