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

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 (175) hide show
  1. package/dist/aerogel-core.d.ts +1840 -1813
  2. package/dist/aerogel-core.js +3129 -0
  3. package/dist/aerogel-core.js.map +1 -0
  4. package/package.json +27 -37
  5. package/src/bootstrap/bootstrap.test.ts +4 -7
  6. package/src/bootstrap/index.ts +14 -15
  7. package/src/bootstrap/options.ts +1 -1
  8. package/src/components/{AGAppLayout.vue → AppLayout.vue} +4 -4
  9. package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
  10. package/src/components/{AGAppOverlays.vue → AppOverlays.vue} +5 -10
  11. package/src/components/AppToasts.vue +16 -0
  12. package/src/components/composition.ts +1 -1
  13. package/src/components/contracts/AlertModal.ts +4 -0
  14. package/src/components/contracts/Button.ts +16 -0
  15. package/src/components/contracts/ConfirmModal.ts +41 -0
  16. package/src/components/contracts/DropdownMenu.ts +11 -0
  17. package/src/components/contracts/ErrorReportModal.ts +29 -0
  18. package/src/components/contracts/Input.ts +26 -0
  19. package/src/components/contracts/LoadingModal.ts +18 -0
  20. package/src/components/contracts/Modal.ts +13 -0
  21. package/src/components/contracts/PromptModal.ts +28 -0
  22. package/src/components/contracts/Select.ts +33 -0
  23. package/src/components/contracts/Toast.ts +13 -0
  24. package/src/components/contracts/index.ts +9 -0
  25. package/src/components/contracts/shared.ts +9 -0
  26. package/src/components/headless/HeadlessButton.vue +50 -0
  27. package/src/components/headless/HeadlessInput.vue +59 -0
  28. package/src/components/headless/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +7 -8
  29. package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
  30. package/src/components/headless/{forms/AGHeadlessInputInput.vue → HeadlessInputInput.vue} +13 -22
  31. package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
  32. package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +8 -11
  33. package/src/components/headless/{modals/AGHeadlessModal.vue → HeadlessModal.vue} +17 -23
  34. package/src/components/headless/HeadlessModalContent.vue +24 -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 +92 -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 +30 -0
  42. package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
  43. package/src/components/headless/HeadlessSelectValue.vue +15 -0
  44. package/src/components/headless/HeadlessToast.vue +18 -0
  45. package/src/components/headless/HeadlessToastAction.vue +13 -0
  46. package/src/components/headless/index.ts +18 -3
  47. package/src/components/index.ts +4 -10
  48. package/src/components/ui/AdvancedOptions.vue +18 -0
  49. package/src/components/ui/AlertModal.vue +13 -0
  50. package/src/components/ui/Button.vue +98 -0
  51. package/src/components/ui/Checkbox.vue +56 -0
  52. package/src/components/ui/ConfirmModal.vue +42 -0
  53. package/src/components/ui/DropdownMenu.vue +33 -0
  54. package/src/components/ui/EditableContent.vue +82 -0
  55. package/src/components/ui/ErrorMessage.vue +15 -0
  56. package/src/components/ui/ErrorReportModal.vue +62 -0
  57. package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +34 -27
  58. package/src/components/ui/ErrorReportModalTitle.vue +24 -0
  59. package/src/components/{forms/AGForm.vue → ui/Form.vue} +4 -5
  60. package/src/components/ui/Input.vue +56 -0
  61. package/src/components/ui/Link.vue +12 -0
  62. package/src/components/ui/LoadingModal.vue +32 -0
  63. package/src/components/ui/Markdown.vue +69 -0
  64. package/src/components/ui/Modal.vue +70 -0
  65. package/src/components/{modals/AGModalContext.vue → ui/ModalContext.vue} +7 -9
  66. package/src/components/ui/ProgressBar.vue +50 -0
  67. package/src/components/ui/PromptModal.vue +35 -0
  68. package/src/components/ui/Select.vue +53 -0
  69. package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +8 -8
  70. package/src/components/ui/Toast.vue +42 -0
  71. package/src/components/ui/index.ts +23 -0
  72. package/src/components/utils.ts +106 -9
  73. package/src/directives/index.ts +9 -5
  74. package/src/directives/measure.ts +1 -1
  75. package/src/errors/Errors.state.ts +1 -1
  76. package/src/errors/Errors.ts +17 -18
  77. package/src/errors/index.ts +9 -6
  78. package/src/errors/utils.ts +1 -1
  79. package/src/forms/{Form.test.ts → FormController.test.ts} +5 -4
  80. package/src/forms/{Form.ts → FormController.ts} +22 -19
  81. package/src/forms/composition.ts +4 -4
  82. package/src/forms/index.ts +2 -2
  83. package/src/forms/utils.ts +2 -2
  84. package/src/index.css +41 -0
  85. package/src/jobs/Job.ts +2 -2
  86. package/src/lang/DefaultLangProvider.ts +7 -4
  87. package/src/lang/Lang.state.ts +1 -1
  88. package/src/lang/Lang.ts +1 -1
  89. package/src/lang/index.ts +8 -6
  90. package/src/plugins/Plugin.ts +1 -1
  91. package/src/plugins/index.ts +10 -7
  92. package/src/services/App.state.ts +4 -3
  93. package/src/services/App.ts +4 -4
  94. package/src/services/Cache.ts +1 -1
  95. package/src/services/Events.ts +2 -2
  96. package/src/services/Service.ts +21 -21
  97. package/src/services/Storage.ts +3 -3
  98. package/src/services/index.ts +5 -4
  99. package/src/services/utils.ts +2 -2
  100. package/src/testing/index.ts +4 -3
  101. package/src/testing/setup.ts +3 -19
  102. package/src/ui/UI.state.ts +12 -7
  103. package/src/ui/UI.ts +63 -58
  104. package/src/ui/index.ts +18 -18
  105. package/src/utils/composition/events.ts +2 -2
  106. package/src/utils/composition/forms.ts +4 -3
  107. package/src/utils/markdown.ts +3 -5
  108. package/src/utils/vdom.ts +31 -0
  109. package/src/utils/vue.ts +7 -16
  110. package/dist/aerogel-core.cjs.js +0 -2
  111. package/dist/aerogel-core.cjs.js.map +0 -1
  112. package/dist/aerogel-core.esm.js +0 -2
  113. package/dist/aerogel-core.esm.js.map +0 -1
  114. package/histoire.config.ts +0 -7
  115. package/noeldemartin.config.js +0 -5
  116. package/postcss.config.js +0 -6
  117. package/src/assets/histoire.css +0 -3
  118. package/src/components/AGAppSnackbars.vue +0 -13
  119. package/src/components/constants.ts +0 -8
  120. package/src/components/forms/AGButton.vue +0 -44
  121. package/src/components/forms/AGCheckbox.vue +0 -41
  122. package/src/components/forms/AGInput.vue +0 -40
  123. package/src/components/forms/AGSelect.story.vue +0 -46
  124. package/src/components/forms/AGSelect.vue +0 -60
  125. package/src/components/forms/index.ts +0 -5
  126. package/src/components/headless/forms/AGHeadlessButton.ts +0 -3
  127. package/src/components/headless/forms/AGHeadlessButton.vue +0 -62
  128. package/src/components/headless/forms/AGHeadlessInput.ts +0 -34
  129. package/src/components/headless/forms/AGHeadlessInput.vue +0 -70
  130. package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
  131. package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
  132. package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
  133. package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
  134. package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
  135. package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
  136. package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
  137. package/src/components/headless/forms/composition.ts +0 -10
  138. package/src/components/headless/forms/index.ts +0 -18
  139. package/src/components/headless/modals/AGHeadlessModal.ts +0 -36
  140. package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -32
  141. package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -23
  142. package/src/components/headless/modals/index.ts +0 -4
  143. package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
  144. package/src/components/headless/snackbars/index.ts +0 -40
  145. package/src/components/interfaces.ts +0 -24
  146. package/src/components/lib/AGErrorMessage.vue +0 -16
  147. package/src/components/lib/AGLink.vue +0 -9
  148. package/src/components/lib/AGMarkdown.vue +0 -54
  149. package/src/components/lib/AGMeasured.vue +0 -16
  150. package/src/components/lib/AGProgressBar.vue +0 -45
  151. package/src/components/lib/index.ts +0 -6
  152. package/src/components/modals/AGAlertModal.ts +0 -18
  153. package/src/components/modals/AGAlertModal.vue +0 -14
  154. package/src/components/modals/AGConfirmModal.ts +0 -42
  155. package/src/components/modals/AGConfirmModal.vue +0 -26
  156. package/src/components/modals/AGErrorReportModal.ts +0 -49
  157. package/src/components/modals/AGErrorReportModal.vue +0 -54
  158. package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
  159. package/src/components/modals/AGLoadingModal.ts +0 -29
  160. package/src/components/modals/AGLoadingModal.vue +0 -15
  161. package/src/components/modals/AGModal.ts +0 -11
  162. package/src/components/modals/AGModal.vue +0 -39
  163. package/src/components/modals/AGModalContext.ts +0 -8
  164. package/src/components/modals/AGModalTitle.vue +0 -9
  165. package/src/components/modals/AGPromptModal.ts +0 -41
  166. package/src/components/modals/AGPromptModal.vue +0 -34
  167. package/src/components/modals/index.ts +0 -17
  168. package/src/components/snackbars/AGSnackbar.vue +0 -36
  169. package/src/components/snackbars/index.ts +0 -3
  170. package/src/directives/initial-focus.ts +0 -11
  171. package/src/main.histoire.ts +0 -1
  172. package/tailwind.config.js +0 -4
  173. package/tsconfig.json +0 -11
  174. package/vite.config.ts +0 -17
  175. /package/src/{main.ts → index.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import type { App as VueApp } from 'vue';
2
2
 
3
- import { definePlugin } from '@/plugins';
3
+ import { definePlugin } from '@aerogel/core/plugins';
4
+ import { isDevelopment, isTesting } from '@noeldemartin/utils';
4
5
 
5
6
  import App from './App';
6
7
  import Cache from './Cache';
@@ -39,7 +40,7 @@ export async function bootServices(app: VueApp, services: Record<string, Service
39
40
 
40
41
  Object.assign(app.config.globalProperties, services);
41
42
 
42
- if (App.development || App.testing) {
43
+ if (isDevelopment() || isTesting()) {
43
44
  Object.assign(globalThis, services);
44
45
  }
45
46
  }
@@ -57,12 +58,12 @@ export default definePlugin({
57
58
  },
58
59
  });
59
60
 
60
- declare module '@/bootstrap/options' {
61
+ declare module '@aerogel/core/bootstrap/options' {
61
62
  export interface AerogelOptions {
62
63
  services?: Record<string, Service>;
63
64
  }
64
65
  }
65
66
 
66
- declare module '@vue/runtime-core' {
67
+ declare module 'vue' {
67
68
  interface ComponentCustomProperties extends Services {}
68
69
  }
@@ -2,14 +2,14 @@ import { objectOnly } from '@noeldemartin/utils';
2
2
 
3
3
  export type Replace<
4
4
  TOriginal extends Record<string, unknown>,
5
- TReplacements extends Partial<Record<keyof TOriginal, unknown>>
5
+ TReplacements extends Partial<Record<keyof TOriginal, unknown>>,
6
6
  > = {
7
7
  [K in keyof TOriginal]: TReplacements extends Record<K, infer Replacement> ? Replacement : TOriginal[K];
8
8
  };
9
9
 
10
10
  export function replaceExisting<
11
11
  TOriginal extends Record<string, unknown>,
12
- TReplacements extends Partial<Record<keyof TOriginal, unknown>>
12
+ TReplacements extends Partial<Record<keyof TOriginal, unknown>>,
13
13
  >(original: TOriginal, replacements: TReplacements): Replace<TOriginal, TReplacements> {
14
14
  return {
15
15
  ...original,
@@ -1,7 +1,8 @@
1
+ import { isTesting } from '@noeldemartin/utils';
1
2
  import type { GetClosureArgs } from '@noeldemartin/utils';
2
3
 
3
- import Events from '@/services/Events';
4
- import { definePlugin } from '@/plugins';
4
+ import Events from '@aerogel/core/services/Events';
5
+ import { definePlugin } from '@aerogel/core/plugins';
5
6
 
6
7
  export interface AerogelTestingRuntime {
7
8
  on: (typeof Events)['on'];
@@ -9,7 +10,7 @@ export interface AerogelTestingRuntime {
9
10
 
10
11
  export default definePlugin({
11
12
  async install() {
12
- if (import.meta.env.MODE !== 'testing') {
13
+ if (!isTesting()) {
13
14
  return;
14
15
  }
15
16
 
@@ -1,27 +1,11 @@
1
- import { mock, tap, toString } from '@noeldemartin/utils';
1
+ import { FakeLocalStorage } from '@noeldemartin/testing';
2
2
  import { beforeEach, vi } from 'vitest';
3
3
 
4
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
- tap(globalThis, (global: any) => {
6
- const localStorage: Record<string, string> = {};
7
-
8
- global.jest = vi;
9
- global.navigator = { languages: ['en'] };
10
- global.localStorage = mock<Storage>({
11
- getItem: (key) => localStorage[key] ?? null,
12
- setItem(key, value) {
13
- localStorage[key] = toString(value);
14
- },
15
- });
16
- });
17
-
18
4
  vi.mock('dompurify', async () => {
19
5
  return { default: { sanitize: (html: string) => html } };
20
6
  });
21
7
 
22
8
  beforeEach(() => {
23
- vi.stubGlobal('document', {
24
- querySelector: () => null,
25
- getElementById: () => null,
26
- });
9
+ FakeLocalStorage.reset();
10
+ FakeLocalStorage.patchGlobal();
27
11
  });
@@ -1,10 +1,10 @@
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;
@@ -12,14 +12,19 @@ export interface Modal<T = unknown> {
12
12
  afterClose: Promise<T | undefined>;
13
13
  }
14
14
 
15
+ export interface UIModalContext {
16
+ modal: UIModal;
17
+ childIndex?: number;
18
+ }
19
+
15
20
  export interface ModalComponent<
16
21
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
- Properties extends Record<string, unknown> = Record<string, unknown>,
22
+ Properties extends object = object,
18
23
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
- Result = unknown
24
+ Result = unknown,
20
25
  > {}
21
26
 
22
- export interface Snackbar {
27
+ export interface UIToast {
23
28
  id: string;
24
29
  component: Component;
25
30
  properties: Record<string, unknown>;
@@ -28,8 +33,8 @@ export interface Snackbar {
28
33
  export default defineServiceState({
29
34
  name: 'ui',
30
35
  initialState: {
31
- modals: [] as Modal[],
32
- snackbars: [] as Snackbar[],
36
+ modals: [] as UIModal[],
37
+ toasts: [] as UIToast[],
33
38
  layout: getCurrentLayout(),
34
39
  },
35
40
  computed: {
package/src/ui/UI.ts CHANGED
@@ -1,18 +1,21 @@
1
- import { after, facade, fail, required, uuid } from '@noeldemartin/utils';
1
+ import { after, facade, fail, isDevelopment, required, 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
- 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';
6
+ import App from '@aerogel/core/services/App';
7
+ import Events from '@aerogel/core/services/Events';
8
+ import type { AcceptRefs } from '@aerogel/core/utils';
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';
12
15
 
13
16
  import Service from './UI.state';
14
17
  import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
15
- import type { Modal, ModalComponent, Snackbar } from './UI.state';
18
+ import type { ModalComponent, UIModal, UIToast } from './UI.state';
16
19
 
17
20
  interface ModalCallbacks<T = unknown> {
18
21
  willClose(result: T | undefined): void;
@@ -20,9 +23,8 @@ interface ModalCallbacks<T = unknown> {
20
23
  }
21
24
 
22
25
  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
+ type ModalResult<TComponent> =
27
+ TComponent extends ModalComponent<Record<string, unknown>, infer TResult> ? TResult : never;
26
28
 
27
29
  export const UIComponents = {
28
30
  AlertModal: 'alert-modal',
@@ -30,19 +32,17 @@ export const UIComponents = {
30
32
  ErrorReportModal: 'error-report-modal',
31
33
  LoadingModal: 'loading-modal',
32
34
  PromptModal: 'prompt-modal',
33
- Snackbar: 'snackbar',
35
+ Toast: 'toast',
34
36
  StartupCrash: 'startup-crash',
35
37
  } as const;
36
38
 
37
39
  export type UIComponent = ObjectValues<typeof UIComponents>;
38
40
 
39
- export type ConfirmCheckboxes = Record<string, { label: string; default?: boolean; required?: boolean }>;
40
-
41
41
  export type ConfirmOptions = AcceptRefs<{
42
42
  acceptText?: string;
43
- acceptColor?: Color;
43
+ acceptVariant?: ButtonVariant;
44
44
  cancelText?: string;
45
- cancelColor?: Color;
45
+ cancelVariant?: ButtonVariant;
46
46
  actions?: Record<string, () => unknown>;
47
47
  required?: boolean;
48
48
  }>;
@@ -53,7 +53,8 @@ export type LoadingOptions = AcceptRefs<{
53
53
  progress?: number;
54
54
  }>;
55
55
 
56
- export interface ConfirmOptionsWithCheckboxes<T extends ConfirmCheckboxes = ConfirmCheckboxes> extends ConfirmOptions {
56
+ export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
57
+ extends ConfirmOptions {
57
58
  checkboxes?: T;
58
59
  }
59
60
 
@@ -62,16 +63,16 @@ export type PromptOptions = AcceptRefs<{
62
63
  defaultValue?: string;
63
64
  placeholder?: string;
64
65
  acceptText?: string;
65
- acceptColor?: Color;
66
+ acceptVariant?: ButtonVariant;
66
67
  cancelText?: string;
67
- cancelColor?: Color;
68
+ cancelVariant?: ButtonVariant;
68
69
  trim?: boolean;
69
70
  }>;
70
71
 
71
- export interface ShowSnackbarOptions {
72
+ export interface ToastOptions {
72
73
  component?: Component;
73
- color?: SnackbarColor;
74
- actions?: SnackbarAction[];
74
+ variant?: ToastVariant;
75
+ actions?: ToastAction[];
75
76
  }
76
77
 
77
78
  export class UIService extends Service {
@@ -86,7 +87,7 @@ export class UIService extends Service {
86
87
  public alert(message: string): void;
87
88
  public alert(title: string, message: string): void;
88
89
  public alert(messageOrTitle: string, message?: string): void {
89
- const getProperties = (): AGAlertModalProps => {
90
+ const getProperties = (): AlertModalProps => {
90
91
  if (typeof message !== 'string') {
91
92
  return { message: messageOrTitle };
92
93
  }
@@ -97,14 +98,17 @@ export class UIService extends Service {
97
98
  };
98
99
  };
99
100
 
100
- this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
101
+ this.openModal<ModalComponent<AlertModalProps>>(
102
+ this.requireComponent(UIComponents.AlertModal),
103
+ getProperties(),
104
+ );
101
105
  }
102
106
 
103
107
  /* eslint-disable max-len */
104
108
  public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
105
109
  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
110
+ public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
111
+ public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
108
112
  /* eslint-enable max-len */
109
113
 
110
114
  public async confirm(
@@ -112,7 +116,7 @@ export class UIService extends Service {
112
116
  messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
113
117
  options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
114
118
  ): Promise<boolean | [boolean, Record<string, boolean>]> {
115
- const getProperties = (): AGConfirmModalProps => {
119
+ const getProperties = (): AcceptRefs<ConfirmModalProps> => {
116
120
  if (typeof messageOrOptions !== 'string') {
117
121
  return {
118
122
  ...(messageOrOptions ?? {}),
@@ -128,13 +132,20 @@ export class UIService extends Service {
128
132
  required: !!options?.required,
129
133
  };
130
134
  };
135
+
136
+ type ConfirmModalComponent = ModalComponent<
137
+ AcceptRefs<ConfirmModalProps>,
138
+ boolean | [boolean, Record<string, boolean>]
139
+ >;
140
+
131
141
  const properties = getProperties();
132
- const modal = await this.openModal<
133
- ModalComponent<AGConfirmModalProps, boolean | [boolean, Record<string, boolean>]>
134
- >(this.requireComponent(UIComponents.ConfirmModal), properties);
142
+ const modal = await this.openModal<ConfirmModalComponent>(
143
+ this.requireComponent(UIComponents.ConfirmModal),
144
+ properties,
145
+ );
135
146
  const result = await modal.beforeClose;
136
147
 
137
- const confirmed = typeof result === 'object' ? result[0] : result ?? false;
148
+ const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
138
149
  const checkboxes =
139
150
  typeof result === 'object'
140
151
  ? result[1]
@@ -151,7 +162,7 @@ export class UIService extends Service {
151
162
  continue;
152
163
  }
153
164
 
154
- if (confirmed && App.development) {
165
+ if (confirmed && isDevelopment()) {
155
166
  // eslint-disable-next-line no-console
156
167
  console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
157
168
  }
@@ -170,22 +181,22 @@ export class UIService extends Service {
170
181
  options?: PromptOptions,
171
182
  ): Promise<string | null> {
172
183
  const trim = options?.trim ?? true;
173
- const getProperties = (): AGPromptModalProps => {
184
+ const getProperties = (): PromptModalProps => {
174
185
  if (typeof messageOrOptions !== 'string') {
175
186
  return {
176
187
  message: messageOrTitle,
177
188
  ...(messageOrOptions ?? {}),
178
- } as AGPromptModalProps;
189
+ } as PromptModalProps;
179
190
  }
180
191
 
181
192
  return {
182
193
  title: messageOrTitle,
183
194
  message: messageOrOptions,
184
195
  ...(options ?? {}),
185
- } as AGPromptModalProps;
196
+ } as PromptModalProps;
186
197
  };
187
198
 
188
- const modal = await this.openModal<ModalComponent<AGPromptModalProps, string | null>>(
199
+ const modal = await this.openModal<ModalComponent<PromptModalProps, string | null>>(
189
200
  this.requireComponent(UIComponents.PromptModal),
190
201
  getProperties(),
191
202
  );
@@ -203,7 +214,7 @@ export class UIService extends Service {
203
214
  operation?: Promise<T> | (() => T),
204
215
  ): Promise<T> {
205
216
  const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
206
- const processArgs = (): { operationPromise: Promise<T>; props?: AGLoadingModalProps } => {
217
+ const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
207
218
  if (typeof operationOrMessageOrOptions === 'string') {
208
219
  return {
209
220
  props: { message: operationOrMessageOrOptions },
@@ -225,7 +236,9 @@ export class UIService extends Service {
225
236
  const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), props);
226
237
 
227
238
  try {
228
- const [result] = await Promise.all([operationPromise, after({ seconds: 1 })]);
239
+ const result = await operationPromise;
240
+
241
+ await after({ ms: 500 });
229
242
 
230
243
  return result;
231
244
  } finally {
@@ -233,23 +246,15 @@ export class UIService extends Service {
233
246
  }
234
247
  }
235
248
 
236
- public showSnackbar(message: string, options: ShowSnackbarOptions = {}): void {
237
- const snackbar: Snackbar = {
249
+ public toast(message: string, options: ToastOptions = {}): void {
250
+ const { component, ...otherOptions } = options;
251
+ const toast: UIToast = {
238
252
  id: uuid(),
239
- properties: { message, ...options },
240
- component: markRaw(options.component ?? this.requireComponent(UIComponents.Snackbar)),
253
+ properties: { message, ...otherOptions },
254
+ component: markRaw(component ?? this.requireComponent(UIComponents.Toast)),
241
255
  };
242
256
 
243
- this.setState('snackbars', this.snackbars.concat(snackbar));
244
-
245
- setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
246
- }
247
-
248
- public hideSnackbar(id: string): void {
249
- this.setState(
250
- 'snackbars',
251
- this.snackbars.filter((snackbar) => snackbar.id !== id),
252
- );
257
+ this.setState('toasts', this.toasts.concat(toast));
253
258
  }
254
259
 
255
260
  public registerComponent(name: UIComponent, component: Component): void {
@@ -259,10 +264,10 @@ export class UIService extends Service {
259
264
  public async openModal<TModalComponent extends ModalComponent>(
260
265
  component: TModalComponent,
261
266
  properties?: ModalProperties<TModalComponent>,
262
- ): Promise<Modal<ModalResult<TModalComponent>>> {
267
+ ): Promise<UIModal<ModalResult<TModalComponent>>> {
263
268
  const id = uuid();
264
269
  const callbacks: Partial<ModalCallbacks<ModalResult<TModalComponent>>> = {};
265
- const modal: Modal<ModalResult<TModalComponent>> = {
270
+ const modal: UIModal<ModalResult<TModalComponent>> = {
266
271
  id,
267
272
  properties: properties ?? {},
268
273
  component: markRaw(component),
@@ -302,7 +307,7 @@ export class UIService extends Service {
302
307
  }
303
308
  }
304
309
 
305
- protected async boot(): Promise<void> {
310
+ protected override async boot(): Promise<void> {
306
311
  this.watchModalEvents();
307
312
  this.watchMountedEvent();
308
313
  this.watchViewportBreakpoints();
@@ -373,13 +378,13 @@ export class UIService extends Service {
373
378
 
374
379
  export default facade(UIService);
375
380
 
376
- declare module '@/services/Events' {
381
+ declare module '@aerogel/core/services/Events' {
377
382
  export interface EventsPayload {
378
383
  'close-modal': { id: string; result?: unknown };
379
384
  'hide-modal': { id: string };
380
385
  'hide-overlays-backdrop': void;
381
- 'modal-closed': { modal: Modal; result?: unknown };
382
- 'modal-will-close': { modal: Modal; result?: unknown };
386
+ 'modal-closed': { modal: UIModal; result?: unknown };
387
+ 'modal-will-close': { modal: UIModal; result?: unknown };
383
388
  'show-modal': { id: string };
384
389
  'show-overlays-backdrop': void;
385
390
  }
package/src/ui/index.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  import type { Component } from 'vue';
2
2
 
3
- import { bootServices } from '@/services';
4
- import { definePlugin } from '@/plugins';
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';
10
+ import { bootServices } from '@aerogel/core/services';
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({
@@ -42,12 +42,12 @@ export default definePlugin({
42
42
  },
43
43
  });
44
44
 
45
- declare module '@/bootstrap/options' {
45
+ declare module '@aerogel/core/bootstrap/options' {
46
46
  export interface AerogelOptions {
47
47
  components?: Partial<Record<UIComponent, Component>>;
48
48
  }
49
49
  }
50
50
 
51
- declare module '@/services' {
51
+ declare module '@aerogel/core/services' {
52
52
  export interface Services extends UIServices {}
53
53
  }
@@ -1,13 +1,13 @@
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
9
  UnknownEvent,
10
- } from '@/services/Events';
10
+ } from '@aerogel/core/services/Events';
11
11
 
12
12
  export function useEvent<Event extends EventWithoutPayload>(event: Event, listener: () => unknown): void;
13
13
  export function useEvent<Event extends EventWithPayload>(
@@ -1,11 +1,12 @@
1
1
  import { objectWithout } from '@noeldemartin/utils';
2
2
  import { computed, useAttrs } from 'vue';
3
+ import type { ClassValue } from 'clsx';
3
4
  import type { ComputedRef } from 'vue';
4
5
 
5
- export function useInputAttrs(): [ComputedRef<{}>, ComputedRef<unknown>] {
6
+ export function useInputAttrs(): [ComputedRef<{}>, ComputedRef<ClassValue>] {
6
7
  const attrs = useAttrs();
7
- const className = computed(() => attrs.class);
8
+ const classes = computed(() => attrs.class);
8
9
  const inputAttrs = computed(() => objectWithout(attrs, 'class'));
9
10
 
10
- return [inputAttrs, className];
11
+ return [inputAttrs, classes as ComputedRef<ClassValue>];
11
12
  }
@@ -4,8 +4,8 @@ import { Renderer, marked } from 'marked';
4
4
 
5
5
  function makeRenderer(): Renderer {
6
6
  return tap(new Renderer(), (renderer) => {
7
- renderer.link = function(href, title, text) {
8
- return Renderer.prototype.link.apply(this, [href, title, text]).replace('<a', '<a target="_blank"');
7
+ renderer.link = function(link) {
8
+ return Renderer.prototype.link.apply(this, [link]).replace('<a', '<a target="_blank"');
9
9
  };
10
10
  });
11
11
  }
@@ -21,7 +21,7 @@ function renderActionLinks(html: string): string {
21
21
  }
22
22
 
23
23
  export function renderMarkdown(markdown: string): string {
24
- let html = marked(markdown, { mangle: false, headerIds: false, renderer: makeRenderer() });
24
+ let html = marked(markdown, { renderer: makeRenderer(), async: false });
25
25
 
26
26
  html = safeHtml(html);
27
27
  html = renderActionLinks(html);
@@ -30,7 +30,5 @@ export function renderMarkdown(markdown: string): string {
30
30
  }
31
31
 
32
32
  export function safeHtml(html: string): string {
33
- // TODO improve target="_blank" exception
34
- // See https://github.com/cure53/DOMPurify/issues/317
35
33
  return DOMPurify.sanitize(html, { ADD_ATTR: ['target'] });
36
34
  }
@@ -0,0 +1,31 @@
1
+ import { Comment, Static, Text } from 'vue';
2
+ import { toString } from '@noeldemartin/utils';
3
+ import type { VNode } from 'vue';
4
+
5
+ function renderAttrs(node: VNode): string {
6
+ return Object.entries(node.props ?? {}).reduce((attrs, [name, value]) => {
7
+ return attrs + `${name}="${toString(value)}"`;
8
+ }, '');
9
+ }
10
+
11
+ export function renderNode(node: VNode | string): string {
12
+ if (typeof node === 'string') {
13
+ return node;
14
+ }
15
+
16
+ if (node.type === Comment) {
17
+ return '';
18
+ }
19
+
20
+ if (node.type === Text || node.type === Static) {
21
+ return node.children as string;
22
+ }
23
+
24
+ if (node.type === 'br') {
25
+ return '\n\n';
26
+ }
27
+
28
+ return `<${node.type} ${renderAttrs(node)}>${Array.from(node.children as Array<VNode | string>)
29
+ .map(renderNode)
30
+ .join('')}</${node.type}>`;
31
+ }
package/src/utils/vue.ts CHANGED
@@ -1,18 +1,13 @@
1
- import { fail } from '@noeldemartin/utils';
1
+ import { fail, toString } from '@noeldemartin/utils';
2
2
  import { computed, inject, reactive, ref, watch } from 'vue';
3
3
  import type { Directive, InjectionKey, MaybeRef, PropType, Ref, UnwrapNestedRefs } from 'vue';
4
4
 
5
- type BaseProp<T> = {
6
- type?: PropType<T>;
7
- validator?(value: unknown): boolean;
8
- };
9
-
10
- type RequiredProp<T> = BaseProp<T> & { required: true };
11
- type OptionalProp<T> = BaseProp<T> & { default: T | (() => T) | null };
12
-
13
5
  export type AcceptRefs<T> = { [K in keyof T]: T[K] | RefUnion<T[K]> };
14
- export type ComponentProps = Record<string, unknown>;
6
+ export type BaseProp<T> = { type?: PropType<T>; validator?(value: unknown): boolean };
7
+ export type ComponentProps<T = {}> = T & Record<string, unknown>;
8
+ export type OptionalProp<T> = BaseProp<T> & { default: T | (() => T) | null };
15
9
  export type RefUnion<T> = T extends infer R ? Ref<R> : never;
10
+ export type RequiredProp<T> = BaseProp<T> & { required: true };
16
11
  export type Unref<T> = { [K in keyof T]: T[K] extends MaybeRef<infer Value> ? Value : T[K] };
17
12
 
18
13
  export function arrayProp<T>(defaultValue?: () => T[]): OptionalProp<T[]> {
@@ -29,10 +24,6 @@ export function booleanProp(defaultValue: boolean = false): OptionalProp<boolean
29
24
  };
30
25
  }
31
26
 
32
- export function componentRef<T>(): Ref<UnwrapNestedRefs<T> | undefined> {
33
- return ref<UnwrapNestedRefs<T>>();
34
- }
35
-
36
27
  export function computedAsync<T>(getter: () => Promise<T>): Ref<T | undefined> {
37
28
  const result = ref<T>();
38
29
  const asyncValue = computed(getter);
@@ -69,11 +60,11 @@ export function injectReactiveOrFail<T extends object>(
69
60
  key: InjectionKey<T> | string,
70
61
  errorMessage?: string,
71
62
  ): UnwrapNestedRefs<T> {
72
- return injectReactive(key) ?? fail(errorMessage ?? `Could not resolve '${key}' injection key`);
63
+ return injectReactive(key) ?? fail(errorMessage ?? `Could not resolve '${toString(key)}' injection key`);
73
64
  }
74
65
 
75
66
  export function injectOrFail<T>(key: InjectionKey<T> | string, errorMessage?: string): T {
76
- return inject(key) ?? fail(errorMessage ?? `Could not resolve '${key}' injection key`);
67
+ return inject(key) ?? fail(errorMessage ?? `Could not resolve '${toString(key)}' injection key`);
77
68
  }
78
69
 
79
70
  export function listenerProp<T extends Function = Function>(): OptionalProp<T | null> {