@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.
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 +110 -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 +112 -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.3",
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,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(`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
+ 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
- </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])"
36
+ @pointerenter="notification.timeout && !notification.isPaused && handler.pause(notification)"
37
+ @blur="notification.timeout && notification.isPaused && handler.resume(notification)"
44
38
  >
45
- <form>
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
- v-if="topNotifications.length > 0 && topNotifications[0]"
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
- </form>
54
- </dialog>
55
- </Transition>
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 { removeIfIn } from "@alanscodelog/utils/removeIfIn";
60
- import { nextTick, onBeforeUnmount, ref, shallowReactive } from "vue";
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 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
- };
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 = 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;