@aerogel/core 0.0.0-next.eed7a057cf5b844cd9f7fc6bda2d8df49fcd6736 → 0.0.0-next.f0368a3107664018eb88652e719d2cb7d24b82e7

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 (157) hide show
  1. package/dist/aerogel-core.d.ts +1699 -2701
  2. package/dist/aerogel-core.js +2544 -2037
  3. package/dist/aerogel-core.js.map +1 -1
  4. package/package.json +14 -6
  5. package/src/components/{AGAppLayout.vue → AppLayout.vue} +3 -3
  6. package/src/components/{AGAppModals.vue → AppModals.vue} +2 -3
  7. package/src/components/AppOverlays.vue +9 -0
  8. package/src/components/AppToasts.vue +16 -0
  9. package/src/components/contracts/AlertModal.ts +4 -0
  10. package/src/components/contracts/Button.ts +16 -0
  11. package/src/components/contracts/ConfirmModal.ts +42 -0
  12. package/src/components/contracts/DropdownMenu.ts +20 -0
  13. package/src/components/{modals/AGErrorReportModal.ts → contracts/ErrorReportModal.ts} +3 -23
  14. package/src/components/contracts/Input.ts +26 -0
  15. package/src/components/contracts/LoadingModal.ts +22 -0
  16. package/src/components/contracts/Modal.ts +21 -0
  17. package/src/components/contracts/PromptModal.ts +31 -0
  18. package/src/components/contracts/Select.ts +45 -0
  19. package/src/components/contracts/Toast.ts +13 -0
  20. package/src/components/contracts/index.ts +11 -0
  21. package/src/components/headless/HeadlessButton.vue +51 -0
  22. package/src/components/headless/HeadlessInput.vue +59 -0
  23. package/src/components/headless/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +6 -7
  24. package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +2 -6
  25. package/src/components/headless/{forms/AGHeadlessInputInput.vue → HeadlessInputInput.vue} +16 -25
  26. package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +2 -6
  27. package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +9 -12
  28. package/src/components/headless/HeadlessModal.vue +57 -0
  29. package/src/components/headless/HeadlessModalContent.vue +30 -0
  30. package/src/components/headless/HeadlessModalDescription.vue +12 -0
  31. package/src/components/headless/HeadlessModalOverlay.vue +12 -0
  32. package/src/components/headless/HeadlessModalTitle.vue +12 -0
  33. package/src/components/headless/HeadlessSelect.vue +120 -0
  34. package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +3 -4
  35. package/src/components/headless/HeadlessSelectLabel.vue +25 -0
  36. package/src/components/headless/HeadlessSelectOption.vue +34 -0
  37. package/src/components/headless/HeadlessSelectOptions.vue +42 -0
  38. package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
  39. package/src/components/headless/HeadlessSelectValue.vue +18 -0
  40. package/src/components/headless/HeadlessToast.vue +18 -0
  41. package/src/components/headless/HeadlessToastAction.vue +13 -0
  42. package/src/components/headless/index.ts +19 -3
  43. package/src/components/index.ts +6 -11
  44. package/src/components/ui/AdvancedOptions.vue +18 -0
  45. package/src/components/ui/AlertModal.vue +13 -0
  46. package/src/components/ui/Button.vue +98 -0
  47. package/src/components/ui/Checkbox.vue +56 -0
  48. package/src/components/ui/ConfirmModal.vue +42 -0
  49. package/src/components/ui/DropdownMenu.vue +32 -0
  50. package/src/components/ui/DropdownMenuOption.vue +14 -0
  51. package/src/components/ui/DropdownMenuOptions.vue +27 -0
  52. package/src/components/ui/EditableContent.vue +82 -0
  53. package/src/components/ui/ErrorMessage.vue +15 -0
  54. package/src/components/ui/ErrorReportModal.vue +62 -0
  55. package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +29 -22
  56. package/src/components/ui/ErrorReportModalTitle.vue +24 -0
  57. package/src/components/{forms/AGForm.vue → ui/Form.vue} +4 -5
  58. package/src/components/ui/Input.vue +56 -0
  59. package/src/components/ui/Link.vue +12 -0
  60. package/src/components/ui/LoadingModal.vue +32 -0
  61. package/src/components/ui/Markdown.vue +69 -0
  62. package/src/components/ui/Modal.vue +121 -0
  63. package/src/components/{modals/AGModalContext.vue → ui/ModalContext.vue} +7 -9
  64. package/src/components/ui/ProgressBar.vue +51 -0
  65. package/src/components/ui/PromptModal.vue +35 -0
  66. package/src/components/ui/Select.vue +27 -0
  67. package/src/components/ui/SelectLabel.vue +17 -0
  68. package/src/components/ui/SelectOption.vue +29 -0
  69. package/src/components/ui/SelectOptions.vue +35 -0
  70. package/src/components/ui/SelectTrigger.vue +29 -0
  71. package/src/components/ui/SettingsModal.vue +15 -0
  72. package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +8 -8
  73. package/src/components/ui/Toast.vue +42 -0
  74. package/src/components/ui/index.ts +30 -0
  75. package/src/directives/index.ts +6 -0
  76. package/src/errors/Errors.ts +9 -10
  77. package/src/forms/{Form.test.ts → FormController.test.ts} +29 -4
  78. package/src/forms/{Form.ts → FormController.ts} +20 -11
  79. package/src/forms/index.ts +2 -3
  80. package/src/forms/utils.ts +23 -2
  81. package/src/index.css +72 -0
  82. package/src/lang/index.ts +5 -1
  83. package/src/lang/settings/Language.vue +48 -0
  84. package/src/lang/settings/index.ts +10 -0
  85. package/src/services/App.state.ts +11 -1
  86. package/src/services/App.ts +9 -1
  87. package/src/services/Events.test.ts +8 -8
  88. package/src/services/Events.ts +2 -8
  89. package/src/services/index.ts +4 -1
  90. package/src/ui/UI.state.ts +13 -6
  91. package/src/ui/UI.ts +70 -75
  92. package/src/ui/index.ts +14 -14
  93. package/src/utils/classes.ts +49 -0
  94. package/src/utils/composition/events.ts +2 -4
  95. package/src/utils/composition/forms.ts +20 -4
  96. package/src/utils/composition/state.ts +11 -2
  97. package/src/utils/index.ts +3 -1
  98. package/src/utils/types.ts +3 -0
  99. package/src/utils/vue.ts +22 -133
  100. package/src/components/AGAppOverlays.vue +0 -41
  101. package/src/components/AGAppSnackbars.vue +0 -13
  102. package/src/components/composition.ts +0 -23
  103. package/src/components/constants.ts +0 -8
  104. package/src/components/forms/AGButton.vue +0 -44
  105. package/src/components/forms/AGCheckbox.vue +0 -42
  106. package/src/components/forms/AGInput.vue +0 -42
  107. package/src/components/forms/AGSelect.story.vue +0 -46
  108. package/src/components/forms/AGSelect.vue +0 -60
  109. package/src/components/forms/index.ts +0 -5
  110. package/src/components/headless/forms/AGHeadlessButton.ts +0 -3
  111. package/src/components/headless/forms/AGHeadlessButton.vue +0 -62
  112. package/src/components/headless/forms/AGHeadlessInput.ts +0 -41
  113. package/src/components/headless/forms/AGHeadlessInput.vue +0 -70
  114. package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
  115. package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
  116. package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
  117. package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
  118. package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
  119. package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
  120. package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
  121. package/src/components/headless/forms/composition.ts +0 -10
  122. package/src/components/headless/forms/index.ts +0 -18
  123. package/src/components/headless/modals/AGHeadlessModal.ts +0 -36
  124. package/src/components/headless/modals/AGHeadlessModal.vue +0 -92
  125. package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -32
  126. package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -23
  127. package/src/components/headless/modals/index.ts +0 -4
  128. package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
  129. package/src/components/headless/snackbars/index.ts +0 -40
  130. package/src/components/interfaces.ts +0 -24
  131. package/src/components/lib/AGErrorMessage.vue +0 -16
  132. package/src/components/lib/AGLink.vue +0 -9
  133. package/src/components/lib/AGMarkdown.vue +0 -54
  134. package/src/components/lib/AGMeasured.vue +0 -16
  135. package/src/components/lib/AGProgressBar.vue +0 -55
  136. package/src/components/lib/index.ts +0 -6
  137. package/src/components/modals/AGAlertModal.ts +0 -18
  138. package/src/components/modals/AGAlertModal.vue +0 -14
  139. package/src/components/modals/AGConfirmModal.ts +0 -42
  140. package/src/components/modals/AGConfirmModal.vue +0 -26
  141. package/src/components/modals/AGErrorReportModal.vue +0 -54
  142. package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
  143. package/src/components/modals/AGLoadingModal.ts +0 -29
  144. package/src/components/modals/AGLoadingModal.vue +0 -15
  145. package/src/components/modals/AGModal.ts +0 -11
  146. package/src/components/modals/AGModal.vue +0 -42
  147. package/src/components/modals/AGModalContext.ts +0 -8
  148. package/src/components/modals/AGModalTitle.vue +0 -9
  149. package/src/components/modals/AGPromptModal.ts +0 -41
  150. package/src/components/modals/AGPromptModal.vue +0 -34
  151. package/src/components/modals/index.ts +0 -17
  152. package/src/components/snackbars/AGSnackbar.vue +0 -36
  153. package/src/components/snackbars/index.ts +0 -3
  154. package/src/components/utils.ts +0 -13
  155. package/src/forms/composition.ts +0 -6
  156. package/src/utils/tailwindcss.test.ts +0 -26
  157. package/src/utils/tailwindcss.ts +0 -7
