@aerogel/core 0.0.0-next.824cf5311c4335d119158f507dad094ed4f4f0b6 → 0.0.0-next.8e6b2bcc764fa682decbb41aa6848c77a744dec3

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aerogel/core",
3
3
  "description": "The Lightest Solid",
4
- "version": "0.0.0-next.824cf5311c4335d119158f507dad094ed4f4f0b6",
4
+ "version": "0.0.0-next.8e6b2bcc764fa682decbb41aa6848c77a744dec3",
5
5
  "main": "dist/aerogel-core.cjs.js",
6
6
  "module": "dist/aerogel-core.esm.js",
7
7
  "types": "dist/aerogel-core.d.ts",
@@ -6,6 +6,7 @@ import errors from '@/errors';
6
6
  import Events from '@/services/Events';
7
7
  import lang from '@/lang';
8
8
  import services from '@/services';
9
+ import testing from '@/testing';
9
10
  import ui from '@/ui';
10
11
  import { installPlugins } from '@/plugins';
11
12
  import type { AerogelOptions } from '@/bootstrap/options';
@@ -13,7 +14,7 @@ import type { AerogelOptions } from '@/bootstrap/options';
13
14
  export { AerogelOptions };
14
15
 
15
16
  export async function bootstrapApplication(app: App, options: AerogelOptions = {}): Promise<void> {
16
- const plugins = [directives, errors, lang, services, ui, ...(options.plugins ?? [])];
17
+ const plugins = [testing, directives, errors, lang, services, ui, ...(options.plugins ?? [])];
17
18
 
18
19
  await installPlugins(plugins, app, options);
19
20
  await options.install?.(app);
@@ -10,7 +10,7 @@ import { objectWithoutEmpty } from '@noeldemartin/utils';
10
10
 
11
11
  import { booleanProp, objectProp, stringProp } from '@/utils/vue';
12
12
 
13
- const { href, url, route, routeParams, routeQuery, submit } = defineProps({
13
+ const props = defineProps({
14
14
  href: stringProp(),
15
15
  url: stringProp(),
16
16
  route: stringProp(),
@@ -20,32 +20,32 @@ const { href, url, route, routeParams, routeQuery, submit } = defineProps({
20
20
  });
21
21
 
22
22
  const component = computed(() => {
23
- if (route) {
23
+ if (props.route) {
24
24
  return {
25
25
  tag: 'router-link',
26
26
  props: {
27
27
  to: objectWithoutEmpty({
28
- name: route,
29
- params: routeParams,
30
- query: routeQuery,
28
+ name: props.route,
29
+ params: props.routeParams,
30
+ query: props.routeQuery,
31
31
  }),
32
32
  },
33
33
  };
34
34
  }
35
35
 
36
- if (href || url) {
36
+ if (props.href || props.url) {
37
37
  return {
38
38
  tag: 'a',
39
39
  props: {
40
40
  target: '_blank',
41
- href: href || url,
41
+ href: props.href || props.url,
42
42
  },
43
43
  };
44
44
  }
45
45
 
46
46
  return {
47
47
  tag: 'button',
48
- props: { type: submit ? 'submit' : 'button' },
48
+ props: { type: props.submit ? 'submit' : 'button' },
49
49
  };
50
50
  });
51
51
  </script>
@@ -2,6 +2,7 @@
2
2
  <input
3
3
  :id="input.id"
4
4
  ref="$input"
5
+ :name="name"
5
6
  :type="type"
6
7
  :value="value"
7
8
  :aria-invalid="input.errors ? 'true' : 'false'"
@@ -26,6 +27,7 @@ const input = injectReactiveOrFail<IAGHeadlessInput>(
26
27
  'input',
27
28
  '<AGHeadlessInputInput> must be a child of a <AGHeadlessInput>',
28
29
  );
30
+ const name = computed(() => input.name ?? undefined);
29
31
  const value = computed(() => input.value);
30
32
  const checked = computed(() => {
31
33
  if (props.type !== 'checkbox') {
@@ -5,12 +5,12 @@
5
5
  <script setup lang="ts">
6
6
  import { computed } from 'vue';
7
7
 
8
- import Errors from '@/errors/Errors';
9
8
  import { requiredObjectProp } from '@/utils/vue';
9
+ import { getErrorMessage } from '@/errors/utils';
10
10
  import type { ErrorSource } from '@/errors/Errors.state';
11
11
 
12
12
  import AGMarkdown from './AGMarkdown.vue';
13
13
 
14
14
  const props = defineProps({ error: requiredObjectProp<ErrorSource>() });
15
- const message = computed(() => Errors.getErrorMessage(props.error));
15
+ const message = computed(() => getErrorMessage(props.error));
16
16
  </script>
@@ -10,6 +10,8 @@ const builtInDirectives: Record<string, Directive> = {
10
10
  'measure': measure,
11
11
  };
12
12
 
13
+ export * from './measure';
14
+
13
15
  export default definePlugin({
14
16
  install(app, options) {
15
17
  const directives = {
@@ -1,12 +1,21 @@
1
1
  import { defineDirective } from '@/utils/vue';
2
2
 
3
+ export interface ElementSize {
4
+ width: number;
5
+ height: number;
6
+ }
7
+
8
+ export type MeasureDirectiveListener = (size: ElementSize) => unknown;
9
+
3
10
  export default defineDirective({
4
- mounted(element: HTMLElement, { value }: { value?: () => unknown }) {
11
+ mounted(element: HTMLElement, { value }) {
12
+ const listener = typeof value === 'function' ? (value as MeasureDirectiveListener) : null;
5
13
  const sizes = element.getBoundingClientRect();
6
14
 
15
+ // TODO guard with modifiers.css once typed properly
7
16
  element.style.setProperty('--width', `${sizes.width}px`);
8
17
  element.style.setProperty('--height', `${sizes.height}px`);
9
18
 
10
- value?.();
19
+ listener?.({ width: sizes.width, height: sizes.height });
11
20
  },
12
21
  });
@@ -7,6 +7,7 @@ import { translateWithDefault } from '@/lang/utils';
7
7
 
8
8
  import Service from './Errors.state';
9
9
  import { Colors } from '@/components/constants';
10
+ import { Events } from '@/services';
10
11
  import type { AGErrorReportModalProps } from '@/components/modals/AGErrorReportModal';
11
12
  import type { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
12
13
  import type { ModalComponent } from '@/ui/UI.state';
@@ -39,6 +40,8 @@ export class ErrorsService extends Service {
39
40
  }
40
41
 
41
42
  public async report(error: ErrorSource, message?: string): Promise<void> {
43
+ await Events.emit('error', { error, message });
44
+
42
45
  if (App.testing) {
43
46
  throw error;
44
47
  }
@@ -114,22 +117,6 @@ export class ErrorsService extends Service {
114
117
  });
115
118
  }
116
119
 
117
- public getErrorMessage(error: ErrorSource): string {
118
- if (typeof error === 'string') {
119
- return error;
120
- }
121
-
122
- if (error instanceof Error || error instanceof JSError) {
123
- return error.message;
124
- }
125
-
126
- if (isObject(error)) {
127
- return toString(error['message'] ?? error['description'] ?? 'Unknown error object');
128
- }
129
-
130
- return translateWithDefault('errors.unknown', 'Unknown Error');
131
- }
132
-
133
120
  private logError(error: unknown): void {
134
121
  // eslint-disable-next-line no-console
135
122
  console.error(error);
@@ -190,3 +177,9 @@ export class ErrorsService extends Service {
190
177
  }
191
178
 
192
179
  export default facade(ErrorsService);
180
+
181
+ declare module '@/services/Events' {
182
+ export interface EventsPayload {
183
+ error: { error: ErrorSource; message?: string };
184
+ }
185
+ }
@@ -6,6 +6,7 @@ import { definePlugin } from '@/plugins';
6
6
  import Errors from './Errors';
7
7
  import { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
8
8
 
9
+ export * from './utils';
9
10
  export { Errors, ErrorSource, ErrorReport, ErrorReportLog };
10
11
 
11
12
  const services = { $errors: Errors };
@@ -0,0 +1,19 @@
1
+ import { JSError, isObject, toString } from '@noeldemartin/utils';
2
+ import { translateWithDefault } from '@/lang/utils';
3
+ import type { ErrorSource } from './Errors.state';
4
+
5
+ export function getErrorMessage(error: ErrorSource): string {
6
+ if (typeof error === 'string') {
7
+ return error;
8
+ }
9
+
10
+ if (error instanceof Error || error instanceof JSError) {
11
+ return error.message;
12
+ }
13
+
14
+ if (isObject(error)) {
15
+ return toString(error['message'] ?? error['description'] ?? 'Unknown error object');
16
+ }
17
+
18
+ return translateWithDefault('errors.unknown', 'Unknown Error');
19
+ }
package/src/lang/Lang.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { facade, toString } from '@noeldemartin/utils';
1
+ import { facade } from '@noeldemartin/utils';
2
2
 
3
3
  import App from '@/services/App';
4
4
  import Service from '@/services/Service';
5
5
 
6
6
  export interface LangProvider {
7
7
  translate(key: string, parameters?: Record<string, unknown>): string;
8
+ translateWithDefault(key: string, defaultMessage: string, parameters?: Record<string, unknown>): string;
8
9
  }
9
10
 
10
11
  export class LangService extends Service {
@@ -21,6 +22,12 @@ export class LangService extends Service {
21
22
 
22
23
  return key;
23
24
  },
25
+ translateWithDefault: (_, defaultMessage) => {
26
+ // eslint-disable-next-line no-console
27
+ App.development && console.warn('Lang provider is missing');
28
+
29
+ return defaultMessage;
30
+ },
24
31
  };
25
32
  }
26
33
 
@@ -32,27 +39,8 @@ export class LangService extends Service {
32
39
  return this.provider.translate(key, parameters) ?? key;
33
40
  }
34
41
 
35
- public translateWithDefault(key: string, defaultMessage: string): string;
36
- public translateWithDefault(key: string, parameters: Record<string, unknown>, defaultMessage: string): string;
37
- public translateWithDefault(
38
- key: string,
39
- defaultMessageOrParameters?: string | Record<string, unknown>,
40
- defaultMessage?: string,
41
- ): string {
42
- defaultMessage ??= defaultMessageOrParameters as string;
43
-
44
- const parameters = typeof defaultMessageOrParameters === 'string' ? {} : defaultMessageOrParameters ?? {};
45
- const message = this.provider.translate(key, parameters) ?? key;
46
-
47
- if (message === key) {
48
- return Object.entries(parameters).reduce(
49
- (renderedMessage, [name, value]) =>
50
- renderedMessage.replace(new RegExp(`\\{\\s*${name}\\s*\\}`, 'g'), toString(value)),
51
- defaultMessage,
52
- );
53
- }
54
-
55
- return message;
42
+ public translateWithDefault(key: string, defaultMessage: string, parameters: Record<string, unknown> = {}): string {
43
+ return this.provider.translateWithDefault(key, defaultMessage, parameters);
56
44
  }
57
45
 
58
46
  }
package/src/main.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  export * from './bootstrap';
2
2
  export * from './components';
3
+ export * from './directives';
3
4
  export * from './errors';
4
5
  export * from './forms';
5
6
  export * from './jobs';
6
7
  export * from './lang';
7
8
  export * from './plugins';
8
9
  export * from './services';
10
+ export * from './testing';
9
11
  export * from './ui';
10
12
  export * from './utils';
@@ -6,6 +6,8 @@ export interface EventsPayload {}
6
6
  export interface EventListenerOptions {
7
7
  priority: number;
8
8
  }
9
+ export type AerogelGlobalEvents = Partial<{ [Event in EventWithoutPayload]: () => unknown }> &
10
+ Partial<{ [Event in EventWithPayload]: EventListener<EventsPayload[Event]> }>;
9
11
 
10
12
  export type EventListener<T = unknown> = (payload: T) => unknown;
11
13
  export type UnknownEvent<T> = T extends keyof EventsPayload ? never : T;
@@ -28,6 +30,11 @@ export class EventsService extends Service {
28
30
 
29
31
  private listeners: Record<string, { priorities: number[]; handlers: Record<number, EventListener[]> }> = {};
30
32
 
33
+ protected async boot(): Promise<void> {
34
+ Object.entries(globalThis.__aerogelEvents__ ?? {}).forEach(([event, listener]) =>
35
+ this.on(event as string, listener as EventListener));
36
+ }
37
+
31
38
  public emit<Event extends EventWithoutPayload>(event: Event): Promise<void>;
32
39
  public emit<Event extends EventWithPayload>(event: Event, payload: EventsPayload[Event]): Promise<void>;
33
40
  public emit<Event extends string>(event: UnknownEvent<Event>, payload?: unknown): Promise<void>;
@@ -141,3 +148,8 @@ export class EventsService extends Service {
141
148
  }
142
149
 
143
150
  export default facade(EventsService);
151
+
152
+ declare global {
153
+ // eslint-disable-next-line no-var
154
+ var __aerogelEvents__: AerogelGlobalEvents | undefined;
155
+ }
@@ -0,0 +1,25 @@
1
+ import type { GetClosureArgs } from '@noeldemartin/utils';
2
+
3
+ import Events from '@/services/Events';
4
+ import { definePlugin } from '@/plugins';
5
+
6
+ export interface AerogelTestingRuntime {
7
+ on: (typeof Events)['on'];
8
+ }
9
+
10
+ export default definePlugin({
11
+ async install() {
12
+ if (import.meta.env.MODE !== 'testing') {
13
+ return;
14
+ }
15
+
16
+ globalThis.testingRuntime = {
17
+ on: ((...args: GetClosureArgs<(typeof Events)['on']>) => Events.on(...args)) as (typeof Events)['on'],
18
+ };
19
+ },
20
+ });
21
+
22
+ declare global {
23
+ // eslint-disable-next-line no-var
24
+ var testingRuntime: AerogelTestingRuntime | undefined;
25
+ }
package/src/ui/UI.ts CHANGED
@@ -167,7 +167,7 @@ export class UIService extends Service {
167
167
  const snackbar: Snackbar = {
168
168
  id: uuid(),
169
169
  properties: { message, ...options },
170
- component: options.component ?? markRaw(this.requireComponent(UIComponents.Snackbar)),
170
+ component: markRaw(options.component ?? this.requireComponent(UIComponents.Snackbar)),
171
171
  };
172
172
 
173
173
  this.setState('snackbars', this.snackbars.concat(snackbar));
@@ -274,10 +274,12 @@ export default facade(UIService);
274
274
 
275
275
  declare module '@/services/Events' {
276
276
  export interface EventsPayload {
277
- 'modal-will-close': { modal: Modal; result?: unknown };
278
- 'modal-closed': { modal: Modal; result?: unknown };
279
277
  'close-modal': { id: string; result?: unknown };
280
278
  'hide-modal': { id: string };
279
+ 'hide-overlays-backdrop': void;
280
+ 'modal-closed': { modal: Modal; result?: unknown };
281
+ 'modal-will-close': { modal: Modal; result?: unknown };
281
282
  'show-modal': { id: string };
283
+ 'show-overlays-backdrop': void;
282
284
  }
283
285
  }