@aerogel/core 0.1.1-next.9bd06e629f34098543a54bdb87e8996767876d42 → 0.1.1-next.a33efa4fd6560f7df50d8a64a9e9e2a2f62e79bb

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.9bd06e629f34098543a54bdb87e8996767876d42",
3
+ "version": "0.1.1-next.a33efa4fd6560f7df50d8a64a9e9e2a2f62e79bb",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -4,7 +4,7 @@
4
4
  </template>
5
5
 
6
6
  <script setup lang="ts">
7
- import { ModalsPortal } from '@noeldemartin/vue-modals';
7
+ import { ModalsPortal } from '@aerogel/core/ui/modals';
8
8
 
9
9
  import AppToasts from './AppToasts.vue';
10
10
  </script>
@@ -1,7 +1,7 @@
1
1
  import type { PrimitiveProps } from 'reka-ui';
2
2
  import type { HTMLAttributes } from 'vue';
3
3
 
4
- export type ButtonVariant = 'default' | 'secondary' | 'danger' | 'ghost' | 'outline' | 'link';
4
+ export type ButtonVariant = 'default' | 'secondary' | 'danger' | 'warning' | 'ghost' | 'outline' | 'link';
5
5
  export type ButtonSize = 'default' | 'small' | 'large' | 'icon';
