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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/dist/aerogel-core.css +1 -0
  2. package/dist/aerogel-core.d.ts +2061 -1962
  3. package/dist/aerogel-core.js +3669 -0
  4. package/dist/aerogel-core.js.map +1 -0
  5. package/package.json +32 -37
  6. package/src/bootstrap/bootstrap.test.ts +4 -7
  7. package/src/bootstrap/index.ts +14 -15
  8. package/src/bootstrap/options.ts +1 -1
  9. package/src/components/AppLayout.vue +14 -0
  10. package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
  11. package/src/components/AppOverlays.vue +9 -0
  12. package/src/components/AppToasts.vue +16 -0
  13. package/src/components/contracts/AlertModal.ts +19 -0
  14. package/src/components/contracts/Button.ts +16 -0
  15. package/src/components/contracts/ConfirmModal.ts +48 -0
  16. package/src/components/contracts/DropdownMenu.ts +25 -0
  17. package/src/components/contracts/ErrorReportModal.ts +33 -0
  18. package/src/components/contracts/Input.ts +26 -0
  19. package/src/components/contracts/LoadingModal.ts +26 -0
  20. package/src/components/contracts/Modal.ts +21 -0
  21. package/src/components/contracts/PromptModal.ts +34 -0
  22. package/src/components/contracts/Select.ts +45 -0
  23. package/src/components/contracts/Toast.ts +15 -0
  24. package/src/components/contracts/index.ts +11 -0
  25. package/src/components/headless/HeadlessButton.vue +51 -0
  26. package/src/components/headless/HeadlessInput.vue +59 -0
  27. package/src/components/headless/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +7 -8
  28. package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
  29. package/src/components/headless/HeadlessInputInput.vue +86 -0
  30. package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
  31. package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +10 -13
  32. package/src/components/headless/HeadlessModal.vue +57 -0
  33. package/src/components/headless/HeadlessModalContent.vue +30 -0
  34. package/src/components/headless/HeadlessModalDescription.vue +12 -0
  35. package/src/components/headless/HeadlessModalOverlay.vue +12 -0
  36. package/src/components/headless/HeadlessModalTitle.vue +12 -0
  37. package/src/components/headless/HeadlessSelect.vue +120 -0
  38. package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +5 -6
  39. package/src/components/headless/HeadlessSelectLabel.vue +25 -0
  40. package/src/components/headless/HeadlessSelectOption.vue +34 -0
  41. package/src/components/headless/HeadlessSelectOptions.vue +42 -0
  42. package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
  43. package/src/components/headless/HeadlessSelectValue.vue +18 -0
  44. package/src/components/headless/HeadlessSwitch.vue +96 -0
  45. package/src/components/headless/HeadlessToast.vue +18 -0
  46. package/src/components/headless/HeadlessToastAction.vue +13 -0
  47. package/src/components/headless/index.ts +20 -3
  48. package/src/components/index.ts +6 -11
  49. package/src/components/ui/AdvancedOptions.vue +18 -0
  50. package/src/components/ui/AlertModal.vue +17 -0
  51. package/src/components/ui/Button.vue +115 -0
  52. package/src/components/ui/Checkbox.vue +56 -0
  53. package/src/components/ui/ConfirmModal.vue +50 -0
  54. package/src/components/ui/DropdownMenu.vue +32 -0
  55. package/src/components/ui/DropdownMenuOption.vue +22 -0
  56. package/src/components/ui/DropdownMenuOptions.vue +44 -0
  57. package/src/components/ui/EditableContent.vue +82 -0
  58. package/src/components/ui/ErrorLogs.vue +19 -0
  59. package/src/components/ui/ErrorLogsModal.vue +48 -0
  60. package/src/components/ui/ErrorMessage.vue +15 -0
  61. package/src/components/ui/ErrorReportModal.vue +73 -0
  62. package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +34 -27
  63. package/src/components/ui/ErrorReportModalTitle.vue +24 -0
  64. package/src/components/{forms/AGForm.vue → ui/Form.vue} +4 -5
  65. package/src/components/ui/Input.vue +56 -0
  66. package/src/components/ui/Link.vue +12 -0
  67. package/src/components/ui/LoadingModal.vue +34 -0
  68. package/src/components/ui/Markdown.vue +97 -0
  69. package/src/components/ui/Modal.vue +131 -0
  70. package/src/components/{modals/AGModalContext.vue → ui/ModalContext.vue} +8 -9
  71. package/src/components/ui/ProgressBar.vue +51 -0
  72. package/src/components/ui/PromptModal.vue +38 -0
  73. package/src/components/ui/Select.vue +27 -0
  74. package/src/components/ui/SelectLabel.vue +21 -0
  75. package/src/components/ui/SelectOption.vue +29 -0
  76. package/src/components/ui/SelectOptions.vue +35 -0
  77. package/src/components/ui/SelectTrigger.vue +29 -0
  78. package/src/components/ui/Setting.vue +31 -0
  79. package/src/components/ui/SettingsModal.vue +15 -0
  80. package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +8 -8
  81. package/src/components/ui/Switch.vue +11 -0
  82. package/src/components/ui/TextArea.vue +56 -0
  83. package/src/components/ui/Toast.vue +46 -0
  84. package/src/components/ui/index.ts +35 -0
  85. package/src/directives/index.ts +9 -5
  86. package/src/directives/measure.ts +12 -6
  87. package/src/errors/Errors.state.ts +1 -1
  88. package/src/errors/Errors.ts +29 -27
  89. package/src/errors/index.ts +15 -8
  90. package/src/errors/settings/Debug.vue +32 -0
  91. package/src/errors/settings/index.ts +10 -0
  92. package/src/errors/utils.ts +1 -1
  93. package/src/forms/{Form.test.ts → FormController.test.ts} +32 -8
  94. package/src/forms/{Form.ts → FormController.ts} +46 -38
  95. package/src/forms/index.ts +2 -3
  96. package/src/forms/utils.ts +35 -35
  97. package/src/index.css +75 -0
  98. package/src/jobs/Job.ts +2 -2
  99. package/src/lang/DefaultLangProvider.ts +7 -4
  100. package/src/lang/Lang.state.ts +1 -1
  101. package/src/lang/Lang.ts +1 -1
  102. package/src/lang/index.ts +12 -6
  103. package/src/lang/settings/Language.vue +48 -0
  104. package/src/lang/settings/index.ts +10 -0
  105. package/src/plugins/Plugin.ts +1 -1
  106. package/src/plugins/index.ts +10 -7
  107. package/src/services/App.state.ts +15 -4
  108. package/src/services/App.ts +12 -4
  109. package/src/services/Cache.ts +1 -1
  110. package/src/services/Events.test.ts +8 -8
  111. package/src/services/Events.ts +4 -10
  112. package/src/services/Service.ts +21 -21
  113. package/src/services/Storage.ts +3 -3
  114. package/src/services/index.ts +10 -6
  115. package/src/services/utils.ts +2 -2
  116. package/src/testing/index.ts +8 -3
  117. package/src/testing/setup.ts +3 -19
  118. package/src/ui/UI.state.ts +8 -13
  119. package/src/ui/UI.ts +143 -114
  120. package/src/ui/index.ts +27 -28
  121. package/src/utils/classes.ts +41 -0
  122. package/src/utils/composition/events.ts +4 -6
  123. package/src/utils/composition/forms.ts +20 -4
  124. package/src/utils/composition/state.ts +11 -2
  125. package/src/utils/index.ts +3 -1
  126. package/src/utils/markdown.ts +37 -5
  127. package/src/utils/types.ts +3 -0
  128. package/src/utils/vue.ts +31 -137
  129. package/dist/aerogel-core.cjs.js +0 -2
  130. package/dist/aerogel-core.cjs.js.map +0 -1
  131. package/dist/aerogel-core.esm.js +0 -2
  132. package/dist/aerogel-core.esm.js.map +0 -1
  133. package/histoire.config.ts +0 -7
  134. package/noeldemartin.config.js +0 -5
  135. package/postcss.config.js +0 -6
  136. package/src/assets/histoire.css +0 -3
  137. package/src/components/AGAppLayout.vue +0 -16
  138. package/src/components/AGAppOverlays.vue +0 -41
  139. package/src/components/AGAppSnackbars.vue +0 -13
  140. package/src/components/composition.ts +0 -23
  141. package/src/components/constants.ts +0 -8
  142. package/src/components/forms/AGButton.vue +0 -44
  143. package/src/components/forms/AGCheckbox.vue +0 -41
  144. package/src/components/forms/AGInput.vue +0 -40
  145. package/src/components/forms/AGSelect.story.vue +0 -46
  146. package/src/components/forms/AGSelect.vue +0 -60
  147. package/src/components/forms/index.ts +0 -5
  148. package/src/components/headless/forms/AGHeadlessButton.ts +0 -3
  149. package/src/components/headless/forms/AGHeadlessButton.vue +0 -62
  150. package/src/components/headless/forms/AGHeadlessInput.ts +0 -34
  151. package/src/components/headless/forms/AGHeadlessInput.vue +0 -70
  152. package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -84
  153. package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
  154. package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
  155. package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
  156. package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
  157. package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
  158. package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
  159. package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
  160. package/src/components/headless/forms/composition.ts +0 -10
  161. package/src/components/headless/forms/index.ts +0 -18
  162. package/src/components/headless/modals/AGHeadlessModal.ts +0 -36
  163. package/src/components/headless/modals/AGHeadlessModal.vue +0 -92
  164. package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -32
  165. package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -23
  166. package/src/components/headless/modals/index.ts +0 -4
  167. package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
  168. package/src/components/headless/snackbars/index.ts +0 -40
  169. package/src/components/interfaces.ts +0 -24
  170. package/src/components/lib/AGErrorMessage.vue +0 -16
  171. package/src/components/lib/AGLink.vue +0 -9
  172. package/src/components/lib/AGMarkdown.vue +0 -54
  173. package/src/components/lib/AGMeasured.vue +0 -16
  174. package/src/components/lib/AGProgressBar.vue +0 -45
  175. package/src/components/lib/index.ts +0 -6
  176. package/src/components/modals/AGAlertModal.ts +0 -18
  177. package/src/components/modals/AGAlertModal.vue +0 -14
  178. package/src/components/modals/AGConfirmModal.ts +0 -42
  179. package/src/components/modals/AGConfirmModal.vue +0 -26
  180. package/src/components/modals/AGErrorReportModal.ts +0 -49
  181. package/src/components/modals/AGErrorReportModal.vue +0 -54
  182. package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
  183. package/src/components/modals/AGLoadingModal.ts +0 -29
  184. package/src/components/modals/AGLoadingModal.vue +0 -15
  185. package/src/components/modals/AGModal.ts +0 -11
  186. package/src/components/modals/AGModal.vue +0 -39
  187. package/src/components/modals/AGModalContext.ts +0 -8
  188. package/src/components/modals/AGModalTitle.vue +0 -9
  189. package/src/components/modals/AGPromptModal.ts +0 -41
  190. package/src/components/modals/AGPromptModal.vue +0 -34
  191. package/src/components/modals/index.ts +0 -17
  192. package/src/components/snackbars/AGSnackbar.vue +0 -36
  193. package/src/components/snackbars/index.ts +0 -3
  194. package/src/components/utils.ts +0 -10
  195. package/src/directives/initial-focus.ts +0 -11
  196. package/src/forms/composition.ts +0 -6
  197. package/src/main.histoire.ts +0 -1
  198. package/src/utils/tailwindcss.test.ts +0 -26
  199. package/src/utils/tailwindcss.ts +0 -7
  200. package/tailwind.config.js +0 -4
  201. package/tsconfig.json +0 -11
  202. package/vite.config.ts +0 -17
  203. /package/src/{main.ts → index.ts} +0 -0
