@aerogel/core 0.0.0-next.c29ffcd25bffdbed37ecce3aac1ba14cde3e9d39 → 0.0.0-next.c3236837f7f8fc319a4a56022accb32757b3db89

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 (203) hide show
  1. package/dist/aerogel-core.css +1 -0
  2. package/dist/aerogel-core.d.ts +2061 -1962
  3. package/dist/aerogel-core.js +3669 -0
  4. package/dist/aerogel-core.js.map +1 -0
  5. package/package.json +32 -37
  6. package/src/bootstrap/bootstrap.test.ts +4 -7
  7. package/src/bootstrap/index.ts +14 -15
  8. package/src/bootstrap/options.ts +1 -1
  9. package/src/components/AppLayout.vue +14 -0
  10. package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
  11. package/src/components/AppOverlays.vue +9 -0
  12. package/src/components/AppToasts.vue +16 -0
  13. package/src/components/contracts/AlertModal.ts +19 -0
  14. package/src/components/contracts/Button.ts +16 -0
  15. package/src/components/contracts/ConfirmModal.ts +48 -0
  16. package/src/components/contracts/DropdownMenu.ts +25 -0
  17. package/src/components/contracts/ErrorReportModal.ts +33 -0
  18. package/src/components/contracts/Input.ts +26 -0
  19. package/src/components/contracts/LoadingModal.ts +26 -0
  20. package/src/components/contracts/Modal.ts +21 -0
  21. package/src/components/contracts/PromptModal.ts +34 -0
  22. package/src/components/contracts/Select.ts +45 -0
  23. package/src/components/contracts/Toast.ts +15 -0
  24. package/src/components/contracts/index.ts +11 -0
  25. package/src/components/headless/HeadlessButton.vue +51 -0
  26. package/src/components/headless/HeadlessInput.vue +59 -0
  27. package/src/components/headless/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +7 -8
  28. package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
  29. package/src/components/headless/HeadlessInputInput.vue +86 -0
  30. package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
  31. package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +10 -13
  32. package/src/components/headless/HeadlessModal.vue +57 -0
  33. package/src/components/headless/HeadlessModalContent.vue +30 -0
  34. package/src/components/headless/HeadlessModalDescription.vue +12 -0
  35. package/src/components/headless/HeadlessModalOverlay.vue +12 -0
  36. package/src/components/headless/HeadlessModalTitle.vue +12 -0
  37. package/src/components/headless/HeadlessSelect.vue +120 -0
  38. package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +5 -6
  39. package/src/components/headless/HeadlessSelectLabel.vue +25 -0
  40. package/src/components/headless/HeadlessSelectOption.vue +34 -0
  41. package/src/components/headless/HeadlessSelectOptions.vue +42 -0
  42. package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
  43. package/src/components/headless/HeadlessSelectValue.vue +18 -0
  44. package/src/components/headless/HeadlessSwitch.vue +96 -0
  45. package/src/components/headless/HeadlessToast.vue +18 -0
  46. package/src/components/headless/HeadlessToastAction.vue +13 -0
  47. package/src/components/headless/index.ts +20 -3
  48. package/src/components/index.ts +6 -11
  49. package/src/components/ui/AdvancedOptions.vue +18 -0
  50. package/src/components/ui/AlertModal.vue +17 -0
  51. package/src/components/ui/Button.vue +115 -0
  52. package/src/components/ui/Checkbox.vue +56 -0
  53. package/src/components/ui/ConfirmModal.vue +50 -0
  54. package/src/components/ui/DropdownMenu.vue +32 -0
  55. package/src/components/ui/DropdownMenuOption.vue +22 -0
  56. package/src/components/ui/DropdownMenuOptions.vue +44 -0
  57. package/src/components/ui/EditableContent.vue +82 -0
  58. package/src/components/ui/ErrorLogs.vue +19 -0
  59. package/src/components/ui/ErrorLogsModal.vue +48 -0
  60. package/src/components/ui/ErrorMessage.vue +15 -0
  61. package/src/components/ui/ErrorReportModal.vue +73 -0
  62. package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +34 -27
  63. package/src/components/ui/ErrorReportModalTitle.vue +24 -0
  64. package/src/components/{forms/AGForm.vue → ui/Form.vue} +4 -5
  65. package/src/components/ui/Input.vue +56 -0
  66. package/src/components/ui/Link.vue +12 -0
  67. package/src/components/ui/LoadingModal.vue +34 -0
  68. package/src/components/ui/Markdown.vue +97 -0
  69. package/src/components/ui/Modal.vue +131 -0
  70. package/src/components/{modals/AGModalContext.vue → ui/ModalContext.vue} +8 -9
  71. package/src/components/ui/ProgressBar.vue +51 -0
  72. package/src/components/ui/PromptModal.vue +38 -0
  73. package/src/components/ui/Select.vue +27 -0
  74. package/src/components/ui/SelectLabel.vue +21 -0
  75. package/src/components/ui/SelectOption.vue +29 -0
  76. package/src/components/ui/SelectOptions.vue +35 -0
  77. package/src/components/ui/SelectTrigger.vue +29 -0
  78. package/src/components/ui/Setting.vue +31 -0
  79. package/src/components/ui/SettingsModal.vue +15 -0
  80. package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +8 -8
  81. package/src/components/ui/Switch.vue +11 -0
  82. package/src/components/ui/TextArea.vue +56 -0
  83. package/src/components/ui/Toast.vue +46 -0
  84. package/src/components/ui/index.ts +35 -0
  85. package/src/directives/index.ts +9 -5
  86. package/src/directives/measure.ts +12 -6
  87. package/src/errors/Errors.state.ts +1 -1
  88. package/src/errors/Errors.ts +29 -27
  89. package/src/errors/index.ts +15 -8
  90. package/src/errors/settings/Debug.vue +32 -0
  91. package/src/errors/settings/index.ts +10 -0
  92. package/src/errors/utils.ts +1 -1
  93. package/src/forms/{Form.test.ts → FormController.test.ts} +32 -8
  94. package/src/forms/{Form.ts → FormController.ts} +46 -38
  95. package/src/forms/index.ts +2 -3
  96. package/src/forms/utils.ts +35 -35
  97. package/src/index.css +75 -0
  98. package/src/jobs/Job.ts +2 -2
  99. package/src/lang/DefaultLangProvider.ts +7 -4
  100. package/src/lang/Lang.state.ts +1 -1
  101. package/src/lang/Lang.ts +1 -1
  102. package/src/lang/index.ts +12 -6
  103. package/src/lang/settings/Language.vue +48 -0
  104. package/src/lang/settings/index.ts +10 -0
  105. package/src/plugins/Plugin.ts +1 -1
  106. package/src/plugins/index.ts +10 -7
  107. package/src/services/App.state.ts +15 -4
  108. package/src/services/App.ts +12 -4
  109. package/src/services/Cache.ts +1 -1
  110. package/src/services/Events.test.ts +8 -8
  111. package/src/services/Events.ts +4 -10
  112. package/src/services/Service.ts +21 -21
  113. package/src/services/Storage.ts +3 -3
  114. package/src/services/index.ts +10 -6
  115. package/src/services/utils.ts +2 -2
  116. package/src/testing/index.ts +8 -3
  117. package/src/testing/setup.ts +3 -19
  118. package/src/ui/UI.state.ts +8 -13
  119. package/src/ui/UI.ts +143 -114
  120. package/src/ui/index.ts +27 -28
  121. package/src/utils/classes.ts +41 -0
  122. package/src/utils/composition/events.ts +4 -6
  123. package/src/utils/composition/forms.ts +20 -4
  124. package/src/utils/composition/state.ts +11 -2
  125. package/src/utils/index.ts +3 -1
  126. package/src/utils/markdown.ts +37 -5
  127. package/src/utils/types.ts +3 -0
  128. package/src/utils/vue.ts +31 -137
  129. package/dist/aerogel-core.cjs.js +0 -2
  130. package/dist/aerogel-core.cjs.js.map +0 -1
  131. package/dist/aerogel-core.esm.js +0 -2
  132. package/dist/aerogel-core.esm.js.map +0 -1
  133. package/histoire.config.ts +0 -7
  134. package/noeldemartin.config.js +0 -5
  135. package/postcss.config.js +0 -6
  136. package/src/assets/histoire.css +0 -3
  137. package/src/components/AGAppLayout.vue +0 -16
  138. package/src/components/AGAppOverlays.vue +0 -41
  139. package/src/components/AGAppSnackbars.vue +0 -13
  140. package/src/components/composition.ts +0 -23
  141. package/src/components/constants.ts +0 -8
  142. package/src/components/forms/AGButton.vue +0 -44
  143. package/src/components/forms/AGCheckbox.vue +0 -41
  144. package/src/components/forms/AGInput.vue +0 -40
  145. package/src/components/forms/AGSelect.story.vue +0 -46
  146. package/src/components/forms/AGSelect.vue +0 -60
  147. package/src/components/forms/index.ts +0 -5
  148. package/src/components/headless/forms/AGHeadlessButton.ts +0 -3
  149. package/src/components/headless/forms/AGHeadlessButton.vue +0 -62
  150. package/src/components/headless/forms/AGHeadlessInput.ts +0 -34
  151. package/src/components/headless/forms/AGHeadlessInput.vue +0 -70
  152. package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -84
  153. package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
  154. package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
  155. package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
  156. package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
  157. package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
  158. package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
  159. package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
  160. package/src/components/headless/forms/composition.ts +0 -10
  161. package/src/components/headless/forms/index.ts +0 -18
  162. package/src/components/headless/modals/AGHeadlessModal.ts +0 -36
  163. package/src/components/headless/modals/AGHeadlessModal.vue +0 -92
  164. package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -32
  165. package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -23
  166. package/src/components/headless/modals/index.ts +0 -4
  167. package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
  168. package/src/components/headless/snackbars/index.ts +0 -40
  169. package/src/components/interfaces.ts +0 -24
  170. package/src/components/lib/AGErrorMessage.vue +0 -16
  171. package/src/components/lib/AGLink.vue +0 -9
  172. package/src/components/lib/AGMarkdown.vue +0 -54
  173. package/src/components/lib/AGMeasured.vue +0 -16
  174. package/src/components/lib/AGProgressBar.vue +0 -45
  175. package/src/components/lib/index.ts +0 -6
  176. package/src/components/modals/AGAlertModal.ts +0 -18
  177. package/src/components/modals/AGAlertModal.vue +0 -14
  178. package/src/components/modals/AGConfirmModal.ts +0 -42
  179. package/src/components/modals/AGConfirmModal.vue +0 -26
  180. package/src/components/modals/AGErrorReportModal.ts +0 -49
  181. package/src/components/modals/AGErrorReportModal.vue +0 -54
  182. package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
  183. package/src/components/modals/AGLoadingModal.ts +0 -29
  184. package/src/components/modals/AGLoadingModal.vue +0 -15
  185. package/src/components/modals/AGModal.ts +0 -11
  186. package/src/components/modals/AGModal.vue +0 -39
  187. package/src/components/modals/AGModalContext.ts +0 -8
  188. package/src/components/modals/AGModalTitle.vue +0 -9
  189. package/src/components/modals/AGPromptModal.ts +0 -41
  190. package/src/components/modals/AGPromptModal.vue +0 -34
  191. package/src/components/modals/index.ts +0 -17
  192. package/src/components/snackbars/AGSnackbar.vue +0 -36
  193. package/src/components/snackbars/index.ts +0 -3
  194. package/src/components/utils.ts +0 -10
  195. package/src/directives/initial-focus.ts +0 -11
  196. package/src/forms/composition.ts +0 -6
  197. package/src/main.histoire.ts +0 -1
  198. package/src/utils/tailwindcss.test.ts +0 -26
  199. package/src/utils/tailwindcss.ts +0 -7
  200. package/tailwind.config.js +0 -4
  201. package/tsconfig.json +0 -11
  202. package/vite.config.ts +0 -17
  203. /package/src/{main.ts → index.ts} +0 -0