package/src/ui/UI.ts CHANGED
@@ -6,22 +6,20 @@ import type { ObjectValues } from '@noeldemartin/utils';
6
6
  import App from '@aerogel/core/services/App';
7
7
  import Events from '@aerogel/core/services/Events';
8
8
  import type { AcceptRefs } from '@aerogel/core/utils';
9
- import type { Color } from '@aerogel/core/components/constants';
10
- import type { SnackbarAction, SnackbarColor } from '@aerogel/core/components/headless/snackbars';
11
- import type {
12
- AGAlertModalProps,
13
- AGConfirmModalProps,
14
- AGLoadingModalProps,
15
- AGPromptModalProps,
16
- } from '@aerogel/core/components';
9
+ import type { AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
10
+ import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
11
+ import type { ConfirmModalCheckboxes, ConfirmModalProps } from '@aerogel/core/components/contracts/ConfirmModal';
12
+ import type { LoadingModalProps } from '@aerogel/core/components/contracts/LoadingModal';
13
+ import type { PromptModalProps } from '@aerogel/core/components/contracts/PromptModal';
14
+ import type { ToastAction, ToastVariant } from '@aerogel/core/components/contracts/Toast';
17
15
 
18
16
  import Service from './UI.state';
19
17
  import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
20
- import type { Modal, ModalComponent, Snackbar } from './UI.state';
18
+ import type { ModalComponent, UIModal, UIToast } from './UI.state';
21
19
 
22
20
  interface ModalCallbacks<T = unknown> {
23
21
  willClose(result: T | undefined): void;
24
- closed(result: T | undefined): void;
22
+ hasClosed(result: T | undefined): void;
25
23
  }
26
24
 
27
25
  type ModalProperties<TComponent> = TComponent extends ModalComponent<infer TProperties, unknown> ? TProperties : never;
@@ -34,19 +32,18 @@ export const UIComponents = {
34
32
  ErrorReportModal: 'error-report-modal',
35
33
  LoadingModal: 'loading-modal',
36
34
  PromptModal: 'prompt-modal',
37
- Snackbar: 'snackbar',
35
+ Toast: 'toast',
38
36
  StartupCrash: 'startup-crash',
37
+ RouterLink: 'router-link',
39
38
  } as const;
40
39
 
41
40
  export type UIComponent = ObjectValues<typeof UIComponents>;
42
41
 
43
- export type ConfirmCheckboxes = Record<string, { label: string; default?: boolean; required?: boolean }>;
44
-
45
42
  export type ConfirmOptions = AcceptRefs<{
46
43
  acceptText?: string;
47
- acceptColor?: Color;
44
+ acceptVariant?: ButtonVariant;
48
45
  cancelText?: string;
49
- cancelColor?: Color;
46
+ cancelVariant?: ButtonVariant;
50
47
  actions?: Record<string, () => unknown>;
51
48
  required?: boolean;
52
49
  }>;
@@ -57,7 +54,8 @@ export type LoadingOptions = AcceptRefs<{
57
54
  progress?: number;
58
55
  }>;
59
56
 
60
- export interface ConfirmOptionsWithCheckboxes<T extends ConfirmCheckboxes = ConfirmCheckboxes> extends ConfirmOptions {
57
+ export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
58
+ extends ConfirmOptions {
61
59
  checkboxes?: T;
62
60
  }
63
61
 
@@ -66,16 +64,16 @@ export type PromptOptions = AcceptRefs<{
66
64
  defaultValue?: string;
67
65
  placeholder?: string;
68
66
  acceptText?: string;
69
- acceptColor?: Color;
67
+ acceptVariant?: ButtonVariant;
70
68
  cancelText?: string;
71
- cancelColor?: Color;
69
+ cancelVariant?: ButtonVariant;
72
70
  trim?: boolean;
73
71
  }>;
74
72
 
75
- export interface ShowSnackbarOptions {
73
+ export interface ToastOptions {
76
74
  component?: Component;
77
- color?: SnackbarColor;
78
- actions?: SnackbarAction[];
75
+ variant?: ToastVariant;
76
+ actions?: ToastAction[];
79
77
  }
80
78
 
81
79
  export class UIService extends Service {
@@ -83,14 +81,18 @@ export class UIService extends Service {
83
81
  private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
84
82
  private components: Partial<Record<UIComponent, Component>> = {};
85
83
 
84
+ public resolveComponent(name: UIComponent): Component | null {
85
+ return this.components[name] ?? null;
86
+ }
87
+
86
88
  public requireComponent(name: UIComponent): Component {
87
- return this.components[name] ?? fail(`UI Component '${name}' is not defined!`);
89
+ return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
88
90
  }
89
91
 
90
92
  public alert(message: string): void;
91
93
  public alert(title: string, message: string): void;
92
94
  public alert(messageOrTitle: string, message?: string): void {
93
- const getProperties = (): AGAlertModalProps => {
95
+ const getProperties = (): AlertModalProps => {
94
96
  if (typeof message !== 'string') {
95
97
  return { message: messageOrTitle };
96
98
  }
@@ -101,14 +103,17 @@ export class UIService extends Service {
101
103
  };
102
104
  };
103
105
 
104
- this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
106
+ this.openModal<ModalComponent<AlertModalProps>>(
107
+ this.requireComponent(UIComponents.AlertModal),
108
+ getProperties(),
109
+ );
105
110
  }
106
111
 
107
112
  /* eslint-disable max-len */
108
113
  public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
109
114
  public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
110
- public async confirm<T extends ConfirmCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
111
- public async confirm<T extends ConfirmCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
115
+ public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
116
+ public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
112
117
  /* eslint-enable max-len */
113
118
 
114
119
  public async confirm(
@@ -116,7 +121,7 @@ export class UIService extends Service {
116
121
  messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
117
122
  options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
118
123
  ): Promise<boolean | [boolean, Record<string, boolean>]> {
119
- const getProperties = (): AGConfirmModalProps => {
124
+ const getProperties = (): AcceptRefs<ConfirmModalProps> => {
120
125
  if (typeof messageOrOptions !== 'string') {
121
126
  return {
122
127
  ...(messageOrOptions ?? {}),
@@ -132,10 +137,17 @@ export class UIService extends Service {
132
137
  required: !!options?.required,
133
138
  };
134
139
  };
140
+
141
+ type ConfirmModalComponent = ModalComponent<
142
+ AcceptRefs<ConfirmModalProps>,
143
+ boolean | [boolean, Record<string, boolean>]
144
+ >;
145
+
135
146
  const properties = getProperties();
136
- const modal = await this.openModal<
137
- ModalComponent<AGConfirmModalProps, boolean | [boolean, Record<string, boolean>]>
138
- >(this.requireComponent(UIComponents.ConfirmModal), properties);
147
+ const modal = await this.openModal<ConfirmModalComponent>(
148
+ this.requireComponent(UIComponents.ConfirmModal),
149
+ properties,
150
+ );
139
151
  const result = await modal.beforeClose;
140
152
 
141
153
  const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
@@ -174,22 +186,22 @@ export class UIService extends Service {
174
186
  options?: PromptOptions,
175
187
  ): Promise<string | null> {
176
188
  const trim = options?.trim ?? true;
177
- const getProperties = (): AGPromptModalProps => {
189
+ const getProperties = (): PromptModalProps => {
178
190
  if (typeof messageOrOptions !== 'string') {
179
191
  return {
180
192
  message: messageOrTitle,
181
193
  ...(messageOrOptions ?? {}),
182
- } as AGPromptModalProps;
194
+ } as PromptModalProps;
183
195
  }
184
196
 
185
197
  return {
186
198
  title: messageOrTitle,
187
199
  message: messageOrOptions,
188
200
  ...(options ?? {}),
189
- } as AGPromptModalProps;
201
+ } as PromptModalProps;
190
202
  };
191
203
 
192
- const modal = await this.openModal<ModalComponent<AGPromptModalProps, string | null>>(
204
+ const modal = await this.openModal<ModalComponent<PromptModalProps, string | null>>(
193
205
  this.requireComponent(UIComponents.PromptModal),
194
206
  getProperties(),
195
207
  );
@@ -207,7 +219,7 @@ export class UIService extends Service {
207
219
  operation?: Promise<T> | (() => T),
208
220
  ): Promise<T> {
209
221
  const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
210
- const processArgs = (): { operationPromise: Promise<T>; props?: AGLoadingModalProps } => {
222
+ const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
211
223
  if (typeof operationOrMessageOrOptions === 'string') {
212
224
  return {
213
225
  props: { message: operationOrMessageOrOptions },
@@ -229,7 +241,9 @@ export class UIService extends Service {
229
241
  const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), props);
230
242
 
231
243
  try {
232
- const [result] = await Promise.all([operationPromise, after({ seconds: 1 })]);
244
+ const result = await operationPromise;
245
+
246
+ await after({ ms: 500 });
233
247
 
234
248
  return result;
235
249
  } finally {
@@ -237,23 +251,15 @@ export class UIService extends Service {
237
251
  }
238
252
  }
239
253
 
240
- public showSnackbar(message: string, options: ShowSnackbarOptions = {}): void {
241
- const snackbar: Snackbar = {
254
+ public toast(message: string, options: ToastOptions = {}): void {
255
+ const { component, ...otherOptions } = options;
256
+ const toast: UIToast = {
242
257
  id: uuid(),
243
- properties: { message, ...options },
244
- component: markRaw(options.component ?? this.requireComponent(UIComponents.Snackbar)),
258
+ properties: { message, ...otherOptions },
259
+ component: markRaw(component ?? this.requireComponent(UIComponents.Toast)),
245
260
  };
246
261
 
247
- this.setState('snackbars', this.snackbars.concat(snackbar));
248
-
249
- setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
250
- }
251
-
252
- public hideSnackbar(id: string): void {
253
- this.setState(
254
- 'snackbars',
255
- this.snackbars.filter((snackbar) => snackbar.id !== id),
256
- );
262
+ this.setState('toasts', this.toasts.concat(toast));
257
263
  }
258
264
 
259
265
  public registerComponent(name: UIComponent, component: Component): void {
@@ -263,17 +269,17 @@ export class UIService extends Service {
263
269
  public async openModal<TModalComponent extends ModalComponent>(
264
270
  component: TModalComponent,
265
271
  properties?: ModalProperties<TModalComponent>,
266
- ): Promise<Modal<ModalResult<TModalComponent>>> {
272
+ ): Promise<UIModal<ModalResult<TModalComponent>>> {
267
273
  const id = uuid();
268
274
  const callbacks: Partial<ModalCallbacks<ModalResult<TModalComponent>>> = {};
269
- const modal: Modal<ModalResult<TModalComponent>> = {
275
+ const modal: UIModal<ModalResult<TModalComponent>> = {
270
276
  id,
277
+ closing: false,
271
278
  properties: properties ?? {},
272
279
  component: markRaw(component),
273
280
  beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
274
- afterClose: new Promise((resolve) => (callbacks.closed = resolve)),
281
+ afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
275
282
  };
276
- const activeModal = this.modals.at(-1);
277
283
  const modals = this.modals.concat(modal);
278
284
 
279
285
  this.modalCallbacks[modal.id] = callbacks;
@@ -281,11 +287,6 @@ export class UIService extends Service {
281
287
  this.setState({ modals });
282
288
 
283
289
  await nextTick();
284
- await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
285
- await Promise.all([
286
- activeModal || Events.emit('show-overlays-backdrop'),
287
- Events.emit('show-modal', { id: modal.id }),
288
- ]);
289
290
 
290
291
  return modal;
291
292
  }
@@ -318,25 +319,23 @@ export class UIService extends Service {
318
319
  this.modals.filter((m) => m.id !== id),
319
320
  );
320
321
 
321
- this.modalCallbacks[id]?.closed?.(result);
322
+ this.modalCallbacks[id]?.hasClosed?.(result);
322
323
 
323
324
  delete this.modalCallbacks[id];
324
-
325
- const activeModal = this.modals.at(-1);
326
-
327
- await (activeModal && Events.emit('show-modal', { id: activeModal.id }));
328
325
  }
329
326
 
330
327
  private watchModalEvents(): void {
331
- Events.on('modal-will-close', ({ modal, result }) => {
332
- this.modalCallbacks[modal.id]?.willClose?.(result);
328
+ Events.on('modal-will-close', ({ modal: { id }, result }) => {
329
+ const modal = this.modals.find((_modal) => id === _modal.id);
333
330
 
334
- if (this.modals.length === 1) {
335
- Events.emit('hide-overlays-backdrop');
331
+ if (modal) {
332
+ modal.closing = true;
336
333
  }
334
+
335
+ this.modalCallbacks[id]?.willClose?.(result);
337
336
  });
338
337
 
339
- Events.on('modal-closed', async ({ modal: { id }, result }) => {
338
+ Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
340
339
  await this.removeModal(id, result);
341
340
  });
342
341
  }
@@ -380,11 +379,7 @@ export default facade(UIService);
380
379
  declare module '@aerogel/core/services/Events' {
381
380
  export interface EventsPayload {
382
381
  'close-modal': { id: string; result?: unknown };
383
- 'hide-modal': { id: string };
384
- 'hide-overlays-backdrop': void;
385
- 'modal-closed': { modal: Modal; result?: unknown };
386
- 'modal-will-close': { modal: Modal; result?: unknown };
387
- 'show-modal': { id: string };
388
- 'show-overlays-backdrop': void;
382
+ 'modal-will-close': { modal: UIModal; result?: unknown };
383
+ 'modal-has-closed': { modal: UIModal; result?: unknown };
389
384
  }
390
385
  }
package/src/ui/index.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  import type { Component } from 'vue';
2
2
 
3
+ import AlertModal from '@aerogel/core/components/ui/AlertModal.vue';
4
+ import ConfirmModal from '@aerogel/core/components/ui/ConfirmModal.vue';
5
+ import ErrorReportModal from '@aerogel/core/components/ui/ErrorReportModal.vue';
6
+ import LoadingModal from '@aerogel/core/components/ui/LoadingModal.vue';
7
+ import PromptModal from '@aerogel/core/components/ui/PromptModal.vue';
8
+ import StartupCrash from '@aerogel/core/components/ui/StartupCrash.vue';
9
+ import Toast from '@aerogel/core/components/ui/Toast.vue';
3
10
  import { bootServices } from '@aerogel/core/services';
4
11
  import { definePlugin } from '@aerogel/core/plugins';
5
12
 
6
13
  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
14
  import type { UIComponent } from './UI';
15
15
 
16
16
  const services = { $ui: UI };
@@ -24,13 +24,13 @@ export type UIServices = typeof services;
24
24
  export default definePlugin({
25
25
  async install(app, options) {
26
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,
27
+ [UIComponents.AlertModal]: AlertModal,
28
+ [UIComponents.ConfirmModal]: ConfirmModal,
29
+ [UIComponents.ErrorReportModal]: ErrorReportModal,
30
+ [UIComponents.LoadingModal]: LoadingModal,
31
+ [UIComponents.PromptModal]: PromptModal,
32
+ [UIComponents.Toast]: Toast,
33
+ [UIComponents.StartupCrash]: StartupCrash,
34
34
  };
35
35
 
36
36
  Object.entries({
@@ -0,0 +1,49 @@
1
+ import clsx from 'clsx';
2
+ import { computed, 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 { ComputedRef, PropType, Ref } 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 RefsObject<T> = { [K in keyof T]: Ref<T[K]> | T[K] };
12
+ export type Variants<T extends Record<string, string | boolean>> = Required<{
13
+ [K in keyof T]: Exclude<T[K], undefined> extends string
14
+ ? { [key in Exclude<T[K], undefined>]: string | null }
15
+ : { true: string | null; false: string | null };
16
+ }>;
17
+
18
+ export type ComponentPropDefinitions<T> = {
19
+ [K in keyof T]: {
20
+ type?: PropType<T[K]>;
21
+ default: T[K] | (() => T[K]) | null;
22
+ };
23
+ };
24
+
25
+ export type PickComponentProps<TValues, TDefinitions> = {
26
+ [K in keyof TValues]: K extends keyof TDefinitions ? TValues[K] : never;
27
+ };
28
+
29
+ export function computedVariantClasses<T>(
30
+ value: RefsObject<{ baseClasses?: string } & CVAProps<T>>,
31
+ config: { baseClasses?: string } & CVAConfig<T>,
32
+ ): ComputedRef<string> {
33
+ return computed(() => {
34
+ const { baseClasses: valueBaseClasses, ...valueRefs } = value;
35
+ const { baseClasses: configBaseClasses, ...configs } = config;
36
+ const variants = cva(configBaseClasses, configs as CVAConfig<T>);
37
+ const values = Object.entries(valueRefs).reduce((extractedValues, [name, valueRef]) => {
38
+ extractedValues[name as keyof CVAProps<T>] = unref(valueRef);
39
+
40
+ return extractedValues;
41
+ }, {} as CVAProps<T>);
42
+
43
+ return classes(variants(values), unref(valueBaseClasses));
44
+ });
45
+ }
46
+
47
+ export function classes(...inputs: ClassValue[]): string {
48
+ return twMerge(clsx(inputs));
49
+ }
@@ -6,7 +6,6 @@ import type {
6
6
  EventWithPayload,
7
7
  EventWithoutPayload,
8
8
  EventsPayload,
9
- UnknownEvent,
10
9
  } from '@aerogel/core/services/Events';
11
10
 
12
11
  export function useEvent<Event extends EventWithoutPayload>(event: Event, listener: () => unknown): void;
@@ -14,11 +13,10 @@ 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
  }
@@ -1,11 +1,27 @@
1
1
  import { objectWithout } from '@noeldemartin/utils';
2
- import { computed, useAttrs } from 'vue';
2
+ import { computed, inject, onUnmounted, useAttrs } from 'vue';
3
+ import type { ClassValue } from 'clsx';
3
4
  import type { ComputedRef } from 'vue';
5
+ import type { Nullable } from '@noeldemartin/utils';
4
6
 
5
- export function useInputAttrs(): [ComputedRef<{}>, ComputedRef<unknown>] {
7
+ import FormController from '@aerogel/core/forms/FormController';
8
+ import type { FormData, FormFieldDefinitions } from '@aerogel/core/forms/FormController';
9
+
10
+ export function onFormFocus(input: { name: Nullable<string> }, listener: () => unknown): void {
11
+ const form = inject<FormController | null>('form', null);
12
+ const stop = form?.on('focus', (name) => input.name === name && listener());
13
+
14
+ onUnmounted(() => stop?.());
15
+ }
16
+
17
+ export function useForm<const T extends FormFieldDefinitions>(fields: T): FormController<T> & FormData<T> {
18
+ return new FormController(fields) as FormController<T> & FormData<T>;
19
+ }
20
+
21
+ export function useInputAttrs(): [ComputedRef<{}>, ComputedRef<ClassValue>] {
6
22
  const attrs = useAttrs();
7
- const className = computed(() => attrs.class);
23
+ const classes = computed(() => attrs.class);
8
24
  const inputAttrs = computed(() => objectWithout(attrs, 'class'));
9
25
 
10
- return [inputAttrs, className];
26
+ return [inputAttrs, classes as ComputedRef<ClassValue>];
11
27
  }
@@ -1,12 +1,21 @@
1
1
  import { debounce } from '@noeldemartin/utils';
2
- import { ref, watchEffect } from 'vue';
3
- import type { ComputedGetter, ComputedRef } from '@vue/runtime-core';
2
+ import { computed, ref, watch, watchEffect } from 'vue';
3
+ import type { ComputedGetter, ComputedRef, Ref } from 'vue';
4
4
 
5
5
  export interface ComputedDebounceOptions<T> {
6
6
  initial?: T;
7
7
  delay?: number;
8
8
  }
9
9
 
10
+ export function computedAsync<T>(getter: () => Promise<T>): Ref<T | undefined> {
11
+ const result = ref<T>();
12
+ const asyncValue = computed(getter);
13
+
14
+ watch(asyncValue, async () => (result.value = await asyncValue.value), { immediate: true });
15
+
16
+ return result;
17
+ }
18
+
10
19
  export function computedDebounce<T>(options: ComputedDebounceOptions<T>, getter: ComputedGetter<T>): ComputedRef<T>;
11
20
  export function computedDebounce<T>(getter: ComputedGetter<T>): ComputedRef<T | null>;
12
21
  export function computedDebounce<T>(
@@ -1,7 +1,9 @@
1
+ export * from './classes';
1
2
  export * from './composition/events';
2
3
  export * from './composition/forms';
3
4
  export * from './composition/hooks';
4
5
  export * from './composition/persistent';
6
+ export * from './composition/state';
5
7
  export * from './markdown';
6
- export * from './tailwindcss';
8
+ export * from './types';
7
9
  export * from './vue';
@@ -0,0 +1,3 @@
1
+ import type { Nullable } from '@noeldemartin/utils';
2
+
3
+ export type Falsifiable<T> = Nullable<T> | false;