@witchcraft/ui 0.3.2 → 0.3.4

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.
Files changed (33) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/assets/animations.css +1 -0
  3. package/dist/runtime/assets/base.css +1 -1
  4. package/dist/runtime/assets/tailwind.css +1 -1
  5. package/dist/runtime/components/LibNotifications/LibNotification.d.vue.ts +15 -2
  6. package/dist/runtime/components/LibNotifications/LibNotification.vue +84 -25
  7. package/dist/runtime/components/LibNotifications/LibNotification.vue.d.ts +15 -2
  8. package/dist/runtime/components/LibNotifications/LibNotifications.vue +109 -87
  9. package/dist/runtime/components/LibPopup/LibPopup.vue +2 -6
  10. package/dist/runtime/components/LibProgressBar/LibProgressBar.d.vue.ts +1 -0
  11. package/dist/runtime/components/LibProgressBar/LibProgressBar.vue +1 -1
  12. package/dist/runtime/components/LibProgressBar/LibProgressBar.vue.d.ts +1 -0
  13. package/dist/runtime/components/LibRecorder/LibRecorder.vue +1 -1
  14. package/dist/runtime/composables/index.d.ts +2 -0
  15. package/dist/runtime/composables/index.js +2 -0
  16. package/dist/runtime/composables/useSlotVars.d.ts +32 -0
  17. package/dist/runtime/composables/useSlotVars.js +12 -0
  18. package/dist/runtime/helpers/NotificationHandler.d.ts +11 -4
  19. package/dist/runtime/helpers/NotificationHandler.js +34 -16
  20. package/package.json +1 -1
  21. package/src/runtime/assets/animations.css +75 -0
  22. package/src/runtime/assets/base.css +0 -27
  23. package/src/runtime/assets/tailwind.css +1 -0
  24. package/src/runtime/components/LibColorPicker/LibColorPicker.stories.ts +1 -1
  25. package/src/runtime/components/LibNotifications/LibNotification.vue +86 -25
  26. package/src/runtime/components/LibNotifications/LibNotifications.stories.ts +4 -4
  27. package/src/runtime/components/LibNotifications/LibNotifications.vue +111 -90
  28. package/src/runtime/components/LibPopup/LibPopup.vue +2 -6
  29. package/src/runtime/components/LibProgressBar/LibProgressBar.vue +2 -1
  30. package/src/runtime/components/LibRecorder/LibRecorder.vue +1 -1
  31. package/src/runtime/composables/index.ts +2 -0
  32. package/src/runtime/composables/useSlotVars.ts +41 -0
  33. package/src/runtime/helpers/NotificationHandler.ts +44 -22
@@ -4,12 +4,13 @@ import { indent } from "@alanscodelog/utils/indent";
4
4
  import { isBlank } from "@alanscodelog/utils/isBlank";
5
5
  import { pretty } from "@alanscodelog/utils/pretty";
6
6
  import { setReadOnly } from "@alanscodelog/utils/setReadOnly";
7
+ import { reactive } from "vue";
7
8
  export class NotificationHandler {
8
9
  timeout = 5e3;
9
10
  debug = false;
10
11
  id = 0;
11
- queue = [];
12
- history = [];
12
+ queue;
13
+ history;
13
14
  maxHistory = 100;
14
15
  listeners = [];
15
16
  stringifier;
@@ -18,6 +19,8 @@ export class NotificationHandler {
18
19
  stringifier,
19
20
  maxHistory
20
21
  } = {}) {
22
+ this.queue = reactive([]);
23
+ this.history = reactive([]);
21
24
  if (timeout) this.timeout = timeout;
22
25
  if (maxHistory) this.maxHistory = maxHistory;
23
26
  if (stringifier) this.stringifier = stringifier;
@@ -102,9 +105,10 @@ export class NotificationHandler {
102
105
  entry.resolve = _resolve;
103
106
  });
104
107
  if (entry.timeout !== void 0) {
105
- setTimeout(() => {
106
- entry.resolve(entry.cancellable);
107
- }, entry.timeout);
108
+ entry._timer = {
109
+ elapsedBeforePause: 0
110
+ };
111
+ this.resume(entry);
108
112
  }
109
113
  this.queue.push(entry);