@@ -1,25 +1,19 @@
1
1
  import type { Component } from 'vue';
2
2
 
3
- import { defineServiceState } from '@/services/Service';
3
+ import { defineServiceState } from '@aerogel/core/services/Service';
4
4
 
5
5
  import { Layouts, getCurrentLayout } from './utils';
6
6
 
7
- export interface Modal<T = unknown> {
7
+ 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 ModalComponent<
16
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
- Properties extends Record<string, unknown> = Record<string, unknown>,
18
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
- Result = unknown
20
- > {}
21
-
22
- export interface Snackbar {
16
+ export interface UIToast {
23
17
  id: string;
24
18
  component: Component;
25
19
  properties: Record<string, unknown>;
@@ -28,12 +22,13 @@ export interface Snackbar {
28
22
  export default defineServiceState({
29
23
  name: 'ui',
30
24
  initialState: {
31
- modals: [] as Modal[],
32
- snackbars: [] as Snackbar[],
25
+ modals: [] as UIModal[],
26
+ toasts: [] as UIToast[],
33
27
  layout: getCurrentLayout(),
34
28
  },
35
29
  computed: {
36
- mobile: ({ layout }) => layout === Layouts.Mobile,
37
30
  desktop: ({ layout }) => layout === Layouts.Desktop,
31
+ mobile: ({ layout }) => layout === Layouts.Mobile,
32
+ openModals: ({ modals }) => modals.filter(({ closing }) => !closing),
38
33
  },
39
34
  });
package/src/ui/UI.ts CHANGED
@@ -1,48 +1,61 @@
1
- import { after, facade, fail, required, uuid } from '@noeldemartin/utils';
2
- import { markRaw, nextTick } from 'vue';
1
+ import { after, facade, fail, isDevelopment, required, uuid } from '@noeldemartin/utils';
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 { ObjectValues } from '@noeldemartin/utils';
5
-
6
- import App from '@/services/App';
7
- import Events from '@/services/Events';
8
- import type { AcceptRefs } from '@/utils';
9
- import type { Color } from '@/components/constants';
10
- import type { SnackbarAction, SnackbarColor } from '@/components/headless/snackbars';
11
- import type { AGAlertModalProps, AGConfirmModalProps, AGLoadingModalProps, AGPromptModalProps } from '@/components';
5
+ import type { ClosureArgs } from '@noeldemartin/utils';
6
+
7
+ import App from '@aerogel/core/services/App';
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';
18
+ import type { AcceptRefs } from '@aerogel/core/utils';
19
+ import type { AlertModalExpose, AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
20
+ import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
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';
12
24
 
13
25
  import Service from './UI.state';
14
26
  import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
15
- import type { Modal, ModalComponent, Snackbar } from './UI.state';
27
+ import type { UIModal, UIToast } from './UI.state';
16
28
 
17
29
  interface ModalCallbacks<T = unknown> {
18
30
  willClose(result: T | undefined): void;
19
- closed(result: T | undefined): void;
31
+ hasClosed(result: T | undefined): void;
20
32
  }
21
33
 
22
- type ModalProperties<TComponent> = TComponent extends ModalComponent<infer TProperties, unknown> ? TProperties : never;
23
- type ModalResult<TComponent> = TComponent extends ModalComponent<Record<string, unknown>, infer TResult>
24
- ? TResult
25
- : never;
26
-
27
- export const UIComponents = {
28
- AlertModal: 'alert-modal',
29
- ConfirmModal: 'confirm-modal',
30
- ErrorReportModal: 'error-report-modal',
31
- LoadingModal: 'loading-modal',
32
- PromptModal: 'prompt-modal',
33
- Snackbar: 'snackbar',
34
- StartupCrash: 'startup-crash',
35
- } as const;
36
-
37
- export type UIComponent = ObjectValues<typeof UIComponents>;
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
+ }
38
48
 
39
- export type ConfirmCheckboxes = Record<string, { label: string; default?: boolean; required?: boolean }>;
49
+ export interface UIModalContext {
50
+ modal: UIModal;
51
+ childIndex?: number;
52
+ }
40
53
 
41
54
  export type ConfirmOptions = AcceptRefs<{
42
55
  acceptText?: string;
43
- acceptColor?: Color;
56
+ acceptVariant?: ButtonVariant;
44
57
  cancelText?: string;
45
- cancelColor?: Color;
58
+ cancelVariant?: ButtonVariant;
46
59
  actions?: Record<string, () => unknown>;
47
60
  required?: boolean;
48
61
  }>;
@@ -51,9 +64,11 @@ export type LoadingOptions = AcceptRefs<{
51
64
  title?: string;
52
65
  message?: string;
53
66
  progress?: number;
67
+ delay?: number;
54
68
  }>;
55
69
 
56
- export interface ConfirmOptionsWithCheckboxes<T extends ConfirmCheckboxes = ConfirmCheckboxes> extends ConfirmOptions {
70
+ export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
71
+ extends ConfirmOptions {
57
72
  checkboxes?: T;
58
73
  }
59
74
 
@@ -62,31 +77,39 @@ export type PromptOptions = AcceptRefs<{
62
77
  defaultValue?: string;
63
78
  placeholder?: string;
64
79
  acceptText?: string;
65
- acceptColor?: Color;
80
+ acceptVariant?: ButtonVariant;
66
81
  cancelText?: string;
67
- cancelColor?: Color;
82
+ cancelVariant?: ButtonVariant;
68
83
  trim?: boolean;
69
84
  }>;
70
85
 
71
- export interface ShowSnackbarOptions {
86
+ export interface ToastOptions {
72
87
  component?: Component;
73
- color?: SnackbarColor;
74
- actions?: SnackbarAction[];
88
+ variant?: ToastVariant;
89
+ actions?: ToastAction[];
75
90
  }
76
91
 
77
92
  export class UIService extends Service {
78
93
 
79
94
  private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
80
- private components: Partial<Record<UIComponent, Component>> = {};
95
+ private components: Partial<UIComponents> = {};
81
96
 
82
- public requireComponent(name: UIComponent): Component {
83
- return this.components[name] ?? fail(`UI Component '${name}' is not defined!`);
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 {
102
+ return this.components[name] ?? null;
103
+ }
104
+
105
+ public requireComponent<T extends keyof UIComponents>(name: T): UIComponents[T] {
106
+ return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
84
107
  }
85
108
 
86
109
  public alert(message: string): void;
87
110
  public alert(title: string, message: string): void;
88
111
  public alert(messageOrTitle: string, message?: string): void {
89
- const getProperties = (): AGAlertModalProps => {
112
+ const getProperties = (): AlertModalProps => {
90
113
  if (typeof message !== 'string') {
91
114
  return { message: messageOrTitle };
92
115
  }
@@ -97,14 +120,14 @@ export class UIService extends Service {
97
120
  };
98
121
  };
99
122
 
100
- this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
123
+ this.modal(this.requireComponent('alert-modal'), getProperties());
101
124
  }
102
125
 
103
126
  /* eslint-disable max-len */
104
127
  public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
105
128
  public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
106
- public async confirm<T extends ConfirmCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
107
- public async confirm<T extends ConfirmCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
129
+ public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
130
+ public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
108
131
  /* eslint-enable max-len */
109
132
 
110
133
  public async confirm(
@@ -112,7 +135,7 @@ export class UIService extends Service {
112
135
  messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
113
136
  options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
114
137
  ): Promise<boolean | [boolean, Record<string, boolean>]> {
115
- const getProperties = (): AGConfirmModalProps => {
138
+ const getProperties = (): AcceptRefs<ConfirmModalProps> => {
116
139
  if (typeof messageOrOptions !== 'string') {
117
140
  return {
118
141
  ...(messageOrOptions ?? {}),
@@ -128,13 +151,10 @@ export class UIService extends Service {
128
151
  required: !!options?.required,
129
152
  };
130
153
  };
131
- const properties = getProperties();
132
- const modal = await this.openModal<
133
- ModalComponent<AGConfirmModalProps, boolean | [boolean, Record<string, boolean>]>
134
- >(this.requireComponent(UIComponents.ConfirmModal), properties);
135
- const result = await modal.beforeClose;
136
154
 
137
- const confirmed = typeof result === 'object' ? result[0] : result ?? false;
155
+ const properties = getProperties();
156
+ const result = await this.modalForm(this.requireComponent('confirm-modal'), properties);
157
+ const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
138
158
  const checkboxes =
139
159
  typeof result === 'object'
140
160
  ? result[1]
@@ -151,7 +171,7 @@ export class UIService extends Service {
151
171
  continue;
152
172
  }
153
173
 
154
- if (confirmed && App.development) {
174
+ if (confirmed && isDevelopment()) {
155
175
  // eslint-disable-next-line no-console
156
176
  console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
157
177
  }
@@ -170,26 +190,22 @@ export class UIService extends Service {
170
190
  options?: PromptOptions,
171
191
  ): Promise<string | null> {
172
192
  const trim = options?.trim ?? true;
173
- const getProperties = (): AGPromptModalProps => {
193
+ const getProperties = (): PromptModalProps => {
174
194
  if (typeof messageOrOptions !== 'string') {
175
195
  return {
176
196
  message: messageOrTitle,
177
197
  ...(messageOrOptions ?? {}),
178
- } as AGPromptModalProps;
198
+ } as PromptModalProps;
179
199
  }
180
200
 
181
201
  return {
182
202
  title: messageOrTitle,
183
203
  message: messageOrOptions,
184
204
  ...(options ?? {}),
185
- } as AGPromptModalProps;
205
+ } as PromptModalProps;
186
206
  };
187
207
 
188
- const modal = await this.openModal<ModalComponent<AGPromptModalProps, string | null>>(
189
- this.requireComponent(UIComponents.PromptModal),
190
- getProperties(),
191
- );
192
- const rawResult = await modal.beforeClose;
208
+ const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
193
209
  const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
194
210
 
195
211
  return result ?? null;
@@ -203,7 +219,11 @@ export class UIService extends Service {
203
219
  operation?: Promise<T> | (() => T),
204
220
  ): Promise<T> {
205
221
  const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
206
- const processArgs = (): { operationPromise: Promise<T>; props?: AGLoadingModalProps } => {
222
+ const processArgs = (): {
223
+ operationPromise: Promise<T>;
224
+ props?: AcceptRefs<LoadingModalProps>;
225
+ delay?: number;
226
+ } => {
207
227
  if (typeof operationOrMessageOrOptions === 'string') {
208
228
  return {
209
229
  props: { message: operationOrMessageOrOptions },
@@ -215,17 +235,30 @@ export class UIService extends Service {
215
235
  return { operationPromise: processOperation(operationOrMessageOrOptions) };
216
236
  }
217
237
 
238
+ const { delay, ...props } = operationOrMessageOrOptions;
239
+
218
240
  return {
219
- props: operationOrMessageOrOptions,
241
+ props,
242
+ delay: unref(delay),
220
243
  operationPromise: processOperation(operation as Promise<T> | (() => T)),
221
244
  };
222
245
  };
223
246
 
224
- const { operationPromise, props } = processArgs();
225
- const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), props);
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);
226
257
 
227
258
  try {
228
- const [result] = await Promise.all([operationPromise, after({ seconds: 1 })]);
259
+ const result = await operationPromise;
260
+
261
+ await after({ ms: 500 });
229
262
 
230
263
  return result;
231
264
  } finally {
@@ -233,43 +266,34 @@ export class UIService extends Service {
233
266
  }
234
267
  }
235
268
 
236
- public showSnackbar(message: string, options: ShowSnackbarOptions = {}): void {
237
- const snackbar: Snackbar = {
269
+ public toast(message: string, options: ToastOptions = {}): void {
270
+ const { component, ...otherOptions } = options;
271
+ const toast: UIToast = {
238
272
  id: uuid(),
239
- properties: { message, ...options },
240
- component: markRaw(options.component ?? this.requireComponent(UIComponents.Snackbar)),
273
+ properties: { message, ...otherOptions },
274
+ component: markRaw(component ?? this.requireComponent('toast')),
241
275
  };
242
276
 
243
- this.setState('snackbars', this.snackbars.concat(snackbar));
244
-
245
- setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
277
+ this.setState('toasts', this.toasts.concat(toast));
246
278
  }
247
279
 
248
- public hideSnackbar(id: string): void {
249
- this.setState(
250
- 'snackbars',
251
- this.snackbars.filter((snackbar) => snackbar.id !== id),
252
- );
253
- }
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>>>;
254
285
 
255
- public registerComponent(name: UIComponent, component: Component): void {
256
- this.components[name] = component;
257
- }
258
-
259
- public async openModal<TModalComponent extends ModalComponent>(
260
- component: TModalComponent,
261
- properties?: ModalProperties<TModalComponent>,
262
- ): Promise<Modal<ModalResult<TModalComponent>>> {
286
+ public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
263
287
  const id = uuid();
264
- const callbacks: Partial<ModalCallbacks<ModalResult<TModalComponent>>> = {};
265
- const modal: Modal<ModalResult<TModalComponent>> = {
288
+ const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
289
+ const modal: UIModal<ModalResult<T>> = {
266
290
  id,
267
- properties: properties ?? {},
291
+ closing: false,
292
+ properties: props ?? {},
268
293
  component: markRaw(component),
269
294
  beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
270
- afterClose: new Promise((resolve) => (callbacks.closed = resolve)),
295
+ afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
271
296
  };
272
- const activeModal = this.modals.at(-1);
273
297
  const modals = this.modals.concat(modal);
274
298
 
275
299
  this.modalCallbacks[modal.id] = callbacks;
@@ -277,15 +301,26 @@ export class UIService extends Service {
277
301
  this.setState({ modals });
278
302
 
279
303
  await nextTick();
280
- await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
281
- await Promise.all([
282
- activeModal || Events.emit('show-overlays-backdrop'),
283
- Events.emit('show-modal', { id: modal.id }),
284
- ]);
285
304
 
286
305
  return modal;
287
306
  }
288
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
+
289
324
  public async closeModal(id: string, result?: unknown): Promise<void> {
290
325
  if (!App.isMounted()) {
291
326
  await this.removeModal(id, result);
@@ -302,7 +337,7 @@ export class UIService extends Service {
302
337
  }
303
338
  }
304
339
 
305
- protected async boot(): Promise<void> {
340
+ protected override async boot(): Promise<void> {
306
341
  this.watchModalEvents();
307
342
  this.watchMountedEvent();
308
343
  this.watchViewportBreakpoints();
@@ -314,25 +349,23 @@ export class UIService extends Service {
314
349
  this.modals.filter((m) => m.id !== id),
315
350
  );
316
351
 
317
- this.modalCallbacks[id]?.closed?.(result);
352
+ this.modalCallbacks[id]?.hasClosed?.(result);
318
353
 
319
354
  delete this.modalCallbacks[id];
320
-
321
- const activeModal = this.modals.at(-1);
322
-
323
- await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
324
355
  }
325
356
 
326
357
  private watchModalEvents(): void {
327
- Events.on('modal-will-close', ({ modal, result }) => {
328
- this.modalCallbacks[modal.id]?.willClose?.(result);
358
+ Events.on('modal-will-close', ({ modal: { id }, result }) => {
359
+ const modal = this.modals.find((_modal) => id === _modal.id);
329
360
 
330
- if (this.modals.length === 1) {
331
- Events.emit('hide-overlays-backdrop');
361
+ if (modal) {
362
+ modal.closing = true;
332
363
  }
364
+
365
+ this.modalCallbacks[id]?.willClose?.(result);
333
366
  });
334
367
 
335
- Events.on('modal-closed', async ({ modal: { id }, result }) => {
368
+ Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
336
369
  await this.removeModal(id, result);
337
370
  });
338
371
  }
@@ -373,14 +406,10 @@ export class UIService extends Service {
373
406
 
374
407
  export default facade(UIService);
375
408
 
376
- declare module '@/services/Events' {
409
+ declare module '@aerogel/core/services/Events' {
377
410
  export interface EventsPayload {
378
411
  'close-modal': { id: string; result?: unknown };
379
- 'hide-modal': { id: string };
380
- 'hide-overlays-backdrop': void;
381
- 'modal-closed': { modal: Modal; result?: unknown };
382
- 'modal-will-close': { modal: Modal; result?: unknown };
383
- 'show-modal': { id: string };
384
- 'show-overlays-backdrop': void;
412
+ 'modal-will-close': { modal: UIModal; result?: unknown };
413
+ 'modal-has-closed': { modal: UIModal; result?: unknown };
385
414
  }
386
415
  }
package/src/ui/index.ts CHANGED
@@ -1,18 +1,17 @@
1
+ import AlertModal from '@aerogel/core/components/ui/AlertModal.vue';
2
+ import ConfirmModal from '@aerogel/core/components/ui/ConfirmModal.vue';
3
+ import ErrorReportModal from '@aerogel/core/components/ui/ErrorReportModal.vue';
4
+ import LoadingModal from '@aerogel/core/components/ui/LoadingModal.vue';
5
+ import PromptModal from '@aerogel/core/components/ui/PromptModal.vue';
6
+ import StartupCrash from '@aerogel/core/components/ui/StartupCrash.vue';
7
+ import Toast from '@aerogel/core/components/ui/Toast.vue';
8
+ import { bootServices } from '@aerogel/core/services';
9
+ import { definePlugin } from '@aerogel/core/plugins';
10
+
11
+ import UI from './UI';
12
+ import type { UIComponents } from './UI';
1
13
  import type { Component } from 'vue';
2
14
 
3
- import { bootServices } from '@/services';
4
- import { definePlugin } from '@/plugins';
5
-
6
- import UI, { UIComponents } from './UI';
7
- import AGAlertModal from '../components/modals/AGAlertModal.vue';
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';
14
- import type { UIComponent } from './UI';
15
-
16
15
  const services = { $ui: UI };
17
16
 
18
17
  export * from './UI';
@@ -23,31 +22,31 @@ export type UIServices = typeof services;
23
22
 
24
23
  export default definePlugin({
25
24
  async install(app, options) {
26
- const defaultComponents = {
27
- [UIComponents.AlertModal]: AGAlertModal,
28
- [UIComponents.ConfirmModal]: AGConfirmModal,
29
- [UIComponents.ErrorReportModal]: AGErrorReportModal,
30
- [UIComponents.LoadingModal]: AGLoadingModal,
31
- [UIComponents.PromptModal]: AGPromptModal,
32
- [UIComponents.Snackbar]: AGSnackbar,
33
- [UIComponents.StartupCrash]: AGStartupCrash,
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
- ...defaultComponents,
38
- ...options.components,
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
  },
43
42
  });
44
43
 
45
- declare module '@/bootstrap/options' {
44
+ declare module '@aerogel/core/bootstrap/options' {
46
45
  export interface AerogelOptions {
47
- components?: Partial<Record<UIComponent, Component>>;
46
+ components?: Partial<Partial<UIComponents>>;
48
47
  }
49
48
  }
50
49
 
51
- declare module '@/services' {
50
+ declare module '@aerogel/core/services' {
52
51
  export interface Services extends UIServices {}
53
52
  }
@@ -0,0 +1,41 @@
1
+ import clsx from 'clsx';
2
+ import { unref } from 'vue';
3
+ import { cva } from 'class-variance-authority';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import type { ClassValue } from 'clsx';
6
+ import type { PropType } from 'vue';
7
+ import type { GetClosureArgs, GetClosureResult } from '@noeldemartin/utils';
8
+
9
+ export type CVAConfig<T> = NonNullable<GetClosureArgs<typeof cva<T>>[1]>;
10
+ export type CVAProps<T> = NonNullable<GetClosureArgs<GetClosureResult<typeof cva<T>>>[0]>;
11
+ export type Variants<T extends Record<string, string | boolean>> = Required<{
12
+ [K in keyof T]: Exclude<T[K], undefined> extends string
13
+ ? { [key in Exclude<T[K], undefined>]: string | null }
14
+ : { true: string | null; false: string | null };
15
+ }>;
16
+
17
+ export type ComponentPropDefinitions<T> = {
18
+ [K in keyof T]: {
19
+ type?: PropType<T[K]>;
20
+ default: T[K] | (() => T[K]) | null;
21
+ };
22
+ };
23
+
24
+ export type PickComponentProps<TValues, TDefinitions> = {
25
+ [K in keyof TValues]: K extends keyof TDefinitions ? TValues[K] : never;
26
+ };
27
+
28
+ export function variantClasses<T>(
29
+ value: { baseClasses?: string } & CVAProps<T>,
30
+ config: { baseClasses?: string } & CVAConfig<T>,
31
+ ): string {
32
+ const { baseClasses: valueBaseClasses, ...values } = value;
33
+ const { baseClasses: configBaseClasses, ...configs } = config;
34
+ const variants = cva(configBaseClasses, configs as CVAConfig<T>);
35
+
36
+ return classes(variants(values as CVAProps<T>), unref(valueBaseClasses));
37
+ }
38
+
39
+ export function classes(...inputs: ClassValue[]): string {
40
+ return twMerge(clsx(inputs));
41
+ }
@@ -1,24 +1,22 @@
1
1
  import { onUnmounted } from 'vue';
2
2
 
3
- import Events from '@/services/Events';
3
+ import Events from '@aerogel/core/services/Events';
4
4
  import type {
5
5
  EventListener,
6
6
  EventWithPayload,
7
7
  EventWithoutPayload,
8
8
  EventsPayload,
9
- UnknownEvent,
10
- } from '@/services/Events';
9
+ } from '@aerogel/core/services/Events';
11
10
 
12
11
  export function useEvent<Event extends EventWithoutPayload>(event: Event, listener: () => unknown): void;
13
12
  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
- const unsubscribe = Events.on(event, listener);
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
  }