6
6
  export interface ButtonProps extends PrimitiveProps {
7
7
  class?: HTMLAttributes['class'];
@@ -5,6 +5,8 @@ export type ModalContentInstance = Nullable<InstanceType<typeof DialogContent>>;
5
5
 
6
6
  export interface ModalProps {
7
7
  persistent?: boolean;
8
+ fullscreen?: boolean;
9
+ fullscreenMobile?: boolean;
8
10
  title?: string;
9
11
  titleHidden?: boolean;
10
12
  description?: string;
@@ -1,4 +1,4 @@
1
- export type ToastVariant = 'secondary' | 'danger';
1
+ export type ToastVariant = 'secondary' | 'warning' | 'danger';
2
2
 
3
3
  export interface ToastAction {
4
4
  label: string;
@@ -19,6 +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
23
  import type FormController from '@aerogel/core/forms/FormController';
23
24
  import type { FormFieldValue } from '@aerogel/core/forms/FormController';
24
25
  import type { InputExpose } from '@aerogel/core/components/contracts/Input';
@@ -39,7 +40,7 @@ const renderedType = computed(() => {
39
40
  return ['text', 'email', 'number', 'tel', 'url'].includes(fieldType) ? fieldType : 'text';
40
41
  });
41
42
  const checked = computed(() => {
42
- if (type !== 'checkbox') {
43
+ if (renderedType.value !== 'checkbox') {
43
44
  return;
44
45
  }
45
46
 
@@ -59,11 +60,15 @@ function getValue(): FormFieldValue | null {
59
60
  return null;
60
61
  }
61
62
 
62
- switch (type) {
63
+ switch (renderedType.value) {
63
64
  case 'checkbox':
64
65
  return $input.value.checked;
65
66
  case 'date':
66
- return $input.value.valueAsDate;
67
+ case 'time':
68
+ case 'datetime-local':
69
+ return new Date(Math.round($input.value.valueAsNumber / 60000) * 60000 + LOCAL_TIMEZONE_OFFSET);
70
+ case 'number':
71
+ return $input.value.valueAsNumber;
67
72
  default:
68
73
  return $input.value.value;
69
74
  }
@@ -75,8 +80,11 @@ watchEffect(() => {
75
80
  return;
76
81
  }
77
82
 
78
- if (type === 'date' && value.value instanceof Date) {
79
- $input.value.valueAsDate = value.value;
83
+ if (['date', 'time', 'datetime-local'].includes(renderedType.value) && value.value instanceof Date) {
84
+ const roundedValue = Math.round(value.value.getTime() / 60000) * 60000;
85
+
86
+ $input.value.valueAsNumber = roundedValue - LOCAL_TIMEZONE_OFFSET;
87
+ input.update(new Date(roundedValue));
80
88
 
81
89
  return;
82
90
  }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <DialogRoot :ref="forwardRef" open @update:open="persistent || close()">
2
+ <DialogRoot :ref="forwardRef" open @update:open="persistent || $event || close()">
3
3
  <DialogPortal>
4
4
  <slot :close />
5
5
  </DialogPortal>
@@ -7,18 +7,17 @@
7
7
  </template>
8
8
 
9
9
  <script setup lang="ts" generic="T = void">
10
- import { after } from '@noeldemartin/utils';
11
10
  import { DialogPortal, DialogRoot, useForwardExpose } from 'reka-ui';
12
11
  import { provide, ref } from 'vue';
13
- import { useModal } from '@noeldemartin/vue-modals';
14
12
  import type { DialogContent } from 'reka-ui';
15
13
  import type { Nullable } from '@noeldemartin/utils';
16
14
 
15
+ import { useModal } from '@aerogel/core/ui/modals';
17
16
  import type { AcceptRefs } from '@aerogel/core/utils/vue';
18
17
  import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
19
18
 
20
19
  const $content = ref<Nullable<InstanceType<typeof DialogContent>>>(null);
21
- const modal = useModal<T>({ removeOnClose: false });
20
+ const { close } = useModal<T>();
22
21
 
23
22
  defineProps<ModalProps>();
24
23
  defineSlots<ModalSlots<T>>();
@@ -27,12 +26,4 @@ defineExpose<AcceptRefs<ModalExpose>>({ $content });
27
26
  const { forwardRef } = useForwardExpose();
28
27
 
29
28
  provide('$modalContentRef', $content);
30
-
31
- async function close(result?: T) {
32
- modal.close(result);
33
-
34
- await after(1000);
35
-
36
- modal.remove();
37
- }
38
29
  </script>
@@ -9,9 +9,9 @@
9
9
  <script setup lang="ts">
10
10
  import { useTemplateRef, watchEffect } from 'vue';
11
11
  import { DialogContent } from 'reka-ui';
12
- import { ModalComponent, useModal } from '@noeldemartin/vue-modals';
13
12
  import type { Ref } from 'vue';
14
13
 
14
+ import { ModalComponent, useModal } from '@aerogel/core/ui/modals';
15
15
  import { injectOrFail } from '@aerogel/core/utils/vue';
16
16
  import type { ModalContentInstance } from '@aerogel/core/components/contracts/Modal';
17
17
 
@@ -1,18 +1,9 @@
1
1
  <template>
2
- <details class="group">
3
- <summary
4
- class="-ml-2 flex w-[max-content] items-center rounded-lg py-2 pr-3 pl-1 hover:bg-gray-100 focus-visible:outline focus-visible:outline-gray-700"
5
- >
6
- <IconCheveronRight class="size-6 transition-transform group-open:rotate-90" />
7
- <span>{{ $td('ui.advancedOptions', 'Advanced options') }}</span>
8
- </summary>
9
-
10
- <div class="pt-2 pl-4">
11
- <slot />
12
- </div>
13
- </details>
2
+ <Details :label="$td('ui.advancedOptions', 'Advanced options')">
3
+ <slot />
4
+ </Details>
14
5
  </template>
15
6
 
16
7
  <script setup lang="ts">
17
- import IconCheveronRight from '~icons/zondicons/cheveron-right';
8
+ import Details from './Details.vue';
18
9
  </script>
@@ -25,6 +25,7 @@ const renderedClasses = computed(() => variantClasses<Variants<Pick<ButtonProps,
25
25
  default: 'bg-primary-600 text-white focus-visible:outline-primary-600',
26
26
  secondary: 'bg-background text-gray-900 ring-gray-300',
27
27
  danger: 'bg-red-600 text-white focus-visible:outline-red-600',
28
+ warning: 'bg-yellow-600 text-white focus-visible:outline-yellow-600',
28
29
  ghost: 'bg-transparent',
29
30
  outline: 'bg-transparent text-primary-600 ring-primary-600',
30
31
  link: 'text-links',
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <details class="group">
3
+ <summary
4
+ class="-ml-2 flex w-[max-content] items-center rounded-lg py-2 pr-3 pl-1 hover:bg-gray-100 focus-visible:outline focus-visible:outline-gray-700"
5
+ >
6
+ <IconCheveronRight class="size-6 transition-transform group-open:rotate-90" />
7
+ <span>{{ label }}</span>
8
+ </summary>
9
+
10
+ <div class="pt-2 pl-4">
11
+ <slot />
12
+ </div>
13
+ </details>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import IconCheveronRight from '~icons/zondicons/cheveron-right';
18
+
19
+ defineProps<{ label: string }>();
20
+ </script>
@@ -12,6 +12,7 @@
12
12
  'animate-[fade-in_var(--tw-duration)_ease-in-out]': !hasRenderedModals,
13
13
  'bg-black/30': firstVisibleModal?.id === id || (!firstVisibleModal && modals[0]?.id === id),
14
14
  'opacity-0': !firstVisibleModal,
15
+ hidden: renderFullscreen,
15
16
  }"
16
17
  />
17
18
  <HeadlessModalContent v-bind="contentProps" :class="renderedWrapperClass">
@@ -58,8 +59,6 @@ import IconClose from '~icons/zondicons/close';
58
59
 
59
60
  import { useForwardExpose } from 'reka-ui';
60
61
  import { computed, onMounted } from 'vue';
61
- import { injectModal, modals, useModal } from '@noeldemartin/vue-modals';
62
- import type { ModalController } from '@noeldemartin/vue-modals';
63
62
  import type { ComponentPublicInstance, HTMLAttributes, Ref } from 'vue';
64
63
  import { type Nullable, after } from '@noeldemartin/utils';
65
64
 
@@ -69,8 +68,11 @@ import HeadlessModalContent from '@aerogel/core/components/headless/HeadlessModa
69
68
  import HeadlessModalDescription from '@aerogel/core/components/headless/HeadlessModalDescription.vue';
70
69
  import HeadlessModalOverlay from '@aerogel/core/components/headless/HeadlessModalOverlay.vue';
71
70
  import HeadlessModalTitle from '@aerogel/core/components/headless/HeadlessModalTitle.vue';
71
+ import UI from '@aerogel/core/ui/UI';
72
72
  import { classes } from '@aerogel/core/utils/classes';
73
73
  import { reactiveSet } from '@aerogel/core/utils';
74
+ import { injectModal, modals, useModal } from '@aerogel/core/ui/modals';
75
+ import type { ModalController } from '@aerogel/core/ui/modals';
74
76
  import type { AcceptRefs } from '@aerogel/core/utils/vue';
75
77
  import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
76
78
 
@@ -88,6 +90,8 @@ const {
88
90
  description,
89
91
  persistent,
90
92
  closeHidden,
93
+ fullscreen,
94
+ fullscreenMobile,
91
95
  ...props
92
96
  } = defineProps<
93
97
  ModalProps & {
@@ -113,18 +117,29 @@ const firstVisibleModal = computed(() => modals.value.find((modal) => modal.visi
113
117
  const hasRenderedModals = computed(() => modals.value.some((modal) => renderedModals.has(modal)));
114
118
  const contentProps = computed(() => (description ? {} : { 'aria-describedby': undefined }));
115
119
  const renderedContentClass = computed(() =>
116
- classes('max-h-[90vh] overflow-auto px-4 pb-4', { 'pt-4': !title || titleHidden }, contentClass));
120
+ classes(
121
+ 'overflow-auto px-4 pb-4',
122
+ { 'pt-4': !title || titleHidden, 'max-h-[90vh]': !renderFullscreen.value },
123
+ contentClass,
124
+ ));
125
+ const renderFullscreen = computed(() => fullscreen || (fullscreenMobile && UI.mobile));
117
126
  const renderedWrapperClass = computed(() =>
118
127
  classes(
119
- 'isolate fixed top-1/2 left-1/2 z-50 w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2',
120
- 'overflow-hidden rounded-lg bg-white text-left shadow-xl sm:max-w-lg',
121
- renderedModals.has(modal.value) ||
122
- 'animate-[fade-in_var(--tw-duration)_ease-in-out,grow_var(--tw-duration)_ease-in-out]',
123
- 'transition-[scale,opacity] will-change-[scale,opacity] duration-300',
124
- {
125
- 'scale-50 opacity-0': !inForeground.value,
126
- 'scale-100 opacity-100': inForeground.value,
127
- },
128
+ 'isolate fixed z-50 flex flex-col overflow-hidden bg-white text-left duration-300',
129
+ renderFullscreen.value
130
+ ? [
131
+ 'inset-0 transition-[transform,translate] will-change-[transform,translate]',
132
+ renderedModals.has(modal.value) || 'animate-[slide-in_var(--tw-duration)_ease-in-out]',
133
+ inForeground.value ? 'translate-y-0' : 'translate-y-full',
134
+ ]
135
+ : [
136
+ 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full',
137
+ 'max-w-[calc(100%-2rem)] rounded-lg shadow-xl sm:max-w-lg',
138
+ 'transition-[scale,opacity] will-change-[scale,opacity]',
139
+ renderedModals.has(modal.value) ||
140
+ 'animate-[fade-in_var(--tw-duration)_ease-in-out,grow_var(--tw-duration)_ease-in-out]',
141
+ inForeground.value ? 'scale-100 opacity-100' : 'scale-50 opacity-0',
142
+ ],
128
143
  wrapperClass,
129
144
  ));
130
145
 
@@ -34,6 +34,7 @@ const renderedClasses = computed(() =>
34
34
  variant: {
35
35
  secondary: 'bg-gray-900 text-white ring-black',
36
36
  danger: 'bg-red-50 text-red-900 ring-red-100',
37
+ warning: 'bg-yellow-50 text-yellow-900 ring-yellow-100',
37
38
  },
38
39
  },
39
40
  defaultVariants: {
@@ -3,6 +3,7 @@ export { default as AlertModal } from './AlertModal.vue';
3
3
  export { default as Button } from './Button.vue';
4
4
  export { default as Checkbox } from './Checkbox.vue';
5
5
  export { default as ConfirmModal } from './ConfirmModal.vue';
6
+ export { default as Details } from './Details.vue';
6
7
  export { default as DropdownMenu } from './DropdownMenu.vue';
7
8
  export { default as DropdownMenuOption } from './DropdownMenuOption.vue';
8
9
  export { default as DropdownMenuOptions } from './DropdownMenuOptions.vue';
@@ -112,6 +112,10 @@ export default class FormController<Fields extends FormFieldDefinitions = FormFi
112
112
  return this._fields[field]?.rules?.split('|') ?? [];
113
113
  }
114
114
 
115
+ public setFieldErrors<T extends keyof Fields>(field: T, errors: string[] | null): void {
116
+ this._errors[field] = errors;
117
+ }
118
+
115
119
  public getFieldType<T extends keyof Fields>(field: T): FormFieldType | null {
116
120
  return this._fields[field]?.type ?? null;
117
121
  }
package/src/index.css CHANGED
@@ -66,6 +66,15 @@ button[data-markdown-action] {
66
66
  }
67
67
  }
68
68
 
69
+ @keyframes slide-in {
70
+ 0% {
71
+ transform: translateY(100%);
72
+ }
73
+ 100% {
74
+ transform: translateY(0);
75
+ }
76
+ }
77
+
69
78
  @keyframes grow {
70
79
  0% {
71
80
  scale: 0;
package/src/ui/UI.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { after, facade, fail, isDevelopment, uuid } from '@noeldemartin/utils';
2
2
  import { markRaw, unref } from 'vue';
3
- import { closeModal, createModal, modals, showModal } from '@noeldemartin/vue-modals';
4
3
  import type { Constructor } from '@noeldemartin/utils';
5
4
  import type { Component, ComputedOptions, MethodOptions } from 'vue';
6
- import type { GetModalProps, GetModalResponse } from '@noeldemartin/vue-modals';
7
5
 
8
6
  import Events from '@aerogel/core/services/Events';
7
+ import { closeModal, createModal, modals, showModal } from '@aerogel/core/ui/modals';
8
+ import type { GetModalProps, GetModalResponse } from '@aerogel/core/ui/modals';
9
9
  import type { AcceptRefs } from '@aerogel/core/utils';
10
10
  import type { AlertModalExpose, AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
11
11
  import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
package/src/ui/index.ts CHANGED
@@ -14,20 +14,10 @@ import type { Component } from 'vue';
14
14
 
15
15
  const services = { $ui: UI };
16
16
 
17
+ export * from './modals';
17
18
  export * from './UI';
18
19
  export * from './utils';
19
20
  export { default as UI } from './UI';
20
- export {
21
- useModal,
22
- createModal,
23
- showModal,
24
- closeModal,
25
- modals,
26
- ModalComponent,
27
- ModalsPortal,
28
- type GetModalProps,
29
- type GetModalResponse,
30
- } from '@noeldemartin/vue-modals';
31
21
 
32
22
  export type UIServices = typeof services;
33
23
 
@@ -0,0 +1,36 @@
1
+ import { after } from '@noeldemartin/utils';
2
+ import { injectModal, useModal as useModalBase } from '@noeldemartin/vue-modals';
3
+
4
+ export {
5
+ createModal,
6
+ showModal,
7
+ injectModal,
8
+ closeModal,
9
+ modals,
10
+ ModalComponent,
11
+ ModalsPortal,
12
+ type GetModalProps,
13
+ type GetModalResponse,
14
+ type ModalController,
15
+ } from '@noeldemartin/vue-modals';
16
+
17
+ const instances = new WeakSet();
18
+
19
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
20
+ export function useModal<T = never>() {
21
+ const instance = injectModal<T>();
22
+ const { close, remove, ...modal } = useModalBase<T>(instances.has(instance) ? {} : { removeOnClose: false });
23
+
24
+ instances.add(instance);
25
+
26
+ return {
27
+ ...modal,
28
+ async close(result?: T) {
29
+ close(result);
30
+
31
+ await after(1000);
32
+
33
+ remove();
34
+ },
35
+ };
36
+ }
@@ -7,5 +7,6 @@ export * from './composition/persistent';
7
7
  export * from './composition/reactiveSet';
8
8
  export * from './composition/state';
9
9
  export * from './markdown';
10
+ export * from './time';
10
11
  export * from './types';
11
12
  export * from './vue';
@@ -0,0 +1,2 @@
1
+ export const MINUTE_MILLISECONDS = 60000;
2
+ export const LOCAL_TIMEZONE_OFFSET = -new Date().getTimezoneOffset() * -MINUTE_MILLISECONDS;