@aerogel/core 0.1.1-next.785e2c42ed379bc0f268c637d1023a51f5832d80 → 0.1.1-next.7f33b133934b479bdeee0808001759d92e987cf9
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/dist/aerogel-core.d.ts +226 -265
- package/dist/aerogel-core.js +1368 -1376
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +3 -2
- package/src/components/AppOverlays.vue +3 -2
- package/src/components/contracts/AlertModal.ts +1 -1
- package/src/components/contracts/Button.ts +1 -1
- package/src/components/contracts/ConfirmModal.ts +5 -2
- package/src/components/contracts/Modal.ts +6 -3
- package/src/components/contracts/PromptModal.ts +6 -2
- package/src/components/contracts/Toast.ts +1 -1
- package/src/components/headless/HeadlessInputInput.vue +5 -3
- package/src/components/headless/HeadlessModal.vue +6 -34
- package/src/components/headless/HeadlessModalContent.vue +5 -12
- package/src/components/index.ts +0 -1
- package/src/components/ui/AdvancedOptions.vue +4 -13
- package/src/components/ui/Button.vue +1 -0
- package/src/components/ui/ConfirmModal.vue +7 -2
- package/src/components/ui/Details.vue +20 -0
- package/src/components/ui/Form.vue +1 -1
- package/src/components/ui/Modal.vue +26 -25
- package/src/components/ui/PromptModal.vue +7 -2
- package/src/components/ui/Toast.vue +1 -0
- package/src/components/ui/index.ts +1 -1
- package/src/directives/index.ts +7 -7
- package/src/forms/FormController.ts +4 -0
- package/src/ui/UI.state.ts +0 -11
- package/src/ui/UI.ts +42 -125
- package/src/ui/index.ts +1 -0
- package/src/ui/modals.ts +36 -0
- package/src/utils/composition/reactiveSet.test.ts +32 -0
- package/src/utils/composition/reactiveSet.ts +53 -0
- package/src/utils/index.ts +1 -0
- package/src/components/AppModals.vue +0 -14
- package/src/components/ui/ModalContext.vue +0 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aerogel/core",
|
|
3
|
-
"version": "0.1.1-next.
|
|
3
|
+
"version": "0.1.1-next.7f33b133934b479bdeee0808001759d92e987cf9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"vue": "^3.5.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@noeldemartin/utils": "
|
|
32
|
+
"@noeldemartin/utils": "0.7.1-next.5f7bc66cad4aaa2dce4a2314e99d562ff3ab993b",
|
|
33
|
+
"@noeldemartin/vue-modals": "0.0.0-next.124c6a1c5e8a2cef4ec43d6a01de0fc450f155b1",
|
|
33
34
|
"class-variance-authority": "^0.7.1",
|
|
34
35
|
"clsx": "^2.1.1",
|
|
35
36
|
"dompurify": "^3.2.4",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<ModalsPortal nested />
|
|
3
3
|
<AppToasts />
|
|
4
4
|
</template>
|
|
5
5
|
|
|
6
6
|
<script setup lang="ts">
|
|
7
|
-
import
|
|
7
|
+
import { ModalsPortal } from '@aerogel/core/ui/modals';
|
|
8
|
+
|
|
8
9
|
import AppToasts from './AppToasts.vue';
|
|
9
10
|
</script>
|
|
@@ -8,7 +8,7 @@ export interface AlertModalProps {
|
|
|
8
8
|
message: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export interface AlertModalExpose extends ModalExpose
|
|
11
|
+
export interface AlertModalExpose extends ModalExpose {}
|
|
12
12
|
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
14
14
|
export function useAlertModal(props: AlertModalProps) {
|
|
@@ -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'];
|
|
@@ -4,10 +4,11 @@ import { translateWithDefault } from '@aerogel/core/lang';
|
|
|
4
4
|
import { useForm } from '@aerogel/core/utils/composition/forms';
|
|
5
5
|
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
6
6
|
import type { FormFieldDefinition } from '@aerogel/core/forms/FormController';
|
|
7
|
-
import type { ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
8
7
|
import type { Nullable } from '@noeldemartin/utils';
|
|
8
|
+
import type { ModalEmits, ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
9
9
|
|
|
10
10
|
export type ConfirmModalCheckboxes = Record<string, { label: string; default?: boolean; required?: boolean }>;
|
|
11
|
+
export type ConfirmModalResult = boolean | [boolean, Record<string, Nullable<boolean>>];
|
|
11
12
|
|
|
12
13
|
export interface ConfirmModalProps {
|
|
13
14
|
title?: string;
|
|
@@ -21,7 +22,9 @@ export interface ConfirmModalProps {
|
|
|
21
22
|
required?: boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export interface ConfirmModalExpose extends ModalExpose
|
|
25
|
+
export interface ConfirmModalExpose extends ModalExpose {}
|
|
26
|
+
|
|
27
|
+
export interface ConfirmModalEmits extends ModalEmits<ConfirmModalResult> {}
|
|
25
28
|
|
|
26
29
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
27
30
|
export function useConfirmModal(props: ConfirmModalProps) {
|
|
@@ -11,11 +11,14 @@ export interface ModalProps {
|
|
|
11
11
|
descriptionHidden?: boolean;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export interface ModalSlots<Result =
|
|
14
|
+
export interface ModalSlots<Result = never> {
|
|
15
15
|
default(props: { close(result?: Result): Promise<void> }): unknown;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export interface ModalExpose
|
|
19
|
-
close(result?: Result): Promise<void>;
|
|
18
|
+
export interface ModalExpose {
|
|
20
19
|
$content: ModalContentInstance;
|
|
21
20
|
}
|
|
21
|
+
|
|
22
|
+
export interface ModalEmits<Result = never> {
|
|
23
|
+
(event: 'close', payload: Result): void;
|
|
24
|
+
}
|
|
@@ -4,7 +4,9 @@ import { useForm } from '@aerogel/core/utils/composition/forms';
|
|
|
4
4
|
import { requiredStringInput } from '@aerogel/core/forms/utils';
|
|
5
5
|
import { translateWithDefault } from '@aerogel/core/lang';
|
|
6
6
|
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
7
|
-
import type { ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
7
|
+
import type { ModalEmits, ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
8
|
+
|
|
9
|
+
export type PromptModalResult = string;
|
|
8
10
|
|
|
9
11
|
export interface PromptModalProps {
|
|
10
12
|
title?: string;
|
|
@@ -18,7 +20,9 @@ export interface PromptModalProps {
|
|
|
18
20
|
cancelVariant?: ButtonVariant;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export interface PromptModalExpose extends ModalExpose
|
|
23
|
+
export interface PromptModalExpose extends ModalExpose {}
|
|
24
|
+
|
|
25
|
+
export interface PromptModalEmits extends ModalEmits<PromptModalResult> {}
|
|
22
26
|
|
|
23
27
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
24
28
|
export function usePromptModal(props: PromptModalProps) {
|
|
@@ -39,7 +39,7 @@ const renderedType = computed(() => {
|
|
|
39
39
|
return ['text', 'email', 'number', 'tel', 'url'].includes(fieldType) ? fieldType : 'text';
|
|
40
40
|
});
|
|
41
41
|
const checked = computed(() => {
|
|
42
|
-
if (
|
|
42
|
+
if (renderedType.value !== 'checkbox') {
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -59,11 +59,13 @@ function getValue(): FormFieldValue | null {
|
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
switch (
|
|
62
|
+
switch (renderedType.value) {
|
|
63
63
|
case 'checkbox':
|
|
64
64
|
return $input.value.checked;
|
|
65
65
|
case 'date':
|
|
66
66
|
return $input.value.valueAsDate;
|
|
67
|
+
case 'number':
|
|
68
|
+
return $input.value.valueAsNumber;
|
|
67
69
|
default:
|
|
68
70
|
return $input.value.value;
|
|
69
71
|
}
|
|
@@ -75,7 +77,7 @@ watchEffect(() => {
|
|
|
75
77
|
return;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
if (
|
|
80
|
+
if (renderedType.value === 'date' && value.value instanceof Date) {
|
|
79
81
|
$input.value.valueAsDate = value.value;
|
|
80
82
|
|
|
81
83
|
return;
|
|
@@ -1,57 +1,29 @@
|
|
|
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
|
-
<slot :close
|
|
4
|
+
<slot :close />
|
|
5
5
|
</DialogPortal>
|
|
6
6
|
</DialogRoot>
|
|
7
7
|
</template>
|
|
8
8
|
|
|
9
9
|
<script setup lang="ts" generic="T = void">
|
|
10
|
-
import { provide, ref } from 'vue';
|
|
11
10
|
import { DialogPortal, DialogRoot, useForwardExpose } from 'reka-ui';
|
|
11
|
+
import { provide, ref } from 'vue';
|
|
12
12
|
import type { DialogContent } from 'reka-ui';
|
|
13
13
|
import type { Nullable } from '@noeldemartin/utils';
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
import { useEvent } from '@aerogel/core/utils/composition/events';
|
|
17
|
-
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
15
|
+
import { useModal } from '@aerogel/core/ui/modals';
|
|
18
16
|
import type { AcceptRefs } from '@aerogel/core/utils/vue';
|
|
19
|
-
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
20
17
|
import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
|
|
21
18
|
|
|
22
19
|
const $content = ref<Nullable<InstanceType<typeof DialogContent>>>(null);
|
|
23
|
-
const {
|
|
24
|
-
'modal',
|
|
25
|
-
'could not obtain modal reference from <HeadlessModal>, ' +
|
|
26
|
-
'did you render this component manually? Show it using $ui.modal() instead',
|
|
27
|
-
);
|
|
20
|
+
const { close } = useModal<T>();
|
|
28
21
|
|
|
29
22
|
defineProps<ModalProps>();
|
|
30
23
|
defineSlots<ModalSlots<T>>();
|
|
31
|
-
defineExpose<AcceptRefs<ModalExpose
|
|
24
|
+
defineExpose<AcceptRefs<ModalExpose>>({ $content });
|
|
32
25
|
|
|
33
26
|
const { forwardRef } = useForwardExpose();
|
|
34
|
-
const closed = ref(false);
|
|
35
27
|
|
|
36
28
|
provide('$modalContentRef', $content);
|
|
37
|
-
|
|
38
|
-
useEvent('close-modal', async ({ id, result }) => {
|
|
39
|
-
if (id !== modal.id) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
await close(result);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
async function close(result?: unknown) {
|
|
47
|
-
if (closed.value) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
await Events.emit('modal-will-close', { modal, result });
|
|
52
|
-
|
|
53
|
-
closed.value = true;
|
|
54
|
-
|
|
55
|
-
await Events.emit('modal-has-closed', { modal, result });
|
|
56
|
-
}
|
|
57
29
|
</script>
|
|
@@ -2,29 +2,22 @@
|
|
|
2
2
|
<DialogContent ref="$contentRef">
|
|
3
3
|
<slot />
|
|
4
4
|
|
|
5
|
-
<
|
|
5
|
+
<ModalComponent :is="child" v-if="child" />
|
|
6
6
|
</DialogContent>
|
|
7
7
|
</template>
|
|
8
8
|
|
|
9
9
|
<script setup lang="ts">
|
|
10
|
-
import {
|
|
10
|
+
import { useTemplateRef, watchEffect } from 'vue';
|
|
11
11
|
import { DialogContent } from 'reka-ui';
|
|
12
12
|
import type { Ref } from 'vue';
|
|
13
13
|
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import { injectOrFail, injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
17
|
-
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
14
|
+
import { ModalComponent, useModal } from '@aerogel/core/ui/modals';
|
|
15
|
+
import { injectOrFail } from '@aerogel/core/utils/vue';
|
|
18
16
|
import type { ModalContentInstance } from '@aerogel/core/components/contracts/Modal';
|
|
19
17
|
|
|
20
|
-
const {
|
|
21
|
-
'modal',
|
|
22
|
-
'could not obtain modal reference from <HeadlessModalContent>, ' +
|
|
23
|
-
'did you render this component manually? Show it using $ui.modal() instead',
|
|
24
|
-
);
|
|
18
|
+
const { child } = useModal();
|
|
25
19
|
const $modalContentRef = injectOrFail<Ref<ModalContentInstance>>('$modalContentRef');
|
|
26
20
|
const $content = useTemplateRef('$contentRef');
|
|
27
|
-
const childModal = computed(() => UI.modals[childIndex] ?? null);
|
|
28
21
|
|
|
29
22
|
watchEffect(() => ($modalContentRef.value = $content.value));
|
|
30
23
|
</script>
|
package/src/components/index.ts
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
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
|
|
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',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<!-- @vue-generic {import('@aerogel/core/
|
|
2
|
+
<!-- @vue-generic {import('@aerogel/core/components/contracts/ConfirmModal').ConfirmModalResult} -->
|
|
3
3
|
<Modal
|
|
4
4
|
v-slot="{ close }"
|
|
5
5
|
:title="renderedTitle"
|
|
@@ -41,10 +41,15 @@ import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
|
41
41
|
import Button from '@aerogel/core/components/ui/Button.vue';
|
|
42
42
|
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
43
43
|
import { useConfirmModal } from '@aerogel/core/components/contracts/ConfirmModal';
|
|
44
|
-
import type {
|
|
44
|
+
import type {
|
|
45
|
+
ConfirmModalEmits,
|
|
46
|
+
ConfirmModalExpose,
|
|
47
|
+
ConfirmModalProps,
|
|
48
|
+
} from '@aerogel/core/components/contracts/ConfirmModal';
|
|
45
49
|
|
|
46
50
|
const { cancelVariant = 'secondary', ...props } = defineProps<ConfirmModalProps>();
|
|
47
51
|
const { form, renderedTitle, titleHidden, renderedAcceptText, renderedCancelText } = useConfirmModal(props);
|
|
48
52
|
|
|
53
|
+
defineEmits<ConfirmModalEmits>();
|
|
49
54
|
defineExpose<ConfirmModalExpose>();
|
|
50
55
|
</script>
|
|
@@ -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>
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
:persistent
|
|
8
8
|
>
|
|
9
9
|
<HeadlessModalOverlay
|
|
10
|
-
class="fixed inset-0
|
|
10
|
+
class="fixed inset-0 transition-opacity duration-300 will-change-[opacity]"
|
|
11
11
|
:class="{
|
|
12
|
-
'
|
|
13
|
-
'
|
|
12
|
+
'animate-[fade-in_var(--tw-duration)_ease-in-out]': !hasRenderedModals,
|
|
13
|
+
'bg-black/30': firstVisibleModal?.id === id || (!firstVisibleModal && modals[0]?.id === id),
|
|
14
|
+
'opacity-0': !firstVisibleModal,
|
|
14
15
|
}"
|
|
15
16
|
/>
|
|
16
17
|
<HeadlessModalContent v-bind="contentProps" :class="renderedWrapperClass">
|
|
@@ -52,14 +53,13 @@
|
|
|
52
53
|
</HeadlessModal>
|
|
53
54
|
</template>
|
|
54
55
|
|
|
55
|
-
<script
|
|
56
|
+
<script lang="ts">
|
|
56
57
|
import IconClose from '~icons/zondicons/close';
|
|
57
58
|
|
|
58
|
-
import { after } from '@noeldemartin/utils';
|
|
59
|
-
import { computed } from 'vue';
|
|
60
59
|
import { useForwardExpose } from 'reka-ui';
|
|
60
|
+
import { computed, onMounted } from 'vue';
|
|
61
61
|
import type { ComponentPublicInstance, HTMLAttributes, Ref } from 'vue';
|
|
62
|
-
import type
|
|
62
|
+
import { type Nullable, after } from '@noeldemartin/utils';
|
|
63
63
|
|
|
64
64
|
import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
65
65
|
import HeadlessModal from '@aerogel/core/components/headless/HeadlessModal.vue';
|
|
@@ -67,15 +67,18 @@ import HeadlessModalContent from '@aerogel/core/components/headless/HeadlessModa
|
|
|
67
67
|
import HeadlessModalDescription from '@aerogel/core/components/headless/HeadlessModalDescription.vue';
|
|
68
68
|
import HeadlessModalOverlay from '@aerogel/core/components/headless/HeadlessModalOverlay.vue';
|
|
69
69
|
import HeadlessModalTitle from '@aerogel/core/components/headless/HeadlessModalTitle.vue';
|
|
70
|
-
import UI from '@aerogel/core/ui/UI';
|
|
71
70
|
import { classes } from '@aerogel/core/utils/classes';
|
|
72
|
-
import {
|
|
73
|
-
import {
|
|
71
|
+
import { reactiveSet } from '@aerogel/core/utils';
|
|
72
|
+
import { injectModal, modals, useModal } from '@aerogel/core/ui/modals';
|
|
73
|
+
import type { ModalController } from '@aerogel/core/ui/modals';
|
|
74
74
|
import type { AcceptRefs } from '@aerogel/core/utils/vue';
|
|
75
75
|
import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
|
|
76
|
-
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
const renderedModals = reactiveSet<ModalController>();
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<script setup lang="ts" generic="T = void">
|
|
81
|
+
type HeadlessModalInstance = ComponentPublicInstance & ModalExpose;
|
|
79
82
|
|
|
80
83
|
const {
|
|
81
84
|
class: contentClass = '',
|
|
@@ -95,15 +98,19 @@ const {
|
|
|
95
98
|
>();
|
|
96
99
|
|
|
97
100
|
defineSlots<ModalSlots<T>>();
|
|
98
|
-
defineExpose<AcceptRefs<ModalExpose
|
|
99
|
-
close: async (result) => $modal.value?.close(result),
|
|
101
|
+
defineExpose<AcceptRefs<ModalExpose>>({
|
|
100
102
|
$content: computed(() => $modal.value?.$content),
|
|
101
103
|
});
|
|
102
104
|
|
|
103
105
|
const { forwardRef, currentRef } = useForwardExpose<HeadlessModalInstance>();
|
|
106
|
+
const { id, visible } = useModal();
|
|
104
107
|
const $modal = currentRef as Ref<Nullable<HeadlessModalInstance>>;
|
|
105
|
-
const
|
|
106
|
-
const inForeground = computed(
|
|
108
|
+
const modal = injectModal();
|
|
109
|
+
const inForeground = computed(
|
|
110
|
+
() => visible.value && modals.value.toReversed().find((modal) => modal.visible.value)?.id === id.value,
|
|
111
|
+
);
|
|
112
|
+
const firstVisibleModal = computed(() => modals.value.find((modal) => modal.visible.value));
|
|
113
|
+
const hasRenderedModals = computed(() => modals.value.some((modal) => renderedModals.has(modal)));
|
|
107
114
|
const contentProps = computed(() => (description ? {} : { 'aria-describedby': undefined }));
|
|
108
115
|
const renderedContentClass = computed(() =>
|
|
109
116
|
classes('max-h-[90vh] overflow-auto px-4 pb-4', { 'pt-4': !title || titleHidden }, contentClass));
|
|
@@ -111,7 +118,8 @@ const renderedWrapperClass = computed(() =>
|
|
|
111
118
|
classes(
|
|
112
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',
|
|
113
120
|
'overflow-hidden rounded-lg bg-white text-left shadow-xl sm:max-w-lg',
|
|
114
|
-
|
|
121
|
+
renderedModals.has(modal.value) ||
|
|
122
|
+
'animate-[fade-in_var(--tw-duration)_ease-in-out,grow_var(--tw-duration)_ease-in-out]',
|
|
115
123
|
'transition-[scale,opacity] will-change-[scale,opacity] duration-300',
|
|
116
124
|
{
|
|
117
125
|
'scale-50 opacity-0': !inForeground.value,
|
|
@@ -120,12 +128,5 @@ const renderedWrapperClass = computed(() =>
|
|
|
120
128
|
wrapperClass,
|
|
121
129
|
));
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
if (id !== context.modal.id) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Wait for transitions to finish
|
|
129
|
-
await after({ ms: 300 });
|
|
130
|
-
});
|
|
131
|
+
onMounted(() => after(500).then(() => renderedModals.add(modal.value)));
|
|
131
132
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<!-- @vue-generic {import('@aerogel/core/
|
|
2
|
+
<!-- @vue-generic {import('@aerogel/core/components/contracts/PromptModal').PromptModalResult} -->
|
|
3
3
|
<Modal v-slot="{ close }" :title="renderedTitle" persistent>
|
|
4
4
|
<Form :form @submit="close(form.draft)">
|
|
5
5
|
<Markdown v-if="renderedMessage" :text="renderedMessage" />
|
|
@@ -29,10 +29,15 @@ import Form from '@aerogel/core/components/ui/Form.vue';
|
|
|
29
29
|
import Input from '@aerogel/core/components/ui/Input.vue';
|
|
30
30
|
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
31
31
|
import { usePromptModal } from '@aerogel/core/components/contracts/PromptModal';
|
|
32
|
-
import type {
|
|
32
|
+
import type {
|
|
33
|
+
PromptModalEmits,
|
|
34
|
+
PromptModalExpose,
|
|
35
|
+
PromptModalProps,
|
|
36
|
+
} from '@aerogel/core/components/contracts/PromptModal';
|
|
33
37
|
|
|
34
38
|
const { cancelVariant = 'secondary', ...props } = defineProps<PromptModalProps>();
|
|
35
39
|
const { form, renderedTitle, renderedMessage, renderedAcceptText, renderedCancelText } = usePromptModal(props);
|
|
36
40
|
|
|
41
|
+
defineEmits<PromptModalEmits>();
|
|
37
42
|
defineExpose<PromptModalExpose>();
|
|
38
43
|
</script>
|
|
@@ -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';
|
|
@@ -19,7 +20,6 @@ export { default as Link } from './Link.vue';
|
|
|
19
20
|
export { default as LoadingModal } from './LoadingModal.vue';
|
|
20
21
|
export { default as Markdown } from './Markdown.vue';
|
|
21
22
|
export { default as Modal } from './Modal.vue';
|
|
22
|
-
export { default as ModalContext } from './ModalContext.vue';
|
|
23
23
|
export { default as ProgressBar } from './ProgressBar.vue';
|
|
24
24
|
export { default as PromptModal } from './PromptModal.vue';
|
|
25
25
|
export { default as Select } from './Select.vue';
|
package/src/directives/index.ts
CHANGED
|
@@ -5,22 +5,24 @@ import { definePlugin } from '@aerogel/core/plugins';
|
|
|
5
5
|
import measure from './measure';
|
|
6
6
|
import safeHtml from './safe-html';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
export const aerogelDirectives = {
|
|
9
9
|
'measure': measure,
|
|
10
10
|
'safe-html': safeHtml,
|
|
11
|
-
}
|
|
11
|
+
} as const satisfies Record<string, Directive>;
|
|
12
|
+
|
|
13
|
+
export type AerogelDirectives = typeof aerogelDirectives;
|
|
12
14
|
|
|
13
15
|
export * from './measure';
|
|
14
16
|
|
|
15
17
|
export default definePlugin({
|
|
16
18
|
install(app, options) {
|
|
17
19
|
const directives = {
|
|
18
|
-
...
|
|
20
|
+
...aerogelDirectives,
|
|
19
21
|
...options.directives,
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
for (const [name, directive] of Object.entries(directives)) {
|
|
23
|
-
app.directive(name, directive);
|
|
25
|
+
app.directive(name, directive as Directive);
|
|
24
26
|
}
|
|
25
27
|
},
|
|
26
28
|
});
|
|
@@ -32,7 +34,5 @@ declare module '@aerogel/core/bootstrap/options' {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
declare module 'vue' {
|
|
35
|
-
interface ComponentCustomDirectives {
|
|
36
|
-
measure: Directive<string, string>;
|
|
37
|
-
}
|
|
37
|
+
interface ComponentCustomDirectives extends AerogelDirectives {}
|
|
38
38
|
}
|
|
@@ -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/ui/UI.state.ts
CHANGED
|
@@ -4,15 +4,6 @@ import { defineServiceState } from '@aerogel/core/services/Service';
|
|
|
4
4
|
|
|
5
5
|
import { Layouts, getCurrentLayout } from './utils';
|
|
6
6
|
|
|
7
|
-
export interface UIModal<T = unknown> {
|
|
8
|
-
id: string;
|
|
9
|
-
properties: Record<string, unknown>;
|
|
10
|
-
component: Component;
|
|
11
|
-
closing: boolean;
|
|
12
|
-
beforeClose: Promise<T | undefined>;
|
|
13
|
-
afterClose: Promise<T | undefined>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
7
|
export interface UIToast {
|
|
17
8
|
id: string;
|
|
18
9
|
component: Component;
|
|
@@ -22,13 +13,11 @@ export interface UIToast {
|
|
|
22
13
|
export default defineServiceState({
|
|
23
14
|
name: 'ui',
|
|
24
15
|
initialState: {
|
|
25
|
-
modals: [] as UIModal[],
|
|
26
16
|
toasts: [] as UIToast[],
|
|
27
17
|
layout: getCurrentLayout(),
|
|
28
18
|
},
|
|
29
19
|
computed: {
|
|
30
20
|
desktop: ({ layout }) => layout === Layouts.Desktop,
|
|
31
21
|
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
32
|
-
openModals: ({ modals }) => modals.filter(({ closing }) => !closing),
|
|
33
22
|
},
|
|
34
23
|
});
|