@aerogel/core 0.0.0-next.9a02fcd3bcf698211dd7a71d4c48257c96dd7832 → 0.0.0-next.9aa7c279868edbedbcee075aef52212597d803fb

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.d.ts +2080 -1616
  2. package/dist/aerogel-core.js +3265 -0
  3. package/dist/aerogel-core.js.map +1 -0
  4. package/package.json +31 -37
  5. package/src/bootstrap/bootstrap.test.ts +4 -8
  6. package/src/bootstrap/index.ts +25 -16
  7. package/src/bootstrap/options.ts +1 -1
  8. package/src/components/AppLayout.vue +14 -0
  9. package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
  10. package/src/components/AppOverlays.vue +9 -0
  11. package/src/components/AppToasts.vue +16 -0
  12. package/src/components/contracts/AlertModal.ts +8 -0
  13. package/src/components/contracts/Button.ts +16 -0
  14. package/src/components/contracts/ConfirmModal.ts +46 -0
  15. package/src/components/contracts/DropdownMenu.ts +20 -0
  16. package/src/components/contracts/ErrorReportModal.ts +32 -0
  17. package/src/components/contracts/Input.ts +26 -0
  18. package/src/components/contracts/LoadingModal.ts +26 -0
  19. package/src/components/contracts/Modal.ts +21 -0
  20. package/src/components/contracts/PromptModal.ts +34 -0
  21. package/src/components/contracts/Select.ts +45 -0
  22. package/src/components/contracts/Toast.ts +15 -0
  23. package/src/components/contracts/index.ts +11 -0
  24. package/src/components/headless/HeadlessButton.vue +51 -0
  25. package/src/components/headless/HeadlessInput.vue +59 -0
  26. package/src/components/headless/HeadlessInputDescription.vue +27 -0
  27. package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
  28. package/src/components/headless/HeadlessInputInput.vue +75 -0
  29. package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
  30. package/src/components/headless/HeadlessInputTextArea.vue +40 -0
  31. package/src/components/headless/HeadlessModal.vue +57 -0
  32. package/src/components/headless/HeadlessModalContent.vue +30 -0
  33. package/src/components/headless/HeadlessModalDescription.vue +12 -0
  34. package/src/components/headless/HeadlessModalOverlay.vue +12 -0
  35. package/src/components/headless/HeadlessModalTitle.vue +12 -0
  36. package/src/components/headless/HeadlessSelect.vue +120 -0
  37. package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +5 -6
  38. package/src/components/headless/HeadlessSelectLabel.vue +25 -0
  39. package/src/components/headless/HeadlessSelectOption.vue +34 -0
  40. package/src/components/headless/HeadlessSelectOptions.vue +42 -0
  41. package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
  42. package/src/components/headless/HeadlessSelectValue.vue +18 -0
  43. package/src/components/headless/HeadlessToast.vue +18 -0
  44. package/src/components/headless/HeadlessToastAction.vue +13 -0
  45. package/src/components/headless/index.ts +19 -3
  46. package/src/components/index.ts +6 -9
  47. package/src/components/ui/AdvancedOptions.vue +18 -0
  48. package/src/components/ui/AlertModal.vue +14 -0
  49. package/src/components/ui/Button.vue +98 -0
  50. package/src/components/ui/Checkbox.vue +56 -0
  51. package/src/components/ui/ConfirmModal.vue +45 -0
  52. package/src/components/ui/DropdownMenu.vue +32 -0
  53. package/src/components/ui/DropdownMenuOption.vue +14 -0
  54. package/src/components/ui/DropdownMenuOptions.vue +27 -0
  55. package/src/components/ui/EditableContent.vue +82 -0
  56. package/src/components/ui/ErrorMessage.vue +15 -0
  57. package/src/components/ui/ErrorReportModal.vue +67 -0
  58. package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +34 -27
  59. package/src/components/ui/ErrorReportModalTitle.vue +24 -0
  60. package/src/components/ui/Form.vue +24 -0
  61. package/src/components/ui/Input.vue +56 -0
  62. package/src/components/ui/Link.vue +12 -0
  63. package/src/components/ui/LoadingModal.vue +34 -0
  64. package/src/components/ui/Markdown.vue +69 -0
  65. package/src/components/ui/Modal.vue +122 -0
  66. package/src/components/ui/ModalContext.vue +31 -0
  67. package/src/components/ui/ProgressBar.vue +51 -0
  68. package/src/components/ui/PromptModal.vue +38 -0
  69. package/src/components/ui/Select.vue +27 -0
  70. package/src/components/ui/SelectLabel.vue +17 -0
  71. package/src/components/ui/SelectOption.vue +29 -0
  72. package/src/components/ui/SelectOptions.vue +35 -0
  73. package/src/components/ui/SelectTrigger.vue +29 -0
  74. package/src/components/ui/SettingsModal.vue +15 -0
  75. package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +8 -8
  76. package/src/components/ui/Toast.vue +44 -0
  77. package/src/components/ui/index.ts +30 -0
  78. package/src/directives/index.ts +11 -5
  79. package/src/directives/measure.ts +34 -6
  80. package/src/errors/Errors.state.ts +1 -1
  81. package/src/errors/Errors.ts +14 -23
  82. package/src/errors/JobCancelledError.ts +3 -0
  83. package/src/errors/index.ts +9 -6
  84. package/src/errors/utils.ts +17 -1
  85. package/src/forms/FormController.test.ts +110 -0
  86. package/src/forms/FormController.ts +246 -0
  87. package/src/forms/index.ts +3 -2
  88. package/src/forms/utils.ts +51 -20
  89. package/src/forms/validation.ts +19 -0
  90. package/src/index.css +72 -0
  91. package/src/{main.ts → index.ts} +1 -0
  92. package/src/jobs/Job.ts +144 -2
  93. package/src/jobs/index.ts +4 -1
  94. package/src/jobs/listeners.ts +3 -0
  95. package/src/jobs/status.ts +4 -0
  96. package/src/lang/DefaultLangProvider.ts +46 -0
  97. package/src/lang/Lang.state.ts +11 -0
  98. package/src/lang/Lang.ts +48 -21
  99. package/src/lang/index.ts +12 -6
  100. package/src/lang/settings/Language.vue +48 -0
  101. package/src/lang/settings/index.ts +10 -0
  102. package/src/plugins/Plugin.ts +1 -1
  103. package/src/plugins/index.ts +10 -7
  104. package/src/services/App.state.ts +36 -3
  105. package/src/services/App.ts +19 -3
  106. package/src/services/Cache.ts +43 -0
  107. package/src/services/Events.test.ts +8 -8
  108. package/src/services/Events.ts +16 -12
  109. package/src/services/Service.ts +125 -54
  110. package/src/services/Storage.ts +20 -0
  111. package/src/services/index.ts +16 -5
  112. package/src/services/utils.ts +18 -0
  113. package/src/testing/index.ts +4 -3
  114. package/src/testing/setup.ts +11 -0
  115. package/src/ui/UI.state.ts +14 -12
  116. package/src/ui/UI.ts +224 -120
  117. package/src/ui/index.ts +28 -28
  118. package/src/ui/utils.ts +16 -0
  119. package/src/utils/classes.ts +49 -0
  120. package/src/utils/composition/events.ts +4 -6
  121. package/src/utils/composition/forms.ts +20 -4
  122. package/src/utils/composition/persistent.test.ts +33 -0
  123. package/src/utils/composition/persistent.ts +11 -0
  124. package/src/utils/composition/state.test.ts +47 -0
  125. package/src/utils/composition/state.ts +33 -0
  126. package/src/utils/index.ts +5 -1
  127. package/src/utils/markdown.test.ts +50 -0
  128. package/src/utils/markdown.ts +19 -6
  129. package/src/utils/types.ts +3 -0
  130. package/src/utils/vue.ts +28 -127
  131. package/dist/aerogel-core.cjs.js +0 -2
  132. package/dist/aerogel-core.cjs.js.map +0 -1
  133. package/dist/aerogel-core.esm.js +0 -2
  134. package/dist/aerogel-core.esm.js.map +0 -1
  135. package/histoire.config.ts +0 -7
  136. package/noeldemartin.config.js +0 -5
  137. package/postcss.config.js +0 -6
  138. package/src/assets/histoire.css +0 -3
  139. package/src/components/AGAppLayout.vue +0 -16
  140. package/src/components/AGAppOverlays.vue +0 -41
  141. package/src/components/AGAppSnackbars.vue +0 -13
  142. package/src/components/constants.ts +0 -8
  143. package/src/components/forms/AGButton.vue +0 -44
  144. package/src/components/forms/AGCheckbox.vue +0 -41
  145. package/src/components/forms/AGForm.vue +0 -26
  146. package/src/components/forms/AGInput.vue +0 -38
  147. package/src/components/forms/AGSelect.story.vue +0 -46
  148. package/src/components/forms/AGSelect.vue +0 -60
  149. package/src/components/forms/index.ts +0 -5
  150. package/src/components/headless/forms/AGHeadlessButton.vue +0 -51
  151. package/src/components/headless/forms/AGHeadlessInput.ts +0 -28
  152. package/src/components/headless/forms/AGHeadlessInput.vue +0 -57
  153. package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -47
  154. package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
  155. package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
  156. package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
  157. package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
  158. package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
  159. package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
  160. package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
  161. package/src/components/headless/forms/index.ts +0 -14
  162. package/src/components/headless/modals/AGHeadlessModal.ts +0 -34
  163. package/src/components/headless/modals/AGHeadlessModal.vue +0 -86
  164. package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -28
  165. package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -13
  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/lib/AGErrorMessage.vue +0 -16
  170. package/src/components/lib/AGLink.vue +0 -9
  171. package/src/components/lib/AGMarkdown.vue +0 -36
  172. package/src/components/lib/AGMeasured.vue +0 -15
  173. package/src/components/lib/index.ts +0 -5
  174. package/src/components/modals/AGAlertModal.ts +0 -15
  175. package/src/components/modals/AGAlertModal.vue +0 -14
  176. package/src/components/modals/AGConfirmModal.ts +0 -27
  177. package/src/components/modals/AGConfirmModal.vue +0 -26
  178. package/src/components/modals/AGErrorReportModal.ts +0 -46
  179. package/src/components/modals/AGErrorReportModal.vue +0 -54
  180. package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
  181. package/src/components/modals/AGLoadingModal.ts +0 -23
  182. package/src/components/modals/AGLoadingModal.vue +0 -15
  183. package/src/components/modals/AGModal.ts +0 -10
  184. package/src/components/modals/AGModal.vue +0 -39
  185. package/src/components/modals/AGModalContext.ts +0 -8
  186. package/src/components/modals/AGModalContext.vue +0 -22
  187. package/src/components/modals/AGModalTitle.vue +0 -9
  188. package/src/components/modals/AGPromptModal.ts +0 -30
  189. package/src/components/modals/AGPromptModal.vue +0 -34
  190. package/src/components/modals/index.ts +0 -17
  191. package/src/components/snackbars/AGSnackbar.vue +0 -36
  192. package/src/components/snackbars/index.ts +0 -3
  193. package/src/components/utils.ts +0 -10
  194. package/src/directives/initial-focus.ts +0 -11
  195. package/src/forms/Form.test.ts +0 -58
  196. package/src/forms/Form.ts +0 -185
  197. package/src/forms/composition.ts +0 -6
  198. package/src/main.histoire.ts +0 -1
  199. package/src/utils/tailwindcss.test.ts +0 -26
  200. package/src/utils/tailwindcss.ts +0 -7
  201. package/tailwind.config.js +0 -4
  202. package/tsconfig.json +0 -11
  203. package/vite.config.ts +0 -14
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <HeadlessSelectOptions :class="renderedClasses">
3
+ <slot v-if="select?.options?.length">
4
+ <SelectOption v-for="option of select?.options ?? []" :key="option.key" :value="option.value">
5
+ {{ option.label }}
6
+ </SelectOption>
7
+ </slot>
8
+ <slot v-else name="empty">
9
+ <SelectOption disabled :value="null">
10
+ {{ $td('ui.selectEmpty', 'No options available') }}
11
+ </SelectOption>
12
+ </slot>
13
+ </HeadlessSelectOptions>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { computed } from 'vue';
18
+ import type { HTMLAttributes } from 'vue';
19
+
20
+ import HeadlessSelectOptions from '@aerogel/core/components/headless/HeadlessSelectOptions.vue';
21
+ import { classes, injectReactiveOrFail } from '@aerogel/core/utils';
22
+ import type { SelectExpose } from '@aerogel/core/components/contracts/Select';
23
+
24
+ import SelectOption from './SelectOption.vue';
25
+
26
+ const { class: rootClasses } = defineProps<{ class?: HTMLAttributes['class'] }>();
27
+
28
+ const select = injectReactiveOrFail<SelectExpose>('select', '<SelectOptions> must be a child of a <Select>');
29
+ const renderedClasses = computed(() =>
30
+ classes(
31
+ 'z-50 overflow-auto rounded-lg bg-white text-base shadow-lg ring-1 ring-black/5 focus:outline-hidden',
32
+ select.optionsClass,
33
+ rootClasses,
34
+ ));
35
+ </script>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <HeadlessSelectTrigger :class="renderedClasses">
3
+ <HeadlessSelectValue class="col-start-1 row-start-1 truncate pr-6" />
4
+ <IconCheveronDown class="col-start-1 row-start-1 size-5 self-center justify-self-end text-gray-500 sm:size-4" />
5
+ </HeadlessSelectTrigger>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import IconCheveronDown from '~icons/zondicons/cheveron-down';
10
+
11
+ import { computed } from 'vue';
12
+ import type { HTMLAttributes } from 'vue';
13
+
14
+ import HeadlessSelectTrigger from '@aerogel/core/components/headless/HeadlessSelectTrigger.vue';
15
+ import HeadlessSelectValue from '@aerogel/core/components/headless/HeadlessSelectValue.vue';
16
+ import { injectReactiveOrFail } from '@aerogel/core/utils';
17
+ import { classes } from '@aerogel/core/utils/classes';
18
+ import type { SelectExpose } from '@aerogel/core/components/contracts/Select';
19
+
20
+ const { class: rootClasses } = defineProps<{ class?: HTMLAttributes['class'] }>();
21
+ const select = injectReactiveOrFail<SelectExpose>('select', '<SelectTrigger> must be a child of a <Select>');
22
+ const renderedClasses = computed(() =>
23
+ classes(
24
+ // eslint-disable-next-line vue/max-len
25
+ 'focus:outline-primary-600 data-[state=open]:outline-primary-600 grid w-full cursor-default grid-cols-1 rounded-md bg-white py-1.5 pr-2 pl-3 text-left text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 sm:text-sm/6',
26
+ { 'mt-1': select.label },
27
+ rootClasses,
28
+ ));
29
+ </script>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <Modal :title="$td('settings.title', 'Settings')">
3
+ <component :is="setting.component" v-for="(setting, key) in settings" :key />
4
+ </Modal>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { arraySorted } from '@noeldemartin/utils';
9
+ import { computed } from 'vue';
10
+
11
+ import App from '@aerogel/core/services/App';
12
+ import Modal from '@aerogel/core/components/ui/Modal.vue';
13
+
14
+ const settings = computed(() => arraySorted(App.settings, 'priority', 'desc'));
15
+ </script>
@@ -1,10 +1,10 @@
1
1
  <template>
