@xen-orchestra/web-core 0.31.1 → 0.32.0

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 (64) hide show
  1. package/lib/assets/css/_colors.pcss +8 -0
  2. package/lib/components/button-group/VtsButtonGroup.vue +5 -1
  3. package/lib/components/menu/MenuList.vue +1 -0
  4. package/lib/components/modal/VtsModal.vue +82 -0
  5. package/lib/components/modal/VtsModalButton.vue +36 -0
  6. package/lib/components/modal/VtsModalCancelButton.vue +37 -0
  7. package/lib/components/modal/VtsModalConfirmButton.vue +21 -0
  8. package/lib/components/modal/VtsModalList.vue +34 -0
  9. package/lib/components/object-icon/VtsObjectIcon.vue +3 -8
  10. package/lib/components/status/VtsStatus.vue +66 -0
  11. package/lib/components/task/VtsQuickTaskList.vue +17 -5
  12. package/lib/components/tree/VtsTreeItem.vue +2 -2
  13. package/lib/components/ui/button/UiButton.vue +13 -67
  14. package/lib/components/ui/input/UiInput.vue +4 -1
  15. package/lib/components/ui/modal/UiModal.vue +164 -0
  16. package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +2 -2
  17. package/lib/composables/context.composable.ts +3 -5
  18. package/lib/composables/link-component.composable.ts +3 -2
  19. package/lib/composables/pagination.composable.ts +3 -2
  20. package/lib/composables/tree-filter.composable.ts +5 -3
  21. package/lib/icons/fa-icons.ts +4 -0
  22. package/lib/icons/index.ts +17 -0
  23. package/lib/locales/en.json +14 -1
  24. package/lib/locales/fr.json +14 -1
  25. package/lib/packages/collection/use-collection.ts +3 -2
  26. package/lib/packages/form-select/use-form-option-controller.ts +3 -2
  27. package/lib/packages/form-select/use-form-select.ts +8 -7
  28. package/lib/packages/menu/action.ts +4 -3
  29. package/lib/packages/menu/link.ts +5 -4
  30. package/lib/packages/menu/router-link.ts +3 -2
  31. package/lib/packages/menu/toggle-target.ts +3 -2
  32. package/lib/packages/modal/ModalProvider.vue +17 -0
  33. package/lib/packages/modal/README.md +253 -0
  34. package/lib/packages/modal/create-modal-opener.ts +103 -0
  35. package/lib/packages/modal/modal.store.ts +22 -0
  36. package/lib/packages/modal/types.ts +92 -0
  37. package/lib/packages/modal/use-modal.ts +53 -0
  38. package/lib/packages/progress/use-progress.ts +4 -3
  39. package/lib/packages/table/README.md +336 -0
  40. package/lib/packages/table/apply-extensions.ts +26 -0
  41. package/lib/packages/table/define-columns.ts +62 -0
  42. package/lib/packages/table/define-renderer/define-table-cell-renderer.ts +27 -0
  43. package/lib/packages/table/define-renderer/define-table-renderer.ts +47 -0
  44. package/lib/packages/table/define-renderer/define-table-row-renderer.ts +29 -0
  45. package/lib/packages/table/define-renderer/define-table-section-renderer.ts +29 -0
  46. package/lib/packages/table/define-table/define-multi-source-table.ts +39 -0
  47. package/lib/packages/table/define-table/define-table.ts +13 -0
  48. package/lib/packages/table/define-table/define-typed-table.ts +18 -0
  49. package/lib/packages/table/index.ts +11 -0
  50. package/lib/packages/table/transform-sources.ts +13 -0
  51. package/lib/packages/table/types/extensions.ts +16 -0
  52. package/lib/packages/table/types/index.ts +47 -0
  53. package/lib/packages/table/types/table-cell.ts +18 -0
  54. package/lib/packages/table/types/table-row.ts +20 -0
  55. package/lib/packages/table/types/table-section.ts +19 -0
  56. package/lib/packages/table/types/table.ts +28 -0
  57. package/lib/packages/threshold/use-threshold.ts +4 -3
  58. package/lib/types/vue-virtual-scroller.d.ts +101 -0
  59. package/lib/utils/injection-keys.util.ts +3 -0
  60. package/lib/utils/progress.util.ts +2 -1
  61. package/lib/utils/to-computed.util.ts +15 -0
  62. package/package.json +3 -2
  63. package/lib/components/backup-state/VtsBackupState.vue +0 -37
  64. package/lib/components/connection-status/VtsConnectionStatus.vue +0 -36
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <slot />
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import { IK_MODAL, type RegisteredModal } from '@core/packages/modal/types.ts'
7
+ import { computed, provide } from 'vue'
8
+
9
+ const { modal } = defineProps<{
10
+ modal: RegisteredModal
11
+ }>()
12
+
13
+ provide(
14
+ IK_MODAL,
15
+ computed(() => modal)
16
+ )
17
+ </script>
@@ -0,0 +1,253 @@
1
+ # Modal System
2
+
3
+ ### Modals list component
4
+
5
+ First, create a component which will display the modals.
6
+
7
+ If needed, you can use the `ModalProvider` component to provide the `modal` to children components.
8
+
9
+ For example:
10
+
11
+ ```vue
12
+ <template>
13
+ <div v-if="modalStore.modals.length > 0" class="modals">
14
+ <ModalProvider v-for="modal of modalStore.modals" :key="modal.id" :modal>
15
+ <component :is="modal.component" class="modal" v-bind="modal.bindings" />
16
+ </ModalProvider>
17
+ </div>
18
+ </template>
19
+
20
+ <script lang="ts" setup>
21
+ const modalStore = useModalStore()
22
+ </script>
23
+
24
+ <style lang="postcss" scoped>
25
+ .modals {
26
+ position: fixed;
27
+ inset: 0;
28
+ background-color: var(--color-opacity-primary);
29
+ z-index: 1010;
30
+
31
+ .modal:not(:last-child) {
32
+ filter: brightness(0.8);
33
+ }
34
+ }
35
+ </style>
36
+ ```
37
+
38
+ ### Modal component
39
+
40
+ You then need to create a Modal component which will emit `confirm` and `cancel` events, as needed.
41
+
42
+ ```vue
43
+ <template>
44
+ <div class="modal-container">
45
+ <div class="modal">
46
+ <h1>My modal</h1>
47
+ <button @click="emit('confirm')">Confirm</button>
48
+ <button @click="emit('cancel')">Cancel</button>
49
+ </div>
50
+ </div>
51
+ </template>
52
+
53
+ <script lang="ts" setup>
54
+ const emit = defineEmits<{
55
+ confirm: []
56
+ cancel: []
57
+ }>()
58
+ </script>
59
+
60
+ <style lang="postcss" scoped>
61
+ .modal-container {
62
+ position: fixed;
63
+ inset: 0;
64
+ display: flex;
65
+ justify-content: center;
66
+ align-items: center;
67
+ }
68
+
69
+ .modal {
70
+ background: white;
71
+ border-radius: 8px;
72
+ padding: 2rem;
73
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
74
+ }
75
+ </style>
76
+ ```
77
+
78
+ ### Opening a modal (`useModal` composable)
79
+
80
+ You can create a function to open a modal with `useModal`
81
+
82
+ ```ts
83
+ const openModal = useModal(config)
84
+ ```
85
+
86
+ | Property | Type | Required | Default | Description |
87
+ | --------------------- | ------------------------------------- | :------: | :---------: | -------------------------------------------------------------------------------------------------------------------------------------------- |
88
+ | component | `Promise<Component>` | ✓ | | The promise of the modal component to open. (e.g: `component: import('path/to/modal.vue')` |
89
+ | props | `Record<string, MaybeRef<any>>` | | `{}` | The props to pass to the modal component |
90
+ | onConfirm | `(...args: any[]) => TConfirmPayload` | | `undefined` | An optional callback to call when the modal is confirmed. It will take the args of the `confirm` event of the modal's `defineEmits`, if any. |
91
+ | onCancel | `(...args: any[]) => TCancelPayload` | | `undefined` | An optional callback to call when the modal is cancelled. It will take the args of the `cancel` event of the modal's `defineEmits`, if any. |
92
+ | keepOpenOnRouteChange | `boolean` | | `false` | By default, the modal will close when the use navigates away from the page. If `true`, the modal will stay open until it is closed manually. |
93
+
94
+ The result of `openModal()` will be `Promise<ModalConfirmResponse<TConfirmPayload> | ModalCancelResponse<TCancelPayload>>`
95
+
96
+ If `onConfirm` / `onCancel` returns a `Promise`, then the modal will not close until the promise is resolved, and its `busy` state will be set to `true`.
97
+
98
+ ```ts
99
+ const openMyModal = useModal({
100
+ component: import('path/to/MyModal.vue'),
101
+ props: {
102
+ foo: 'Foo',
103
+ bar: computed(() => someBar),
104
+ },
105
+ onConfirm: () => console.log('Confirmed!'),
106
+ onCancel: () => console.log('Cancelled!'),
107
+ })
108
+ ```
109
+
110
+ ```html
111
+ <button @click="openModal()">Open modal</button>
112
+ ```
113
+
114
+ ## Modal payload
115
+
116
+ Modal payloads are fully typed.
117
+
118
+ ```ts
119
+ const openMyModal = useModal({
120
+ component: import('path/to/MyModal.vue'),
121
+ onConfirm: () => 'Hello',
122
+ onCancel: () => 1234,
123
+ })
124
+
125
+ const result = await openMyModal()
126
+
127
+ if (result.confirmed) {
128
+ result.payload // string
129
+ } else {
130
+ result.payload // number
131
+ }
132
+ ```
133
+
134
+ ## Opening a modal with arguments
135
+
136
+ You can pass a function returning a config instead of a config object.
137
+
138
+ ```ts
139
+ const openMyModal = useModal((name: string) => ({
140
+ component: import('path/to/MyModal.vue'),
141
+ props: { name },
142
+ onConfirm: () => console.log('Confirmed for', name),
143
+ onCancel: () => console.log('Cancelled for', name),
144
+ })
145
+ ```
146
+
147
+ ```html
148
+ <button @click="openMyModal('John')">Open John modal</button>
149
+ <button @click="openMyModal('Jane')">Open Jane modal</button>
150
+ ```
151
+
152
+ ## `onConfirm` and `onCancel` event args
153
+
154
+ If your Modal component defines args for `confirm` and `cancel` events, you'll get them as arguments of the `onConfirm` and `onCancel` callbacks.
155
+
156
+ ```vue
157
+ <!-- MyModal.vue -->
158
+ <template>
159
+ <div class="modal-container">
160
+ <div class="modal">
161
+ <h1>My modal</h1>
162
+ <button @click="emit('confirm', 1)">Select 1</button>
163
+ <button @click="emit('confirm', 10)">Select 10</button>
164
+ <button @click="emit('cancel')">Cancel</button>
165
+ </div>
166
+ </div>
167
+ </template>
168
+
169
+ <script lang="ts" setup>
170
+ defineEmits<{
171
+ confirm: [count: number]
172
+ cancel: []
173
+ }>()
174
+ ```
175
+
176
+ ```ts
177
+ const openModal = useModal({
178
+ component: import('path/to/MyModal.vue'),
179
+ onConfirm: (count: number) => console.log('Confirmed with count', count),
180
+ })
181
+ </script>
182
+ ```
183
+
184
+ ## Opening a modal from elsewhere
185
+
186
+ `useModal` muse be called at the root of your component.
187
+
188
+ If you need to open a modal from elsewhere, you can use `useModal()` with no arguments to get a function that you can call later to open the modal.
189
+
190
+ ```ts
191
+ const openModal = useModal()
192
+
193
+ function myHandler() {
194
+ return someApiManager.doSomething(myArg, openModal)
195
+ }
196
+
197
+ // api-manager.ts
198
+
199
+ function doSomething(arg: string, openModal: OpenModal) {
200
+ // ... do something
201
+
202
+ openModal('some-id', {
203
+ component: import('path/to/MyModal.vue'),
204
+ props: { arg },
205
+ onConfirm: () => console.log('Confirmed!'),
206
+ onCancel: () => console.log('Cancelled!'),
207
+ })
208
+ }
209
+ ```
210
+
211
+ ## Chaining modals
212
+
213
+ You can open a modal from another modal.
214
+
215
+ For example, you can open a confirmation modal ("Are you sure...") from the `onConfirm` of a delete modal, or from the `onCancel` of a form modal which has unsaved changes.
216
+
217
+ If the second modal is canceled, the first modal will stay open.
218
+
219
+ ```ts
220
+ const openModal = useModal()
221
+
222
+ const openDeleteModal = useModal({
223
+ component: import('path/to/DeleteModal.vue'),
224
+ onConfirm: () =>
225
+ openModal('confirm-delete', {
226
+ component: import('path/to/ConfirmModal.vue'),
227
+ props: { message: 'Are you sure?' },
228
+ onConfirm: async () => {
229
+ console.log('Deleting...')
230
+ await deleteResource()
231
+ },
232
+ }),
233
+ })
234
+ ```
235
+
236
+ ## Aborting the closing of a modal
237
+
238
+ If you want to prevent a modal from closing manually, you can return the `ABORT_MODAL` symbol from the `onConfirm` or `onCancel` handlers.
239
+
240
+ ```ts
241
+ const openModal = useModal()
242
+
243
+ const openDeleteModal = useModal({
244
+ component: import('path/to/DeleteModal.vue'),
245
+ onConfirm: async () => {
246
+ try {
247
+ await tryingToDeleteResource()
248
+ } catch (e) {
249
+ return ABORT_MODAL
250
+ }
251
+ },
252
+ })
253
+ ```
@@ -0,0 +1,103 @@
1
+ import { useModalStore } from '@core/packages/modal/modal.store.ts'
2
+ import {
3
+ ABORT_MODAL,
4
+ ModalCancelResponse,
5
+ type ModalConfig,
6
+ ModalConfirmResponse,
7
+ type ModalHandlerArgs,
8
+ type ModalResponse,
9
+ } from '@core/packages/modal/types.ts'
10
+ import { computed, defineAsyncComponent, reactive, ref, watch } from 'vue'
11
+ import { useRoute } from 'vue-router'
12
+
13
+ export function createModalOpener() {
14
+ const modalStore = useModalStore()
15
+
16
+ const route = useRoute()
17
+
18
+ const ids = new Set<symbol | string>()
19
+
20
+ function closeById(id: string | symbol) {
21
+ modalStore.removeModal(id)
22
+ ids.delete(id)
23
+ }
24
+
25
+ watch(
26
+ () => route.path,
27
+ () => {
28
+ ids.forEach(id => {
29
+ if (!modalStore.getModal(id)?.keepOpenOnRouteChange) {
30
+ closeById(id)
31
+ }
32
+ })
33
+ }
34
+ )
35
+
36
+ return function openModal<
37
+ TProps,
38
+ TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
39
+ TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
40
+ TConfirmPayload = undefined,
41
+ TCancelPayload = undefined,
42
+ >(id: string | symbol, config: ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>) {
43
+ const close = () => closeById(id)
44
+
45
+ const promise = new Promise<ModalResponse<TConfirmPayload, TCancelPayload>>(resolve => {
46
+ const isBusy = ref(false)
47
+
48
+ modalStore.addModal({
49
+ id,
50
+ component: defineAsyncComponent(() => config.component),
51
+ isBusy: computed(() => isBusy.value),
52
+ props: reactive(config.props ?? {}),
53
+ keepOpenOnRouteChange: config.keepOpenOnRouteChange ?? false,
54
+ onConfirm: async (...args: TConfirmArgs) => {
55
+ try {
56
+ isBusy.value = true
57
+
58
+ const result = config.onConfirm ? await config.onConfirm(...args) : (undefined as TConfirmPayload)
59
+
60
+ if (result === ABORT_MODAL || result instanceof ModalCancelResponse) {
61
+ return
62
+ }
63
+
64
+ if (result instanceof ModalConfirmResponse) {
65
+ resolve(result)
66
+ } else {
67
+ resolve(new ModalConfirmResponse(result))
68
+ }
69
+
70
+ close()
71
+ } finally {
72
+ isBusy.value = false
73
+ }
74
+ },
75
+ onCancel: async (...args: TCancelArgs) => {
76
+ try {
77
+ isBusy.value = true
78
+
79
+ const result = config.onCancel ? await config.onCancel(...args) : (undefined as TCancelPayload)
80
+
81
+ if (result === ABORT_MODAL || result instanceof ModalCancelResponse) {
82
+ return
83
+ }
84
+
85
+ if (result instanceof ModalConfirmResponse) {
86
+ resolve(new ModalCancelResponse(result.payload))
87
+ } else {
88
+ resolve(new ModalCancelResponse(result))
89
+ }
90
+
91
+ close()
92
+ } finally {
93
+ isBusy.value = false
94
+ }
95
+ },
96
+ })
97
+ })
98
+
99
+ return Object.assign(promise, {
100
+ close,
101
+ })
102
+ }
103
+ }
@@ -0,0 +1,22 @@
1
+ import type { RegisteredModal } from '@core/packages/modal/types.ts'
2
+ import { defineStore } from 'pinia'
3
+ import { computed, shallowReactive } from 'vue'
4
+
5
+ export const useModalStore = defineStore('new-modal', () => {
6
+ const modals = shallowReactive(new Map<symbol | string, RegisteredModal>())
7
+
8
+ const addModal = (modal: RegisteredModal) => {
9
+ modals.set(modal.id, modal)
10
+ }
11
+
12
+ const removeModal = (id: symbol | string) => {
13
+ modals.delete(id)
14
+ }
15
+
16
+ return {
17
+ modals: computed(() => Array.from(modals.values())),
18
+ getModal: (id: symbol | string) => modals.get(id),
19
+ addModal,
20
+ removeModal,
21
+ }
22
+ })
@@ -0,0 +1,92 @@
1
+ import type { MaybeRef } from '@vueuse/core'
2
+ import type { Component, ComputedRef, InjectionKey } from 'vue'
3
+
4
+ export type ModalPropsOption<TProps> = {
5
+ [K in keyof TProps]: MaybeRef<TProps[K]>
6
+ }
7
+
8
+ export type MaybePromise<T> = T | Promise<T>
9
+
10
+ export type AreAllPropsOptional<TProps> = Record<string, never> extends TProps ? true : false
11
+
12
+ export type RegisteredModal = {
13
+ id: symbol | string
14
+ component: Component
15
+ keepOpenOnRouteChange: boolean
16
+ props: object
17
+ isBusy: ComputedRef<boolean>
18
+ onConfirm: (...args: any[]) => void | Promise<void>
19
+ onCancel: (...args: any[]) => void | Promise<void>
20
+ }
21
+
22
+ export type ModalHandlerArgs<TProps, THandler extends 'onConfirm' | 'onCancel'> = TProps extends {
23
+ [K in THandler]?: (...args: infer TArgs) => any
24
+ }
25
+ ? TArgs
26
+ : never
27
+
28
+ export abstract class AbstractModalResponse<TPayload> {
29
+ constructor(public readonly payload: TPayload) {}
30
+
31
+ abstract get confirmed(): boolean
32
+ abstract get canceled(): boolean
33
+ }
34
+
35
+ export class ModalConfirmResponse<TPayload> extends AbstractModalResponse<TPayload> {
36
+ public readonly confirmed = true
37
+ public readonly canceled = false
38
+ }
39
+
40
+ export class ModalCancelResponse<TPayload> extends AbstractModalResponse<TPayload> {
41
+ public readonly confirmed = false
42
+ public readonly canceled = true
43
+ }
44
+
45
+ export type ModalResponse<TConfirmPayload, TCancelPayload> =
46
+ | ModalConfirmResponse<TConfirmPayload>
47
+ | ModalCancelResponse<TCancelPayload>
48
+
49
+ export const ABORT_MODAL = Symbol('abort modal')
50
+
51
+ export type OpenModalReturn<TConfirmPayload, TCancelPayload> = Promise<
52
+ ModalResponse<TConfirmPayload, TCancelPayload>
53
+ > & {
54
+ close: () => void
55
+ }
56
+
57
+ export const IK_MODAL = Symbol('modal') as InjectionKey<ComputedRef<RegisteredModal>>
58
+
59
+ export type ModalPropsConfigEntry<TProps> =
60
+ AreAllPropsOptional<TProps> extends true ? { props?: ModalPropsOption<TProps> } : { props: ModalPropsOption<TProps> }
61
+
62
+ export type ModalConfig<
63
+ TProps,
64
+ TConfirmArgs extends any[],
65
+ TCancelArgs extends any[],
66
+ TConfirmPayload,
67
+ TCancelPayload,
68
+ > = {
69
+ component: Promise<{
70
+ default: abstract new () => {
71
+ $props: TProps
72
+ }
73
+ }>
74
+ onConfirm?: (...args: TConfirmArgs) => MaybePromise<ModalResponse<TConfirmPayload, any> | TConfirmPayload>
75
+ onCancel?: (...args: TCancelArgs) => MaybePromise<ModalResponse<TCancelPayload, any> | TCancelPayload>
76
+ keepOpenOnRouteChange?: boolean
77
+ } & ModalPropsConfigEntry<TProps>
78
+
79
+ export type UseModalReturn<TArgs extends any[] = any[], TConfirmPayload = any, TCancelPayload = any> = (
80
+ ...args: TArgs
81
+ ) => OpenModalReturn<TConfirmPayload, TCancelPayload>
82
+
83
+ export type OpenModal = <
84
+ TProps,
85
+ TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
86
+ TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
87
+ TConfirmPayload = undefined,
88
+ TCancelPayload = undefined,
89
+ >(
90
+ id: string,
91
+ config: ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
92
+ ) => OpenModalReturn<TConfirmPayload, TCancelPayload>
@@ -0,0 +1,53 @@
1
+ import { createModalOpener } from '@core/packages/modal/create-modal-opener.ts'
2
+ import { type ModalConfig, type ModalHandlerArgs, type UseModalReturn } from '@core/packages/modal/types.ts'
3
+
4
+ export function useModal(): ReturnType<typeof createModalOpener>
5
+
6
+ export function useModal<
7
+ TProps,
8
+ TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
9
+ TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
10
+ TConfirmPayload = undefined,
11
+ TCancelPayload = undefined,
12
+ >(
13
+ config: ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
14
+ ): UseModalReturn<[], TConfirmPayload, TCancelPayload>
15
+
16
+ export function useModal<
17
+ TConfigBuilderArgs extends any[],
18
+ TProps,
19
+ TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
20
+ TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
21
+ TConfirmPayload = undefined,
22
+ TCancelPayload = undefined,
23
+ >(
24
+ configBuilder: (
25
+ ...args: TConfigBuilderArgs
26
+ ) => ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>
27
+ ): UseModalReturn<TConfigBuilderArgs, TConfirmPayload, TCancelPayload>
28
+
29
+ export function useModal<
30
+ TConfigBuilderArgs extends any[],
31
+ TProps,
32
+ TConfirmArgs extends ModalHandlerArgs<TProps, 'onConfirm'>,
33
+ TCancelArgs extends ModalHandlerArgs<TProps, 'onCancel'>,
34
+ TConfig extends ModalConfig<TProps, TConfirmArgs, TCancelArgs, TConfirmPayload, TCancelPayload>,
35
+ TConfirmPayload = undefined,
36
+ TCancelPayload = undefined,
37
+ >(
38
+ configOrBuilder?: TConfig | ((...args: TConfigBuilderArgs) => TConfig)
39
+ ): UseModalReturn<TConfigBuilderArgs, TConfirmPayload, TCancelPayload> | ReturnType<typeof createModalOpener> {
40
+ const openModal = createModalOpener()
41
+
42
+ if (configOrBuilder === undefined) {
43
+ return openModal
44
+ }
45
+
46
+ const id = Symbol('modal')
47
+
48
+ return (...args: TConfigBuilderArgs) => {
49
+ const config = typeof configOrBuilder === 'function' ? configOrBuilder(...args) : configOrBuilder
50
+
51
+ return openModal(id, config)
52
+ }
53
+ }
@@ -1,10 +1,11 @@
1
+ import { toComputed } from '@core/utils/to-computed.util.ts'
1
2
  import type { Progress } from './types.ts'
2
- import { computed, type MaybeRefOrGetter, toValue } from 'vue'
3
+ import { computed, type MaybeRefOrGetter } from 'vue'
3
4
 
4
5
  export function useProgress(rawCurrent: MaybeRefOrGetter<number>, rawTotal: MaybeRefOrGetter<number>): Progress {
5
- const current = computed(() => toValue(rawCurrent))
6
+ const current = toComputed(rawCurrent)
6
7
 
7
- const total = computed(() => toValue(rawTotal))
8
+ const total = toComputed(rawTotal)
8
9
 
9
10
  const percentage = computed(() => (total.value === 0 ? 0 : (current.value / total.value) * 100))
10
11