@aerogel/core 0.1.0 → 0.1.1-next.01bc3a1d850a91b650f8cade6c0dd8e44109e0d8
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 +163 -228
- package/dist/aerogel-core.js +1501 -1524
- 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/Button.vue +1 -0
- package/src/components/ui/ConfirmModal.vue +7 -2
- 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 +0 -1
- package/src/directives/index.ts +10 -8
- package/src/directives/safe-html.ts +10 -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.
|
|
3
|
+
"version": "0.1.1-next.01bc3a1d850a91b650f8cade6c0dd8e44109e0d8",
|
|
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
|
@@ -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>
|
|
@@ -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>
|
|
@@ -19,7 +19,6 @@ export { default as Link } from './Link.vue';
|
|
|
19
19
|
export { default as LoadingModal } from './LoadingModal.vue';
|
|
20
20
|
export { default as Markdown } from './Markdown.vue';
|
|
21
21
|
export { default as Modal } from './Modal.vue';
|
|
22
|
-
export { default as ModalContext } from './ModalContext.vue';
|
|
23
22
|
export { default as ProgressBar } from './ProgressBar.vue';
|
|
24
23
|
export { default as PromptModal } from './PromptModal.vue';
|
|
25
24
|
export { default as Select } from './Select.vue';
|
package/src/directives/index.ts
CHANGED
|
@@ -3,22 +3,26 @@ import type { Directive } from 'vue';
|
|
|
3
3
|
import { definePlugin } from '@aerogel/core/plugins';
|
|
4
4
|
|
|
5
5
|
import measure from './measure';
|
|
6
|
+
import safeHtml from './safe-html';
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
measure: measure,
|
|
9
|
-
|
|
8
|
+
export const aerogelDirectives = {
|
|
9
|
+
'measure': measure,
|
|
10
|
+
'safe-html': safeHtml,
|
|
11
|
+
} as const satisfies Record<string, Directive>;
|
|
12
|
+
|
|
13
|
+
export type AerogelDirectives = typeof aerogelDirectives;
|
|
10
14
|
|
|
11
15
|
export * from './measure';
|
|
12
16
|
|
|
13
17
|
export default definePlugin({
|
|
14
18
|
install(app, options) {
|
|
15
19
|
const directives = {
|
|
16
|
-
...
|
|
20
|
+
...aerogelDirectives,
|
|
17
21
|
...options.directives,
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
for (const [name, directive] of Object.entries(directives)) {
|
|
21
|
-
app.directive(name, directive);
|
|
25
|
+
app.directive(name, directive as Directive);
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
28
|
});
|
|
@@ -30,7 +34,5 @@ declare module '@aerogel/core/bootstrap/options' {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
declare module 'vue' {
|
|
33
|
-
interface ComponentCustomDirectives {
|
|
34
|
-
measure: Directive<string, string>;
|
|
35
|
-
}
|
|
37
|
+
interface ComponentCustomDirectives extends AerogelDirectives {}
|
|
36
38
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { safeHtml } from '@aerogel/core/utils';
|
|
2
|
+
import { defineDirective } from '@aerogel/core/utils/vue';
|
|
3
|
+
|
|
4
|
+
export type SafeHTMLDirectiveValue = string;
|
|
5
|
+
|
|
6
|
+
export default defineDirective<SafeHTMLDirectiveValue>({
|
|
7
|
+
mounted(element: HTMLElement, { value }) {
|
|
8
|
+
element.innerHTML = safeHtml(value);
|
|
9
|
+
},
|
|
10
|
+
});
|
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
|
});
|