@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
|
@@ -86,7 +86,7 @@ const backgroundEl = ref<IPopupReference | null>(null)
|
|
|
86
86
|
|
|
87
87
|
const pos = ref<PopupPosition>({} as any)
|
|
88
88
|
const modelValue = defineModel<boolean>({ default: false })
|
|
89
|
-
let isOpen =
|
|
89
|
+
let isOpen = modelValue.value
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* We don't have access to the dialog backdrop and without extra styling, it's of 0 width/height, positioned in the center of the screen, with margins taking up all the space.
|
|
@@ -341,10 +341,6 @@ const close = () => {
|
|
|
341
341
|
}
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
const toggle = () => {
|
|
345
|
-
if (!isOpen) show()
|
|
346
|
-
else close()
|
|
347
|
-
}
|
|
348
344
|
|
|
349
345
|
const recomputeListener = () => recompute()
|
|
350
346
|
|
|
@@ -369,7 +365,7 @@ watch([modelValue, popupEl], () => {
|
|
|
369
365
|
|
|
370
366
|
const handleMouseup = ($event: MouseEvent) => {
|
|
371
367
|
$event.preventDefault()
|
|
372
|
-
|
|
368
|
+
close()
|
|
373
369
|
}
|
|
374
370
|
onMounted(() => {
|
|
375
371
|
recompute()
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
before:shadow-black/50
|
|
29
29
|
before:rounded-sm
|
|
30
30
|
before:bg-bars-gradient
|
|
31
|
-
before:animate-
|
|
31
|
+
before:animate-slideBgInf
|
|
32
32
|
before:[background-size:15px_15px]
|
|
33
33
|
before:absolute
|
|
34
34
|
before:w-[var(--progress)]
|
|
@@ -171,6 +171,7 @@ type RealProps
|
|
|
171
171
|
& BaseInteractiveProps
|
|
172
172
|
& LabelProps
|
|
173
173
|
& {
|
|
174
|
+
/** A number from 0-100. It is auto-clamped. */
|
|
174
175
|
progress: number
|
|
175
176
|
/** 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. */
|
|
176
177
|
autohideOnComplete?: number
|
|
@@ -13,5 +13,7 @@ export { useNotificationHandler } from "./useNotificationHandler.js"
|
|
|
13
13
|
export { usePreHydrationValue } from "./usePreHydrationValue.js"
|
|
14
14
|
export { useScrollNearContainerEdges } from "./useScrollNearContainerEdges.js"
|
|
15
15
|
export { useSetupDarkMode } from "./useSetupDarkMode.js"
|
|
16
|
+
export { useSetupLocale } from "./useSetupLocale.js"
|
|
16
17
|
export { useShowDevOnlyKey } from "./useShowDevOnlyKey.js"
|
|
18
|
+
export { useSlotVars } from "./useSlotVars.js"
|
|
17
19
|
export { useSuggestions } from "./useSuggestions.js"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { reactive } from "vue"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper for managing props passed to slots and their fallbacks.
|
|
5
|
+
*
|
|
6
|
+
* Most slots have a default style but vue makes it hard to pass them both to the slot and the fallback content without repitition.
|
|
7
|
+
*
|
|
8
|
+
* 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}.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```vue
|
|
12
|
+
* <template>
|
|
13
|
+
* <div>
|
|
14
|
+
* <slot
|
|
15
|
+
* v-bind="setSlotVar('title', {
|
|
16
|
+
* class: 'title focus-outline flex rounded-sm font-bold'
|
|
17
|
+
* someOtherProp: true
|
|
18
|
+
* })"
|
|
19
|
+
* >
|
|
20
|
+
* <FallbackComponent v-bind="slotVars.title"/>
|
|
21
|
+
* <slot/>
|
|
22
|
+
* </div>
|
|
23
|
+
* </template>
|
|
24
|
+
* <script setup lang="ts">
|
|
25
|
+
* import { useSlotVars } from "@witchcraft/ui/composables/useSlotVars"
|
|
26
|
+
* const { slotVars, setSlotVar } = useSlotVars()
|
|
27
|
+
* </script>
|
|
28
|
+
* ```
|
|
29
|
+
* 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.
|
|
30
|
+
*/
|
|
31
|
+
export function useSlotVars<T extends Record<string, Record<string, any>>, TKey extends keyof T>() {
|
|
32
|
+
const state = reactive<Record<TKey, Record<string, any>>>({} as any)
|
|
33
|
+
function setSlotVar<T extends Record<string, any>>(name: TKey, obj: T): T {
|
|
34
|
+
state[name as keyof typeof state] = obj as any
|
|
35
|
+
return state[name as keyof typeof state] as T
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
slotVars: state,
|
|
39
|
+
setSlotVar
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -5,6 +5,7 @@ import { indent } from "@alanscodelog/utils/indent"
|
|
|
5
5
|
import { isBlank } from "@alanscodelog/utils/isBlank"
|
|
6
6
|
import { pretty } from "@alanscodelog/utils/pretty"
|
|
7
7
|
import { setReadOnly } from "@alanscodelog/utils/setReadOnly"
|
|
8
|
+
import { type Reactive, reactive } from "vue"
|
|
8
9
|
|
|
9
10
|
export class NotificationHandler<
|
|
10
11
|
TRawEntry extends RawNotificationEntry<any, any> = RawNotificationEntry<any, any>,
|
|
@@ -16,9 +17,9 @@ export class NotificationHandler<
|
|
|
16
17
|
|
|
17
18
|
private id: number = 0
|
|
18
19
|
|
|
19
|
-
readonly queue: TEntry[]
|
|
20
|
+
readonly queue: Reactive<TEntry[]>
|
|
20
21
|
|
|
21
|
-
readonly history: TEntry[]
|
|
22
|
+
readonly history: Readonly<TEntry[]>
|
|
22
23
|
|
|
23
24
|
maxHistory: number = 100
|
|
24
25
|
|
|
@@ -35,6 +36,8 @@ export class NotificationHandler<
|
|
|
35
36
|
stringifier?: NotificationHandler<TRawEntry>["stringifier"]
|
|
36
37
|
maxHistory?: NotificationHandler<TRawEntry>["maxHistory"]
|
|
37
38
|
} = {}) {
|
|
39
|
+
this.queue = reactive([])
|
|
40
|
+
this.history = reactive([])
|
|
38
41
|
if (timeout) this.timeout = timeout
|
|
39
42
|
if (maxHistory) this.maxHistory = maxHistory
|
|
40
43
|
if (stringifier) this.stringifier = stringifier
|
|
@@ -126,11 +129,12 @@ export class NotificationHandler<
|
|
|
126
129
|
}) as NotificationPromise
|
|
127
130
|
|
|
128
131
|
if (entry.timeout !== undefined) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
+
entry._timer = {
|
|
133
|
+
elapsedBeforePause: 0
|
|
134
|
+
}
|
|
135
|
+
this.resume(entry as any)
|
|
132
136
|
}
|
|
133
|
-
this.queue.push(entry)
|
|
137
|
+
this.queue.push(entry as any)
|
|
134
138
|
for (const listener of this.listeners) {
|
|
135
139
|
listener(entry, "added")
|
|
136
140
|
}
|
|
@@ -140,18 +144,43 @@ export class NotificationHandler<
|
|
|
140
144
|
for (const listener of this.listeners) {
|
|
141
145
|
listener(entry, "resolved")
|
|
142
146
|
}
|
|
143
|
-
this.history.push(entry)
|
|
147
|
+
;(this.history as any).push(entry)
|
|
144
148
|
if (this.history.length > this.maxHistory) {
|
|
145
|
-
this.history.splice(0, 1)
|
|
149
|
+
;(this.history as any).splice(0, 1)
|
|
146
150
|
for (const listener of this.listeners) {
|
|
147
151
|
listener(entry, "deleted")
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
|
-
this.queue.splice(this.queue.indexOf(entry), 1)
|
|
154
|
+
this.queue.splice(this.queue.indexOf(entry as any), 1)
|
|
151
155
|
return res
|
|
152
156
|
}) satisfies NotificationPromise as any
|
|
153
157
|
}
|
|
154
158
|
|
|
159
|
+
pause(notification: NotificationEntry): void {
|
|
160
|
+
if (notification.timeout === undefined) {
|
|
161
|
+
throw new Error(`Cannot pause notification with no timeout: ${notification.id}`)
|
|
162
|
+
}
|
|
163
|
+
if (notification.isPaused) {
|
|
164
|
+
throw new Error(`Cannot pause notification that is already paused: ${notification.id}`)
|
|
165
|
+
}
|
|
166
|
+
notification.isPaused = true
|
|
167
|
+
clearTimeout(notification._timer.id)
|
|
168
|
+
notification._timer.elapsedBeforePause += (Date.now() - notification.startTime)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
resume(notification: NotificationEntry): void {
|
|
172
|
+
if (notification.timeout === undefined) {
|
|
173
|
+
throw new Error(`Cannot resume notification with no timeout: ${notification.id}`)
|
|
174
|
+
}
|
|
175
|
+
notification.isPaused = false
|
|
176
|
+
notification.startTime = Date.now()
|
|
177
|
+
const remaining = notification.timeout - notification._timer.elapsedBeforePause
|
|
178
|
+
clearTimeout(notification._timer.id)
|
|
179
|
+
notification._timer.id = setTimeout(() => {
|
|
180
|
+
notification.resolve(notification.cancellable)
|
|
181
|
+
}, remaining)
|
|
182
|
+
}
|
|
183
|
+
|
|
155
184
|
static resolveToDefault(notification: NotificationEntry): void {
|
|
156
185
|
notification.resolve(notification.default)
|
|
157
186
|
}
|
|
@@ -174,19 +203,6 @@ export class NotificationHandler<
|
|
|
174
203
|
clear(): void {
|
|
175
204
|
setReadOnly(this, "history", [])
|
|
176
205
|
}
|
|
177
|
-
|
|
178
|
-
addNotificationListener(cb: NotificationListener<TEntry>): void {
|
|
179
|
-
this.listeners.push(cb)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
removeNotificationListener(cb: NotificationListener<TEntry>): void {
|
|
183
|
-
const exists = this.listeners.indexOf(cb)
|
|
184
|
-
if (exists > -1) {
|
|
185
|
-
this.listeners.splice(exists, 1)
|
|
186
|
-
} else {
|
|
187
|
-
throw new Error(`Listener does not exist: ${cb.toString()}`)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
206
|
}
|
|
191
207
|
|
|
192
208
|
export type NotificationPromise<TOption extends string = string> = Promise<TOption>
|
|
@@ -221,6 +237,12 @@ export type NotificationEntry<
|
|
|
221
237
|
timeout?: number
|
|
222
238
|
resolution?: string
|
|
223
239
|
id: number
|
|
240
|
+
startTime: number
|
|
241
|
+
isPaused: boolean
|
|
242
|
+
_timer: {
|
|
243
|
+
id?: ReturnType<typeof setTimeout>
|
|
244
|
+
elapsedBeforePause: number
|
|
245
|
+
}
|
|
224
246
|
}
|
|
225
247
|
|
|
226
248
|
export type NotificationListener<TEntry extends NotificationEntry<any>> = (notification: TEntry, type: "added" | "resolved" | "deleted") => void
|