110
114
  for (const listener of this.listeners) {
@@ -115,8 +119,10 @@ export class NotificationHandler {
115
119
  for (const listener of this.listeners) {
116
120
  listener(entry, "resolved");
117
121
  }
122
+ ;
118
123
  this.history.push(entry);
119
124
  if (this.history.length > this.maxHistory) {
125
+ ;
120
126
  this.history.splice(0, 1);
121
127
  for (const listener of this.listeners) {
122
128
  listener(entry, "deleted");
@@ -126,6 +132,29 @@ export class NotificationHandler {
126
132
  return res;
127
133
  });
128
134
  }
135
+ pause(notification) {
136
+ if (notification.timeout === void 0) {
137
+ throw new Error(`Cannot pause notification with no timeout: ${notification.id}`);
138
+ }
139
+ if (notification.isPaused) {
140
+ throw new Error(`Cannot pause notification that is already paused: ${notification.id}`);
141
+ }
142
+ notification.isPaused = true;
143
+ clearTimeout(notification._timer.id);
144
+ notification._timer.elapsedBeforePause += Date.now() - notification.startTime;
145
+ }
146
+ resume(notification) {
147
+ if (notification.timeout === void 0) {
148
+ throw new Error(`Cannot resume notification with no timeout: ${notification.id}`);
149
+ }
150
+ notification.isPaused = false;
151
+ notification.startTime = Date.now();
152
+ const remaining = notification.timeout - notification._timer.elapsedBeforePause;
153
+ clearTimeout(notification._timer.id);
154
+ notification._timer.id = setTimeout(() => {
155
+ notification.resolve(notification.cancellable);
156
+ }, remaining);
157
+ }
129
158
  static resolveToDefault(notification) {
130
159
  notification.resolve(notification.default);
131
160
  }
@@ -148,15 +177,4 @@ export class NotificationHandler {
148
177
  clear() {
149
178
  setReadOnly(this, "history", []);
150
179
  }
151
- addNotificationListener(cb) {
152
- this.listeners.push(cb);
153
- }
154
- removeNotificationListener(cb) {
155
- const exists = this.listeners.indexOf(cb);
156
- if (exists > -1) {
157
- this.listeners.splice(exists, 1);
158
- } else {
159
- throw new Error(`Listener does not exist: ${cb.toString()}`);
160
- }
161
- }
162
180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@witchcraft/ui",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Vue component library.",
5
5
  "type": "module",
6
6
  "main": "./dist/runtime/main.lib.js",
@@ -0,0 +1,75 @@
1
+ @theme {
2
+ --animate-blinkInf: blink 1s linear-infinite;
3
+ @keyframes blink {
4
+ 0% {
5
+ opacity: 0;
6
+ }
7
+
8
+ 25% {
9
+ opacity: 1;
10
+ }
11
+
12
+ 75% {
13
+ opacity: 1;
14
+ }
15
+
16
+ 100% {
17
+ opacity: 0;
18
+ }
19
+ }
20
+
21
+ --animate-slideBgInf: slide-bg 10s linear-infinite;
22
+ @keyframes slide {
23
+ 0% {
24
+ background-position:0%;
25
+ }
26
+ 100% {
27
+ background-position: 100%;
28
+ }
29
+ }
30
+
31
+ --animate-hide: hide 500ms ease-in;
32
+ @keyframes hide {
33
+ from {
34
+ opacity: 1;
35
+ }
36
+ to {
37
+ opacity: 0;
38
+ }
39
+ }
40
+
41
+ --animate-slideIn: slideIn 500ms cubic-bezier(0.16, 1, 0.3, 1);
42
+ @keyframes slideIn {
43
+ from {
44
+ transform: translateX(100%);
45
+ opacity: 0;
46
+ }
47
+ to {
48
+ transform: translateX(0);
49
+ opacity: 1;
50
+ }
51
+ }
52
+
53
+ --animate-overlayShow: overlayShow 500ms cubic-bezier(0.16, 1, 0.3, 1);
54
+ @keyframes overlayShow {
55
+ from {
56
+ opacity: 0;
57
+ }
58
+ to {
59
+ opacity: 1;
60
+ }
61
+ }
62
+
63
+ --animate-contentShow: contentShow 500ms cubic-bezier(0.16, 1, 0.3, 1);
64
+ @keyframes contentShow {
65
+ from {
66
+ opacity: 0;
67
+ transform: translate(0, -10%) scale(0.96);
68
+ }
69
+ to {
70
+ opacity: 1;
71
+ transform: scale(1);
72
+ }
73
+ }
74
+ }
75
+
@@ -34,33 +34,6 @@ animations can be calculated correctly. */
34
34
  user-select: none;
35
35
  }
36
36
 
37
- /* Animations */
38
- @keyframes blink {
39
- 0% {
40
- opacity: 0;
41
- }
42
-
43
- 25% {
44
- opacity: 1;
45
- }
46
-
47
- 75% {
48
- opacity: 1;
49
- }
50
-
51
- 100% {
52
- opacity: 0;
53
- }
54
- }
55
-
56
- @keyframes slide {
57
- 0% {
58
- background-position:0%;
59
- }
60
- 100% {
61
- background-position: 100%;
62
- }
63
- }
64
37
 
65
38
  a {
66
39
  @apply link-like;
@@ -1,5 +1,6 @@
1
1
  /* For storybook, not exported. */
2
2
  @import "tailwindcss" source("../../");
3
3
  @import "./theme.css";
4
+ @import "./animations.css";
4
5
  @import "./base.css";
5
6
  @import "./utils.css";
@@ -23,7 +23,7 @@ export const Primary: Story = {
23
23
  components,
24
24
  setup: () => {
25
25
  const color = ref({ r: 0, g: 0, b: 0/* , a: 0.5 */, ...(args.modelValue) })
26
- delete args.modelValue
26
+ delete (args as any).modelValue
27
27
  const handleChange = (e: any) => {
28
28
  color.value = { ...e }
29
29
  }
@@ -1,6 +1,8 @@
1
1
  <template>
2
2
  <div
3
- :class="twMerge(`notification
3
+ v-if="notification"
4
+ :class="twMerge(`
5
+ notification
4
6
  max-w-700px
5
7
  bg-neutral-50
6
8
  dark:bg-neutral-900
@@ -8,11 +10,15 @@
8
10
  dark:text-bg
9
11
  border
10
12
  border-neutral-400
13
+ dark:border-neutral-700
11
14
  rounded-sm
12
15
  focus-outline
13
- flex-flex-col
16
+ flex
17
+ flex-col
14
18
  gap-2
15
- p-2 m-2
19
+ p-1
20
+ text-sm
21
+ focus:border-accent-500
16
22
  `,
17
23
  ($attrs as any).class)"
18
24
  v-bind="{ ...$attrs, class: undefined }"
@@ -20,30 +26,59 @@
20
26
  ref="notificationEl"
21
27
  @keydown.enter.self="NotificationHandler.resolveToDefault(notification)"
22
28
  >
23
- <div class="notification--header flex-reverse flex justify-between">
24
- <div
29
+ <slot
30
+ name="top"
31
+ :notification="notification"
32
+ />
33
+ <div
34
+ class="
35
+ notification--header
36
+ flex-reverse
37
+ flex
38
+ justify-between
39
+ items-center
40
+ "
41
+ >
42
+ <slot
25
43
  v-if="notification.title"
26
- tabindex="0"
27
- class="title
28
- focus-outline flex
29
- rounded-sm
30
- font-bold
31
- "
44
+ name="title"
45
+ v-bind="setSlotVar('title', {
46
+ title: notification.title,
47
+ class: `
48
+ notification--title
49
+ focus-outline
50
+ rounded-sm
51
+ font-bold
52
+ `,
53
+ tabindex: 0
54
+ })"
32
55
  >
33
- {{ notification.title }}
34
- </div>
56
+ <div
57
+ v-bind="slotVars.title"
58
+ >
59
+ {{ notification.title }}
60
+ </div>
61
+ </slot>
35
62
  <div class="notification--spacer flex-1"/>
36
63
  <div class="actions flex">
37
64
  <LibButton
38
65
  :border="false"
39
- class="notification--copy-button text-neutral-700"
66
+ class="
67
+ notification--title-button
68
+ notification--copy-button
69
+ text-neutral-700
70
+ dark:text-neutral-300
71
+ "
40
72
  @click="copy(handler ? handler.stringify(notification) : JSON.stringify(notification))"
41
73
  >
42
74
  <icon><i-fa6-regular-copy/></icon>
43
75
  </LibButton>
44
76
  <lib-button
45
77
  v-if="notification.cancellable"
46
- class="notification--cancel-button"
78
+ class="
79
+ notification--title-button
80
+ notification--cancel-button
81
+ "
47
82
  :border="false"
48
83
  @click="NotificationHandler.dismiss(notification)"
49
84
  >
@@ -51,12 +86,27 @@
51
86
  </lib-button>
52
87
  </div>
53
88
  </div>
54
- <div
55
- class="notification--message whitespace-pre-wrap"
56
- tabindex="0"
89
+ <slot
90
+ v-if="notification.message"
91
+ name="message"
92
+ v-bind="setSlotVar('message', {
93
+ class: `
94
+ notification--message
95
+ whitespace-pre-wrap
96
+ text-neutral-800
97
+ dark:text-neutral-200
98
+ mb-1
99
+ `,
100
+ message: notification.message,
101
+ tabindex: 0
102
+ })"
57
103
  >
58
- {{ notification.message }}
59
- </div>
104
+ <div
105
+ v-bind="slotVars.message"
106
+ >
107
+ {{ notification.message }}
108
+ </div>
109
+ </slot>
60
110
  <div class="notification--footer flex items-end justify-between">
61
111
  <div
62
112
  v-if="notification.code"
@@ -67,19 +117,23 @@
67
117
  <div class="notification--footer-spacer flex-1 py-1"/>
68
118
  <div
69
119
  v-if="notification.options"
70
- class="notification--options
71
- flex flex-wrap justify-end
120
+ class="
121
+ notification--options
122
+ flex
123
+ flex-wrap
124
+ justify-end
72
125
  gap-2
73
126
  "
74
127
  >
75
128
  <lib-button
76
129
  :label="option"
77
130
  :class="twMerge(`
131
+ notification--button
78
132
  notification--option-button
133
+ px-2
79
134
  `,
80
- buttonColors[i] == 'secondary' && 'p-0'
135
+ notification.default === option && `notification--default`
81
136
  )"
82
- :border="buttonColors[i] !== 'secondary'"
83
137
  :color="buttonColors[i]"
84
138
  v-for="option, i in notification.options"
85
139
  :key="option"
@@ -91,11 +145,12 @@
91
145
  </template>
92
146
 
93
147
  <script setup lang="ts">
94
- import { computed, type HTMLAttributes, ref, useAttrs } from "vue"
148
+ import { computed, type HTMLAttributes, onMounted, ref, useAttrs } from "vue"
95
149
 
96
150
  import IFa6RegularCopy from "~icons/fa6-regular/copy"
97
151
  import IFa6SolidXmark from "~icons/fa6-solid/xmark"
98
152
 
153
+ import { useSlotVars } from "../../composables/useSlotVars.js"
99
154
  import { copy } from "../../helpers/copy.js"
100
155
  import { type NotificationEntry, NotificationHandler } from "../../helpers/NotificationHandler.js"
101
156
  import { twMerge } from "../../utils/twMerge.js"
@@ -109,6 +164,9 @@ defineOptions({
109
164
  })
110
165
  const $attrs = useAttrs()
111
166
 
167
+ const { setSlotVar, slotVars } = useSlotVars()
168
+
169
+
112
170
  const props = withDefaults(defineProps<Props>(), {
113
171
  handler: undefined
114
172
  })
@@ -120,6 +178,9 @@ const getColor = (notification: NotificationEntry, option: string): "ok" | "prim
120
178
  const buttonColors = computed(() => props.notification.options.map((option: any /* what ??? */) => getColor(props.notification, option)))
121
179
 
122
180
  const notificationEl = ref<null | HTMLElement>(null)
181
+ onMounted(() => {
182
+ notificationEl.value?.focus()
183
+ })
123
184
  defineExpose({
124
185
  focus: () => {
125
186
  notificationEl.value?.focus()
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/naming-convention */
2
2
  import type { Meta, StoryObj } from "@storybook/vue3"
3
- import { reactive, ref } from "vue"
3
+ import { ref } from "vue"
4
4
 
5
5
  import LibNotifications from "./LibNotifications.vue"
6
6
 
@@ -23,7 +23,7 @@ export const Primary: Story = {
23
23
  render: args => ({
24
24
  components,
25
25
  setup() {
26
- const handler = reactive(new NotificationHandler())
26
+ const handler = new NotificationHandler({})
27
27
 
28
28
  let count = 0
29
29
 
@@ -76,7 +76,7 @@ export const Primary: Story = {
76
76
  void handler.notify({
77
77
  title: withTitle.value ? `Notification(${count})` : undefined,
78
78
  message: `This is a notification. No action required.`,
79
- timeout: disableTimeout.value ? false : 2000
79
+ timeout: disableTimeout.value ? false : 5000
80
80
  })
81
81
  }
82
82
  return {
@@ -97,7 +97,7 @@ export const Primary: Story = {
97
97
  backgrounds: { disable: true },
98
98
  // <lib-debug>{{args.handler}}</lib-debug>
99
99
  template: `
100
- <lib-root :outline="args.outline">
100
+ <lib-root :outline="args.outline" :notification-handler="handler">
101
101
  <lib-button :label="'Notify Timeoutable'" @click="notifyTimeoutable()"></lib-button>
102
102
  <lib-button :label="'Notify RequiresAction'" @click="notifyRequiresAction()"></lib-button>
103
103
  <lib-button :label="'Notify Non-Cancellable that RequiresAction'" @click="notifyNonCancellableRequiresAction()"></lib-button>
@@ -1,69 +1,130 @@
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(`notifications
6
- absolute
7
- z-50
8
- inset-y-0 right-0
9
- w-1/3
10
- min-w-[300px]
11
- pointer-events-none
12
- overflow-hidden
13
- flex flex-col
14
- `, ($attrs as any).class)"
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
+ w-[calc(var(--spacing)*2+var(--notification-width)*2)]
14
+ [&_.notification]:w-[var(--notification-width)]
15
+ max-h-[100dvh]
16
+ flex
17
+ flex-col
18
+ [&_.notification]:shrink-0
19
+ gap-1
20
+ list-none
21
+ outline-none
22
+ overflow-y-auto
23
+ overflow-x-clip
24
+ scrollbar-hidden
25
+ `, ($attrs as any).class)"
15
26
  v-bind="{ ...$attrs, class: undefined }"
16
27
  >
17
28
  <lib-notification
18
- class="pointer-events-auto"
19
29
  :handler="handler"
20
30
  tabindex="0"
21
31
  :notification="notification"
32
+ class="overflow-hidden my-2"
22
33
  v-for="notification of notifications"
23
34
  :key="notification.id"
24
- />
25
- </TransitionGroup>
26
- <Transition>
27
- <div
28
- v-show="topNotifications.length > 0"
29
- :class="twMerge(`notifications--none`, ($attrs as any).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 as any).class)"
42
- ref="dialogEl"
43
- @click.self.prevent="topNotifications[0] && NotificationHandler.dismiss(topNotifications[0])"
35
+ @pointerenter="notification.timeout && !notification.isPaused && handler.pause(notification)"
36
+ @blur="notification.timeout && notification.isPaused && handler.resume(notification)"
44
37
  >
45
- <form>
38
+ <template #top>
39
+ <LibProgressBar
40
+ v-if="notification.timeout !== undefined"
41
+ class="
42
+ w-full
43
+ h-1
44
+ before:duration-[10ms]
45
+ -mt-1
46
+ -mx-[calc(var(--spacing)*2+2px)]
47
+ rounded-none
48
+ "
49
+ :progress="100 - (((notification.isPaused ? (notification._timer.elapsedBeforePause): (notification._timer.elapsedBeforePause + (time - notification.startTime))) / notification.timeout) * 100)"
50
+ />
51
+ </template>
52
+ </lib-notification>
53
+ </TransitionGroup>
54
+ <!-- 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 -->
55
+ <AlertDialogRoot
56
+ :open="topNotifications.length > 0 && topNotifications[0] !== undefined"
57
+ @update:open="topNotifications[0] && NotificationHandler.dismiss(topNotifications[0])"
58
+ >
59
+ <AlertDialogPortal :to="'#root'">
60
+ <AlertDialogOverlay
61
+ class="
62
+ fixed inset-0 z-30
63
+ bg-neutral-950/20
64
+ data-[state=open]:animate-overlayShow
65
+ "
66
+ />
67
+ <AlertDialogContent
68
+ class="
69
+ data-[state=open]:animate-contentShow
70
+ fixed
71
+ top-[50%]
72
+ left-[50%]
73
+ translate-x-[-50%]
74
+ translate-y-[-50%]
75
+ max-h-[80dvh]
76
+ max-w-[700px]
77
+ z-100
78
+ "
79
+ >
46
80
  <lib-notification
47
- v-if="topNotifications.length > 0 && topNotifications[0]"
81
+ class="
82
+ top-notification
83
+ text-md
84
+ gap-2
85
+ p-2
86
+ [&_.notification--button]:p-2
87
+ [&_.notification--button]:py-1
88
+ [&_.notification--header]:text-lg
89
+ [&_.notification--message]:py-3
90
+ "
48
91
  :handler="handler"
49
- class="top-notification"
50
- :notification="topNotifications[0]"
92
+ :notification="topNotifications[0]!"
51
93
  ref="topNotificationComp"
52
- />
53
- </form>
54
- </dialog>
55
- </Transition>
94
+ >
95
+ <template #title="slotProps">
96
+ <AlertDialogTitle v-bind="slotProps">
97
+ {{ slotProps.title }}
98
+ </AlertDialogTitle>
99
+ </template>
100
+ <template #message="slotProps">
101
+ <AlertDialogDescription v-bind="slotProps">
102
+ {{ slotProps.message }}
103
+ </AlertDialogDescription>
104
+ </template>
105
+ </lib-notification>
106
+ </AlertDialogContent>
107
+ </AlertDialogPortal>
108
+ </AlertDialogRoot>
56
109
  </template>
57
110
 
58
111
  <script setup lang="ts">
59
- import { removeIfIn } from "@alanscodelog/utils/removeIfIn"
60
- import { nextTick, onBeforeUnmount, ref, shallowReactive } from "vue"
112
+ import {
113
+ AlertDialogContent,
114
+ AlertDialogDescription,
115
+ AlertDialogOverlay,
116
+ AlertDialogPortal,
117
+ AlertDialogRoot,
118
+ AlertDialogTitle
119
+ } from "reka-ui"
120
+ import { computed, ref } from "vue"
61
121
 
62
122
  import LibNotification from "./LibNotification.vue"
63
123
 
64
124
  import { useNotificationHandler } from "../../composables/useNotificationHandler.js"
65
- import { type NotificationEntry, NotificationHandler } from "../../helpers/NotificationHandler.js"
125
+ import { NotificationHandler } from "../../helpers/NotificationHandler.js"
66
126
  import { twMerge } from "../../utils/twMerge.js"
127
+ import LibProgressBar from "../LibProgressBar/LibProgressBar.vue"
67
128
  import type { LinkableByIdProps, TailwindClassProp } from "../shared/props.js"
68
129
 
69
130
  defineOptions({
@@ -73,58 +134,18 @@ defineOptions({
73
134
 
74
135
  const props = defineProps<Props>()
75
136
 
76
- const dialogEl = ref<HTMLDialogElement | null>(null)
137
+ const topNotifications = computed(() => handler.queue.filter(entry => entry.requiresAction).reverse())
138
+ const notifications = computed(() => handler.queue.filter(entry => !entry.requiresAction))
77
139
 
78
- const isOpen = ref(false)
79
- const notifications = shallowReactive<NotificationEntry[]>([])
80
- const topNotifications = shallowReactive<NotificationEntry[]>([])
81
- const open = () => {
82
- if (!isOpen.value) {
83
- void nextTick(() => {
84
- dialogEl.value!.showModal()
85
- isOpen.value = true
86
- })
87
- }
88
- }
89
- const close = () => {
90
- if (isOpen.value && topNotifications.length === 0) {
91
- dialogEl.value!.close()
92
- isOpen.value = false
93
- }
94
- }
140
+ const time = ref(Date.now())
141
+ setInterval(() => {
142
+ requestAnimationFrame(() => {
143
+ time.value = Date.now()
144
+ })
145
+ }, 50)
95
146
 
96
- const addNotification = (entry: NotificationEntry) => {
97
- if (entry.resolution === undefined) {
98
- if (entry.requiresAction) {
99
- topNotifications.push(entry)
100
- open()
101
- entry.promise.then(() => {
102
- removeIfIn(topNotifications, entry)
103
- close()
104
- })
105
- } else {
106
- notifications.splice(0, 0, entry)
107
- entry.promise.then(() => {
108
- removeIfIn(notifications, entry)
109
- })
110
- }
111
- }
112
- }
113
-
114
- const notificationListener = (entry: NotificationEntry, type: "added" | "resolved" | "deleted"): void => {
115
- if (type === "added") {
116
- addNotification(entry)
117
- }
118
- }
119
147
 
120
148
  const handler = props.handler ?? useNotificationHandler()
121
-
122
- handler.addNotificationListener(notificationListener)
123
-
124
- for (const entry of handler.queue) { addNotification(entry) }
125
- onBeforeUnmount(() => {
126
- handler.removeNotificationListener(notificationListener)
127
- })
128
149
  </script>
129
150
 
130
151
  <script lang="ts">