@witchcraft/ui 0.3.2 → 0.3.3
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/module.json +1 -1
- package/dist/runtime/assets/animations.css +1 -0
- package/dist/runtime/assets/base.css +1 -1
- package/dist/runtime/assets/tailwind.css +1 -1
- package/dist/runtime/components/LibNotifications/LibNotification.d.vue.ts +15 -2
- package/dist/runtime/components/LibNotifications/LibNotification.vue +84 -25
- package/dist/runtime/components/LibNotifications/LibNotification.vue.d.ts +15 -2
- package/dist/runtime/components/LibNotifications/LibNotifications.vue +110 -87
- package/dist/runtime/components/LibPopup/LibPopup.vue +2 -6
- package/dist/runtime/components/LibProgressBar/LibProgressBar.d.vue.ts +1 -0
- package/dist/runtime/components/LibProgressBar/LibProgressBar.vue +1 -1
- package/dist/runtime/components/LibProgressBar/LibProgressBar.vue.d.ts +1 -0
- package/dist/runtime/components/LibRecorder/LibRecorder.vue +1 -1
- package/dist/runtime/composables/index.d.ts +2 -0
- package/dist/runtime/composables/index.js +2 -0
- package/dist/runtime/composables/useSlotVars.d.ts +32 -0
- package/dist/runtime/composables/useSlotVars.js +12 -0
- package/dist/runtime/helpers/NotificationHandler.d.ts +11 -4
- package/dist/runtime/helpers/NotificationHandler.js +34 -16
- package/package.json +1 -1
- package/src/runtime/assets/animations.css +75 -0
- package/src/runtime/assets/base.css +0 -27
- package/src/runtime/assets/tailwind.css +1 -0
- package/src/runtime/components/LibColorPicker/LibColorPicker.stories.ts +1 -1
- package/src/runtime/components/LibNotifications/LibNotification.vue +86 -25
- package/src/runtime/components/LibNotifications/LibNotifications.stories.ts +4 -4
- package/src/runtime/components/LibNotifications/LibNotifications.vue +112 -90
- package/src/runtime/components/LibPopup/LibPopup.vue +2 -6
- package/src/runtime/components/LibProgressBar/LibProgressBar.vue +2 -1
- package/src/runtime/components/LibRecorder/LibRecorder.vue +1 -1
- package/src/runtime/composables/index.ts +2 -0
- package/src/runtime/composables/useSlotVars.ts +41 -0
- package/src/runtime/helpers/NotificationHandler.ts +44 -22
package/dist/module.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@theme{--animate-blinkInf:blink 1s linear-infinite;@keyframes blink{0%{opacity:0}25%{opacity:1}75%{opacity:1}to{opacity:0}}--animate-slideBgInf:slide-bg 10s linear-infinite;@keyframes slide{0%{background-position:0}to{background-position:100%}}--animate-hide:hide 500ms ease-in;@keyframes hide{0%{opacity:1}to{opacity:0}}--animate-slideIn:slideIn 500ms cubic-bezier(0.16,1,0.3,1);@keyframes slideIn{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}--animate-overlayShow:overlayShow 500ms cubic-bezier(0.16,1,0.3,1);@keyframes overlayShow{0%{opacity:0}to{opacity:1}}--animate-contentShow:contentShow 500ms cubic-bezier(0.16,1,0.3,1);@keyframes contentShow{0%{opacity:0;transform:translateY(-10%) scale(.96)}to{opacity:1;transform:scale(1)}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.v-enter-active,.v-leave-active{transition:opacity .5s ease}.v-enter-from,.v-leave-to{opacity:0}.list-enter-active,.list-leave-active,.list-move{transition:all .5s ease}.list-enter-from,.list-leave-to{opacity:0;transform:translateX(100%)}.list-leave-active{position:absolute}.list-leave-to,.list-move{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}
|
|
1
|
+
.v-enter-active,.v-leave-active{transition:opacity .5s ease}.v-enter-from,.v-leave-to{opacity:0}.list-enter-active,.list-leave-active,.list-move{transition:all .5s ease}.list-enter-from,.list-leave-to{opacity:0;transform:translateX(100%)}.list-leave-active{position:absolute}.list-leave-to,.list-move{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}a{@apply link-like}*{@apply styled-scrollbar}textarea{@apply styled-resizer}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@import "tailwindcss" source("../../");@import "./theme.css";@import "./base.css";@import "./utils.css";
|
|
1
|
+
@import "tailwindcss" source("../../");@import "./theme.css";@import "./animations.css";@import "./base.css";@import "./utils.css";
|
|
@@ -9,9 +9,22 @@ interface Props extends
|
|
|
9
9
|
/** @vue-ignore */
|
|
10
10
|
Partial<Omit<HTMLAttributes, "class"> & TailwindClassProp>, RealProps {
|
|
11
11
|
}
|
|
12
|
-
declare const _default: import("vue").DefineComponent<Props, {
|
|
12
|
+
declare const _default: __VLS_WithSlots<import("vue").DefineComponent<Props, {
|
|
13
13
|
focus: () => void;
|
|
14
14
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
|
|
15
15
|
handler: NotificationHandler;
|
|
16
|
-
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any
|
|
16
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
17
|
+
top?: (props: {
|
|
18
|
+
notification: any;
|
|
19
|
+
}) => any;
|
|
20
|
+
} & {
|
|
21
|
+
title?: (props: any) => any;
|
|
22
|
+
} & {
|
|
23
|
+
message?: (props: any) => any;
|
|
24
|
+
}>;
|
|
17
25
|
export default _default;
|
|
26
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
27
|
+
new (): {
|
|
28
|
+
$slots: S;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
+
v-if="notification"
|
|
3
4
|
:class="twMerge(
|
|
4
|
-
`
|
|
5
|
+
`
|
|
6
|
+
notification
|
|
5
7
|
max-w-700px
|
|
6
8
|
bg-neutral-50
|
|
7
9
|
dark:bg-neutral-900
|
|
@@ -9,11 +11,15 @@
|
|
|
9
11
|
dark:text-bg
|
|
10
12
|
border
|
|
11
13
|
border-neutral-400
|
|
14
|
+
dark:border-neutral-700
|
|
12
15
|
rounded-sm
|
|
13
16
|
focus-outline
|
|
14
|
-
flex
|
|
17
|
+
flex
|
|
18
|
+
flex-col
|
|
15
19
|
gap-2
|
|
16
|
-
p-
|
|
20
|
+
p-1
|
|
21
|
+
text-sm
|
|
22
|
+
focus:border-accent-500
|
|
17
23
|
`,
|
|
18
24
|
$attrs.class
|
|
19
25
|
)"
|
|
@@ -22,30 +28,59 @@
|
|
|
22
28
|
ref="notificationEl"
|
|
23
29
|
@keydown.enter.self="NotificationHandler.resolveToDefault(notification)"
|
|
24
30
|
>
|
|
25
|
-
<
|
|
26
|
-
|
|
31
|
+
<slot
|
|
32
|
+
name="top"
|
|
33
|
+
:notification="notification"
|
|
34
|
+
/>
|
|
35
|
+
<div
|
|
36
|
+
class="
|
|
37
|
+
notification--header
|
|
38
|
+
flex-reverse
|
|
39
|
+
flex
|
|
40
|
+
justify-between
|
|
41
|
+
items-center
|
|
42
|
+
"
|
|
43
|
+
>
|
|
44
|
+
<slot
|
|
27
45
|
v-if="notification.title"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
name="title"
|
|
47
|
+
v-bind="setSlotVar('title', {
|
|
48
|
+
title: notification.title,
|
|
49
|
+
class: `
|
|
50
|
+
notification--title
|
|
51
|
+
focus-outline
|
|
52
|
+
rounded-sm
|
|
53
|
+
font-bold
|
|
54
|
+
`,
|
|
55
|
+
tabindex: 0
|
|
56
|
+
})"
|
|
34
57
|
>
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
<div
|
|
59
|
+
v-bind="slotVars.title"
|
|
60
|
+
>
|
|
61
|
+
{{ notification.title }}
|
|
62
|
+
</div>
|
|
63
|
+
</slot>
|
|
37
64
|
<div class="notification--spacer flex-1"/>
|
|
38
65
|
<div class="actions flex">
|
|
39
66
|
<LibButton
|
|
40
67
|
:border="false"
|
|
41
|
-
class="
|
|
68
|
+
class="
|
|
69
|
+
notification--title-button
|
|
70
|
+
notification--copy-button
|
|
71
|
+
text-neutral-700
|
|
72
|
+
dark:text-neutral-300
|
|
73
|
+
"
|
|
42
74
|
@click="copy(handler ? handler.stringify(notification) : JSON.stringify(notification))"
|
|
43
75
|
>
|
|
44
76
|
<icon><i-fa6-regular-copy/></icon>
|
|
45
77
|
</LibButton>
|
|
46
78
|
<lib-button
|
|
47
79
|
v-if="notification.cancellable"
|
|
48
|
-
class="
|
|
80
|
+
class="
|
|
81
|
+
notification--title-button
|
|
82
|
+
notification--cancel-button
|
|
83
|
+
"
|
|
49
84
|
:border="false"
|
|
50
85
|
@click="NotificationHandler.dismiss(notification)"
|
|
51
86
|
>
|
|
@@ -53,12 +88,27 @@
|
|
|
53
88
|
</lib-button>
|
|
54
89
|
</div>
|
|
55
90
|
</div>
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
91
|
+
<slot
|
|
92
|
+
v-if="notification.message"
|
|
93
|
+
name="message"
|
|
94
|
+
v-bind="setSlotVar('message', {
|
|
95
|
+
class: `
|
|
96
|
+
notification--message
|
|
97
|
+
whitespace-pre-wrap
|
|
98
|
+
text-neutral-800
|
|
99
|
+
dark:text-neutral-200
|
|
100
|
+
mb-1
|
|
101
|
+
`,
|
|
102
|
+
message: notification.message,
|
|
103
|
+
tabindex: 0
|
|
104
|
+
})"
|
|
59
105
|
>
|
|
60
|
-
|
|
61
|
-
|
|
106
|
+
<div
|
|
107
|
+
v-bind="slotVars.message"
|
|
108
|
+
>
|
|
109
|
+
{{ notification.message }}
|
|
110
|
+
</div>
|
|
111
|
+
</slot>
|
|
62
112
|
<div class="notification--footer flex items-end justify-between">
|
|
63
113
|
<div
|
|
64
114
|
v-if="notification.code"
|
|
@@ -69,8 +119,11 @@
|
|
|
69
119
|
<div class="notification--footer-spacer flex-1 py-1"/>
|
|
70
120
|
<div
|
|
71
121
|
v-if="notification.options"
|
|
72
|
-
class="
|
|
73
|
-
|
|
122
|
+
class="
|
|
123
|
+
notification--options
|
|
124
|
+
flex
|
|
125
|
+
flex-wrap
|
|
126
|
+
justify-end
|
|
74
127
|
gap-2
|
|
75
128
|
"
|
|
76
129
|
>
|
|
@@ -78,11 +131,12 @@
|
|
|
78
131
|
:label="option"
|
|
79
132
|
:class="twMerge(
|
|
80
133
|
`
|
|
134
|
+
notification--button
|
|
81
135
|
notification--option-button
|
|
136
|
+
px-2
|
|
82
137
|
`,
|
|
83
|
-
|
|
138
|
+
notification.default === option && `notification--default`
|
|
84
139
|
)"
|
|
85
|
-
:border="buttonColors[i] !== 'secondary'"
|
|
86
140
|
:color="buttonColors[i]"
|
|
87
141
|
v-for="option, i in notification.options"
|
|
88
142
|
:key="option"
|
|
@@ -94,9 +148,10 @@
|
|
|
94
148
|
</template>
|
|
95
149
|
|
|
96
150
|
<script setup>
|
|
97
|
-
import { computed, ref, useAttrs } from "vue";
|
|
151
|
+
import { computed, onMounted, ref, useAttrs } from "vue";
|
|
98
152
|
import IFa6RegularCopy from "~icons/fa6-regular/copy";
|
|
99
153
|
import IFa6SolidXmark from "~icons/fa6-solid/xmark";
|
|
154
|
+
import { useSlotVars } from "../../composables/useSlotVars.js";
|
|
100
155
|
import { copy } from "../../helpers/copy.js";
|
|
101
156
|
import { NotificationHandler } from "../../helpers/NotificationHandler.js";
|
|
102
157
|
import { twMerge } from "../../utils/twMerge.js";
|
|
@@ -107,6 +162,7 @@ defineOptions({
|
|
|
107
162
|
inheritAttrs: false
|
|
108
163
|
});
|
|
109
164
|
const $attrs = useAttrs();
|
|
165
|
+
const { setSlotVar, slotVars } = useSlotVars();
|
|
110
166
|
const props = defineProps({
|
|
111
167
|
notification: { type: null, required: true },
|
|
112
168
|
handler: { type: Object, required: false, default: void 0 }
|
|
@@ -114,6 +170,9 @@ const props = defineProps({
|
|
|
114
170
|
const getColor = (notification, option) => notification.default === option ? "primary" : notification.dangerous.includes(option) ? "danger" : "secondary";
|
|
115
171
|
const buttonColors = computed(() => props.notification.options.map((option) => getColor(props.notification, option)));
|
|
116
172
|
const notificationEl = ref(null);
|
|
173
|
+
onMounted(() => {
|
|
174
|
+
notificationEl.value?.focus();
|
|
175
|
+
});
|
|
117
176
|
defineExpose({
|
|
118
177
|
focus: () => {
|
|
119
178
|
notificationEl.value?.focus();
|
|
@@ -9,9 +9,22 @@ interface Props extends
|
|
|
9
9
|
/** @vue-ignore */
|
|
10
10
|
Partial<Omit<HTMLAttributes, "class"> & TailwindClassProp>, RealProps {
|
|
11
11
|
}
|
|
12
|
-
declare const _default: import("vue").DefineComponent<Props, {
|
|
12
|
+
declare const _default: __VLS_WithSlots<import("vue").DefineComponent<Props, {
|
|
13
13
|
focus: () => void;
|
|
14
14
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
|
|
15
15
|
handler: NotificationHandler;
|
|
16
|
-
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any
|
|
16
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
17
|
+
top?: (props: {
|
|
18
|
+
notification: any;
|
|
19
|
+
}) => any;
|
|
20
|
+
} & {
|
|
21
|
+
title?: (props: any) => any;
|
|
22
|
+
} & {
|
|
23
|
+
message?: (props: any) => any;
|
|
24
|
+
}>;
|
|
17
25
|
export default _default;
|
|
26
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
27
|
+
new (): {
|
|
28
|
+
$slots: S;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -1,67 +1,129 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<!-- using custom toasts, reka-ui toasts still have issues, like like of control over pause, and I can't get the leave event to animate or transition with vue transitions to work -->
|
|
2
3
|
<TransitionGroup
|
|
3
4
|
name="list"
|
|
4
5
|
tag="div"
|
|
5
|
-
:class="twMerge(`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
:class="twMerge(`
|
|
7
|
+
notifications
|
|
8
|
+
[--notification-width:300px]
|
|
9
|
+
fixed
|
|
10
|
+
top-0
|
|
11
|
+
z-50
|
|
12
|
+
right-[calc(var(--notification-width)*-1)]
|
|
13
|
+
py-2
|
|
14
|
+
w-[calc(var(--spacing)*2+var(--notification-width)*2)]
|
|
15
|
+
[&_.notification]:w-[var(--notification-width)]
|
|
16
|
+
max-h-[100dvh]
|
|
17
|
+
flex
|
|
18
|
+
flex-col
|
|
19
|
+
[&_.notification]:shrink-0
|
|
20
|
+
gap-1
|
|
21
|
+
list-none
|
|
22
|
+
outline-none
|
|
23
|
+
overflow-y-auto
|
|
24
|
+
overflow-x-clip
|
|
25
|
+
scrollbar-hidden
|
|
26
|
+
`, $attrs.class)"
|
|
15
27
|
v-bind="{ ...$attrs, class: void 0 }"
|
|
16
28
|
>
|
|
17
29
|
<lib-notification
|
|
18
|
-
class="pointer-events-auto"
|
|
19
30
|
:handler="handler"
|
|
20
31
|
tabindex="0"
|
|
21
32
|
:notification="notification"
|
|
33
|
+
class="overflow-hidden"
|
|
22
34
|
v-for="notification of notifications"
|
|
23
35
|
:key="notification.id"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<Transition>
|
|
27
|
-
<div
|
|
28
|
-
v-show="topNotifications.length > 0"
|
|
29
|
-
:class="twMerge(`notifications--none`, $attrs.class)"
|
|
30
|
-
/>
|
|
31
|
-
</Transition>
|
|
32
|
-
<Transition>
|
|
33
|
-
<dialog
|
|
34
|
-
v-show="topNotifications.length > 0"
|
|
35
|
-
:id="id"
|
|
36
|
-
:class="twMerge(`notifications-modal
|
|
37
|
-
bg-transparent
|
|
38
|
-
p-0
|
|
39
|
-
backdrop:bg-black/50
|
|
40
|
-
backdrop:p-5
|
|
41
|
-
`, $attrs.class)"
|
|
42
|
-
ref="dialogEl"
|
|
43
|
-
@click.self.prevent="topNotifications[0] && NotificationHandler.dismiss(topNotifications[0])"
|
|
36
|
+
@pointerenter="notification.timeout && !notification.isPaused && handler.pause(notification)"
|
|
37
|
+
@blur="notification.timeout && notification.isPaused && handler.resume(notification)"
|
|
44
38
|
>
|
|
45
|
-
<
|
|
39
|
+
<template #top>
|
|
40
|
+
<LibProgressBar
|
|
41
|
+
v-if="notification.timeout !== void 0"
|
|
42
|
+
class="
|
|
43
|
+
w-full
|
|
44
|
+
h-1
|
|
45
|
+
before:duration-[10ms]
|
|
46
|
+
-mt-1
|
|
47
|
+
-mx-[calc(var(--spacing)*2+2px)]
|
|
48
|
+
rounded-none
|
|
49
|
+
"
|
|
50
|
+
:progress="100 - (notification.isPaused ? notification._timer.elapsedBeforePause : notification._timer.elapsedBeforePause + (time - notification.startTime)) / notification.timeout * 100"
|
|
51
|
+
/>
|
|
52
|
+
</template>
|
|
53
|
+
</lib-notification>
|
|
54
|
+
</TransitionGroup>
|
|
55
|
+
<!-- we don't need to worry about the user accidentally closing a non-closable dialog as keeping open=true (which the handler handles when the component tries to close) is enough to keep it open without issues -->
|
|
56
|
+
<AlertDialogRoot
|
|
57
|
+
:open="topNotifications.length > 0 && topNotifications[0] !== void 0"
|
|
58
|
+
@update:open="topNotifications[0] && NotificationHandler.dismiss(topNotifications[0])"
|
|
59
|
+
>
|
|
60
|
+
<AlertDialogPortal :to="'#root'">
|
|
61
|
+
<AlertDialogOverlay
|
|
62
|
+
class="
|
|
63
|
+
fixed inset-0 z-30
|
|
64
|
+
bg-neutral-950/20
|
|
65
|
+
data-[state=open]:animate-overlayShow
|
|
66
|
+
"
|
|
67
|
+
/>
|
|
68
|
+
<AlertDialogContent
|
|
69
|
+
class="
|
|
70
|
+
data-[state=open]:animate-contentShow
|
|
71
|
+
fixed
|
|
72
|
+
top-[50%]
|
|
73
|
+
left-[50%]
|
|
74
|
+
translate-x-[-50%]
|
|
75
|
+
translate-y-[-50%]
|
|
76
|
+
max-h-[80dvh]
|
|
77
|
+
max-w-[700px]
|
|
78
|
+
z-100
|
|
79
|
+
"
|
|
80
|
+
>
|
|
46
81
|
<lib-notification
|
|
47
|
-
|
|
82
|
+
class="
|
|
83
|
+
top-notification
|
|
84
|
+
text-md
|
|
85
|
+
gap-2
|
|
86
|
+
p-2
|
|
87
|
+
[&_.notification--button]:p-2
|
|
88
|
+
[&_.notification--button]:py-1
|
|
89
|
+
[&_.notification--header]:text-lg
|
|
90
|
+
[&_.notification--message]:py-3
|
|
91
|
+
"
|
|
48
92
|
:handler="handler"
|
|
49
|
-
class="top-notification"
|
|
50
93
|
:notification="topNotifications[0]"
|
|
51
94
|
ref="topNotificationComp"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
95
|
+
>
|
|
96
|
+
<template #title="slotProps">
|
|
97
|
+
<AlertDialogTitle v-bind="slotProps">
|
|
98
|
+
{{ slotProps.title }}
|
|
99
|
+
</AlertDialogTitle>
|
|
100
|
+
</template>
|
|
101
|
+
<template #message="slotProps">
|
|
102
|
+
<AlertDialogDescription v-bind="slotProps">
|
|
103
|
+
{{ slotProps.message }}
|
|
104
|
+
</AlertDialogDescription>
|
|
105
|
+
</template>
|
|
106
|
+
</lib-notification>
|
|
107
|
+
</AlertDialogContent>
|
|
108
|
+
</AlertDialogPortal>
|
|
109
|
+
</AlertDialogRoot>
|
|
56
110
|
</template>
|
|
57
111
|
|
|
58
112
|
<script setup>
|
|
59
|
-
import {
|
|
60
|
-
|
|
113
|
+
import {
|
|
114
|
+
AlertDialogContent,
|
|
115
|
+
AlertDialogDescription,
|
|
116
|
+
AlertDialogOverlay,
|
|
117
|
+
AlertDialogPortal,
|
|
118
|
+
AlertDialogRoot,
|
|
119
|
+
AlertDialogTitle
|
|
120
|
+
} from "reka-ui";
|
|
121
|
+
import { computed, ref } from "vue";
|
|
61
122
|
import LibNotification from "./LibNotification.vue";
|
|
62
123
|
import { useNotificationHandler } from "../../composables/useNotificationHandler.js";
|
|
63
124
|
import { NotificationHandler } from "../../helpers/NotificationHandler.js";
|
|
64
125
|
import { twMerge } from "../../utils/twMerge.js";
|
|
126
|
+
import LibProgressBar from "../LibProgressBar/LibProgressBar.vue";
|
|
65
127
|
defineOptions({
|
|
66
128
|
name: "LibNotifications",
|
|
67
129
|
inheritAttrs: false
|
|
@@ -70,54 +132,15 @@ const props = defineProps({
|
|
|
70
132
|
id: { type: String, required: false },
|
|
71
133
|
handler: { type: Object, required: false }
|
|
72
134
|
});
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
isOpen.value = true;
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const close = () => {
|
|
86
|
-
if (isOpen.value && topNotifications.length === 0) {
|
|
87
|
-
dialogEl.value.close();
|
|
88
|
-
isOpen.value = false;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
const addNotification = (entry) => {
|
|
92
|
-
if (entry.resolution === void 0) {
|
|
93
|
-
if (entry.requiresAction) {
|
|
94
|
-
topNotifications.push(entry);
|
|
95
|
-
open();
|
|
96
|
-
entry.promise.then(() => {
|
|
97
|
-
removeIfIn(topNotifications, entry);
|
|
98
|
-
close();
|
|
99
|
-
});
|
|
100
|
-
} else {
|
|
101
|
-
notifications.splice(0, 0, entry);
|
|
102
|
-
entry.promise.then(() => {
|
|
103
|
-
removeIfIn(notifications, entry);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
const notificationListener = (entry, type) => {
|
|
109
|
-
if (type === "added") {
|
|
110
|
-
addNotification(entry);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
135
|
+
const topNotifications = computed(() => handler.queue.filter((entry) => entry.requiresAction).reverse());
|
|
136
|
+
const notifications = computed(() => handler.queue.filter((entry) => !entry.requiresAction));
|
|
137
|
+
const time = ref(Date.now());
|
|
138
|
+
setInterval(() => {
|
|
139
|
+
requestAnimationFrame(() => {
|
|
140
|
+
time.value = Date.now();
|
|
141
|
+
});
|
|
142
|
+
}, 50);
|
|
113
143
|
const handler = props.handler ?? useNotificationHandler();
|
|
114
|
-
handler.addNotificationListener(notificationListener);
|
|
115
|
-
for (const entry of handler.queue) {
|
|
116
|
-
addNotification(entry);
|
|
117
|
-
}
|
|
118
|
-
onBeforeUnmount(() => {
|
|
119
|
-
handler.removeNotificationListener(notificationListener);
|
|
120
|
-
});
|
|
121
144
|
</script>
|
|
122
145
|
|
|
123
146
|
<script>
|
|
@@ -76,7 +76,7 @@ const buttonEl = ref(null);
|
|
|
76
76
|
const backgroundEl = ref(null);
|
|
77
77
|
const pos = ref({});
|
|
78
78
|
const modelValue = defineModel({ type: Boolean, ...{ default: false } });
|
|
79
|
-
let isOpen =
|
|
79
|
+
let isOpen = modelValue.value;
|
|
80
80
|
const getDialogBoundingRect = () => ({
|
|
81
81
|
x: 0,
|
|
82
82
|
y: 0,
|
|
@@ -324,10 +324,6 @@ const close = () => {
|
|
|
324
324
|
if (props.useBackdrop && props.useDialogForBackdrop) dialogEl.value?.close();
|
|
325
325
|
}
|
|
326
326
|
};
|
|
327
|
-
const toggle = () => {
|
|
328
|
-
if (!isOpen) show();
|
|
329
|
-
else close();
|
|
330
|
-
};
|
|
331
327
|
const recomputeListener = () => recompute();
|
|
332
328
|
const bindListeners = () => {
|
|
333
329
|
window.addEventListener("resize", recomputeListener);
|
|
@@ -348,7 +344,7 @@ watch([modelValue, popupEl], () => {
|
|
|
348
344
|
});
|
|
349
345
|
const handleMouseup = ($event) => {
|
|
350
346
|
$event.preventDefault();
|
|
351
|
-
|
|
347
|
+
close();
|
|
352
348
|
};
|
|
353
349
|
onMounted(() => {
|
|
354
350
|
recompute();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BaseInteractiveProps, type LabelProps, type LinkableByIdProps, type TailwindClassProp } from "../shared/props.js";
|
|
2
2
|
import type { HTMLAttributes } from "vue";
|
|
3
3
|
type RealProps = LinkableByIdProps & BaseInteractiveProps & LabelProps & {
|
|
4
|
+
/** A number from 0-100. It is auto-clamped. */
|
|
4
5
|
progress: number;
|
|
5
6
|
/** Will auto hide after this given time if progress is 100% or more or less than 0% until progress is set to something else. Disabled (-1) by default. */
|
|
6
7
|
autohideOnComplete?: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BaseInteractiveProps, type LabelProps, type LinkableByIdProps, type TailwindClassProp } from "../shared/props.js";
|
|
2
2
|
import type { HTMLAttributes } from "vue";
|
|
3
3
|
type RealProps = LinkableByIdProps & BaseInteractiveProps & LabelProps & {
|
|
4
|
+
/** A number from 0-100. It is auto-clamped. */
|
|
4
5
|
progress: number;
|
|
5
6
|
/** Will auto hide after this given time if progress is 100% or more or less than 0% until progress is set to something else. Disabled (-1) by default. */
|
|
6
7
|
autohideOnComplete?: number;
|
|
@@ -11,5 +11,7 @@ export { useNotificationHandler } from "./useNotificationHandler.js";
|
|
|
11
11
|
export { usePreHydrationValue } from "./usePreHydrationValue.js";
|
|
12
12
|
export { useScrollNearContainerEdges } from "./useScrollNearContainerEdges.js";
|
|
13
13
|
export { useSetupDarkMode } from "./useSetupDarkMode.js";
|
|
14
|
+
export { useSetupLocale } from "./useSetupLocale.js";
|
|
14
15
|
export { useShowDevOnlyKey } from "./useShowDevOnlyKey.js";
|
|
16
|
+
export { useSlotVars } from "./useSlotVars.js";
|
|
15
17
|
export { useSuggestions } from "./useSuggestions.js";
|
|
@@ -11,5 +11,7 @@ export { useNotificationHandler } from "./useNotificationHandler.js";
|
|
|
11
11
|
export { usePreHydrationValue } from "./usePreHydrationValue.js";
|
|
12
12
|
export { useScrollNearContainerEdges } from "./useScrollNearContainerEdges.js";
|
|
13
13
|
export { useSetupDarkMode } from "./useSetupDarkMode.js";
|
|
14
|
+
export { useSetupLocale } from "./useSetupLocale.js";
|
|
14
15
|
export { useShowDevOnlyKey } from "./useShowDevOnlyKey.js";
|
|
16
|
+
export { useSlotVars } from "./useSlotVars.js";
|
|
15
17
|
export { useSuggestions } from "./useSuggestions.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper for managing props passed to slots and their fallbacks.
|
|
3
|
+
*
|
|
4
|
+
* Most slots have a default style but vue makes it hard to pass them both to the slot and the fallback content without repitition.
|
|
5
|
+
*
|
|
6
|
+
* This helper allows setting the variables from the template when creating the slot WHILE also using the created state without having to use any wrapper components, {@link https://github.com/vuejs/core/issues/1172 | see also this issue}.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```vue
|
|
10
|
+
* <template>
|
|
11
|
+
* <div>
|
|
12
|
+
* <slot
|
|
13
|
+
* v-bind="setSlotVar('title', {
|
|
14
|
+
* class: 'title focus-outline flex rounded-sm font-bold'
|
|
15
|
+
* someOtherProp: true
|
|
16
|
+
* })"
|
|
17
|
+
* >
|
|
18
|
+
* <FallbackComponent v-bind="slotVars.title"/>
|
|
19
|
+
* <slot/>
|
|
20
|
+
* </div>
|
|
21
|
+
* </template>
|
|
22
|
+
* <script setup lang="ts">
|
|
23
|
+
* import { useSlotVars } from "@witchcraft/ui/composables/useSlotVars"
|
|
24
|
+
* const { slotVars, setSlotVar } = useSlotVars()
|
|
25
|
+
* </script>
|
|
26
|
+
* ```
|
|
27
|
+
* The magic is that setSlotVar both sets and returns the value for the slot name passed. You can then access the state in a fallback component by accessing slotVars[slotName]. Unfortunately this is untyped unless you set the generic in useSlotsVars, but we usually don't need them to be typed.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useSlotVars<T extends Record<string, Record<string, any>>, TKey extends keyof T>(): {
|
|
30
|
+
slotVars: import("vue").Reactive<Record<TKey, Record<string, any>>>;
|
|
31
|
+
setSlotVar: <T_1 extends Record<string, any>>(name: TKey, obj: T_1) => T_1;
|
|
32
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { AnyFunction, MakeRequired } from "@alanscodelog/utils";
|
|
2
|
+
import { type Reactive } from "vue";
|
|
2
3
|
export declare class NotificationHandler<TRawEntry extends RawNotificationEntry<any, any> = RawNotificationEntry<any, any>, TEntry extends NotificationEntry<TRawEntry> = NotificationEntry<TRawEntry>> {
|
|
3
4
|
timeout: number;
|
|
4
5
|
debug: boolean;
|
|
5
6
|
private id;
|
|
6
|
-
readonly queue: TEntry[]
|
|
7
|
-
readonly history: TEntry[]
|
|
7
|
+
readonly queue: Reactive<TEntry[]>;
|
|
8
|
+
readonly history: Readonly<TEntry[]>;
|
|
8
9
|
maxHistory: number;
|
|
9
10
|
listeners: NotificationListener<TEntry>[];
|
|
10
11
|
stringifier?: NotificationStringifier<TEntry>;
|
|
@@ -16,12 +17,12 @@ export declare class NotificationHandler<TRawEntry extends RawNotificationEntry<
|
|
|
16
17
|
private _checkEntry;
|
|
17
18
|
protected _createEntry<TNotifyEntry extends RawNotificationEntry<any, any>>(rawEntry: TNotifyEntry): TEntry;
|
|
18
19
|
notify<TNotifyEntry extends RawNotificationEntry<any, any>>(rawEntry: TNotifyEntry): NotificationPromise<TNotifyEntry["options"][number] extends string ? TNotifyEntry["options"][number] : "Ok" | "Cancel">;
|
|
20
|
+
pause(notification: NotificationEntry): void;
|
|
21
|
+
resume(notification: NotificationEntry): void;
|
|
19
22
|
static resolveToDefault(notification: NotificationEntry): void;
|
|
20
23
|
static dismiss(notification: NotificationEntry): void;
|
|
21
24
|
stringify(notification: NotificationEntry): string;
|
|
22
25
|
clear(): void;
|
|
23
|
-
addNotificationListener(cb: NotificationListener<TEntry>): void;
|
|
24
|
-
removeNotificationListener(cb: NotificationListener<TEntry>): void;
|
|
25
26
|
}
|
|
26
27
|
export type NotificationPromise<TOption extends string = string> = Promise<TOption>;
|
|
27
28
|
export type RawNotificationEntry<TOptions extends string[] = ["Ok", "Cancel"], TCancellable extends boolean | TOptions[number] = "Cancel"> = {
|
|
@@ -48,6 +49,12 @@ export type NotificationEntry<TRawEntry extends RawNotificationEntry<any, any> =
|
|
|
48
49
|
timeout?: number;
|
|
49
50
|
resolution?: string;
|
|
50
51
|
id: number;
|
|
52
|
+
startTime: number;
|
|
53
|
+
isPaused: boolean;
|
|
54
|
+
_timer: {
|
|
55
|
+
id?: ReturnType<typeof setTimeout>;
|
|
56
|
+
elapsedBeforePause: number;
|
|
57
|
+
};
|
|
51
58
|
};
|
|
52
59
|
export type NotificationListener<TEntry extends NotificationEntry<any>> = (notification: TEntry, type: "added" | "resolved" | "deleted") => void;
|
|
53
60
|
export type NotificationStringifier<T extends NotificationEntry<any>> = (notification: T) => string;
|