@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
@@ -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 = false
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
- toggle()
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-[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)]
@@ -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
@@ -53,7 +53,7 @@
53
53
  hover:bg-red-500
54
54
  `,
55
55
  recording && `
56
- animate-[blink_1s_infinite]
56
+ animate-blinkInf
57
57
  bg-red-500
58
58
  `,
59
59
  (disabled || readonly) && `
@@ -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
- setTimeout(() => {
130
- entry.resolve(entry.cancellable)
131
- }, entry.timeout)
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