@aerogel/core 0.1.1-next.b342b522bd4e8a154c68962bb545cd8ae66010c3 → 0.1.1-next.bf5c51083c0817e96fa4f38273c32d388441f514

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerogel/core",
3
- "version": "0.1.1-next.b342b522bd4e8a154c68962bb545cd8ae66010c3",
3
+ "version": "0.1.1-next.bf5c51083c0817e96fa4f38273c32d388441f514",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -29,8 +29,8 @@
29
29
  "vue": "^3.5.0"
30
30
  },
31
31
  "dependencies": {
32
- "@noeldemartin/utils": "0.7.1-next.5f7bc66cad4aaa2dce4a2314e99d562ff3ab993b",
33
- "@noeldemartin/vue-modals": "0.0.0-next.124c6a1c5e8a2cef4ec43d6a01de0fc450f155b1",
32
+ "@noeldemartin/utils": "0.7.3",
33
+ "@noeldemartin/vue-modals": "0.1.1",
34
34
  "class-variance-authority": "^0.7.1",
35
35
  "clsx": "^2.1.1",
36
36
  "dompurify": "^3.2.4",
@@ -19,7 +19,7 @@ import { computed, inject, useTemplateRef, watchEffect } from 'vue';
19
19
 
20
20
  import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
21
21
  import { onFormFocus } from '@aerogel/core/utils/composition/forms';
22
- import { LOCAL_TIMEZONE_OFFSET } from '@aerogel/core/utils';
22
+ import { getLocalTimezoneOffset } from '@aerogel/core/utils';
23
23
  import type FormController from '@aerogel/core/forms/FormController';
24
24
  import type { FormFieldValue } from '@aerogel/core/forms/FormController';
25
25
  import type { InputExpose } from '@aerogel/core/components/contracts/Input';
@@ -65,8 +65,19 @@ function getValue(): FormFieldValue | null {
65
65
  return $input.value.checked;
66
66
  case 'date':
67
67
  case 'time':
68
- case 'datetime-local':
69
- return new Date(Math.round($input.value.valueAsNumber / 60000) * 60000 + LOCAL_TIMEZONE_OFFSET);
68
+ case 'datetime-local': {
69
+ const date = new Date(
70
+ Math.round($input.value.valueAsNumber / 60000) * 60000 +
71
+ getLocalTimezoneOffset($input.value.valueAsDate ?? new Date($input.value.valueAsNumber)),
72
+ );
73
+
74
+ if (isNaN(date.getTime())) {
75
+ return null;
76
+ }
77
+
78
+ return date;
79
+ }
80
+
70
81
  case 'number':
71
82
  return $input.value.valueAsNumber;
72
83
  default:
@@ -83,8 +94,11 @@ watchEffect(() => {
83
94
  if (['date', 'time', 'datetime-local'].includes(renderedType.value) && value.value instanceof Date) {
84
95
  const roundedValue = Math.round(value.value.getTime() / 60000) * 60000;
85
96
 
86
- $input.value.valueAsNumber = roundedValue - LOCAL_TIMEZONE_OFFSET;
87
- input.update(new Date(roundedValue));
97
+ $input.value.valueAsNumber = roundedValue - getLocalTimezoneOffset(value.value);
98
+
99
+ if (value.value.getTime() !== roundedValue) {
100
+ input.update(new Date(roundedValue));
101
+ }
88
102
 
89
103
  return;
90
104
  }
@@ -52,6 +52,8 @@ const {
52
52
  emit,
53
53
  );
54
54
  const open = ref(false);
55
+ const optionsByLabel = computed(() =>
56
+ Object.fromEntries(expose.options.value?.map((option) => [option.label, option.value]) ?? []));
55
57
  const combobox = {
56
58
  input: ref(acceptableValue.value ? renderOption(acceptableValue.value as T) : ''),
57
59
  preventChange: ref(false),
@@ -64,12 +66,24 @@ function update(value: AcceptableValue) {
64
66
  baseUpdate(value);
65
67
  }
66
68
 
69
+ watch(expose.value, (value) => {
70
+ const newOptionLabel = renderOption(value as T);
71
+
72
+ if (combobox.input.value === newOptionLabel) {
73
+ return;
74
+ }
75
+
76
+ combobox.preventChange.value = true;
77
+ combobox.input.value = newOptionLabel;
78
+ });
79
+
67
80
  watch(combobox.input, (value) => {
68
81
  const newInputOption = newInputValue ? (newInputValue(value) as AcceptableValue) : value;
69
- const renderedValue = renderOption(expose.value.value);
70
- const renderedNewInputOption = renderOption(expose.value.value as T);
82
+ const newInputOptionLabel = renderOption(newInputOption as T);
83
+
84
+ if (newInputOptionLabel in optionsByLabel.value) {
85
+ update(optionsByLabel.value[newInputOptionLabel] as AcceptableValue);
71
86
 
72
- if (renderedValue === renderedNewInputOption) {
73
87
  return;
74
88
  }
75
89
 
@@ -3,6 +3,7 @@ import type { App as AppInstance } from 'vue';
3
3
  import App from '@aerogel/core/services/App';
4
4
  import { bootServices } from '@aerogel/core/services';
5
5
  import { definePlugin } from '@aerogel/core/plugins';
6
+ import { getErrorMessage } from '@aerogel/core/errors/utils';
6
7
 
7
8
  import Errors from './Errors';
8
9
  import settings from './settings';
@@ -16,6 +17,10 @@ export type { ErrorSource, ErrorReport, ErrorReportLog };
16
17
 
17
18
  const services = { $errors: Errors };
18
19
  const frameworkHandler: ErrorHandler = (error) => {
20
+ if (getErrorMessage(error).includes('ResizeObserver loop completed with undelivered notifications.')) {
21
+ return true;
22
+ }
23
+
19
24
  Errors.report(error);
20
25
 
21
26
  return true;
@@ -1,5 +1,5 @@
1
1
  import { computed, nextTick, reactive, readonly, ref } from 'vue';
2
- import { MagicObject, arrayRemove, fail, toString } from '@noeldemartin/utils';
2
+ import { MagicObject, arrayRemove, fail } from '@noeldemartin/utils';
3
3
  import type { ComputedRef, DeepReadonly, Ref, UnwrapNestedRefs } from 'vue';
4
4
 
5
5
  import { validate, validateType } from './validation';
@@ -91,17 +91,13 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
91
91
  }
92
92
 
93
93
  public setFieldValue<T extends keyof Fields>(field: T, value: FormData<Fields>[T]): void {
94
- const definition =
95
- this._fields[field] ?? fail<FormFieldDefinition>(`Trying to set undefined '${toString(field)}' field`);
94
+ this._data[field] = value;
96
95
 
97
- this._data[field] =
98
- definition.type === 'string' && (definition.trim ?? true)
99
- ? (toString(value).trim() as FormData<Fields>[T])
100
- : value;
101
-
102
- if (this._submitted.value) {
103
- this.validate();
96
+ if (!this._submitted.value) {
97
+ return;
104
98
  }
99
+
100
+ this.validate();
105
101
  }
106
102
 
107
103
  public getFieldValue<T extends keyof Fields>(field: T): GetFormFieldValue<Fields[T]['type']> {
@@ -149,6 +145,16 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
149
145
  public submit(): boolean {
150
146
  this._submitted.value = true;
151
147
 
148
+ for (const [field, value] of Object.entries(this._data)) {
149
+ const definition = this._fields[field] ?? fail<FormFieldDefinition>();
150
+
151
+ if (typeof value !== 'string' || !(definition.trim ?? true)) {
152
+ continue;
153
+ }
154
+
155
+ this._data[field as keyof Fields] = value.trim() as FormData<Fields>[string];
156
+ }
157
+
152
158
  const valid = this.validate();
153
159
 
154
160
  valid && this._listeners['submit']?.forEach((listener) => listener());
@@ -1,5 +1,3 @@
1
- import type { GetClosureArgs } from '@noeldemartin/utils';
2
-
3
1
  import App from '@aerogel/core/services/App';
4
2
 
5
3
  import type { Plugin } from './Plugin';
@@ -10,7 +8,7 @@ export function definePlugin<T extends Plugin>(plugin: T): T {
10
8
  return plugin;
11
9
  }
12
10
 
13
- export async function installPlugins(plugins: Plugin[], ...args: GetClosureArgs<Plugin['install']>): Promise<void> {
11
+ export async function installPlugins(plugins: Plugin[], ...args: Parameters<Plugin['install']>): Promise<void> {
14
12
  App.setState(
15
13
  'plugins',
16
14
  plugins.reduce(
@@ -1,5 +1,4 @@
1
1
  import { isTesting } from '@noeldemartin/utils';
2
- import type { GetClosureArgs } from '@noeldemartin/utils';
3
2
 
4
3
  import Events from '@aerogel/core/services/Events';
5
4
  import { App } from '@aerogel/core/services';
@@ -18,7 +17,7 @@ export default definePlugin({
18
17
  }
19
18
 
20
19
  globalThis.testingRuntime = {
21
- on: ((...args: GetClosureArgs<(typeof Events)['on']>) => Events.on(...args)) as (typeof Events)['on'],
20
+ on: ((...args: Parameters<(typeof Events)['on']>) => Events.on(...args)) as (typeof Events)['on'],
22
21
  service: (name) => App.service(name),
23
22
  };
24
23
  },
package/src/ui/UI.ts CHANGED
@@ -283,7 +283,8 @@ export class UIService extends Service {
283
283
  ): Promise<GetModalResponse<T>>;
284
284
 
285
285
  public modal<T extends Component>(component: T, componentProps?: GetModalProps<T>): Promise<GetModalResponse<T>> {
286
- return showModal(component, componentProps ?? {});
286
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
+ return showModal(component as any, componentProps ?? {}) as Promise<GetModalResponse<T>>;
287
288
  }
288
289
 
289
290
  public async closeAllModals(): Promise<void> {
@@ -4,10 +4,9 @@ import { cva } from 'class-variance-authority';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import type { ClassValue } from 'clsx';
6
6
  import type { PropType } from 'vue';
7
- import type { GetClosureArgs, GetClosureResult } from '@noeldemartin/utils';
8
7
 
9
- export type CVAConfig<T> = NonNullable<GetClosureArgs<typeof cva<T>>[1]>;
10
- export type CVAProps<T> = NonNullable<GetClosureArgs<GetClosureResult<typeof cva<T>>>[0]>;
8
+ export type CVAConfig<T> = NonNullable<Parameters<typeof cva<T>>[1]>;
9
+ export type CVAProps<T> = NonNullable<Parameters<ReturnType<typeof cva<T>>>[0]>;
11
10
  export type Variants<T extends Record<string, string | boolean>> = Required<{
12
11
  [K in keyof T]: Exclude<T[K], undefined> extends string
13
12
  ? { [key in Exclude<T[K], undefined>]: string | null }
@@ -2,10 +2,14 @@ import { fail } from '@noeldemartin/utils';
2
2
  import { customRef } from 'vue';
3
3
 
4
4
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
5
- export function reactiveSet<T>(initial?: T[] | Set<T>) {
5
+ export function reactiveSet<T>(initial?: T[] | Set<T>, options: { equals?: (a: T, b: T) => boolean } = {}) {
6
6
  let set: Set<T> = new Set(initial);
7
7
  let trigger: () => void;
8
8
  let track: () => void;
9
+ const equals = options?.equals;
10
+ const hasEqual = equals
11
+ ? (item: T) => ref.value.values().some((existingItem) => equals(item, existingItem))
12
+ : () => false;
9
13
  const ref = customRef((_track, _trigger) => {
10
14
  track = _track;
11
15
  trigger = _trigger;
@@ -25,11 +29,15 @@ export function reactiveSet<T>(initial?: T[] | Set<T>) {
25
29
  has(item: T) {
26
30
  track();
27
31
 
28
- return ref.value.has(item);
32
+ return ref.value.has(item) || hasEqual(item);
29
33
  },
30
34
  add(item: T) {
31
35
  trigger();
32
36
 
37
+ if (hasEqual(item)) {
38
+ return;
39
+ }
40
+
33
41
  ref.value.add(item);
34
42
  },
35
43
  delete(item: T) {
package/src/utils/time.ts CHANGED
@@ -1,2 +1,7 @@
1
+ import type { Nullable } from '@noeldemartin/utils';
2
+
1
3
  export const MINUTE_MILLISECONDS = 60000;
2
- export const LOCAL_TIMEZONE_OFFSET = -new Date().getTimezoneOffset() * -MINUTE_MILLISECONDS;
4
+
5
+ export function getLocalTimezoneOffset(date?: Nullable<Date>): number {
6
+ return -(date ?? new Date()).getTimezoneOffset() * -MINUTE_MILLISECONDS;
7
+ }