@aerogel/core 0.0.0-next.88c59e62f64db70aedfbc4c31b5bbc287be44483 → 0.0.0-next.8e6b2bcc764fa682decbb41aa6848c77a744dec3

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 (95) 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 +856 -124
  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 +4 -3
  11. package/src/bootstrap/index.ts +22 -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 +1 -1
  16. package/src/components/forms/AGCheckbox.vue +7 -1
  17. package/src/components/forms/AGInput.vue +8 -6
  18. package/src/components/forms/AGSelect.story.vue +46 -0
  19. package/src/components/forms/AGSelect.vue +60 -0
  20. package/src/components/forms/index.ts +1 -0
  21. package/src/components/headless/forms/AGHeadlessButton.vue +8 -8
  22. package/src/components/headless/forms/AGHeadlessInput.ts +21 -1
  23. package/src/components/headless/forms/AGHeadlessInput.vue +7 -4
  24. package/src/components/headless/forms/AGHeadlessInputInput.vue +2 -0
  25. package/src/components/headless/forms/AGHeadlessInputLabel.vue +8 -2
  26. package/src/components/headless/forms/AGHeadlessSelect.ts +28 -17
  27. package/src/components/headless/forms/AGHeadlessSelect.vue +60 -28
  28. package/src/components/headless/forms/AGHeadlessSelectButton.vue +24 -0
  29. package/src/components/headless/forms/AGHeadlessSelectError.vue +26 -0
  30. package/src/components/headless/forms/AGHeadlessSelectLabel.vue +24 -0
  31. package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
  32. package/src/components/headless/forms/AGHeadlessSelectOption.vue +39 -0
  33. package/src/components/headless/forms/index.ts +5 -3
  34. package/src/components/headless/modals/AGHeadlessModal.ts +27 -0
  35. package/src/components/headless/modals/AGHeadlessModal.vue +3 -5
  36. package/src/components/headless/modals/index.ts +4 -6
  37. package/src/components/headless/snackbars/index.ts +23 -8
  38. package/src/components/index.ts +1 -1
  39. package/src/components/{basic → lib}/AGErrorMessage.vue +2 -2
  40. package/src/components/lib/AGMeasured.vue +15 -0
  41. package/src/components/lib/AGStartupCrash.vue +31 -0
  42. package/src/components/lib/index.ts +5 -0
  43. package/src/components/modals/AGAlertModal.ts +15 -0
  44. package/src/components/modals/AGAlertModal.vue +4 -15
  45. package/src/components/modals/AGConfirmModal.ts +27 -0
  46. package/src/components/modals/AGConfirmModal.vue +8 -12
  47. package/src/components/modals/AGErrorReportModal.ts +27 -1
  48. package/src/components/modals/AGErrorReportModal.vue +8 -16
  49. package/src/components/modals/AGErrorReportModalButtons.vue +4 -2
  50. package/src/components/modals/AGErrorReportModalTitle.vue +1 -1
  51. package/src/components/modals/AGLoadingModal.ts +23 -0
  52. package/src/components/modals/AGLoadingModal.vue +4 -8
  53. package/src/components/modals/AGModal.ts +2 -2
  54. package/src/components/modals/AGModal.vue +14 -12
  55. package/src/components/modals/AGPromptModal.ts +30 -0
  56. package/src/components/modals/AGPromptModal.vue +34 -0
  57. package/src/components/modals/index.ts +13 -19
  58. package/src/components/snackbars/AGSnackbar.vue +3 -9
  59. package/src/components/utils.ts +10 -0
  60. package/src/directives/index.ts +5 -1
  61. package/src/directives/measure.ts +21 -0
  62. package/src/errors/Errors.ts +26 -24
  63. package/src/errors/index.ts +10 -23
  64. package/src/errors/utils.ts +19 -0
  65. package/src/forms/Form.ts +9 -3
  66. package/src/jobs/Job.ts +5 -0
  67. package/src/jobs/index.ts +7 -0
  68. package/src/lang/Lang.ts +11 -23
  69. package/src/main.histoire.ts +1 -0
  70. package/src/main.ts +3 -0
  71. package/src/services/App.state.ts +4 -5
  72. package/src/services/App.ts +30 -3
  73. package/src/services/Events.test.ts +39 -0
  74. package/src/services/Events.ts +100 -30
  75. package/src/services/Service.ts +50 -16
  76. package/src/services/index.ts +2 -1
  77. package/src/services/store.ts +8 -5
  78. package/src/testing/index.ts +25 -0
  79. package/src/ui/UI.ts +124 -19
  80. package/src/ui/index.ts +8 -3
  81. package/src/utils/composition/events.ts +1 -0
  82. package/src/utils/index.ts +1 -0
  83. package/src/utils/tailwindcss.test.ts +26 -0
  84. package/src/utils/tailwindcss.ts +7 -0
  85. package/src/utils/vue.ts +11 -2
  86. package/tailwind.config.js +4 -0
  87. package/tsconfig.json +1 -1
  88. package/.eslintrc.js +0 -3
  89. package/dist/virtual.d.ts +0 -11
  90. package/src/components/basic/index.ts +0 -5
  91. package/src/components/headless/forms/AGHeadlessSelectButton.ts +0 -3
  92. package/src/components/headless/forms/AGHeadlessSelectLabel.ts +0 -3
  93. package/src/types/virtual.d.ts +0 -11
  94. /package/src/components/{basic → lib}/AGLink.vue +0 -0
  95. /package/src/components/{basic → lib}/AGMarkdown.vue +0 -0
