@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
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "witchcraftUi",
3
3
  "configKey": "witchcraftUi",
4
- "version": "0.3.2",
4
+ "version": "0.3.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -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}@keyframes blink{0%{opacity:0}25%{opacity:1}75%{opacity:1}to{opacity:0}}@keyframes slide{0%{background-position:0}to{background-position:100%}}a{@apply link-like}*{@apply styled-scrollbar}textarea{@apply styled-resizer}
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
- `notification
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-flex-col
17
+ flex
18
+ flex-col
15
19
  gap-2
16
- p-2 m-2
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
- <div class="notification--header flex-reverse flex justify-between">
26
- <div
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
- tabindex="0"
29
- class="title
30
- focus-outline flex
31
- rounded-sm
32
- font-bold
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
- {{ notification.title }}
36
- </div>
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="notification--copy-button text-neutral-700"
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="notification--cancel-button"
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
- <div
57
- class="notification--message whitespace-pre-wrap"
58
- tabindex="0"
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
- {{ notification.message }}
61
- </div>
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="notification--options
73
- flex flex-wrap justify-end
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
- buttonColors[i] == 'secondary' && 'p-0'
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,128 @@
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.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.class)"
15
26
  v-bind="{ ...$attrs, class: void 0 }"
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.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])"
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 !== void 0"
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] !== void 0"
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
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>
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
  import LibNotification from "./LibNotification.vue";
62
122
  import { useNotificationHandler } from "../../composables/useNotificationHandler.js";
63
123
  import { NotificationHandler } from "../../helpers/NotificationHandler.js";
64
124
  import { twMerge } from "../../utils/twMerge.js";
125
+ import LibProgressBar from "../LibProgressBar/LibProgressBar.vue";
65
126
  defineOptions({
66
127
  name: "LibNotifications",
67
128
  inheritAttrs: false
@@ -70,54 +131,15 @@ const props = defineProps({
70
131
  id: { type: String, required: false },
71
132
  handler: { type: Object, required: false }
72
133
  });
73
- const dialogEl = ref(null);
74
- const isOpen = ref(false);
75
- const notifications = shallowReactive([]);
76
- const topNotifications = shallowReactive([]);
77
- const open = () => {
78
- if (!isOpen.value) {
79
- void nextTick(() => {
80
- dialogEl.value.showModal();
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
- };
134
+ const topNotifications = computed(() => handler.queue.filter((entry) => entry.requiresAction).reverse());
135
+ const notifications = computed(() => handler.queue.filter((entry) => !entry.requiresAction));
136
+ const time = ref(Date.now());
137
+ setInterval(() => {
138
+ requestAnimationFrame(() => {
139
+ time.value = Date.now();
140
+ });
141
+ }, 50);
113
142
  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
143
  </script>
122
144
 
123
145
  <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 = false;
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
- toggle();
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;
@@ -28,7 +28,7 @@
28
28
  before:shadow-black/50
29
29
  before:rounded-sm
30
30
  before:bg-bars-gradient
31
- before:animate-[slide_10s_linear_infinite]
31
+ before:animate-slideBgInf
32
32
  before:[background-size:15px_15px]
33
33
  before:absolute
34
34
  before:w-[var(--progress)]
@@ -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;
@@ -55,7 +55,7 @@
55
55
  hover:bg-red-500
56
56
  `,
57
57
  recording && `
58
- animate-[blink_1s_infinite]
58
+ animate-blinkInf
59
59
  bg-red-500
60
60
  `,
61
61
  (disabled || readonly) && `
@@ -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
+ };
@@ -0,0 +1,12 @@
1
+ import { reactive } from "vue";
2
+ export function useSlotVars() {
3
+ const state = reactive({});
4
+ function setSlotVar(name, obj) {
5
+ state[name] = obj;
6
+ return state[name];
7
+ }
8
+ return {
9
+ slotVars: state,
10
+ setSlotVar
11
+ };
12
+ }
@@ -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;