@vue-modality/core 0.1.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.
- package/README.md +233 -0
- package/dist/index.cjs +379 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +131 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +338 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# @vue-modality/core
|
|
2
|
+
|
|
3
|
+
Modal system for Vue 3 with namespace queues, close guards, and prompt pattern.
|
|
4
|
+
|
|
5
|
+
- **Namespace queues** — dialogs, toasts, and sidebars live in isolated stacks
|
|
6
|
+
- **Close guards** — async functions that can cancel or delay closing
|
|
7
|
+
- **Prompt pattern** — `await` a result directly from a modal
|
|
8
|
+
- **Event bus** — `on`/`emit` between the modal and its caller
|
|
9
|
+
- **Zero dependencies** — only `vue` as a peer dependency
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pnpm add https://github.com/rh00x/vue-modality
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
Mount the renderers once in your root component. They subscribe to the reactive queues and render whatever is currently in them.
|
|
20
|
+
|
|
21
|
+
```vue
|
|
22
|
+
<!-- App.vue -->
|
|
23
|
+
<template>
|
|
24
|
+
<RouterView />
|
|
25
|
+
|
|
26
|
+
<DialogRenderer />
|
|
27
|
+
<ToastRenderer />
|
|
28
|
+
<SidebarRenderer />
|
|
29
|
+
</template>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
A renderer is just a component that reads the queue and renders `<component :is="...">`. Here is a minimal example:
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<!-- DialogRenderer.vue -->
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import { useDialog } from '@vue-modality/core'
|
|
38
|
+
|
|
39
|
+
const { dialogs } = useDialog()
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<Teleport to="body">
|
|
44
|
+
<div v-if="dialogs.length" class="overlay" @click.self="dialogs.at(-1)?.close()">
|
|
45
|
+
<component
|
|
46
|
+
v-for="dialog in dialogs"
|
|
47
|
+
:key="dialog.id"
|
|
48
|
+
:is="dialog.component"
|
|
49
|
+
v-bind="dialog.props.value"
|
|
50
|
+
:modal="dialog"
|
|
51
|
+
@close="dialog.close()"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
</Teleport>
|
|
55
|
+
</template>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### Dialog
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { useDialog } from '@vue-modality/core'
|
|
64
|
+
|
|
65
|
+
const { openDialog, closeDialog, promptDialog, dialogs } = useDialog()
|
|
66
|
+
|
|
67
|
+
// Open — closes any existing dialogs first
|
|
68
|
+
await openDialog(MyDialog, { title: 'Hello' })
|
|
69
|
+
|
|
70
|
+
// Close the topmost dialog
|
|
71
|
+
await closeDialog()
|
|
72
|
+
|
|
73
|
+
// Close a specific dialog by ID
|
|
74
|
+
await closeDialog(dialog.id)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Prompt
|
|
78
|
+
|
|
79
|
+
Open a dialog and wait for a typed result. The modal resolves by calling `props.modal.emit(MODAL_EVENT_PROMPT, value)`.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { useDialog, MODAL_EVENT_PROMPT } from '@vue-modality/core'
|
|
83
|
+
|
|
84
|
+
const { promptDialog } = useDialog()
|
|
85
|
+
|
|
86
|
+
const result = await promptDialog<string>(ConfirmDialog, { message: 'Are you sure?' })
|
|
87
|
+
// result is the value passed to MODAL_EVENT_PROMPT, or null if closed without a result
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Inside the modal component:
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<script setup lang="ts">
|
|
94
|
+
import { MODAL_EVENT_PROMPT, type ModalRecord } from '@vue-modality/core'
|
|
95
|
+
|
|
96
|
+
const props = defineProps<{ modal: ModalRecord }>()
|
|
97
|
+
|
|
98
|
+
function confirm() {
|
|
99
|
+
props.modal.emit(MODAL_EVENT_PROMPT, 'confirmed')
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Toast
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { useToast } from '@vue-modality/core'
|
|
108
|
+
|
|
109
|
+
const { openToast, closeToast, closeAllToasts, toasts } = useToast()
|
|
110
|
+
|
|
111
|
+
// Toasts auto-close after 5 seconds
|
|
112
|
+
openToast(MyToast, { message: 'Saved!', type: 'success' })
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Sidebar
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { useSidebarPanel } from '@vue-modality/core'
|
|
119
|
+
|
|
120
|
+
const { openSidebar, closeSidebar, sidebar } = useSidebarPanel()
|
|
121
|
+
|
|
122
|
+
await openSidebar(MyPanel, { userId: 42 })
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Close guards
|
|
126
|
+
|
|
127
|
+
A close guard is an async function registered on a modal. If it returns `false` (or throws), the close is cancelled.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { useDialog } from '@vue-modality/core'
|
|
131
|
+
|
|
132
|
+
const { openDialog } = useDialog()
|
|
133
|
+
|
|
134
|
+
const dialog = await openDialog(EditDialog)
|
|
135
|
+
|
|
136
|
+
dialog.addCloseGuard(async () => {
|
|
137
|
+
const confirmed = await confirm('Discard changes?')
|
|
138
|
+
return confirmed // false cancels close
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Stack (push / pop)
|
|
143
|
+
|
|
144
|
+
Use `pushModal` / `popModal` directly to manage a stack without closing other modals.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { pushModal, popModal } from '@vue-modality/core'
|
|
148
|
+
|
|
149
|
+
// Push on top of the current stack
|
|
150
|
+
const modal = pushModal(MyModal, { step: 1 }, { namespace: 'dialog' })
|
|
151
|
+
|
|
152
|
+
// Close only the topmost modal
|
|
153
|
+
await popModal({ namespace: 'dialog' })
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Event bus
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { pushModal } from '@vue-modality/core'
|
|
160
|
+
|
|
161
|
+
const modal = pushModal(MyModal, {})
|
|
162
|
+
|
|
163
|
+
// Caller subscribes
|
|
164
|
+
const unsubscribe = modal.on('result', (value) => {
|
|
165
|
+
console.log('Got:', value)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Inside the modal component
|
|
169
|
+
props.modal.emit('result', { foo: 'bar' })
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Low-level API
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import {
|
|
176
|
+
openModal, // close all + push
|
|
177
|
+
pushModal, // push without closing
|
|
178
|
+
popModal, // close topmost
|
|
179
|
+
closeModal, // close all in namespace
|
|
180
|
+
closeById, // close by modal ID
|
|
181
|
+
getCurrentModal, // get topmost modal
|
|
182
|
+
getQueueByNamespace, // get raw reactive queue
|
|
183
|
+
} from '@vue-modality/core'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Namespaces
|
|
187
|
+
|
|
188
|
+
Every hook uses its own namespace constant:
|
|
189
|
+
|
|
190
|
+
| Hook | Namespace constant | Default value |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `useDialog` | `DIALOG_NAMESPACE` | `'dialog'` |
|
|
193
|
+
| `useToast` | `TOAST_NAMESPACE` | `'toast'` |
|
|
194
|
+
| `useSidebarPanel` | `SIDEBAR_NAMESPACE` | `'sidebar'` |
|
|
195
|
+
|
|
196
|
+
You can create custom namespaces by passing `{ namespace: 'my-namespace' }` to any low-level method.
|
|
197
|
+
|
|
198
|
+
## Types
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import type {
|
|
202
|
+
ModalRecord, // the modal instance object
|
|
203
|
+
ModalID, // number alias
|
|
204
|
+
ModalOptions, // { namespace, isRoute }
|
|
205
|
+
ModalCloseOptions, // { namespace }
|
|
206
|
+
GuardFunction, // () => void | boolean | Promise<boolean>
|
|
207
|
+
NamespaceKey, // string | number
|
|
208
|
+
ComponentProps, // extracts $props type from a component constructor
|
|
209
|
+
} from '@vue-modality/core'
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### `ModalRecord`
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
interface ModalRecord {
|
|
216
|
+
readonly id: ModalID
|
|
217
|
+
readonly component: Component
|
|
218
|
+
readonly namespace: NamespaceKey
|
|
219
|
+
readonly isRoute: boolean
|
|
220
|
+
readonly closed: Ref<boolean>
|
|
221
|
+
props: Ref<any>
|
|
222
|
+
|
|
223
|
+
close(): Promise<void>
|
|
224
|
+
emit(eventName: string, data?: unknown): void
|
|
225
|
+
on<T>(eventName: string, callback: (v: T) => void): () => void
|
|
226
|
+
addCloseGuard(fn: GuardFunction): void
|
|
227
|
+
addDestroyGuard(fn: () => void): void
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DIALOG_NAMESPACE: () => DIALOG_NAMESPACE,
|
|
24
|
+
MODAL_EVENT_PROMPT: () => MODAL_EVENT_PROMPT,
|
|
25
|
+
SIDEBAR_NAMESPACE: () => SIDEBAR_NAMESPACE,
|
|
26
|
+
TOAST_NAMESPACE: () => TOAST_NAMESPACE,
|
|
27
|
+
closeById: () => closeById,
|
|
28
|
+
closeModal: () => closeModal,
|
|
29
|
+
getCurrentModal: () => getCurrentModal,
|
|
30
|
+
getQueueByNamespace: () => getQueueByNamespace,
|
|
31
|
+
openModal: () => openModal,
|
|
32
|
+
popModal: () => popModal,
|
|
33
|
+
promptModal: () => promptModal,
|
|
34
|
+
pushModal: () => pushModal,
|
|
35
|
+
useDialog: () => useDialog,
|
|
36
|
+
useSidebarPanel: () => useSidebarPanel,
|
|
37
|
+
useToast: () => useToast
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/modal/state.ts
|
|
42
|
+
var import_vue = require("vue");
|
|
43
|
+
var DEFAULT_NAMESPACE = "default";
|
|
44
|
+
var namespaceMap = /* @__PURE__ */ new Map();
|
|
45
|
+
function getNamespaceState(namespace = DEFAULT_NAMESPACE) {
|
|
46
|
+
if (!namespaceMap.has(namespace)) {
|
|
47
|
+
namespaceMap.set(namespace, { queue: (0, import_vue.reactive)([]) });
|
|
48
|
+
}
|
|
49
|
+
return namespaceMap.get(namespace);
|
|
50
|
+
}
|
|
51
|
+
function getQueueByNamespace(namespace) {
|
|
52
|
+
return getNamespaceState(namespace).queue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/modal/methods/close-modal.ts
|
|
56
|
+
async function closeModal(options = {}) {
|
|
57
|
+
const queue = [...getNamespaceState(options.namespace).queue].reverse();
|
|
58
|
+
for (const modal of queue) {
|
|
59
|
+
await modal.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/modal/methods/push-modal.ts
|
|
64
|
+
var import_vue3 = require("vue");
|
|
65
|
+
|
|
66
|
+
// src/modal/modal.ts
|
|
67
|
+
var import_vue2 = require("vue");
|
|
68
|
+
|
|
69
|
+
// src/modal/guards.ts
|
|
70
|
+
var store = /* @__PURE__ */ new Map();
|
|
71
|
+
var guardRegistry = {
|
|
72
|
+
add(modalId, name, func) {
|
|
73
|
+
let guard = store.get(modalId);
|
|
74
|
+
if (!guard) {
|
|
75
|
+
guard = {};
|
|
76
|
+
store.set(modalId, guard);
|
|
77
|
+
}
|
|
78
|
+
guard[name] ??= [];
|
|
79
|
+
guard[name].push(func);
|
|
80
|
+
},
|
|
81
|
+
get(id, name) {
|
|
82
|
+
return store.get(id)?.[name] ?? [];
|
|
83
|
+
},
|
|
84
|
+
delete(id) {
|
|
85
|
+
store.delete(id);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
async function runGuardQueue(fns) {
|
|
89
|
+
for (const fn of fns) {
|
|
90
|
+
await fn();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function guardToPromiseFn(guard) {
|
|
94
|
+
return async () => {
|
|
95
|
+
const result = await guard();
|
|
96
|
+
if (result !== false) return;
|
|
97
|
+
throw new Error("Guard returned false. Close cancelled.");
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/modal/modal.ts
|
|
102
|
+
var MODAL_EVENT_PROMPT = "modal:prompt";
|
|
103
|
+
var modalStore = /* @__PURE__ */ new Map();
|
|
104
|
+
var idCounter = 0;
|
|
105
|
+
function createModal(component, props, options = {}) {
|
|
106
|
+
const id = idCounter++;
|
|
107
|
+
const namespace = options.namespace ?? DEFAULT_NAMESPACE;
|
|
108
|
+
const isRoute = options.isRoute ?? false;
|
|
109
|
+
let closing = false;
|
|
110
|
+
const propsRef = (0, import_vue2.ref)(props);
|
|
111
|
+
const events = (0, import_vue2.reactive)({});
|
|
112
|
+
const closed = (0, import_vue2.ref)(false);
|
|
113
|
+
const modal = {
|
|
114
|
+
id,
|
|
115
|
+
component,
|
|
116
|
+
namespace,
|
|
117
|
+
isRoute,
|
|
118
|
+
props: propsRef,
|
|
119
|
+
events,
|
|
120
|
+
closed,
|
|
121
|
+
async close() {
|
|
122
|
+
if (closing || closed.value) return;
|
|
123
|
+
closing = true;
|
|
124
|
+
const queue = getNamespaceState(namespace).queue;
|
|
125
|
+
const guardFns = guardRegistry.get(id, "close").map((guard) => guardToPromiseFn(guard));
|
|
126
|
+
try {
|
|
127
|
+
await runGuardQueue(guardFns);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
closing = false;
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
const current = queue.findIndex((m) => m.id === id);
|
|
133
|
+
if (current !== -1) {
|
|
134
|
+
queue.splice(current, 1);
|
|
135
|
+
}
|
|
136
|
+
closed.value = true;
|
|
137
|
+
for (const runnable of guardRegistry.get(id, "destroy")) {
|
|
138
|
+
try {
|
|
139
|
+
runnable();
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.warn("Error running modal destroy guard", error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
guardRegistry.delete(id);
|
|
145
|
+
modalStore.delete(id);
|
|
146
|
+
for (const key in events) {
|
|
147
|
+
delete events[key];
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
emit(eventName, data) {
|
|
151
|
+
const handlers = events[eventName];
|
|
152
|
+
if (!handlers) return;
|
|
153
|
+
for (const handler of [...handlers]) {
|
|
154
|
+
handler(data);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
on(eventName, callback) {
|
|
158
|
+
let handlers = events[eventName];
|
|
159
|
+
if (!Array.isArray(handlers)) {
|
|
160
|
+
handlers = [];
|
|
161
|
+
events[eventName] = handlers;
|
|
162
|
+
}
|
|
163
|
+
handlers.push(callback);
|
|
164
|
+
return () => {
|
|
165
|
+
const arr = events[eventName];
|
|
166
|
+
if (!arr) return;
|
|
167
|
+
const i = arr.indexOf(callback);
|
|
168
|
+
if (i !== -1) {
|
|
169
|
+
arr.splice(i, 1);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
addCloseGuard(fn) {
|
|
174
|
+
guardRegistry.add(id, "close", fn);
|
|
175
|
+
},
|
|
176
|
+
addDestroyGuard(fn) {
|
|
177
|
+
guardRegistry.add(id, "destroy", fn);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
return modal;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/modal/methods/push-modal.ts
|
|
184
|
+
function pushModal(component, props = {}, options = {}) {
|
|
185
|
+
const ns = getNamespaceState(options.namespace);
|
|
186
|
+
const modal = createModal(component, props, options);
|
|
187
|
+
ns.queue.push((0, import_vue3.markRaw)(modal));
|
|
188
|
+
modalStore.set(modal.id, modal);
|
|
189
|
+
return modal;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/modal/methods/open-modal.ts
|
|
193
|
+
async function openModal(component, props = {}, options = {}) {
|
|
194
|
+
await closeModal({ namespace: options.namespace });
|
|
195
|
+
return pushModal(component, props, options);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/modal/methods/prompt-modal.ts
|
|
199
|
+
function promptModal(component, props = {}, options = {}) {
|
|
200
|
+
const modal = pushModal(component, props, options);
|
|
201
|
+
let isPrompted = false;
|
|
202
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
203
|
+
const unsubscribe = modal.on(MODAL_EVENT_PROMPT, async (data) => {
|
|
204
|
+
if (isPrompted) return;
|
|
205
|
+
isPrompted = true;
|
|
206
|
+
try {
|
|
207
|
+
await modal.close();
|
|
208
|
+
resolve(data);
|
|
209
|
+
} catch {
|
|
210
|
+
isPrompted = false;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
modal.addDestroyGuard(() => {
|
|
214
|
+
unsubscribe();
|
|
215
|
+
if (!isPrompted) {
|
|
216
|
+
resolve(null);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
return promise;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/modal/methods/get-current-modal.ts
|
|
223
|
+
function getCurrentModal(namespace) {
|
|
224
|
+
const ns = getNamespaceState(namespace);
|
|
225
|
+
return ns.queue.at(-1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/modal/methods/pop-modal.ts
|
|
229
|
+
async function popModal(options = {}) {
|
|
230
|
+
const modal = getCurrentModal(options.namespace);
|
|
231
|
+
if (!modal) return;
|
|
232
|
+
await modal.close();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/modal/methods/close-by-id.ts
|
|
236
|
+
async function closeById(id) {
|
|
237
|
+
const modal = modalStore.get(id);
|
|
238
|
+
if (!modal) throw new Error(`Modal with ID ${id} not found`);
|
|
239
|
+
await modal.close();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/hooks/dialog.ts
|
|
243
|
+
var DIALOG_NAMESPACE = "dialog";
|
|
244
|
+
var dialogs = getQueueByNamespace(DIALOG_NAMESPACE);
|
|
245
|
+
function useDialog() {
|
|
246
|
+
async function closeDialog(dialogId) {
|
|
247
|
+
if (typeof dialogId === "number") {
|
|
248
|
+
await dialogs.find((d) => d.id === dialogId)?.close();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const reversed = [...dialogs].reverse();
|
|
252
|
+
for (let i = 0; i < reversed.length; i++) {
|
|
253
|
+
await reversed[i].close();
|
|
254
|
+
if (reversed[i + 1]?.isRoute) break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function promptDialog(Component, props) {
|
|
258
|
+
return promptModal(Component, props, { namespace: DIALOG_NAMESPACE });
|
|
259
|
+
}
|
|
260
|
+
function openDialog(Component, props) {
|
|
261
|
+
return openModal(Component, props, { namespace: DIALOG_NAMESPACE });
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
/** Reactive list of active dialogs */
|
|
265
|
+
dialogs,
|
|
266
|
+
/** Opens a dialog with the given component and props */
|
|
267
|
+
openDialog,
|
|
268
|
+
/**
|
|
269
|
+
* Closes a dialog.
|
|
270
|
+
* If an ID is provided, closes that specific dialog.
|
|
271
|
+
* Otherwise closes all dialogs, stopping before any route-bound dialog.
|
|
272
|
+
*/
|
|
273
|
+
closeDialog,
|
|
274
|
+
/**
|
|
275
|
+
* Opens a dialog in prompt mode and waits for a result.
|
|
276
|
+
* Useful for confirmation flows or data-input dialogs.
|
|
277
|
+
*/
|
|
278
|
+
promptDialog
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/hooks/toast.ts
|
|
283
|
+
var import_vue4 = require("vue");
|
|
284
|
+
var TOAST_NAMESPACE = "toast";
|
|
285
|
+
var DURATION = 5e3;
|
|
286
|
+
var toasts = getQueueByNamespace(TOAST_NAMESPACE);
|
|
287
|
+
var timers = /* @__PURE__ */ new Map();
|
|
288
|
+
function removeTimer(toastId) {
|
|
289
|
+
const timer = timers.get(toastId);
|
|
290
|
+
if (!timer) return;
|
|
291
|
+
clearTimeout(timer);
|
|
292
|
+
timers.delete(toastId);
|
|
293
|
+
}
|
|
294
|
+
function addTimer(toastId) {
|
|
295
|
+
if (timers.has(toastId)) return;
|
|
296
|
+
const timer = setTimeout(() => closeToast(toastId), DURATION);
|
|
297
|
+
timers.set(toastId, timer);
|
|
298
|
+
}
|
|
299
|
+
function closeToast(toastId) {
|
|
300
|
+
removeTimer(toastId);
|
|
301
|
+
const toast = toasts.find((t) => t.id === toastId);
|
|
302
|
+
toast?.close().catch(() => {
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function closeAllToasts() {
|
|
306
|
+
toasts.forEach((t) => closeToast(t.id));
|
|
307
|
+
}
|
|
308
|
+
(0, import_vue4.watch)(
|
|
309
|
+
toasts,
|
|
310
|
+
(nextToasts) => {
|
|
311
|
+
const activeIds = new Set(nextToasts.map((t) => t.id));
|
|
312
|
+
nextToasts.forEach((t) => addTimer(t.id));
|
|
313
|
+
for (const id of timers.keys()) {
|
|
314
|
+
if (!activeIds.has(id)) {
|
|
315
|
+
removeTimer(id);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{ immediate: true }
|
|
320
|
+
);
|
|
321
|
+
function openToast(Component, props) {
|
|
322
|
+
return pushModal(Component, props, { namespace: TOAST_NAMESPACE });
|
|
323
|
+
}
|
|
324
|
+
function useToast() {
|
|
325
|
+
return {
|
|
326
|
+
/** Reactive list of active toasts */
|
|
327
|
+
toasts,
|
|
328
|
+
/** Opens a toast with a custom component */
|
|
329
|
+
openToast,
|
|
330
|
+
/** Closes a toast by ID */
|
|
331
|
+
closeToast,
|
|
332
|
+
/** Closes all active toasts */
|
|
333
|
+
closeAllToasts
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/hooks/sidebar-panel.ts
|
|
338
|
+
var import_vue5 = require("vue");
|
|
339
|
+
var SIDEBAR_NAMESPACE = "sidebar";
|
|
340
|
+
var sidebars = getQueueByNamespace(SIDEBAR_NAMESPACE);
|
|
341
|
+
var sidebar = (0, import_vue5.computed)(() => sidebars.at(-1));
|
|
342
|
+
function useSidebarPanel() {
|
|
343
|
+
function openSidebar(Component, props) {
|
|
344
|
+
return openModal(Component, props, { namespace: SIDEBAR_NAMESPACE });
|
|
345
|
+
}
|
|
346
|
+
async function closeSidebar() {
|
|
347
|
+
if (!sidebar.value) return;
|
|
348
|
+
await sidebar.value.close();
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
/** Reactive list of active sidebars */
|
|
352
|
+
sidebars,
|
|
353
|
+
/** The topmost (currently active) sidebar */
|
|
354
|
+
sidebar,
|
|
355
|
+
/** Opens a component inside a sidebar panel */
|
|
356
|
+
openSidebar,
|
|
357
|
+
/** Closes the current sidebar */
|
|
358
|
+
closeSidebar
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
362
|
+
0 && (module.exports = {
|
|
363
|
+
DIALOG_NAMESPACE,
|
|
364
|
+
MODAL_EVENT_PROMPT,
|
|
365
|
+
SIDEBAR_NAMESPACE,
|
|
366
|
+
TOAST_NAMESPACE,
|
|
367
|
+
closeById,
|
|
368
|
+
closeModal,
|
|
369
|
+
getCurrentModal,
|
|
370
|
+
getQueueByNamespace,
|
|
371
|
+
openModal,
|
|
372
|
+
popModal,
|
|
373
|
+
promptModal,
|
|
374
|
+
pushModal,
|
|
375
|
+
useDialog,
|
|
376
|
+
useSidebarPanel,
|
|
377
|
+
useToast
|
|
378
|
+
});
|
|
379
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/modal/state.ts","../src/modal/methods/close-modal.ts","../src/modal/methods/push-modal.ts","../src/modal/modal.ts","../src/modal/guards.ts","../src/modal/methods/open-modal.ts","../src/modal/methods/prompt-modal.ts","../src/modal/methods/get-current-modal.ts","../src/modal/methods/pop-modal.ts","../src/modal/methods/close-by-id.ts","../src/hooks/dialog.ts","../src/hooks/toast.ts","../src/hooks/sidebar-panel.ts"],"sourcesContent":["// Core\nexport { openModal } from './modal/methods/open-modal'\nexport { promptModal } from './modal/methods/prompt-modal'\nexport { pushModal } from './modal/methods/push-modal'\nexport { popModal } from './modal/methods/pop-modal'\nexport { closeModal } from './modal/methods/close-modal'\nexport { closeById } from './modal/methods/close-by-id'\nexport { getCurrentModal } from './modal/methods/get-current-modal'\nexport { getQueueByNamespace } from './modal/state'\nexport { MODAL_EVENT_PROMPT } from './modal/modal'\nexport type { ModalRecord, ModalID } from './modal/modal'\nexport type { ModalOptions, ModalCloseOptions, GuardFunction, NamespaceKey } from './modal/types'\n\n// Hooks\nexport { useDialog, DIALOG_NAMESPACE } from './hooks/dialog'\nexport { useToast, TOAST_NAMESPACE } from './hooks/toast'\nexport { useSidebarPanel, SIDEBAR_NAMESPACE } from './hooks/sidebar-panel'\n\n// Utils\nexport type { ComponentProps } from './component-props'\n","import { reactive } from 'vue'\nimport type { ModalRecord } from './modal'\nimport type { NamespaceKey } from './types'\n\nexport const DEFAULT_NAMESPACE: NamespaceKey = 'default'\n\nexport interface NamespaceState {\n queue: ModalRecord[]\n}\n\nconst namespaceMap = new Map<NamespaceKey, NamespaceState>()\n\n/** Returns (or lazily creates) the reactive state for the given namespace */\nexport function getNamespaceState(namespace: NamespaceKey = DEFAULT_NAMESPACE): NamespaceState {\n if (!namespaceMap.has(namespace)) {\n namespaceMap.set(namespace, { queue: reactive([]) })\n }\n\n return namespaceMap.get(namespace)!\n}\n\n/** Returns the reactive modal queue for the given namespace */\nexport function getQueueByNamespace(namespace?: NamespaceKey) {\n return getNamespaceState(namespace).queue\n}\n","import { getNamespaceState } from '../state'\nimport type { ModalCloseOptions } from '../types'\n\n/** Closes all modals in the namespace sequentially (LIFO order) */\nexport async function closeModal(options: ModalCloseOptions = {}): Promise<void> {\n // Snapshot and reverse: close top-to-bottom so a guard rejection on an upper modal\n // leaves lower modals untouched\n const queue = [...getNamespaceState(options.namespace).queue].reverse()\n for (const modal of queue) {\n await modal.close()\n }\n}\n","import { markRaw, type Component } from 'vue'\nimport { createModal, modalStore } from '../modal'\nimport { getNamespaceState } from '../state'\nimport type { ModalOptions } from '../types'\n\n/** Pushes a new modal on top of the current stack. Use `popModal` to close it */\nexport function pushModal(\n component: Component,\n props: any = {},\n options: Partial<ModalOptions> = {}\n) {\n const ns = getNamespaceState(options.namespace)\n const modal = createModal(component, props, options)\n\n // markRaw prevents Vue from making the modal record itself reactive\n // (individual Ref fields inside remain reactive as intended)\n ns.queue.push(markRaw(modal))\n\n // Register in modalStore only after the modal has been successfully queued\n modalStore.set(modal.id, modal)\n\n return modal\n}\n","import { reactive, ref, type Component, type Ref } from 'vue'\nimport { guardRegistry, guardToPromiseFn, runGuardQueue } from './guards'\nimport { DEFAULT_NAMESPACE, getNamespaceState } from './state'\nimport type { EventCallback, GuardFunction, ModalOptions, NamespaceKey } from './types'\n\nexport type ModalID = number\n\n/** Event name used by `promptModal` to receive a result value from the modal component */\nexport const MODAL_EVENT_PROMPT = 'modal:prompt'\n\n/** Global store of all active modal records, keyed by modal ID */\nexport const modalStore = new Map<ModalID, ModalRecord>()\n\nlet idCounter = 0\n\nexport interface ModalRecord {\n readonly id: ModalID\n readonly component: Component\n readonly namespace: NamespaceKey\n readonly isRoute: boolean\n readonly closed: Ref<boolean>\n props: Ref<any>\n readonly events: Record<string, EventCallback[]>\n /** Closes the modal, running all registered close guards first */\n close(): Promise<void>\n /** Emits an event on the modal's internal event bus */\n emit(eventName: string, data?: unknown): void\n /** Subscribes to an event on the modal's internal event bus. Returns an unsubscribe function */\n on<T = unknown>(eventName: string, callback: (v: T) => void): () => void\n /** Registers a guard function that can cancel or delay closing */\n addCloseGuard(fn: GuardFunction): void\n /** Registers a callback invoked after the modal is fully destroyed */\n addDestroyGuard(fn: () => void): void\n}\n\n/** Creates a new modal record without mounting it into any queue */\nexport function createModal(\n component: Component,\n props: any,\n options: Partial<ModalOptions> = {}\n): ModalRecord {\n const id = idCounter++\n const namespace = options.namespace ?? DEFAULT_NAMESPACE\n const isRoute = options.isRoute ?? false\n\n let closing = false\n\n const propsRef = ref(props)\n const events = reactive<Record<string, EventCallback[]>>({})\n const closed = ref(false)\n\n const modal: ModalRecord = {\n id,\n component,\n namespace,\n isRoute,\n props: propsRef,\n events,\n closed,\n\n async close(): Promise<void> {\n // Already closed or close in progress\n if (closing || closed.value) return\n closing = true\n\n const queue = getNamespaceState(namespace).queue\n const guardFns = guardRegistry.get(id, 'close').map((guard) => guardToPromiseFn(guard))\n\n try {\n await runGuardQueue(guardFns)\n } catch (error) {\n // Guard rejected the close — reset the flag so the user can retry\n closing = false\n throw error\n }\n\n // Re-find the index: the queue may have changed during async guards\n const current = queue.findIndex((m) => m.id === id)\n if (current !== -1) {\n queue.splice(current, 1)\n }\n\n closed.value = true\n\n for (const runnable of guardRegistry.get(id, 'destroy')) {\n try {\n runnable()\n } catch (error) {\n console.warn('Error running modal destroy guard', error)\n }\n }\n\n guardRegistry.delete(id)\n modalStore.delete(id)\n\n for (const key in events) {\n delete events[key]\n }\n },\n\n emit(eventName: string, data?: unknown): void {\n const handlers = events[eventName]\n if (!handlers) return\n for (const handler of [...handlers]) {\n handler(data)\n }\n },\n\n on<T = unknown>(eventName: string, callback: (v: T) => void): () => void {\n let handlers = events[eventName]\n if (!Array.isArray(handlers)) {\n handlers = []\n events[eventName] = handlers\n }\n\n handlers.push(callback as EventCallback)\n\n return () => {\n const arr = events[eventName]\n if (!arr) return\n\n const i = arr.indexOf(callback)\n if (i !== -1) {\n arr.splice(i, 1)\n }\n }\n },\n\n addCloseGuard(fn: GuardFunction): void {\n guardRegistry.add(id, 'close', fn)\n },\n\n addDestroyGuard(fn: () => void): void {\n guardRegistry.add(id, 'destroy', fn)\n }\n }\n\n return modal\n}\n","import type { GuardFunction } from './types'\n\ntype GuardFunctionPromisify = () => Promise<void>\n\ntype AvailableKeys = 'close' | 'destroy'\n\nconst store = new Map<number, Partial<Record<AvailableKeys, GuardFunction[]>>>()\n\n/** Registry for per-modal guard functions, keyed by modal ID */\nexport const guardRegistry = {\n add(modalId: number, name: AvailableKeys, func: GuardFunction): void {\n let guard = store.get(modalId)\n if (!guard) {\n guard = {}\n store.set(modalId, guard)\n }\n\n guard[name] ??= []\n guard[name].push(func)\n },\n\n get(id: number, name: AvailableKeys): GuardFunction[] {\n return store.get(id)?.[name] ?? []\n },\n\n delete(id: number): void {\n store.delete(id)\n }\n}\n\n/** Runs an array of promisified guard functions sequentially */\nexport async function runGuardQueue(fns: GuardFunctionPromisify[]): Promise<void> {\n for (const fn of fns) {\n await fn()\n }\n}\n\n/** Wraps a guard function so it throws when the guard returns `false` */\nexport function guardToPromiseFn(guard: GuardFunction): GuardFunctionPromisify {\n return async () => {\n const result = await guard()\n if (result !== false) return\n throw new Error('Guard returned false. Close cancelled.')\n }\n}\n","import type { Component } from 'vue'\nimport type { ModalOptions } from '../types'\nimport { closeModal } from './close-modal'\nimport { pushModal } from './push-modal'\n\n/** Closes all active modals in the namespace and opens a new one */\nexport async function openModal(\n component: Component,\n props: any = {},\n options: Partial<ModalOptions> = {}\n) {\n await closeModal({ namespace: options.namespace })\n return pushModal(component, props, options)\n}\n","import type { Component } from 'vue'\nimport { MODAL_EVENT_PROMPT } from '../modal'\nimport type { ModalOptions } from '../types'\nimport { pushModal } from './push-modal'\n\n/**\n * Opens a modal and waits for a result via the `MODAL_EVENT_PROMPT` event.\n * Resolves with the value passed to the event, or `null` if the modal is\n * closed without emitting a result.\n */\nexport function promptModal<T = unknown>(\n component: Component,\n props: any = {},\n options: Partial<ModalOptions> = {}\n): Promise<T | null> {\n const modal = pushModal(component, props, options)\n let isPrompted = false\n\n const { promise, resolve } = Promise.withResolvers<T | null>()\n\n const unsubscribe = modal.on(MODAL_EVENT_PROMPT, async (data: T) => {\n // Guard against concurrent calls: ignore a second emit while close is in progress\n if (isPrompted) return\n isPrompted = true\n\n try {\n await modal.close()\n resolve(data)\n } catch {\n // Guard rejected the close — reset the flag so the user can retry\n isPrompted = false\n }\n })\n\n // addDestroyGuard is called after the modal is fully closed (non-cancellable)\n modal.addDestroyGuard(() => {\n unsubscribe()\n if (!isPrompted) {\n resolve(null)\n }\n })\n\n return promise\n}\n","import { getNamespaceState } from '../state'\nimport type { NamespaceKey } from '../types'\n\n/** Returns the topmost active modal in the namespace, or `undefined` if the queue is empty */\nexport function getCurrentModal(namespace?: NamespaceKey) {\n const ns = getNamespaceState(namespace)\n return ns.queue.at(-1)\n}\n","import type { ModalCloseOptions } from '../types'\nimport { getCurrentModal } from './get-current-modal'\n\n/** Closes the topmost active modal in the namespace */\nexport async function popModal(options: ModalCloseOptions = {}): Promise<void> {\n const modal = getCurrentModal(options.namespace)\n if (!modal) return\n await modal.close()\n}\n","import { modalStore } from '../modal'\n\n/** Closes a modal by its ID. Throws if no modal with the given ID exists */\nexport async function closeById(id: number): Promise<void> {\n const modal = modalStore.get(id)\n if (!modal) throw new Error(`Modal with ID ${id} not found`)\n await modal.close()\n}\n","import type { ComponentProps } from '../component-props'\nimport type { ModalRecord } from '../modal/modal'\nimport { getQueueByNamespace } from '../modal/state'\nimport { openModal as openModalFn } from '../modal/methods/open-modal'\nimport { promptModal as promptModalFn } from '../modal/methods/prompt-modal'\n\nexport const DIALOG_NAMESPACE = 'dialog'\n\nconst dialogs = getQueueByNamespace(DIALOG_NAMESPACE)\n\n/**\n * Composable for managing dialog modals.\n * Provides methods to open, close, and prompt dialogs within the `dialog` namespace.\n */\nexport function useDialog() {\n async function closeDialog(dialogId?: number): Promise<void> {\n if (typeof dialogId === 'number') {\n await dialogs.find((d) => d.id === dialogId)?.close()\n return\n }\n\n const reversed = [...dialogs].reverse()\n for (let i = 0; i < reversed.length; i++) {\n await reversed[i]!.close()\n // Stop before a route-bound dialog\n if (reversed[i + 1]?.isRoute) break\n }\n }\n\n function promptDialog<\n Result = unknown,\n C extends new (...args: unknown[]) => unknown = new (...args: unknown[]) => unknown\n >(Component: C, props?: ComponentProps<C>): Promise<Result | null> {\n return promptModalFn<Result>(Component, props, { namespace: DIALOG_NAMESPACE })\n }\n\n function openDialog<C extends new (...args: unknown[]) => unknown>(\n Component: C,\n props?: ComponentProps<C>\n ): Promise<ModalRecord> {\n return openModalFn(Component, props, { namespace: DIALOG_NAMESPACE })\n }\n\n return {\n /** Reactive list of active dialogs */\n dialogs,\n /** Opens a dialog with the given component and props */\n openDialog,\n /**\n * Closes a dialog.\n * If an ID is provided, closes that specific dialog.\n * Otherwise closes all dialogs, stopping before any route-bound dialog.\n */\n closeDialog,\n /**\n * Opens a dialog in prompt mode and waits for a result.\n * Useful for confirmation flows or data-input dialogs.\n */\n promptDialog\n }\n}\n","import type { ComponentProps } from '../component-props'\nimport type { ModalRecord } from '../modal/modal'\nimport { getQueueByNamespace } from '../modal/state'\nimport { pushModal } from '../modal/methods/push-modal'\nimport { watch } from 'vue'\n\nexport const TOAST_NAMESPACE = 'toast'\nconst DURATION = 5_000\n\n// Queue and timers are module-level singletons shared across all useToast() calls\nconst toasts = getQueueByNamespace(TOAST_NAMESPACE)\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\n\nfunction removeTimer(toastId: number) {\n const timer = timers.get(toastId)\n if (!timer) return\n\n clearTimeout(timer)\n timers.delete(toastId)\n}\n\nfunction addTimer(toastId: number) {\n if (timers.has(toastId)) return\n const timer = setTimeout(() => closeToast(toastId), DURATION)\n timers.set(toastId, timer)\n}\n\nfunction closeToast(toastId: number) {\n removeTimer(toastId)\n const toast = toasts.find((t) => t.id === toastId)\n toast?.close().catch(() => {})\n}\n\nfunction closeAllToasts() {\n toasts.forEach((t) => closeToast(t.id))\n}\n\n// Sync timers with the queue — runs once at module level\nwatch(\n toasts,\n (nextToasts) => {\n const activeIds = new Set(nextToasts.map((t) => t.id))\n nextToasts.forEach((t) => addTimer(t.id))\n for (const id of timers.keys()) {\n if (!activeIds.has(id)) {\n removeTimer(id)\n }\n }\n },\n { immediate: true }\n)\n\nfunction openToast<C extends new (...args: unknown[]) => unknown>(\n Component: C,\n props?: ComponentProps<C>\n): ModalRecord {\n return pushModal(Component, props, { namespace: TOAST_NAMESPACE })\n}\n\n/** Composable for managing toast notifications */\nexport function useToast() {\n return {\n /** Reactive list of active toasts */\n toasts,\n /** Opens a toast with a custom component */\n openToast,\n /** Closes a toast by ID */\n closeToast,\n /** Closes all active toasts */\n closeAllToasts\n }\n}\n","import type { ComponentProps } from '../component-props'\nimport { getQueueByNamespace } from '../modal/state'\nimport { openModal } from '../modal/methods/open-modal'\nimport { computed } from 'vue'\n\nexport const SIDEBAR_NAMESPACE = 'sidebar'\n\nconst sidebars = getQueueByNamespace(SIDEBAR_NAMESPACE)\nconst sidebar = computed(() => sidebars.at(-1))\n\n/**\n * Composable for managing sidebar panels.\n * Provides methods to open and close sidebars within the `sidebar` namespace.\n */\nexport function useSidebarPanel() {\n function openSidebar<C extends new (...args: unknown[]) => unknown>(\n Component: C,\n props?: ComponentProps<C>\n ) {\n return openModal(Component, props, { namespace: SIDEBAR_NAMESPACE })\n }\n\n async function closeSidebar(): Promise<void> {\n if (!sidebar.value) return\n await sidebar.value.close()\n }\n\n return {\n /** Reactive list of active sidebars */\n sidebars,\n /** The topmost (currently active) sidebar */\n sidebar,\n /** Opens a component inside a sidebar panel */\n openSidebar,\n /** Closes the current sidebar */\n closeSidebar\n }\n}\n"],"mappings":"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","names":["import_vue","import_vue","import_vue","import_vue"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { Component, Ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
type NamespaceKey = number | string;
|
|
5
|
+
/**
|
|
6
|
+
* Guard function for a modal.
|
|
7
|
+
* Return `false` to cancel closing, `true` or `void` to allow it.
|
|
8
|
+
* Supports async: `Promise<boolean>`.
|
|
9
|
+
*/
|
|
10
|
+
type GuardFunction = () => void | boolean | Promise<boolean>;
|
|
11
|
+
interface ModalCloseOptions {
|
|
12
|
+
namespace?: NamespaceKey;
|
|
13
|
+
}
|
|
14
|
+
interface ModalOptions {
|
|
15
|
+
isRoute: boolean;
|
|
16
|
+
namespace: NamespaceKey;
|
|
17
|
+
}
|
|
18
|
+
type EventCallback = (v: any) => any;
|
|
19
|
+
|
|
20
|
+
type ModalID = number;
|
|
21
|
+
/** Event name used by `promptModal` to receive a result value from the modal component */
|
|
22
|
+
declare const MODAL_EVENT_PROMPT = "modal:prompt";
|
|
23
|
+
interface ModalRecord {
|
|
24
|
+
readonly id: ModalID;
|
|
25
|
+
readonly component: Component;
|
|
26
|
+
readonly namespace: NamespaceKey;
|
|
27
|
+
readonly isRoute: boolean;
|
|
28
|
+
readonly closed: Ref<boolean>;
|
|
29
|
+
props: Ref<any>;
|
|
30
|
+
readonly events: Record<string, EventCallback[]>;
|
|
31
|
+
/** Closes the modal, running all registered close guards first */
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
/** Emits an event on the modal's internal event bus */
|
|
34
|
+
emit(eventName: string, data?: unknown): void;
|
|
35
|
+
/** Subscribes to an event on the modal's internal event bus. Returns an unsubscribe function */
|
|
36
|
+
on<T = unknown>(eventName: string, callback: (v: T) => void): () => void;
|
|
37
|
+
/** Registers a guard function that can cancel or delay closing */
|
|
38
|
+
addCloseGuard(fn: GuardFunction): void;
|
|
39
|
+
/** Registers a callback invoked after the modal is fully destroyed */
|
|
40
|
+
addDestroyGuard(fn: () => void): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Closes all active modals in the namespace and opens a new one */
|
|
44
|
+
declare function openModal(component: Component, props?: any, options?: Partial<ModalOptions>): Promise<ModalRecord>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Opens a modal and waits for a result via the `MODAL_EVENT_PROMPT` event.
|
|
48
|
+
* Resolves with the value passed to the event, or `null` if the modal is
|
|
49
|
+
* closed without emitting a result.
|
|
50
|
+
*/
|
|
51
|
+
declare function promptModal<T = unknown>(component: Component, props?: any, options?: Partial<ModalOptions>): Promise<T | null>;
|
|
52
|
+
|
|
53
|
+
/** Pushes a new modal on top of the current stack. Use `popModal` to close it */
|
|
54
|
+
declare function pushModal(component: Component, props?: any, options?: Partial<ModalOptions>): ModalRecord;
|
|
55
|
+
|
|
56
|
+
/** Closes the topmost active modal in the namespace */
|
|
57
|
+
declare function popModal(options?: ModalCloseOptions): Promise<void>;
|
|
58
|
+
|
|
59
|
+
/** Closes all modals in the namespace sequentially (LIFO order) */
|
|
60
|
+
declare function closeModal(options?: ModalCloseOptions): Promise<void>;
|
|
61
|
+
|
|
62
|
+
/** Closes a modal by its ID. Throws if no modal with the given ID exists */
|
|
63
|
+
declare function closeById(id: number): Promise<void>;
|
|
64
|
+
|
|
65
|
+
/** Returns the topmost active modal in the namespace, or `undefined` if the queue is empty */
|
|
66
|
+
declare function getCurrentModal(namespace?: NamespaceKey): ModalRecord | undefined;
|
|
67
|
+
|
|
68
|
+
/** Returns the reactive modal queue for the given namespace */
|
|
69
|
+
declare function getQueueByNamespace(namespace?: NamespaceKey): ModalRecord[];
|
|
70
|
+
|
|
71
|
+
/** Extracts the `$props` type from a Vue component constructor, making all props optional */
|
|
72
|
+
type ComponentProps<T> = T extends new (...args: unknown[]) => infer R ? R extends {
|
|
73
|
+
$props: infer P;
|
|
74
|
+
} ? Partial<P> : Record<string, unknown> : never;
|
|
75
|
+
|
|
76
|
+
declare const DIALOG_NAMESPACE = "dialog";
|
|
77
|
+
/**
|
|
78
|
+
* Composable for managing dialog modals.
|
|
79
|
+
* Provides methods to open, close, and prompt dialogs within the `dialog` namespace.
|
|
80
|
+
*/
|
|
81
|
+
declare function useDialog(): {
|
|
82
|
+
/** Reactive list of active dialogs */
|
|
83
|
+
dialogs: ModalRecord[];
|
|
84
|
+
/** Opens a dialog with the given component and props */
|
|
85
|
+
openDialog: <C extends new (...args: unknown[]) => unknown>(Component: C, props?: ComponentProps<C>) => Promise<ModalRecord>;
|
|
86
|
+
/**
|
|
87
|
+
* Closes a dialog.
|
|
88
|
+
* If an ID is provided, closes that specific dialog.
|
|
89
|
+
* Otherwise closes all dialogs, stopping before any route-bound dialog.
|
|
90
|
+
*/
|
|
91
|
+
closeDialog: (dialogId?: number) => Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Opens a dialog in prompt mode and waits for a result.
|
|
94
|
+
* Useful for confirmation flows or data-input dialogs.
|
|
95
|
+
*/
|
|
96
|
+
promptDialog: <Result = unknown, C extends new (...args: unknown[]) => unknown = new (...args: unknown[]) => unknown>(Component: C, props?: ComponentProps<C>) => Promise<Result | null>;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
declare const TOAST_NAMESPACE = "toast";
|
|
100
|
+
declare function closeToast(toastId: number): void;
|
|
101
|
+
declare function closeAllToasts(): void;
|
|
102
|
+
declare function openToast<C extends new (...args: unknown[]) => unknown>(Component: C, props?: ComponentProps<C>): ModalRecord;
|
|
103
|
+
/** Composable for managing toast notifications */
|
|
104
|
+
declare function useToast(): {
|
|
105
|
+
/** Reactive list of active toasts */
|
|
106
|
+
toasts: ModalRecord[];
|
|
107
|
+
/** Opens a toast with a custom component */
|
|
108
|
+
openToast: typeof openToast;
|
|
109
|
+
/** Closes a toast by ID */
|
|
110
|
+
closeToast: typeof closeToast;
|
|
111
|
+
/** Closes all active toasts */
|
|
112
|
+
closeAllToasts: typeof closeAllToasts;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
declare const SIDEBAR_NAMESPACE = "sidebar";
|
|
116
|
+
/**
|
|
117
|
+
* Composable for managing sidebar panels.
|
|
118
|
+
* Provides methods to open and close sidebars within the `sidebar` namespace.
|
|
119
|
+
*/
|
|
120
|
+
declare function useSidebarPanel(): {
|
|
121
|
+
/** Reactive list of active sidebars */
|
|
122
|
+
sidebars: ModalRecord[];
|
|
123
|
+
/** The topmost (currently active) sidebar */
|
|
124
|
+
sidebar: vue.ComputedRef<ModalRecord | undefined>;
|
|
125
|
+
/** Opens a component inside a sidebar panel */
|
|
126
|
+
openSidebar: <C extends new (...args: unknown[]) => unknown>(Component: C, props?: ComponentProps<C>) => Promise<ModalRecord>;
|
|
127
|
+
/** Closes the current sidebar */
|
|
128
|
+
closeSidebar: () => Promise<void>;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export { type ComponentProps, DIALOG_NAMESPACE, type GuardFunction, MODAL_EVENT_PROMPT, type ModalCloseOptions, type ModalID, type ModalOptions, type ModalRecord, type NamespaceKey, SIDEBAR_NAMESPACE, TOAST_NAMESPACE, closeById, closeModal, getCurrentModal, getQueueByNamespace, openModal, popModal, promptModal, pushModal, useDialog, useSidebarPanel, useToast };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { Component, Ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
type NamespaceKey = number | string;
|
|
5
|
+
/**
|
|
6
|
+
* Guard function for a modal.
|
|
7
|
+
* Return `false` to cancel closing, `true` or `void` to allow it.
|
|
8
|
+
* Supports async: `Promise<boolean>`.
|
|
9
|
+
*/
|
|
10
|
+
type GuardFunction = () => void | boolean | Promise<boolean>;
|
|
11
|
+
interface ModalCloseOptions {
|
|
12
|
+
namespace?: NamespaceKey;
|
|
13
|
+
}
|
|
14
|
+
interface ModalOptions {
|
|
15
|
+
isRoute: boolean;
|
|
16
|
+
namespace: NamespaceKey;
|
|
17
|
+
}
|
|
18
|
+
type EventCallback = (v: any) => any;
|
|
19
|
+
|
|
20
|
+
type ModalID = number;
|
|
21
|
+
/** Event name used by `promptModal` to receive a result value from the modal component */
|
|
22
|
+
declare const MODAL_EVENT_PROMPT = "modal:prompt";
|
|
23
|
+
interface ModalRecord {
|
|
24
|
+
readonly id: ModalID;
|
|
25
|
+
readonly component: Component;
|
|
26
|
+
readonly namespace: NamespaceKey;
|
|
27
|
+
readonly isRoute: boolean;
|
|
28
|
+
readonly closed: Ref<boolean>;
|
|
29
|
+
props: Ref<any>;
|
|
30
|
+
readonly events: Record<string, EventCallback[]>;
|
|
31
|
+
/** Closes the modal, running all registered close guards first */
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
/** Emits an event on the modal's internal event bus */
|
|
34
|
+
emit(eventName: string, data?: unknown): void;
|
|
35
|
+
/** Subscribes to an event on the modal's internal event bus. Returns an unsubscribe function */
|
|
36
|
+
on<T = unknown>(eventName: string, callback: (v: T) => void): () => void;
|
|
37
|
+
/** Registers a guard function that can cancel or delay closing */
|
|
38
|
+
addCloseGuard(fn: GuardFunction): void;
|
|
39
|
+
/** Registers a callback invoked after the modal is fully destroyed */
|
|
40
|
+
addDestroyGuard(fn: () => void): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Closes all active modals in the namespace and opens a new one */
|
|
44
|
+
declare function openModal(component: Component, props?: any, options?: Partial<ModalOptions>): Promise<ModalRecord>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Opens a modal and waits for a result via the `MODAL_EVENT_PROMPT` event.
|
|
48
|
+
* Resolves with the value passed to the event, or `null` if the modal is
|
|
49
|
+
* closed without emitting a result.
|
|
50
|
+
*/
|
|
51
|
+
declare function promptModal<T = unknown>(component: Component, props?: any, options?: Partial<ModalOptions>): Promise<T | null>;
|
|
52
|
+
|
|
53
|
+
/** Pushes a new modal on top of the current stack. Use `popModal` to close it */
|
|
54
|
+
declare function pushModal(component: Component, props?: any, options?: Partial<ModalOptions>): ModalRecord;
|
|
55
|
+
|
|
56
|
+
/** Closes the topmost active modal in the namespace */
|
|
57
|
+
declare function popModal(options?: ModalCloseOptions): Promise<void>;
|
|
58
|
+
|
|
59
|
+
/** Closes all modals in the namespace sequentially (LIFO order) */
|
|
60
|
+
declare function closeModal(options?: ModalCloseOptions): Promise<void>;
|
|
61
|
+
|
|
62
|
+
/** Closes a modal by its ID. Throws if no modal with the given ID exists */
|
|
63
|
+
declare function closeById(id: number): Promise<void>;
|
|
64
|
+
|
|
65
|
+
/** Returns the topmost active modal in the namespace, or `undefined` if the queue is empty */
|
|
66
|
+
declare function getCurrentModal(namespace?: NamespaceKey): ModalRecord | undefined;
|
|
67
|
+
|
|
68
|
+
/** Returns the reactive modal queue for the given namespace */
|
|
69
|
+
declare function getQueueByNamespace(namespace?: NamespaceKey): ModalRecord[];
|
|
70
|
+
|
|
71
|
+
/** Extracts the `$props` type from a Vue component constructor, making all props optional */
|
|
72
|
+
type ComponentProps<T> = T extends new (...args: unknown[]) => infer R ? R extends {
|
|
73
|
+
$props: infer P;
|
|
74
|
+
} ? Partial<P> : Record<string, unknown> : never;
|
|
75
|
+
|
|
76
|
+
declare const DIALOG_NAMESPACE = "dialog";
|
|
77
|
+
/**
|
|
78
|
+
* Composable for managing dialog modals.
|
|
79
|
+
* Provides methods to open, close, and prompt dialogs within the `dialog` namespace.
|
|
80
|
+
*/
|
|
81
|
+
declare function useDialog(): {
|
|
82
|
+
/** Reactive list of active dialogs */
|
|
83
|
+
dialogs: ModalRecord[];
|
|
84
|
+
/** Opens a dialog with the given component and props */
|
|
85
|
+
openDialog: <C extends new (...args: unknown[]) => unknown>(Component: C, props?: ComponentProps<C>) => Promise<ModalRecord>;
|
|
86
|
+
/**
|
|
87
|
+
* Closes a dialog.
|
|
88
|
+
* If an ID is provided, closes that specific dialog.
|
|
89
|
+
* Otherwise closes all dialogs, stopping before any route-bound dialog.
|
|
90
|
+
*/
|
|
91
|
+
closeDialog: (dialogId?: number) => Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Opens a dialog in prompt mode and waits for a result.
|
|
94
|
+
* Useful for confirmation flows or data-input dialogs.
|
|
95
|
+
*/
|
|
96
|
+
promptDialog: <Result = unknown, C extends new (...args: unknown[]) => unknown = new (...args: unknown[]) => unknown>(Component: C, props?: ComponentProps<C>) => Promise<Result | null>;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
declare const TOAST_NAMESPACE = "toast";
|
|
100
|
+
declare function closeToast(toastId: number): void;
|
|
101
|
+
declare function closeAllToasts(): void;
|
|
102
|
+
declare function openToast<C extends new (...args: unknown[]) => unknown>(Component: C, props?: ComponentProps<C>): ModalRecord;
|
|
103
|
+
/** Composable for managing toast notifications */
|
|
104
|
+
declare function useToast(): {
|
|
105
|
+
/** Reactive list of active toasts */
|
|
106
|
+
toasts: ModalRecord[];
|
|
107
|
+
/** Opens a toast with a custom component */
|
|
108
|
+
openToast: typeof openToast;
|
|
109
|
+
/** Closes a toast by ID */
|
|
110
|
+
closeToast: typeof closeToast;
|
|
111
|
+
/** Closes all active toasts */
|
|
112
|
+
closeAllToasts: typeof closeAllToasts;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
declare const SIDEBAR_NAMESPACE = "sidebar";
|
|
116
|
+
/**
|
|
117
|
+
* Composable for managing sidebar panels.
|
|
118
|
+
* Provides methods to open and close sidebars within the `sidebar` namespace.
|
|
119
|
+
*/
|
|
120
|
+
declare function useSidebarPanel(): {
|
|
121
|
+
/** Reactive list of active sidebars */
|
|
122
|
+
sidebars: ModalRecord[];
|
|
123
|
+
/** The topmost (currently active) sidebar */
|
|
124
|
+
sidebar: vue.ComputedRef<ModalRecord | undefined>;
|
|
125
|
+
/** Opens a component inside a sidebar panel */
|
|
126
|
+
openSidebar: <C extends new (...args: unknown[]) => unknown>(Component: C, props?: ComponentProps<C>) => Promise<ModalRecord>;
|
|
127
|
+
/** Closes the current sidebar */
|
|
128
|
+
closeSidebar: () => Promise<void>;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export { type ComponentProps, DIALOG_NAMESPACE, type GuardFunction, MODAL_EVENT_PROMPT, type ModalCloseOptions, type ModalID, type ModalOptions, type ModalRecord, type NamespaceKey, SIDEBAR_NAMESPACE, TOAST_NAMESPACE, closeById, closeModal, getCurrentModal, getQueueByNamespace, openModal, popModal, promptModal, pushModal, useDialog, useSidebarPanel, useToast };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// src/modal/state.ts
|
|
2
|
+
import { reactive } from "vue";
|
|
3
|
+
var DEFAULT_NAMESPACE = "default";
|
|
4
|
+
var namespaceMap = /* @__PURE__ */ new Map();
|
|
5
|
+
function getNamespaceState(namespace = DEFAULT_NAMESPACE) {
|
|
6
|
+
if (!namespaceMap.has(namespace)) {
|
|
7
|
+
namespaceMap.set(namespace, { queue: reactive([]) });
|
|
8
|
+
}
|
|
9
|
+
return namespaceMap.get(namespace);
|
|
10
|
+
}
|
|
11
|
+
function getQueueByNamespace(namespace) {
|
|
12
|
+
return getNamespaceState(namespace).queue;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/modal/methods/close-modal.ts
|
|
16
|
+
async function closeModal(options = {}) {
|
|
17
|
+
const queue = [...getNamespaceState(options.namespace).queue].reverse();
|
|
18
|
+
for (const modal of queue) {
|
|
19
|
+
await modal.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/modal/methods/push-modal.ts
|
|
24
|
+
import { markRaw } from "vue";
|
|
25
|
+
|
|
26
|
+
// src/modal/modal.ts
|
|
27
|
+
import { reactive as reactive2, ref } from "vue";
|
|
28
|
+
|
|
29
|
+
// src/modal/guards.ts
|
|
30
|
+
var store = /* @__PURE__ */ new Map();
|
|
31
|
+
var guardRegistry = {
|
|
32
|
+
add(modalId, name, func) {
|
|
33
|
+
let guard = store.get(modalId);
|
|
34
|
+
if (!guard) {
|
|
35
|
+
guard = {};
|
|
36
|
+
store.set(modalId, guard);
|
|
37
|
+
}
|
|
38
|
+
guard[name] ??= [];
|
|
39
|
+
guard[name].push(func);
|
|
40
|
+
},
|
|
41
|
+
get(id, name) {
|
|
42
|
+
return store.get(id)?.[name] ?? [];
|
|
43
|
+
},
|
|
44
|
+
delete(id) {
|
|
45
|
+
store.delete(id);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
async function runGuardQueue(fns) {
|
|
49
|
+
for (const fn of fns) {
|
|
50
|
+
await fn();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function guardToPromiseFn(guard) {
|
|
54
|
+
return async () => {
|
|
55
|
+
const result = await guard();
|
|
56
|
+
if (result !== false) return;
|
|
57
|
+
throw new Error("Guard returned false. Close cancelled.");
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/modal/modal.ts
|
|
62
|
+
var MODAL_EVENT_PROMPT = "modal:prompt";
|
|
63
|
+
var modalStore = /* @__PURE__ */ new Map();
|
|
64
|
+
var idCounter = 0;
|
|
65
|
+
function createModal(component, props, options = {}) {
|
|
66
|
+
const id = idCounter++;
|
|
67
|
+
const namespace = options.namespace ?? DEFAULT_NAMESPACE;
|
|
68
|
+
const isRoute = options.isRoute ?? false;
|
|
69
|
+
let closing = false;
|
|
70
|
+
const propsRef = ref(props);
|
|
71
|
+
const events = reactive2({});
|
|
72
|
+
const closed = ref(false);
|
|
73
|
+
const modal = {
|
|
74
|
+
id,
|
|
75
|
+
component,
|
|
76
|
+
namespace,
|
|
77
|
+
isRoute,
|
|
78
|
+
props: propsRef,
|
|
79
|
+
events,
|
|
80
|
+
closed,
|
|
81
|
+
async close() {
|
|
82
|
+
if (closing || closed.value) return;
|
|
83
|
+
closing = true;
|
|
84
|
+
const queue = getNamespaceState(namespace).queue;
|
|
85
|
+
const guardFns = guardRegistry.get(id, "close").map((guard) => guardToPromiseFn(guard));
|
|
86
|
+
try {
|
|
87
|
+
await runGuardQueue(guardFns);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
closing = false;
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
const current = queue.findIndex((m) => m.id === id);
|
|
93
|
+
if (current !== -1) {
|
|
94
|
+
queue.splice(current, 1);
|
|
95
|
+
}
|
|
96
|
+
closed.value = true;
|
|
97
|
+
for (const runnable of guardRegistry.get(id, "destroy")) {
|
|
98
|
+
try {
|
|
99
|
+
runnable();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.warn("Error running modal destroy guard", error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
guardRegistry.delete(id);
|
|
105
|
+
modalStore.delete(id);
|
|
106
|
+
for (const key in events) {
|
|
107
|
+
delete events[key];
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
emit(eventName, data) {
|
|
111
|
+
const handlers = events[eventName];
|
|
112
|
+
if (!handlers) return;
|
|
113
|
+
for (const handler of [...handlers]) {
|
|
114
|
+
handler(data);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
on(eventName, callback) {
|
|
118
|
+
let handlers = events[eventName];
|
|
119
|
+
if (!Array.isArray(handlers)) {
|
|
120
|
+
handlers = [];
|
|
121
|
+
events[eventName] = handlers;
|
|
122
|
+
}
|
|
123
|
+
handlers.push(callback);
|
|
124
|
+
return () => {
|
|
125
|
+
const arr = events[eventName];
|
|
126
|
+
if (!arr) return;
|
|
127
|
+
const i = arr.indexOf(callback);
|
|
128
|
+
if (i !== -1) {
|
|
129
|
+
arr.splice(i, 1);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
addCloseGuard(fn) {
|
|
134
|
+
guardRegistry.add(id, "close", fn);
|
|
135
|
+
},
|
|
136
|
+
addDestroyGuard(fn) {
|
|
137
|
+
guardRegistry.add(id, "destroy", fn);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
return modal;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/modal/methods/push-modal.ts
|
|
144
|
+
function pushModal(component, props = {}, options = {}) {
|
|
145
|
+
const ns = getNamespaceState(options.namespace);
|
|
146
|
+
const modal = createModal(component, props, options);
|
|
147
|
+
ns.queue.push(markRaw(modal));
|
|
148
|
+
modalStore.set(modal.id, modal);
|
|
149
|
+
return modal;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/modal/methods/open-modal.ts
|
|
153
|
+
async function openModal(component, props = {}, options = {}) {
|
|
154
|
+
await closeModal({ namespace: options.namespace });
|
|
155
|
+
return pushModal(component, props, options);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/modal/methods/prompt-modal.ts
|
|
159
|
+
function promptModal(component, props = {}, options = {}) {
|
|
160
|
+
const modal = pushModal(component, props, options);
|
|
161
|
+
let isPrompted = false;
|
|
162
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
163
|
+
const unsubscribe = modal.on(MODAL_EVENT_PROMPT, async (data) => {
|
|
164
|
+
if (isPrompted) return;
|
|
165
|
+
isPrompted = true;
|
|
166
|
+
try {
|
|
167
|
+
await modal.close();
|
|
168
|
+
resolve(data);
|
|
169
|
+
} catch {
|
|
170
|
+
isPrompted = false;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
modal.addDestroyGuard(() => {
|
|
174
|
+
unsubscribe();
|
|
175
|
+
if (!isPrompted) {
|
|
176
|
+
resolve(null);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return promise;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/modal/methods/get-current-modal.ts
|
|
183
|
+
function getCurrentModal(namespace) {
|
|
184
|
+
const ns = getNamespaceState(namespace);
|
|
185
|
+
return ns.queue.at(-1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/modal/methods/pop-modal.ts
|
|
189
|
+
async function popModal(options = {}) {
|
|
190
|
+
const modal = getCurrentModal(options.namespace);
|
|
191
|
+
if (!modal) return;
|
|
192
|
+
await modal.close();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/modal/methods/close-by-id.ts
|
|
196
|
+
async function closeById(id) {
|
|
197
|
+
const modal = modalStore.get(id);
|
|
198
|
+
if (!modal) throw new Error(`Modal with ID ${id} not found`);
|
|
199
|
+
await modal.close();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/hooks/dialog.ts
|
|
203
|
+
var DIALOG_NAMESPACE = "dialog";
|
|
204
|
+
var dialogs = getQueueByNamespace(DIALOG_NAMESPACE);
|
|
205
|
+
function useDialog() {
|
|
206
|
+
async function closeDialog(dialogId) {
|
|
207
|
+
if (typeof dialogId === "number") {
|
|
208
|
+
await dialogs.find((d) => d.id === dialogId)?.close();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const reversed = [...dialogs].reverse();
|
|
212
|
+
for (let i = 0; i < reversed.length; i++) {
|
|
213
|
+
await reversed[i].close();
|
|
214
|
+
if (reversed[i + 1]?.isRoute) break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function promptDialog(Component, props) {
|
|
218
|
+
return promptModal(Component, props, { namespace: DIALOG_NAMESPACE });
|
|
219
|
+
}
|
|
220
|
+
function openDialog(Component, props) {
|
|
221
|
+
return openModal(Component, props, { namespace: DIALOG_NAMESPACE });
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
/** Reactive list of active dialogs */
|
|
225
|
+
dialogs,
|
|
226
|
+
/** Opens a dialog with the given component and props */
|
|
227
|
+
openDialog,
|
|
228
|
+
/**
|
|
229
|
+
* Closes a dialog.
|
|
230
|
+
* If an ID is provided, closes that specific dialog.
|
|
231
|
+
* Otherwise closes all dialogs, stopping before any route-bound dialog.
|
|
232
|
+
*/
|
|
233
|
+
closeDialog,
|
|
234
|
+
/**
|
|
235
|
+
* Opens a dialog in prompt mode and waits for a result.
|
|
236
|
+
* Useful for confirmation flows or data-input dialogs.
|
|
237
|
+
*/
|
|
238
|
+
promptDialog
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/hooks/toast.ts
|
|
243
|
+
import { watch } from "vue";
|
|
244
|
+
var TOAST_NAMESPACE = "toast";
|
|
245
|
+
var DURATION = 5e3;
|
|
246
|
+
var toasts = getQueueByNamespace(TOAST_NAMESPACE);
|
|
247
|
+
var timers = /* @__PURE__ */ new Map();
|
|
248
|
+
function removeTimer(toastId) {
|
|
249
|
+
const timer = timers.get(toastId);
|
|
250
|
+
if (!timer) return;
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
timers.delete(toastId);
|
|
253
|
+
}
|
|
254
|
+
function addTimer(toastId) {
|
|
255
|
+
if (timers.has(toastId)) return;
|
|
256
|
+
const timer = setTimeout(() => closeToast(toastId), DURATION);
|
|
257
|
+
timers.set(toastId, timer);
|
|
258
|
+
}
|
|
259
|
+
function closeToast(toastId) {
|
|
260
|
+
removeTimer(toastId);
|
|
261
|
+
const toast = toasts.find((t) => t.id === toastId);
|
|
262
|
+
toast?.close().catch(() => {
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function closeAllToasts() {
|
|
266
|
+
toasts.forEach((t) => closeToast(t.id));
|
|
267
|
+
}
|
|
268
|
+
watch(
|
|
269
|
+
toasts,
|
|
270
|
+
(nextToasts) => {
|
|
271
|
+
const activeIds = new Set(nextToasts.map((t) => t.id));
|
|
272
|
+
nextToasts.forEach((t) => addTimer(t.id));
|
|
273
|
+
for (const id of timers.keys()) {
|
|
274
|
+
if (!activeIds.has(id)) {
|
|
275
|
+
removeTimer(id);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
{ immediate: true }
|
|
280
|
+
);
|
|
281
|
+
function openToast(Component, props) {
|
|
282
|
+
return pushModal(Component, props, { namespace: TOAST_NAMESPACE });
|
|
283
|
+
}
|
|
284
|
+
function useToast() {
|
|
285
|
+
return {
|
|
286
|
+
/** Reactive list of active toasts */
|
|
287
|
+
toasts,
|
|
288
|
+
/** Opens a toast with a custom component */
|
|
289
|
+
openToast,
|
|
290
|
+
/** Closes a toast by ID */
|
|
291
|
+
closeToast,
|
|
292
|
+
/** Closes all active toasts */
|
|
293
|
+
closeAllToasts
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/hooks/sidebar-panel.ts
|
|
298
|
+
import { computed } from "vue";
|
|
299
|
+
var SIDEBAR_NAMESPACE = "sidebar";
|
|
300
|
+
var sidebars = getQueueByNamespace(SIDEBAR_NAMESPACE);
|
|
301
|
+
var sidebar = computed(() => sidebars.at(-1));
|
|
302
|
+
function useSidebarPanel() {
|
|
303
|
+
function openSidebar(Component, props) {
|
|
304
|
+
return openModal(Component, props, { namespace: SIDEBAR_NAMESPACE });
|
|
305
|
+
}
|
|
306
|
+
async function closeSidebar() {
|
|
307
|
+
if (!sidebar.value) return;
|
|
308
|
+
await sidebar.value.close();
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
/** Reactive list of active sidebars */
|
|
312
|
+
sidebars,
|
|
313
|
+
/** The topmost (currently active) sidebar */
|
|
314
|
+
sidebar,
|
|
315
|
+
/** Opens a component inside a sidebar panel */
|
|
316
|
+
openSidebar,
|
|
317
|
+
/** Closes the current sidebar */
|
|
318
|
+
closeSidebar
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
export {
|
|
322
|
+
DIALOG_NAMESPACE,
|
|
323
|
+
MODAL_EVENT_PROMPT,
|
|
324
|
+
SIDEBAR_NAMESPACE,
|
|
325
|
+
TOAST_NAMESPACE,
|
|
326
|
+
closeById,
|
|
327
|
+
closeModal,
|
|
328
|
+
getCurrentModal,
|
|
329
|
+
getQueueByNamespace,
|
|
330
|
+
openModal,
|
|
331
|
+
popModal,
|
|
332
|
+
promptModal,
|
|
333
|
+
pushModal,
|
|
334
|
+
useDialog,
|
|
335
|
+
useSidebarPanel,
|
|
336
|
+
useToast
|
|
337
|
+
};
|
|
338
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/modal/state.ts","../src/modal/methods/close-modal.ts","../src/modal/methods/push-modal.ts","../src/modal/modal.ts","../src/modal/guards.ts","../src/modal/methods/open-modal.ts","../src/modal/methods/prompt-modal.ts","../src/modal/methods/get-current-modal.ts","../src/modal/methods/pop-modal.ts","../src/modal/methods/close-by-id.ts","../src/hooks/dialog.ts","../src/hooks/toast.ts","../src/hooks/sidebar-panel.ts"],"sourcesContent":["import { reactive } from 'vue'\nimport type { ModalRecord } from './modal'\nimport type { NamespaceKey } from './types'\n\nexport const DEFAULT_NAMESPACE: NamespaceKey = 'default'\n\nexport interface NamespaceState {\n queue: ModalRecord[]\n}\n\nconst namespaceMap = new Map<NamespaceKey, NamespaceState>()\n\n/** Returns (or lazily creates) the reactive state for the given namespace */\nexport function getNamespaceState(namespace: NamespaceKey = DEFAULT_NAMESPACE): NamespaceState {\n if (!namespaceMap.has(namespace)) {\n namespaceMap.set(namespace, { queue: reactive([]) })\n }\n\n return namespaceMap.get(namespace)!\n}\n\n/** Returns the reactive modal queue for the given namespace */\nexport function getQueueByNamespace(namespace?: NamespaceKey) {\n return getNamespaceState(namespace).queue\n}\n","import { getNamespaceState } from '../state'\nimport type { ModalCloseOptions } from '../types'\n\n/** Closes all modals in the namespace sequentially (LIFO order) */\nexport async function closeModal(options: ModalCloseOptions = {}): Promise<void> {\n // Snapshot and reverse: close top-to-bottom so a guard rejection on an upper modal\n // leaves lower modals untouched\n const queue = [...getNamespaceState(options.namespace).queue].reverse()\n for (const modal of queue) {\n await modal.close()\n }\n}\n","import { markRaw, type Component } from 'vue'\nimport { createModal, modalStore } from '../modal'\nimport { getNamespaceState } from '../state'\nimport type { ModalOptions } from '../types'\n\n/** Pushes a new modal on top of the current stack. Use `popModal` to close it */\nexport function pushModal(\n component: Component,\n props: any = {},\n options: Partial<ModalOptions> = {}\n) {\n const ns = getNamespaceState(options.namespace)\n const modal = createModal(component, props, options)\n\n // markRaw prevents Vue from making the modal record itself reactive\n // (individual Ref fields inside remain reactive as intended)\n ns.queue.push(markRaw(modal))\n\n // Register in modalStore only after the modal has been successfully queued\n modalStore.set(modal.id, modal)\n\n return modal\n}\n","import { reactive, ref, type Component, type Ref } from 'vue'\nimport { guardRegistry, guardToPromiseFn, runGuardQueue } from './guards'\nimport { DEFAULT_NAMESPACE, getNamespaceState } from './state'\nimport type { EventCallback, GuardFunction, ModalOptions, NamespaceKey } from './types'\n\nexport type ModalID = number\n\n/** Event name used by `promptModal` to receive a result value from the modal component */\nexport const MODAL_EVENT_PROMPT = 'modal:prompt'\n\n/** Global store of all active modal records, keyed by modal ID */\nexport const modalStore = new Map<ModalID, ModalRecord>()\n\nlet idCounter = 0\n\nexport interface ModalRecord {\n readonly id: ModalID\n readonly component: Component\n readonly namespace: NamespaceKey\n readonly isRoute: boolean\n readonly closed: Ref<boolean>\n props: Ref<any>\n readonly events: Record<string, EventCallback[]>\n /** Closes the modal, running all registered close guards first */\n close(): Promise<void>\n /** Emits an event on the modal's internal event bus */\n emit(eventName: string, data?: unknown): void\n /** Subscribes to an event on the modal's internal event bus. Returns an unsubscribe function */\n on<T = unknown>(eventName: string, callback: (v: T) => void): () => void\n /** Registers a guard function that can cancel or delay closing */\n addCloseGuard(fn: GuardFunction): void\n /** Registers a callback invoked after the modal is fully destroyed */\n addDestroyGuard(fn: () => void): void\n}\n\n/** Creates a new modal record without mounting it into any queue */\nexport function createModal(\n component: Component,\n props: any,\n options: Partial<ModalOptions> = {}\n): ModalRecord {\n const id = idCounter++\n const namespace = options.namespace ?? DEFAULT_NAMESPACE\n const isRoute = options.isRoute ?? false\n\n let closing = false\n\n const propsRef = ref(props)\n const events = reactive<Record<string, EventCallback[]>>({})\n const closed = ref(false)\n\n const modal: ModalRecord = {\n id,\n component,\n namespace,\n isRoute,\n props: propsRef,\n events,\n closed,\n\n async close(): Promise<void> {\n // Already closed or close in progress\n if (closing || closed.value) return\n closing = true\n\n const queue = getNamespaceState(namespace).queue\n const guardFns = guardRegistry.get(id, 'close').map((guard) => guardToPromiseFn(guard))\n\n try {\n await runGuardQueue(guardFns)\n } catch (error) {\n // Guard rejected the close — reset the flag so the user can retry\n closing = false\n throw error\n }\n\n // Re-find the index: the queue may have changed during async guards\n const current = queue.findIndex((m) => m.id === id)\n if (current !== -1) {\n queue.splice(current, 1)\n }\n\n closed.value = true\n\n for (const runnable of guardRegistry.get(id, 'destroy')) {\n try {\n runnable()\n } catch (error) {\n console.warn('Error running modal destroy guard', error)\n }\n }\n\n guardRegistry.delete(id)\n modalStore.delete(id)\n\n for (const key in events) {\n delete events[key]\n }\n },\n\n emit(eventName: string, data?: unknown): void {\n const handlers = events[eventName]\n if (!handlers) return\n for (const handler of [...handlers]) {\n handler(data)\n }\n },\n\n on<T = unknown>(eventName: string, callback: (v: T) => void): () => void {\n let handlers = events[eventName]\n if (!Array.isArray(handlers)) {\n handlers = []\n events[eventName] = handlers\n }\n\n handlers.push(callback as EventCallback)\n\n return () => {\n const arr = events[eventName]\n if (!arr) return\n\n const i = arr.indexOf(callback)\n if (i !== -1) {\n arr.splice(i, 1)\n }\n }\n },\n\n addCloseGuard(fn: GuardFunction): void {\n guardRegistry.add(id, 'close', fn)\n },\n\n addDestroyGuard(fn: () => void): void {\n guardRegistry.add(id, 'destroy', fn)\n }\n }\n\n return modal\n}\n","import type { GuardFunction } from './types'\n\ntype GuardFunctionPromisify = () => Promise<void>\n\ntype AvailableKeys = 'close' | 'destroy'\n\nconst store = new Map<number, Partial<Record<AvailableKeys, GuardFunction[]>>>()\n\n/** Registry for per-modal guard functions, keyed by modal ID */\nexport const guardRegistry = {\n add(modalId: number, name: AvailableKeys, func: GuardFunction): void {\n let guard = store.get(modalId)\n if (!guard) {\n guard = {}\n store.set(modalId, guard)\n }\n\n guard[name] ??= []\n guard[name].push(func)\n },\n\n get(id: number, name: AvailableKeys): GuardFunction[] {\n return store.get(id)?.[name] ?? []\n },\n\n delete(id: number): void {\n store.delete(id)\n }\n}\n\n/** Runs an array of promisified guard functions sequentially */\nexport async function runGuardQueue(fns: GuardFunctionPromisify[]): Promise<void> {\n for (const fn of fns) {\n await fn()\n }\n}\n\n/** Wraps a guard function so it throws when the guard returns `false` */\nexport function guardToPromiseFn(guard: GuardFunction): GuardFunctionPromisify {\n return async () => {\n const result = await guard()\n if (result !== false) return\n throw new Error('Guard returned false. Close cancelled.')\n }\n}\n","import type { Component } from 'vue'\nimport type { ModalOptions } from '../types'\nimport { closeModal } from './close-modal'\nimport { pushModal } from './push-modal'\n\n/** Closes all active modals in the namespace and opens a new one */\nexport async function openModal(\n component: Component,\n props: any = {},\n options: Partial<ModalOptions> = {}\n) {\n await closeModal({ namespace: options.namespace })\n return pushModal(component, props, options)\n}\n","import type { Component } from 'vue'\nimport { MODAL_EVENT_PROMPT } from '../modal'\nimport type { ModalOptions } from '../types'\nimport { pushModal } from './push-modal'\n\n/**\n * Opens a modal and waits for a result via the `MODAL_EVENT_PROMPT` event.\n * Resolves with the value passed to the event, or `null` if the modal is\n * closed without emitting a result.\n */\nexport function promptModal<T = unknown>(\n component: Component,\n props: any = {},\n options: Partial<ModalOptions> = {}\n): Promise<T | null> {\n const modal = pushModal(component, props, options)\n let isPrompted = false\n\n const { promise, resolve } = Promise.withResolvers<T | null>()\n\n const unsubscribe = modal.on(MODAL_EVENT_PROMPT, async (data: T) => {\n // Guard against concurrent calls: ignore a second emit while close is in progress\n if (isPrompted) return\n isPrompted = true\n\n try {\n await modal.close()\n resolve(data)\n } catch {\n // Guard rejected the close — reset the flag so the user can retry\n isPrompted = false\n }\n })\n\n // addDestroyGuard is called after the modal is fully closed (non-cancellable)\n modal.addDestroyGuard(() => {\n unsubscribe()\n if (!isPrompted) {\n resolve(null)\n }\n })\n\n return promise\n}\n","import { getNamespaceState } from '../state'\nimport type { NamespaceKey } from '../types'\n\n/** Returns the topmost active modal in the namespace, or `undefined` if the queue is empty */\nexport function getCurrentModal(namespace?: NamespaceKey) {\n const ns = getNamespaceState(namespace)\n return ns.queue.at(-1)\n}\n","import type { ModalCloseOptions } from '../types'\nimport { getCurrentModal } from './get-current-modal'\n\n/** Closes the topmost active modal in the namespace */\nexport async function popModal(options: ModalCloseOptions = {}): Promise<void> {\n const modal = getCurrentModal(options.namespace)\n if (!modal) return\n await modal.close()\n}\n","import { modalStore } from '../modal'\n\n/** Closes a modal by its ID. Throws if no modal with the given ID exists */\nexport async function closeById(id: number): Promise<void> {\n const modal = modalStore.get(id)\n if (!modal) throw new Error(`Modal with ID ${id} not found`)\n await modal.close()\n}\n","import type { ComponentProps } from '../component-props'\nimport type { ModalRecord } from '../modal/modal'\nimport { getQueueByNamespace } from '../modal/state'\nimport { openModal as openModalFn } from '../modal/methods/open-modal'\nimport { promptModal as promptModalFn } from '../modal/methods/prompt-modal'\n\nexport const DIALOG_NAMESPACE = 'dialog'\n\nconst dialogs = getQueueByNamespace(DIALOG_NAMESPACE)\n\n/**\n * Composable for managing dialog modals.\n * Provides methods to open, close, and prompt dialogs within the `dialog` namespace.\n */\nexport function useDialog() {\n async function closeDialog(dialogId?: number): Promise<void> {\n if (typeof dialogId === 'number') {\n await dialogs.find((d) => d.id === dialogId)?.close()\n return\n }\n\n const reversed = [...dialogs].reverse()\n for (let i = 0; i < reversed.length; i++) {\n await reversed[i]!.close()\n // Stop before a route-bound dialog\n if (reversed[i + 1]?.isRoute) break\n }\n }\n\n function promptDialog<\n Result = unknown,\n C extends new (...args: unknown[]) => unknown = new (...args: unknown[]) => unknown\n >(Component: C, props?: ComponentProps<C>): Promise<Result | null> {\n return promptModalFn<Result>(Component, props, { namespace: DIALOG_NAMESPACE })\n }\n\n function openDialog<C extends new (...args: unknown[]) => unknown>(\n Component: C,\n props?: ComponentProps<C>\n ): Promise<ModalRecord> {\n return openModalFn(Component, props, { namespace: DIALOG_NAMESPACE })\n }\n\n return {\n /** Reactive list of active dialogs */\n dialogs,\n /** Opens a dialog with the given component and props */\n openDialog,\n /**\n * Closes a dialog.\n * If an ID is provided, closes that specific dialog.\n * Otherwise closes all dialogs, stopping before any route-bound dialog.\n */\n closeDialog,\n /**\n * Opens a dialog in prompt mode and waits for a result.\n * Useful for confirmation flows or data-input dialogs.\n */\n promptDialog\n }\n}\n","import type { ComponentProps } from '../component-props'\nimport type { ModalRecord } from '../modal/modal'\nimport { getQueueByNamespace } from '../modal/state'\nimport { pushModal } from '../modal/methods/push-modal'\nimport { watch } from 'vue'\n\nexport const TOAST_NAMESPACE = 'toast'\nconst DURATION = 5_000\n\n// Queue and timers are module-level singletons shared across all useToast() calls\nconst toasts = getQueueByNamespace(TOAST_NAMESPACE)\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\n\nfunction removeTimer(toastId: number) {\n const timer = timers.get(toastId)\n if (!timer) return\n\n clearTimeout(timer)\n timers.delete(toastId)\n}\n\nfunction addTimer(toastId: number) {\n if (timers.has(toastId)) return\n const timer = setTimeout(() => closeToast(toastId), DURATION)\n timers.set(toastId, timer)\n}\n\nfunction closeToast(toastId: number) {\n removeTimer(toastId)\n const toast = toasts.find((t) => t.id === toastId)\n toast?.close().catch(() => {})\n}\n\nfunction closeAllToasts() {\n toasts.forEach((t) => closeToast(t.id))\n}\n\n// Sync timers with the queue — runs once at module level\nwatch(\n toasts,\n (nextToasts) => {\n const activeIds = new Set(nextToasts.map((t) => t.id))\n nextToasts.forEach((t) => addTimer(t.id))\n for (const id of timers.keys()) {\n if (!activeIds.has(id)) {\n removeTimer(id)\n }\n }\n },\n { immediate: true }\n)\n\nfunction openToast<C extends new (...args: unknown[]) => unknown>(\n Component: C,\n props?: ComponentProps<C>\n): ModalRecord {\n return pushModal(Component, props, { namespace: TOAST_NAMESPACE })\n}\n\n/** Composable for managing toast notifications */\nexport function useToast() {\n return {\n /** Reactive list of active toasts */\n toasts,\n /** Opens a toast with a custom component */\n openToast,\n /** Closes a toast by ID */\n closeToast,\n /** Closes all active toasts */\n closeAllToasts\n }\n}\n","import type { ComponentProps } from '../component-props'\nimport { getQueueByNamespace } from '../modal/state'\nimport { openModal } from '../modal/methods/open-modal'\nimport { computed } from 'vue'\n\nexport const SIDEBAR_NAMESPACE = 'sidebar'\n\nconst sidebars = getQueueByNamespace(SIDEBAR_NAMESPACE)\nconst sidebar = computed(() => sidebars.at(-1))\n\n/**\n * Composable for managing sidebar panels.\n * Provides methods to open and close sidebars within the `sidebar` namespace.\n */\nexport function useSidebarPanel() {\n function openSidebar<C extends new (...args: unknown[]) => unknown>(\n Component: C,\n props?: ComponentProps<C>\n ) {\n return openModal(Component, props, { namespace: SIDEBAR_NAMESPACE })\n }\n\n async function closeSidebar(): Promise<void> {\n if (!sidebar.value) return\n await sidebar.value.close()\n }\n\n return {\n /** Reactive list of active sidebars */\n sidebars,\n /** The topmost (currently active) sidebar */\n sidebar,\n /** Opens a component inside a sidebar panel */\n openSidebar,\n /** Closes the current sidebar */\n closeSidebar\n }\n}\n"],"mappings":"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","names":["reactive","reactive"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vue-modality/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight modal library for Vue 3 with namespace queues, guard system, and promise-based prompt pattern. Zero dependencies.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/rh00x/vue-modality",
|
|
23
|
+
"directory": "packages/core"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://rh00x.github.io/vue-modality/",
|
|
26
|
+
"bugs": "https://github.com/rh00x/vue-modality/issues",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"vue",
|
|
29
|
+
"vue3",
|
|
30
|
+
"modal",
|
|
31
|
+
"dialog",
|
|
32
|
+
"popup",
|
|
33
|
+
"toast",
|
|
34
|
+
"sidebar",
|
|
35
|
+
"typescript",
|
|
36
|
+
"promise",
|
|
37
|
+
"guard",
|
|
38
|
+
"namespace",
|
|
39
|
+
"lightweight"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"dev": "tsup --watch"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"vue": "^3.4"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"vue-router": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"tsup": "^8.3.5",
|
|
55
|
+
"typescript": "^5.7.2",
|
|
56
|
+
"vue": "^3.5.13"
|
|
57
|
+
}
|
|
58
|
+
}
|