package/src/ui/UI.ts CHANGED
@@ -1,10 +1,11 @@
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
7
  import type { SnackbarAction, SnackbarColor } from '@/components/headless/snackbars';
8
+ import type { AGAlertModalProps, AGConfirmModalProps, AGLoadingModalProps, AGPromptModalProps } from '@/components';
8
9
 
9
10
  import Service from './UI.state';
10
11
  import type { Modal, ModalComponent, Snackbar } from './UI.state';
@@ -24,11 +25,26 @@ export const UIComponents = {
24
25
  ConfirmModal: 'confirm-modal',
25
26
  ErrorReportModal: 'error-report-modal',
26
27
  LoadingModal: 'loading-modal',
28
+ PromptModal: 'prompt-modal',
27
29
  Snackbar: 'snackbar',
30
+ StartupCrash: 'startup-crash',
28
31
  } as const;
29
32
 
30
33
  export type UIComponent = ObjectValues<typeof UIComponents>;
31
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
+
32
48
  export interface ShowSnackbarOptions {
33
49
  component?: Component;
34
50
  color?: SnackbarColor;
@@ -47,43 +63,111 @@ export class UIService extends Service {
47
63
  public alert(message: string): void;
48
64
  public alert(title: string, message: string): void;
49
65
  public alert(messageOrTitle: string, message?: string): void {
50
- const options = typeof message === 'string' ? { title: messageOrTitle, message } : { message: messageOrTitle };
66
+ const getProperties = (): AGAlertModalProps => {
67
+ if (typeof message !== 'string') {
68
+ return { message: messageOrTitle };
69
+ }
51
70
 
52
- this.openModal(this.requireComponent(UIComponents.AlertModal), options);
71
+ return {
72
+ title: messageOrTitle,
73
+ message,
74
+ };
75
+ };
76
+
77
+ this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
53
78
  }
54
79
 
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>>(
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>>(
60
103
  this.requireComponent(UIComponents.ConfirmModal),
61
- options,
104
+ getProperties(),
62
105
  );
63
106
  const result = await modal.beforeClose;
64
107
 
65
108
  return result ?? false;
66
109
  }
67
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
+
68
142
  public async loading<T>(operation: Promise<T>): Promise<T>;
69
143
  public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
70
144
  public async loading<T>(messageOrOperation: string | Promise<T>, operation?: Promise<T>): Promise<T> {
71
- operation = typeof messageOrOperation === 'string' ? (operation as Promise<T>) : messageOrOperation;
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());
72
154
 
73
- const message = typeof messageOrOperation === 'string' ? messageOrOperation : undefined;
74
- const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), { message });
75
- const result = await operation;
155
+ try {
156
+ operation = typeof messageOrOperation === 'string' ? (operation as Promise<T>) : messageOrOperation;
76
157
 
77
- await this.closeModal(modal.id);
158
+ const [result] = await Promise.all([operation, after({ seconds: 1 })]);
78
159
 
79
- return result;
160
+ return result;
161
+ } finally {
162
+ await this.closeModal(modal.id);
163
+ }
80
164
  }
81
165
 
82
166
  public showSnackbar(message: string, options: ShowSnackbarOptions = {}): void {
83
167
  const snackbar: Snackbar = {
84
168
  id: uuid(),
85
169
  properties: { message, ...options },
86
- component: options.component ?? markRaw(this.requireComponent(UIComponents.Snackbar)),
170
+ component: markRaw(options.component ?? this.requireComponent(UIComponents.Snackbar)),
87
171
  };
88
172
 
89
173
  this.setState('snackbars', this.snackbars.concat(snackbar));
@@ -138,6 +222,7 @@ export class UIService extends Service {
138
222
 
139
223
  protected async boot(): Promise<void> {
140
224
  this.watchModalEvents();
225
+ this.watchMountedEvent();
141
226
  }
142
227
 
143
228
  private watchModalEvents(): void {
@@ -165,16 +250,36 @@ export class UIService extends Service {
165
250
  });
166
251
  }
167
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
+
168
271
  }
169
272
 
170
- export default facade(new UIService());
273
+ export default facade(UIService);
171
274
 
172
275
  declare module '@/services/Events' {
173
276
  export interface EventsPayload {
174
- 'modal-will-close': { modal: Modal; result?: unknown };
175
- 'modal-closed': { modal: Modal; result?: unknown };
176
277
  'close-modal': { id: string; result?: unknown };
177
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 };
178
282
  'show-modal': { id: string };
283
+ 'show-overlays-backdrop': void;
179
284
  }
180
285
  }
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,5 +1,5 @@
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> = {
@@ -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
  }
@@ -108,7 +117,7 @@ export function requiredEnumProp<Enum extends Record<string, unknown>>(
108
117
  };
109
118
  }
110
119
 
111
- export function requiredMixedProp<T>(type: PropType<T>): RequiredProp<T> {
120
+ export function requiredMixedProp<T>(type?: PropType<T>): RequiredProp<T> {
112
121
  return {
113
122
  type,
114
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,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/.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,3 +0,0 @@
1
- import { ListboxButton } from '@headlessui/vue';
2
-
3
- export default ListboxButton;
@@ -1,3 +0,0 @@
1
- import { ListboxLabel } from '@headlessui/vue';
2
-
3
- export default ListboxLabel;
@@ -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
File without changes