2
- <div class="grid flex-grow place-items-center">
2
+ <div class="grid grow place-items-center">
3
3
  <div class="flex flex-col items-center space-y-6 p-8">
4
4
  <h1 class="mt-2 text-center text-4xl font-medium text-red-600">
5
5
  {{ $td('startupCrash.title', 'Something went wrong!') }}
6
6
  </h1>
7
- <AGMarkdown
7
+ <Markdown
8
8
  :text="
9
9
  $td(
10
10
  'startupCrash.message',
@@ -14,18 +14,18 @@
14
14
  class="mt-4 text-center"
15
15
  />
16
16
  <div class="mt-4 flex flex-col space-y-4">
17
- <AGButton color="danger" @click="$app.reload()">
17
+ <Button variant="danger" @click="$app.reload()">
18
18
  {{ $td('startupCrash.reload', 'Try again') }}
19
- </AGButton>
20
- <AGButton color="danger" @click="$errors.inspect($errors.startupErrors)">
19
+ </Button>
20
+ <Button variant="danger" @click="$errors.inspect($errors.startupErrors)">
21
21
  {{ $td('startupCrash.inspect', 'View error details') }}
22
- </AGButton>
22
+ </Button>
23
23
  </div>
24
24
  </div>
25
25
  </div>
26
26
  </template>
27
27
 
28
28
  <script setup lang="ts">
29
- import AGMarkdown from './AGMarkdown.vue';
30
- import AGButton from '../forms/AGButton.vue';
29
+ import Markdown from '@aerogel/core/components/ui/Markdown.vue';
30
+ import Button from '@aerogel/core/components/ui/Button.vue';
31
31
  </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <HeadlessToast :class="renderedClasses">
3
+ <Markdown v-if="message" :text="message" inline />
4
+
5
+ <Button
6
+ v-for="(action, key) of actions"
7
+ :key
8
+ :action
9
+ :variant
10
+ :as="HeadlessToastAction"
11
+ />
12
+ </HeadlessToast>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import type { HTMLAttributes } from 'vue';
17
+
18
+ import Button from '@aerogel/core/components/ui/Button.vue';
19
+ import Markdown from '@aerogel/core/components/ui/Markdown.vue';
20
+ import HeadlessToast from '@aerogel/core/components/headless/HeadlessToast.vue';
21
+ import HeadlessToastAction from '@aerogel/core/components/headless/HeadlessToastAction.vue';
22
+ import { computedVariantClasses } from '@aerogel/core/utils/classes';
23
+ import type { ToastExpose, ToastProps } from '@aerogel/core/components/contracts/Toast';
24
+ import type { Variants } from '@aerogel/core/utils/classes';
25
+
26
+ const { class: baseClasses, variant = 'secondary' } = defineProps<ToastProps & { class?: HTMLAttributes['class'] }>();
27
+ const renderedClasses = computedVariantClasses<Variants<Pick<ToastProps, 'variant'>>>(
28
+ { baseClasses, variant },
29
+ {
30
+ baseClasses: 'flex items-center gap-2 rounded-md p-2 ring-1 shadow-lg border-gray-200',
31
+ variants: {
32
+ variant: {
33
+ secondary: 'bg-gray-900 text-white ring-black',
34
+ danger: 'bg-red-50 text-red-900 ring-red-100',
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: 'secondary',
39
+ },
40
+ },
41
+ );
42
+
43
+ defineExpose<ToastExpose>();
44
+ </script>
@@ -0,0 +1,30 @@
1
+ export { default as AdvancedOptions } from './AdvancedOptions.vue';
2
+ export { default as AlertModal } from './AlertModal.vue';
3
+ export { default as Button } from './Button.vue';
4
+ export { default as Checkbox } from './Checkbox.vue';
5
+ export { default as ConfirmModal } from './ConfirmModal.vue';
6
+ export { default as DropdownMenu } from './DropdownMenu.vue';
7
+ export { default as DropdownMenuOption } from './DropdownMenuOption.vue';
8
+ export { default as DropdownMenuOptions } from './DropdownMenuOptions.vue';
9
+ export { default as EditableContent } from './EditableContent.vue';
10
+ export { default as ErrorMessage } from './ErrorMessage.vue';
11
+ export { default as ErrorReportModal } from './ErrorReportModal.vue';
12
+ export { default as ErrorReportModalButtons } from './ErrorReportModalButtons.vue';
13
+ export { default as ErrorReportModalTitle } from './ErrorReportModalTitle.vue';
14
+ export { default as Form } from './Form.vue';
15
+ export { default as Input } from './Input.vue';
16
+ export { default as Link } from './Link.vue';
17
+ export { default as LoadingModal } from './LoadingModal.vue';
18
+ export { default as Markdown } from './Markdown.vue';
19
+ export { default as Modal } from './Modal.vue';
20
+ export { default as ModalContext } from './ModalContext.vue';
21
+ export { default as ProgressBar } from './ProgressBar.vue';
22
+ export { default as PromptModal } from './PromptModal.vue';
23
+ export { default as Select } from './Select.vue';
24
+ export { default as SelectLabel } from './SelectLabel.vue';
25
+ export { default as SelectOption } from './SelectOption.vue';
26
+ export { default as SelectOptions } from './SelectOptions.vue';
27
+ export { default as SelectTrigger } from './SelectTrigger.vue';
28
+ export { default as SettingsModal } from './SettingsModal.vue';
29
+ export { default as StartupCrash } from './StartupCrash.vue';
30
+ export { default as Toast } from './Toast.vue';
@@ -1,15 +1,15 @@
1
1
  import type { Directive } from 'vue';
2
2
 
3
- import { definePlugin } from '@/plugins';
3
+ import { definePlugin } from '@aerogel/core/plugins';
4
4
 
5
- import initialFocus from './initial-focus';
6
5
  import measure from './measure';
7
6
 
8
7
  const builtInDirectives: Record<string, Directive> = {
9
- 'initial-focus': initialFocus,
10
- 'measure': measure,
8
+ measure: measure,
11
9
  };
12
10
 
11
+ export * from './measure';
12
+
13
13
  export default definePlugin({
14
14
  install(app, options) {
15
15
  const directives = {
@@ -23,8 +23,14 @@ export default definePlugin({
23
23
  },
24
24
  });
25
25
 
26
- declare module '@/bootstrap/options' {
26
+ declare module '@aerogel/core/bootstrap/options' {
27
27
  export interface AerogelOptions {
28
28
  directives?: Record<string, Directive>;
29
29
  }
30
30
  }
31
+
32
+ declare module 'vue' {
33
+ interface ComponentCustomDirectives {
34
+ measure: Directive<string, string>;
35
+ }
36
+ }
@@ -1,12 +1,40 @@
1
- import { defineDirective } from '@/utils/vue';
1
+ import { defineDirective } from '@aerogel/core/utils/vue';
2
+ import { tap } from '@noeldemartin/utils';
3
+
4
+ const resizeObservers: WeakMap<HTMLElement, ResizeObserver> = new WeakMap();
5
+
6
+ export interface ElementSize {
7
+ width: number;
8
+ height: number;
9
+ }
10
+
11
+ export type MeasureDirectiveListener = (size: ElementSize) => unknown;
2
12
 
3
13
  export default defineDirective({
4
- mounted(element: HTMLElement, { value }: { value?: () => unknown }) {
5
- const sizes = element.getBoundingClientRect();
14
+ mounted(element: HTMLElement, { value }) {
15
+ // TODO replace with argument when typed properly
16
+ const modifiers = { css: true, watch: true };
17
+
18
+ const listener = typeof value === 'function' ? (value as MeasureDirectiveListener) : null;
19
+ const update = () => {
20
+ const sizes = element.getBoundingClientRect();
6
21
 
7
- element.style.setProperty('--width', `${sizes.width}px`);
8
- element.style.setProperty('--height', `${sizes.height}px`);
22
+ if (modifiers.css) {
23
+ element.style.setProperty('--width', `${sizes.width}px`);
24
+ element.style.setProperty('--height', `${sizes.height}px`);
25
+ }
9
26
 
10
- value?.();
27
+ listener?.({ width: sizes.width, height: sizes.height });
28
+ };
29
+
30
+ if (modifiers.watch) {
31
+ resizeObservers.set(element, tap(new ResizeObserver(update)).observe(element));
32
+ }
33
+
34
+ update();
35
+ },
36
+ unmounted(element) {
37
+ resizeObservers.get(element)?.unobserve(element);
38
+ resizeObservers.delete(element);
11
39
  },
12
40
  });
@@ -1,6 +1,6 @@
1
1
  import type { JSError } from '@noeldemartin/utils';
2
2
 
3
- import { defineServiceState } from '@/services';
3
+ import { defineServiceState } from '@aerogel/core/services';
4
4
 
5
5
  export type ErrorSource = string | Error | JSError | unknown;
6
6
 
@@ -1,16 +1,13 @@
1
- import { JSError, facade, isObject, objectWithoutEmpty, toString } from '@noeldemartin/utils';
1
+ import { JSError, facade, isDevelopment, isObject, isTesting, objectWithoutEmpty, toString } from '@noeldemartin/utils';
2
2
 
3
- import App from '@/services/App';
4
- import ServiceBootError from '@/errors/ServiceBootError';
5
- import UI, { UIComponents } from '@/ui/UI';
6
- import { translateWithDefault } from '@/lang/utils';
3
+ import App from '@aerogel/core/services/App';
4
+ import ServiceBootError from '@aerogel/core/errors/ServiceBootError';
5
+ import UI from '@aerogel/core/ui/UI';
6
+ import { translateWithDefault } from '@aerogel/core/lang/utils';
7
+ import { Events } from '@aerogel/core/services';
7
8
 
8
9
  import Service from './Errors.state';
9
- import { Colors } from '@/components/constants';
10
- import { Events } from '@/services';
11
- import type { AGErrorReportModalProps } from '@/components/modals/AGErrorReportModal';
12
10
  import type { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
13
- import type { ModalComponent } from '@/ui/UI.state';
14
11
 
15
12
  export class ErrorsService extends Service {
16
13
 
@@ -34,19 +31,17 @@ export class ErrorsService extends Service {
34
31
  return;
35
32
  }
36
33
 
37
- UI.openModal<ModalComponent<AGErrorReportModalProps>>(UI.requireComponent(UIComponents.ErrorReportModal), {
38
- reports,
39
- });
34
+ UI.openModal(UI.requireComponent('error-report-modal'), { reports });
40
35
  }
41
36
 
42
37
  public async report(error: ErrorSource, message?: string): Promise<void> {
43
38
  await Events.emit('error', { error, message });
44
39
 
45
- if (App.testing) {
40
+ if (isTesting('unit')) {
46
41
  throw error;
47
42
  }
48
43
 
49
- if (App.development) {
44
+ if (isDevelopment()) {
50
45
  this.logError(error);
51
46
  }
52
47
 
@@ -71,20 +66,16 @@ export class ErrorsService extends Service {
71
66
  date: new Date(),
72
67
  };
73
68
 
74
- UI.showSnackbar(
69
+ UI.toast(
75
70
  message ??
76
71
  translateWithDefault('errors.notice', 'Something went wrong, but it\'s not your fault. Try again!'),
77
72
  {
78
- color: Colors.Danger,
73
+ variant: 'danger',
79
74
  actions: [
80
75
  {
81
- text: translateWithDefault('errors.viewDetails', 'View details'),
76
+ label: translateWithDefault('errors.viewDetails', 'View details'),
82
77
  dismiss: true,
83
- handler: () =>
84
- UI.openModal<ModalComponent<AGErrorReportModalProps>>(
85
- UI.requireComponent(UIComponents.ErrorReportModal),
86
- { reports: [report] },
87
- ),
78
+ click: () => UI.openModal(UI.requireComponent('error-report-modal'), { reports: [report] }),
88
79
  },
89
80
  ],
90
81
  },
@@ -178,7 +169,7 @@ export class ErrorsService extends Service {
178
169
 
179
170
  export default facade(ErrorsService);
180
171
 
181
- declare module '@/services/Events' {
172
+ declare module '@aerogel/core/services/Events' {
182
173
  export interface EventsPayload {
183
174
  error: { error: ErrorSource; message?: string };
184
175
  }
@@ -0,0 +1,3 @@
1
+ import { JSError } from '@noeldemartin/utils';
2
+
3
+ export default class JobCancelledError extends JSError {}
@@ -1,13 +1,16 @@
1
1
  import type { App } from 'vue';
2
2
 
3
- import { bootServices } from '@/services';
4
- import { definePlugin } from '@/plugins';
3
+ import { bootServices } from '@aerogel/core/services';
4
+ import { definePlugin } from '@aerogel/core/plugins';
5
5
 
6
6
  import Errors from './Errors';
7
- import { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
7
+ import type { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
8
8
 
9
9
  export * from './utils';
10
- export { Errors, ErrorSource, ErrorReport, ErrorReportLog };
10
+ export { Errors };
11
+ export { default as JobCancelledError } from './JobCancelledError';
12
+ export { default as ServiceBootError } from './ServiceBootError';
13
+ export type { ErrorSource, ErrorReport, ErrorReportLog };
11
14
 
12
15
  const services = { $errors: Errors };
13
16
  const frameworkHandler: ErrorHandler = (error) => {
@@ -35,12 +38,12 @@ export default definePlugin({
35
38
  },
36
39
  });
37
40
 
38
- declare module '@/bootstrap/options' {
41
+ declare module '@aerogel/core/bootstrap/options' {
39
42
  export interface AerogelOptions {
40
43
  handleError?(error: ErrorSource): boolean;
41
44
  }
42
45
  }
43
46
 
44
- declare module '@/services' {
47
+ declare module '@aerogel/core/services' {
45
48
  export interface Services extends ErrorsServices {}
46
49
  }
@@ -1,8 +1,24 @@
1
1
  import { JSError, isObject, toString } from '@noeldemartin/utils';
2
- import { translateWithDefault } from '@/lang/utils';
2
+ import { translateWithDefault } from '@aerogel/core/lang/utils';
3
3
  import type { ErrorSource } from './Errors.state';
4
4
 
5
+ const handlers: ErrorHandler[] = [];
6
+
7
+ export type ErrorHandler = (error: ErrorSource) => string | undefined;
8
+
9
+ export function registerErrorHandler(handler: ErrorHandler): void {
10
+ handlers.push(handler);
11
+ }
12
+
5
13
  export function getErrorMessage(error: ErrorSource): string {
14
+ for (const handler of handlers) {
15
+ const result = handler(error);
16
+
17
+ if (result) {
18
+ return result;
19
+ }
20
+ }
21
+
6
22
  if (typeof error === 'string') {
7
23
  return error;
8
24
  }
@@ -0,0 +1,110 @@
1
+ import { describe, expect, expectTypeOf, it } from 'vitest';
2
+ import { tt } from '@noeldemartin/testing';
3
+ import type { Equals } from '@noeldemartin/utils';
4
+ import type { Expect } from '@noeldemartin/testing';
5
+
6
+ import {
7
+ numberInput,
8
+ objectInput,
9
+ requiredObjectInput,
10
+ requiredStringInput,
11
+ stringInput,
12
+ } from '@aerogel/core/forms/utils';
13
+ import { useForm } from '@aerogel/core/utils/composition/forms';
14
+
15
+ describe('FormController', () => {
16
+
17
+ it('defines magic fields', () => {
18
+ const form = useForm({
19
+ name: requiredStringInput(),
20
+ age: numberInput(),
21
+ });
22
+
23
+ expectTypeOf(form.name).toEqualTypeOf<string>();
24
+ expectTypeOf(form.age).toEqualTypeOf<number | null>();
25
+ });
26
+
27
+ it('validates required fields', () => {
28
+ // Arrange
29
+ const form = useForm({
30
+ name: {
31
+ type: 'string',
32
+ rules: 'required',
33
+ },
34
+ });
35
+
36
+ // Act
37
+ form.submit();
38
+
39
+ // Assert
40
+ expect(form.valid).toBe(false);
41
+ expect(form.submitted).toBe(true);
42
+ expect(form.errors.name).toEqual(['required']);
43
+ });
44
+
45
+ it('resets form', () => {
46
+ // Arrange
47
+ const form = useForm({
48
+ name: {
49
+ type: 'string',
50
+ rules: 'required',
51
+ },
52
+ });
53
+
54
+ form.name = 'Foo bar';
55
+ form.submit();
56
+
57
+ // Act
58
+ form.reset();
59
+
60
+ // Assert
61
+ expect(form.valid).toBe(true);
62
+ expect(form.submitted).toBe(false);
63
+ expect(form.name).toBeNull();
64
+ });
65
+
66
+ it('trims values', () => {
67
+ // Arrange
68
+ const form = useForm({
69
+ trimmed: {
70
+ type: 'string',
71
+ rules: 'required',
72
+ },
73
+ untrimmed: {
74
+ type: 'string',
75
+ rules: 'required',
76
+ trim: false,
77
+ },
78
+ });
79
+
80
+ // Act
81
+ form.trimmed = ' ';
82
+ form.untrimmed = ' ';
83
+
84
+ form.submit();
85
+
86
+ // Assert
87
+ expect(form.valid).toBe(false);
88
+ expect(form.submitted).toBe(true);
89
+ expect(form.trimmed).toEqual('');
90
+ expect(form.untrimmed).toEqual(' ');
91
+ expect(form.errors).toEqual({ trimmed: ['required'], untrimmed: null });
92
+ });
93
+
94
+ it('infers field types', () => {
95
+ const form = useForm({
96
+ one: stringInput(),
97
+ two: requiredStringInput(),
98
+ three: objectInput(),
99
+ four: requiredObjectInput<{ foo: string; bar?: number }>(),
100
+ });
101
+
102
+ tt<
103
+ | Expect<Equals<typeof form.one, string | null>>
104
+ | Expect<Equals<typeof form.two, string>>
105
+ | Expect<Equals<typeof form.three, object | null>>
106
+ | Expect<Equals<typeof form.four, { foo: string; bar?: number }>>
107
+ >();
108
+ });
109
+
110
+ });