@@ -1,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
 
@@ -25,28 +22,33 @@ export class ErrorsService extends Service {
25
22
  this.enabled = false;
26
23
  }
27
24
 
28
- public async inspect(error: ErrorSource | ErrorReport[]): Promise<void> {
29
- const reports = Array.isArray(error) ? (error as ErrorReport[]) : [await this.createErrorReport(error)];
30
-
31
- if (reports.length === 0) {
25
+ public async inspect(error: ErrorSource | ErrorReport, reports?: ErrorReport[]): Promise<void>;
26
+ public async inspect(reports: ErrorReport[]): Promise<void>;
27
+ public async inspect(errorOrReports: ErrorSource | ErrorReport[], _reports?: ErrorReport[]): Promise<void> {
28
+ if (Array.isArray(errorOrReports) && errorOrReports.length === 0) {
32
29
  UI.alert(translateWithDefault('errors.inspectEmpty', 'Nothing to inspect!'));
33
30
 
34
31
  return;
35
32
  }
36
33
 
37
- UI.openModal<ModalComponent<AGErrorReportModalProps>>(UI.requireComponent(UIComponents.ErrorReportModal), {
38
- reports,
39
- });
34
+ const report = Array.isArray(errorOrReports)
35
+ ? (errorOrReports[0] as ErrorReport)
36
+ : this.isErrorReport(errorOrReports)
37
+ ? errorOrReports
38
+ : await this.createErrorReport(errorOrReports);
39
+ const reports = Array.isArray(errorOrReports) ? (errorOrReports as ErrorReport[]) : (_reports ?? [report]);
40
+
41
+ UI.modal(UI.requireComponent('error-report-modal'), { report, reports });
40
42
  }
41
43
 
42
44
  public async report(error: ErrorSource, message?: string): Promise<void> {
43
45
  await Events.emit('error', { error, message });
44
46
 
45
- if (App.testing) {
47
+ if (isTesting('unit')) {
46
48
  throw error;
47
49
  }
48
50
 
49
- if (App.development) {
51
+ if (isDevelopment()) {
50
52
  this.logError(error);
51
53
  }
52
54
 
@@ -71,20 +73,16 @@ export class ErrorsService extends Service {
71
73
  date: new Date(),
72
74
  };
73
75
 
74
- UI.showSnackbar(
76
+ UI.toast(
75
77
  message ??
76
78
  translateWithDefault('errors.notice', 'Something went wrong, but it\'s not your fault. Try again!'),
77
79
  {
78
- color: Colors.Danger,
80
+ variant: 'danger',
79
81
  actions: [
80
82
  {
81
- text: translateWithDefault('errors.viewDetails', 'View details'),
83
+ label: translateWithDefault('errors.viewDetails', 'View details'),
82
84
  dismiss: true,
83
- handler: () =>
84
- UI.openModal<ModalComponent<AGErrorReportModalProps>>(
85
- UI.requireComponent(UIComponents.ErrorReportModal),
86
- { reports: [report] },
87
- ),
85
+ click: () => UI.modal(UI.requireComponent('error-report-modal'), { report, reports: [report] }),
88
86
  },
89
87
  ],
90
88
  },
@@ -126,6 +124,10 @@ export class ErrorsService extends Service {
126
124
  }
127
125
  }
128
126
 
127
+ private isErrorReport(error: unknown): error is ErrorReport {
128
+ return isObject(error) && 'title' in error;
129
+ }
130
+
129
131
  private async createErrorReport(error: ErrorSource): Promise<ErrorReport> {
130
132
  if (typeof error === 'string') {
131
133
  return { title: error };
@@ -178,7 +180,7 @@ export class ErrorsService extends Service {
178
180
 
179
181
  export default facade(ErrorsService);
180
182
 
181
- declare module '@/services/Events' {
183
+ declare module '@aerogel/core/services/Events' {
182
184
  export interface EventsPayload {
183
185
  error: { error: ErrorSource; message?: string };
184
186
  }
@@ -1,13 +1,18 @@
1
- import type { App } from 'vue';
1
+ import type { App as AppInstance } from 'vue';
2
2
 
3
- import { bootServices } from '@/services';
4
- import { definePlugin } from '@/plugins';
3
+ import App from '@aerogel/core/services/App';
4
+ import { bootServices } from '@aerogel/core/services';
5
+ import { definePlugin } from '@aerogel/core/plugins';
5
6
 
6
7
  import Errors from './Errors';
7
- import { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
8
+ import settings from './settings';
9
+ import type { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
8
10
 
9
11
  export * from './utils';
10
- export { Errors, ErrorSource, ErrorReport, ErrorReportLog };
12
+ export { Errors };
13
+ export { default as JobCancelledError } from './JobCancelledError';
14
+ export { default as ServiceBootError } from './ServiceBootError';
15
+ export type { ErrorSource, ErrorReport, ErrorReportLog };
11
16
 
12
17
  const services = { $errors: Errors };
13
18
  const frameworkHandler: ErrorHandler = (error) => {
@@ -16,7 +21,7 @@ const frameworkHandler: ErrorHandler = (error) => {
16
21
  return true;
17
22
  };
18
23
 
19
- function setUpErrorHandler(app: App, baseHandler: ErrorHandler = () => false): void {
24
+ function setUpErrorHandler(app: AppInstance, baseHandler: ErrorHandler = () => false): void {
20
25
  const errorHandler: ErrorHandler = (error) => baseHandler(error) || frameworkHandler(error);
21
26
 
22
27
  app.config.errorHandler = errorHandler;
@@ -31,16 +36,18 @@ export default definePlugin({
31
36
  async install(app, options) {
32
37
  setUpErrorHandler(app, options.handleError);
33
38
 
39
+ settings.forEach((setting) => App.addSetting(setting));
40
+
34
41
  await bootServices(app, services);
35
42
  },
36
43
  });
37
44
 
38
- declare module '@/bootstrap/options' {
45
+ declare module '@aerogel/core/bootstrap/options' {
39
46
  export interface AerogelOptions {
40
47
  handleError?(error: ErrorSource): boolean;
41
48
  }
42
49
  }
43
50
 
44
- declare module '@/services' {
51
+ declare module '@aerogel/core/services' {
45
52
  export interface Services extends ErrorsServices {}
46
53
  }
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <Setting
3
+ title-id="debug-setting"
4
+ :title="$td('settings.debug', 'Debugging')"
5
+ :description="$td('settings.debugDescription', 'Enable debugging with [Eruda](https://eruda.liriliri.io/).')"
6
+ >
7
+ <Switch v-model="enabled" aria-labelledby="debug-setting" />
8
+ </Setting>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { ref, watchEffect } from 'vue';
13
+ import type Eruda from 'eruda';
14
+
15
+ import Setting from '@aerogel/core/components/ui/Setting.vue';
16
+ import Switch from '@aerogel/core/components/ui/Switch.vue';
17
+
18
+ let eruda: typeof Eruda | null = null;
19
+ const enabled = ref(false);
20
+
21
+ watchEffect(async () => {
22
+ if (!enabled.value) {
23
+ eruda?.destroy();
24
+
25
+ return;
26
+ }
27
+
28
+ eruda ??= (await import('eruda')).default;
29
+
30
+ eruda.init();
31
+ });
32
+ </script>
@@ -0,0 +1,10 @@
1
+ import { defineSettings } from '@aerogel/core/services';
2
+
3
+ import Debug from './Debug.vue';
4
+
5
+ export default defineSettings([
6
+ {
7
+ priority: 10,
8
+ component: Debug,
9
+ },
10
+ ]);
@@ -1,5 +1,5 @@
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
5
  const handlers: ErrorHandler[] = [];
@@ -1,10 +1,18 @@
1
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';
2
5
 
3
- import { useForm } from './composition';
4
- import { FormFieldTypes } from '@/main';
5
- import { numberInput, requiredStringInput } from '@/forms/utils';
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';
6
14
 
7
- describe('Form', () => {
15
+ describe('FormController', () => {
8
16
 
9
17
  it('defines magic fields', () => {
10
18
  const form = useForm({
@@ -20,7 +28,7 @@ describe('Form', () => {
20
28
  // Arrange
21
29
  const form = useForm({
22
30
  name: {
23
- type: FormFieldTypes.String,
31
+ type: 'string',
24
32
  rules: 'required',
25
33
  },
26
34
  });
@@ -38,7 +46,7 @@ describe('Form', () => {
38
46
  // Arrange
39
47
  const form = useForm({
40
48
  name: {
41
- type: FormFieldTypes.String,
49
+ type: 'string',
42
50
  rules: 'required',
43
51
  },
44
52
  });
@@ -59,11 +67,11 @@ describe('Form', () => {
59
67
  // Arrange
60
68
  const form = useForm({
61
69
  trimmed: {
62
- type: FormFieldTypes.String,
70
+ type: 'string',
63
71
  rules: 'required',
64
72
  },
65
73
  untrimmed: {
66
- type: FormFieldTypes.String,
74
+ type: 'string',
67
75
  rules: 'required',
68
76
  trim: false,
69
77
  },
@@ -83,4 +91,20 @@ describe('Form', () => {
83
91
  expect(form.errors).toEqual({ trimmed: ['required'], untrimmed: null });
84
92
  });
85
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
+
86
110
  });
@@ -1,33 +1,32 @@
1
1
  import { computed, nextTick, reactive, readonly, ref } from 'vue';
2
2
  import { MagicObject, arrayRemove, fail, toString } from '@noeldemartin/utils';
3
- import { validate } from './validation';
4
- import type { ObjectValues } from '@noeldemartin/utils';
5
3
  import type { ComputedRef, DeepReadonly, Ref, UnwrapNestedRefs } from 'vue';
6
4
 
7
- export const FormFieldTypes = {
8
- String: 'string',
9
- Number: 'number',
10
- Boolean: 'boolean',
11
- Object: 'object',
12
- Date: 'date',
13
- } as const;
5
+ import { validate } from './validation';
6
+
7
+ export const __objectType: unique symbol = Symbol();
14
8
 
15
- export interface FormFieldDefinition<TType extends FormFieldType = FormFieldType, TRules extends string = string> {
9
+ export interface FormFieldDefinition<
10
+ TType extends FormFieldType = FormFieldType,
11
+ TRules extends string = string,
12
+ TObjectType = object,
13
+ > {
16
14
  type: TType;
17
15
  trim?: boolean;
18
16
  default?: GetFormFieldValue<TType>;
19
17
  rules?: TRules;
18
+ [__objectType]?: TObjectType;
20
19
  }
21
20
 
22
- export type FormFieldDefinitions = Record<string, FormFieldDefinition>;
23
- export type FormFieldType = ObjectValues<typeof FormFieldTypes>;
21
+ export type FormFieldType = 'string' | 'number' | 'boolean' | 'object' | 'date';
24
22
  export type FormFieldValue = GetFormFieldValue<FormFieldType>;
23
+ export type FormFieldDefinitions = Record<string, FormFieldDefinition>;
25
24
 
26
25
  export type FormData<T> = {
27
- -readonly [k in keyof T]: T[k] extends FormFieldDefinition<infer TType, infer TRules>
26
+ -readonly [k in keyof T]: T[k] extends FormFieldDefinition<infer TType, infer TRules, infer TObjectType>
28
27
  ? TRules extends 'required'
29
- ? GetFormFieldValue<TType>
30
- : GetFormFieldValue<TType> | null
28
+ ? GetFormFieldValue<TType, TObjectType>
29
+ : GetFormFieldValue<TType, TObjectType> | null
31
30
  : never;
32
31
  };
33
32
 
@@ -35,24 +34,26 @@ export type FormErrors<T> = {
35
34
  [k in keyof T]: string[] | null;
36
35
  };
37
36
 
38
- export type GetFormFieldValue<TType> = TType extends typeof FormFieldTypes.String
37
+ export type GetFormFieldValue<TType, TObjectType = object> = TType extends 'string'
39
38
  ? string
40
- : TType extends typeof FormFieldTypes.Number
41
- ? number
42
- : TType extends typeof FormFieldTypes.Boolean
43
- ? boolean
44
- : TType extends typeof FormFieldTypes.Object
45
- ? object
46
- : TType extends typeof FormFieldTypes.Date
47
- ? Date
48
- : never;
49
-
50
- const validForms: WeakMap<Form, ComputedRef<boolean>> = new WeakMap();
39
+ : TType extends 'number'
40
+ ? number
41
+ : TType extends 'boolean'
42
+ ? boolean
43
+ : TType extends 'object'
44
+ ? TObjectType extends object
45
+ ? TObjectType
46
+ : object
47
+ : TType extends 'date'
48
+ ? Date
49
+ : never;
50
+
51
+ const validForms: WeakMap<FormController, ComputedRef<boolean>> = new WeakMap();
51
52
 
52
53
  export type SubmitFormListener = () => unknown;
53
54
  export type FocusFormListener = (input: string) => unknown;
54
55
 
55
- export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinitions> extends MagicObject {
56
+ export default class FormController<Fields extends FormFieldDefinitions = FormFieldDefinitions> extends MagicObject {
56
57
 
57
58
  public errors: DeepReadonly<UnwrapNestedRefs<FormErrors<Fields>>>;
58
59
 
@@ -91,7 +92,7 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
91
92
  this._fields[field] ?? fail<FormFieldDefinition>(`Trying to set undefined '${toString(field)}' field`);
92
93
 
93
94
  this._data[field] =
94
- definition.type === FormFieldTypes.String && (definition.trim ?? true)
95
+ definition.type === 'string' && (definition.trim ?? true)
95
96
  ? (toString(value).trim() as FormData<Fields>[T])
96
97
  : value;
97
98
 
@@ -108,16 +109,23 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
108
109
  return this._fields[field]?.rules?.split('|') ?? [];
109
110
  }
110
111
 
112
+ public getFieldType<T extends keyof Fields>(field: T): FormFieldType | null {
113
+ return this._fields[field]?.type ?? null;
114
+ }
115
+
111
116
  public data(): FormData<Fields> {
112
117
  return { ...this._data };
113
118
  }
114
119
 
115
120
  public validate(): boolean {
116
- const errors = Object.entries(this._fields).reduce((formErrors, [name, definition]) => {
117
- formErrors[name] = this.getFieldErrors(name, definition);
121
+ const errors = Object.entries(this._fields).reduce(
122
+ (formErrors, [name, definition]) => {
123
+ formErrors[name] = this.getFieldErrors(name, definition);
118
124
 
119
- return formErrors;
120
- }, {} as Record<string, string[] | null>);
125
+ return formErrors;
126
+ },
127
+ {} as Record<string, string[] | null>,
128
+ );
121
129
 
122
130
  this.resetErrors(errors);
123
131
 
@@ -165,7 +173,7 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
165
173
  this._listeners['focus']?.forEach((listener) => listener(input));
166
174
  }
167
175
 
168
- protected __get(property: string): unknown {
176
+ protected override __get(property: string): unknown {
169
177
  if (!(property in this._fields)) {
170
178
  return super.__get(property);
171
179
  }
@@ -173,7 +181,7 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
173
181
  return this.getFieldValue(property);
174
182
  }
175
183
 
176
- protected __set(property: string, value: unknown): void {
184
+ protected override __set(property: string, value: unknown): void {
177
185
  if (!(property in this._fields)) {
178
186
  super.__set(property, value);
179
187
 
@@ -204,10 +212,10 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
204
212
  return {} as FormData<Fields>;
205
213
  }
206
214
 
207
- const data = Object.entries(fields).reduce((formData, [name, definition]) => {
208
- formData[name as keyof Fields] = (definition.default ?? null) as FormData<Fields>[keyof Fields];
215
+ const data = Object.entries(fields).reduce((initialData, [name, definition]) => {
216
+ initialData[name as keyof Fields] = (definition.default ?? null) as FormData<Fields>[keyof Fields];
209
217
 
210
- return formData;
218
+ return initialData;
211
219
  }, {} as FormData<Fields>);
212
220
 
213
221
  return reactive(data) as FormData<Fields>;
@@ -1,5 +1,4 @@
1
- export * from './composition';
2
- export * from './Form';
1
+ export * from './FormController';
3
2
  export * from './utils';
4
3
  export * from './validation';
5
- export { default as Form } from './Form';
4
+ export { default as FormController } from './FormController';
@@ -1,84 +1,84 @@
1
- import { FormFieldTypes } from './Form';
2
- import type { FormFieldDefinition } from './Form';
1
+ import type { FormFieldDefinition } from './FormController';
3
2
 
4
- export function booleanInput(
5
- defaultValue?: boolean,
6
- options: { rules?: string } = {},
7
- ): FormFieldDefinition<typeof FormFieldTypes.Boolean> {
3
+ export function booleanInput(defaultValue?: boolean, options: { rules?: string } = {}): FormFieldDefinition<'boolean'> {
8
4
  return {
9
5
  default: defaultValue,
10
- type: FormFieldTypes.Boolean,
6
+ type: 'boolean',
11
7
  rules: options.rules,
12
8
  };
13
9
  }
14
10
 
15
- export function dateInput(
16
- defaultValue?: Date,
17
- options: { rules?: string } = {},
18
- ): FormFieldDefinition<typeof FormFieldTypes.Date> {
11
+ export function dateInput(defaultValue?: Date, options: { rules?: string } = {}): FormFieldDefinition<'date'> {
19
12
  return {
20
13
  default: defaultValue,
21
- type: FormFieldTypes.Date,
14
+ type: 'date',
22
15
  rules: options.rules,
23
16
  };
24
17
  }
25
18
 
26
- export function requiredBooleanInput(
27
- defaultValue?: boolean,
28
- ): FormFieldDefinition<typeof FormFieldTypes.Boolean, 'required'> {
19
+ export function requiredBooleanInput(defaultValue?: boolean): FormFieldDefinition<'boolean', 'required'> {
29
20
  return {
30
21
  default: defaultValue,
31
- type: FormFieldTypes.Boolean,
22
+ type: 'boolean',
32
23
  rules: 'required',
33
24
  };
34
25
  }
35
26
 
36
- export function requiredDateInput(defaultValue?: Date): FormFieldDefinition<typeof FormFieldTypes.Date> {
27
+ export function requiredDateInput(defaultValue?: Date): FormFieldDefinition<'date', 'required'> {
37
28
  return {
38
29
  default: defaultValue,
39
- type: FormFieldTypes.Date,
30
+ type: 'date',
40
31
  rules: 'required',
41
32
  };
42
33
  }
43
34
 
44
- export function requiredNumberInput(
45
- defaultValue?: number,
46
- ): FormFieldDefinition<typeof FormFieldTypes.Number, 'required'> {
35
+ export function requiredNumberInput(defaultValue?: number): FormFieldDefinition<'number', 'required'> {
47
36
  return {
48
37
  default: defaultValue,
49
- type: FormFieldTypes.Number,
38
+ type: 'number',
50
39
  rules: 'required',
51
40
  };
52
41
  }
53
42
 
54
- export function requiredStringInput(
55
- defaultValue?: string,
56
- ): FormFieldDefinition<typeof FormFieldTypes.String, 'required'> {
43
+ export function requiredObjectInput<T extends object>(defaultValue?: T): FormFieldDefinition<'object', 'required', T> {
57
44
  return {
58
45
  default: defaultValue,
59
- type: FormFieldTypes.String,
46
+ type: 'object',
60
47
  rules: 'required',
61
48
  };
62
49
  }
63
50
 
64
- export function numberInput(
65
- defaultValue?: number,
66
- options: { rules?: string } = {},
67
- ): FormFieldDefinition<typeof FormFieldTypes.Number> {
51
+ export function requiredStringInput(defaultValue?: string): FormFieldDefinition<'string', 'required'> {
68
52
  return {
69
53
  default: defaultValue,
70
- type: FormFieldTypes.Number,
54
+ type: 'string',
55
+ rules: 'required',
56
+ };
57
+ }
58
+
59
+ export function numberInput(defaultValue?: number, options: { rules?: string } = {}): FormFieldDefinition<'number'> {
60
+ return {
61
+ default: defaultValue,
62
+ type: 'number',
71
63
  rules: options.rules,
72
64
  };
73
65
  }
74
66
 
75
- export function stringInput(
76
- defaultValue?: string,
67
+ export function objectInput<T extends object>(
68
+ defaultValue?: T,
77
69
  options: { rules?: string } = {},
78
- ): FormFieldDefinition<typeof FormFieldTypes.String> {
70
+ ): FormFieldDefinition<'object', string, T> {
71
+ return {
72
+ default: defaultValue,
73
+ type: 'object',
74
+ rules: options.rules,
75
+ };
76
+ }
77
+
78
+ export function stringInput(defaultValue?: string, options: { rules?: string } = {}): FormFieldDefinition<'string'> {
79
79
  return {
80
80
  default: defaultValue,
81
- type: FormFieldTypes.String,
81
+ type: 'string',
82
82
  rules: options.rules,
83
83
  };
84
84
  }
package/src/index.css ADDED
@@ -0,0 +1,75 @@
1
+ @import 'tailwindcss';
2
+
3
+ @plugin '@tailwindcss/forms';
4
+ @plugin '@tailwindcss/typography';
5
+
6
+ @source './';
7
+
8
+ @theme {
9
+ --color-primary: oklch(0.205 0 0);
10
+ --color-primary-50: color-mix(in oklab, var(--color-primary-600) 5%, transparent);
11
+ --color-primary-100: color-mix(in oklab, var(--color-primary-600) 15%, transparent);
12
+ --color-primary-200: color-mix(in oklab, var(--color-primary-600) 30%, transparent);
13
+ --color-primary-300: color-mix(in oklab, var(--color-primary-600) 50%, transparent);
14
+ --color-primary-400: color-mix(in oklab, var(--color-primary-600) 65%, transparent);
15
+ --color-primary-500: color-mix(in oklab, var(--color-primary-600) 80%, transparent);
16
+ --color-primary-600: var(--color-primary);
17
+ --color-primary-700: color-mix(in oklab, var(--color-primary-600) 90%, black);
18
+ --color-primary-800: color-mix(in oklab, var(--color-primary-600) 80%, black);
19
+ --color-primary-900: color-mix(in oklab, var(--color-primary-600) 70%, black);
20
+ --color-primary-950: color-mix(in oklab, var(--color-primary-600) 50%, black);
21
+
22
+ --color-background: oklch(1 0 0);
23
+ --color-links: var(--color-primary);
24
+
25
+ --breakpoint-content: var(--breakpoint-md);
26
+ }
27
+
28
+ .clickable {
29
+ position: relative;
30
+ }
31
+
32
+ .clickable::after {
33
+ --clickable-size: 44px;
34
+ --clickable-inset-by: min(0px, calc((100% - var(--clickable-size)) / 2));
35
+
36
+ content: '';
37
+ position: absolute;
38
+ top: var(--clickable-inset-by);
39
+ left: var(--clickable-inset-by);
40
+ right: var(--clickable-inset-by);
41
+ bottom: var(--clickable-inset-by);
42
+ }
43
+
44
+ input[type='number'].appearance-textfield {
45
+ appearance: textfield;
46
+ }
47
+
48
+ input[type='number'].appearance-textfield::-webkit-outer-spin-button,
49
+ input[type='number'].appearance-textfield::-webkit-inner-spin-button {
50
+ appearance: none;
51
+ }
52
+
53
+ button[data-markdown-action] {
54
+ color: var(--tw-prose-links);
55
+ text-decoration: underline;
56
+ font-weight: 500;
57
+ }
58
+
59
+ @keyframes fade-in {
60
+ 0% {
61
+ opacity: 0;
62
+ }
63
+ 100% {
64
+ opacity: 1;
65
+ }
66
+ }
67
+
68
+ @keyframes grow {
69
+ 0% {
70
+ scale: 0;
71
+ }
72
+ 100% {
73
+ opacity: 1;
74
+ }
75
+ }
package/src/jobs/Job.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ListenersManager, PromisedValue, round, tap, toError } from '@noeldemartin/utils';
2
2
  import type { Listeners } from '@noeldemartin/utils';
3
3
 
4
- import JobCancelledError from '@/errors/JobCancelledError';
4
+ import JobCancelledError from '@aerogel/core/errors/JobCancelledError';
5
5
 
6
6
  import type { JobListener } from './listeners';
7
7
  import type { JobStatus } from './status';
@@ -9,7 +9,7 @@ import type { JobStatus } from './status';
9
9
  export default abstract class Job<
10
10
  Listener extends JobListener = JobListener,
11
11
  Status extends JobStatus = JobStatus,
12
- SerializedStatus extends JobStatus = JobStatus
12
+ SerializedStatus extends JobStatus = JobStatus,
13
13
  > {
14
14
 
15
15
  protected status: Status;