web-mojo 2.1.46
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/LICENSE +198 -0
- package/README.md +510 -0
- package/dist/admin.cjs.js +2 -0
- package/dist/admin.cjs.js.map +1 -0
- package/dist/admin.css +621 -0
- package/dist/admin.es.js +7973 -0
- package/dist/admin.es.js.map +1 -0
- package/dist/auth.cjs.js +2 -0
- package/dist/auth.cjs.js.map +1 -0
- package/dist/auth.css +804 -0
- package/dist/auth.es.js +2168 -0
- package/dist/auth.es.js.map +1 -0
- package/dist/charts.cjs.js +2 -0
- package/dist/charts.cjs.js.map +1 -0
- package/dist/charts.css +1002 -0
- package/dist/charts.es.js +16 -0
- package/dist/charts.es.js.map +1 -0
- package/dist/chunks/ContextMenu-BrHqj0fn.js +80 -0
- package/dist/chunks/ContextMenu-BrHqj0fn.js.map +1 -0
- package/dist/chunks/ContextMenu-gEcpSz56.js +2 -0
- package/dist/chunks/ContextMenu-gEcpSz56.js.map +1 -0
- package/dist/chunks/DataView-DPryYpEW.js +2 -0
- package/dist/chunks/DataView-DPryYpEW.js.map +1 -0
- package/dist/chunks/DataView-DjZQrpba.js +843 -0
- package/dist/chunks/DataView-DjZQrpba.js.map +1 -0
- package/dist/chunks/Dialog-BsRx4eg3.js +2 -0
- package/dist/chunks/Dialog-BsRx4eg3.js.map +1 -0
- package/dist/chunks/Dialog-DSlctbon.js +1377 -0
- package/dist/chunks/Dialog-DSlctbon.js.map +1 -0
- package/dist/chunks/FilePreviewView-BmFHzK5K.js +5868 -0
- package/dist/chunks/FilePreviewView-BmFHzK5K.js.map +1 -0
- package/dist/chunks/FilePreviewView-DcdRl_ta.js +2 -0
- package/dist/chunks/FilePreviewView-DcdRl_ta.js.map +1 -0
- package/dist/chunks/FormView-CmBuwKGD.js +2 -0
- package/dist/chunks/FormView-CmBuwKGD.js.map +1 -0
- package/dist/chunks/FormView-DqUBMPJ9.js +5054 -0
- package/dist/chunks/FormView-DqUBMPJ9.js.map +1 -0
- package/dist/chunks/MetricsChart-CM4CI6eA.js +2095 -0
- package/dist/chunks/MetricsChart-CM4CI6eA.js.map +1 -0
- package/dist/chunks/MetricsChart-CPidSMaN.js +2 -0
- package/dist/chunks/MetricsChart-CPidSMaN.js.map +1 -0
- package/dist/chunks/PDFViewer-BNQlnS83.js +2 -0
- package/dist/chunks/PDFViewer-BNQlnS83.js.map +1 -0
- package/dist/chunks/PDFViewer-Dyo-Oeyd.js +946 -0
- package/dist/chunks/PDFViewer-Dyo-Oeyd.js.map +1 -0
- package/dist/chunks/Page-B524zSQs.js +351 -0
- package/dist/chunks/Page-B524zSQs.js.map +1 -0
- package/dist/chunks/Page-BFgj0pAA.js +2 -0
- package/dist/chunks/Page-BFgj0pAA.js.map +1 -0
- package/dist/chunks/TokenManager-BXNva8Jk.js +287 -0
- package/dist/chunks/TokenManager-BXNva8Jk.js.map +1 -0
- package/dist/chunks/TokenManager-Bzn4guFm.js +2 -0
- package/dist/chunks/TokenManager-Bzn4guFm.js.map +1 -0
- package/dist/chunks/TopNav-D3I3_25f.js +371 -0
- package/dist/chunks/TopNav-D3I3_25f.js.map +1 -0
- package/dist/chunks/TopNav-MDjL4kV0.js +2 -0
- package/dist/chunks/TopNav-MDjL4kV0.js.map +1 -0
- package/dist/chunks/User-BalfYTEF.js +3 -0
- package/dist/chunks/User-BalfYTEF.js.map +1 -0
- package/dist/chunks/User-DwIT-CTQ.js +1937 -0
- package/dist/chunks/User-DwIT-CTQ.js.map +1 -0
- package/dist/chunks/WebApp-B6mgbNn2.js +4767 -0
- package/dist/chunks/WebApp-B6mgbNn2.js.map +1 -0
- package/dist/chunks/WebApp-DqDowtkl.js +2 -0
- package/dist/chunks/WebApp-DqDowtkl.js.map +1 -0
- package/dist/chunks/WebSocketClient-D6i85jl2.js +2 -0
- package/dist/chunks/WebSocketClient-D6i85jl2.js.map +1 -0
- package/dist/chunks/WebSocketClient-Dvl3AYx1.js +297 -0
- package/dist/chunks/WebSocketClient-Dvl3AYx1.js.map +1 -0
- package/dist/core.css +1181 -0
- package/dist/css/web-mojo.css +17 -0
- package/dist/css-manifest.json +6 -0
- package/dist/docit.cjs.js +2 -0
- package/dist/docit.cjs.js.map +1 -0
- package/dist/docit.es.js +959 -0
- package/dist/docit.es.js.map +1 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.es.js +2681 -0
- package/dist/index.es.js.map +1 -0
- package/dist/lightbox.cjs.js +2 -0
- package/dist/lightbox.cjs.js.map +1 -0
- package/dist/lightbox.css +606 -0
- package/dist/lightbox.es.js +3737 -0
- package/dist/lightbox.es.js.map +1 -0
- package/dist/loader.es.js +115 -0
- package/dist/loader.umd.js +85 -0
- package/dist/portal.css +2446 -0
- package/dist/table.css +639 -0
- package/dist/toast.css +181 -0
- package/package.json +179 -0
|
@@ -0,0 +1,1377 @@
|
|
|
1
|
+
import { V as View } from "./WebApp-B6mgbNn2.js";
|
|
2
|
+
class Dialog extends View {
|
|
3
|
+
static _openDialogs = [];
|
|
4
|
+
static _baseZIndex = {
|
|
5
|
+
backdrop: 1050,
|
|
6
|
+
modal: 1055
|
|
7
|
+
};
|
|
8
|
+
static _busyIndicator = null;
|
|
9
|
+
static _busyCounter = 0;
|
|
10
|
+
static _busyTimeout = null;
|
|
11
|
+
/**
|
|
12
|
+
* Shows a full-screen busy indicator.
|
|
13
|
+
* Manages a counter for nested calls, only showing one indicator.
|
|
14
|
+
* @param {object} options - Options { timeout, message }
|
|
15
|
+
*/
|
|
16
|
+
static showBusy(options = {}) {
|
|
17
|
+
const { timeout = 3e4, message = "Loading..." } = options;
|
|
18
|
+
this._busyCounter++;
|
|
19
|
+
if (this._busyCounter === 1) {
|
|
20
|
+
if (this._busyTimeout) {
|
|
21
|
+
clearTimeout(this._busyTimeout);
|
|
22
|
+
}
|
|
23
|
+
if (!this._busyIndicator) {
|
|
24
|
+
this._busyIndicator = document.createElement("div");
|
|
25
|
+
this._busyIndicator.className = "mojo-busy-indicator";
|
|
26
|
+
this._busyIndicator.innerHTML = `
|
|
27
|
+
<div class="mojo-busy-spinner">
|
|
28
|
+
<div class="spinner-border text-light" role="status">
|
|
29
|
+
<span class="visually-hidden">Loading...</span>
|
|
30
|
+
</div>
|
|
31
|
+
<p class="mojo-busy-message mt-3 text-light">${message}</p>
|
|
32
|
+
</div>
|
|
33
|
+
<style>
|
|
34
|
+
.mojo-busy-indicator {
|
|
35
|
+
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
|
36
|
+
background-color: rgba(0, 0, 0, 0.5); z-index: 9999;
|
|
37
|
+
display: flex; align-items: center; justify-content: center;
|
|
38
|
+
opacity: 0; transition: opacity 0.15s linear;
|
|
39
|
+
}
|
|
40
|
+
.mojo-busy-indicator.show { opacity: 1; }
|
|
41
|
+
.mojo-busy-spinner .spinner-border { width: 3rem; height: 3rem; }
|
|
42
|
+
</style>
|
|
43
|
+
`;
|
|
44
|
+
document.body.appendChild(this._busyIndicator);
|
|
45
|
+
}
|
|
46
|
+
const msgElement = this._busyIndicator.querySelector(".mojo-busy-message");
|
|
47
|
+
if (msgElement) msgElement.textContent = message;
|
|
48
|
+
setTimeout(() => this._busyIndicator.classList.add("show"), 10);
|
|
49
|
+
this._busyTimeout = setTimeout(() => {
|
|
50
|
+
console.error("Busy indicator timed out.");
|
|
51
|
+
this.hideBusy(true);
|
|
52
|
+
this.alert({
|
|
53
|
+
title: "Operation Timed Out",
|
|
54
|
+
message: "The operation took too long. Please check your connection and try again.",
|
|
55
|
+
type: "danger"
|
|
56
|
+
});
|
|
57
|
+
}, timeout);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Hides the full-screen busy indicator.
|
|
62
|
+
* Decrements the counter and only hides when the counter reaches zero.
|
|
63
|
+
* @param {boolean} force - If true, forces the indicator to hide immediately.
|
|
64
|
+
*/
|
|
65
|
+
static hideBusy(force = false) {
|
|
66
|
+
if (force) {
|
|
67
|
+
this._busyCounter = 0;
|
|
68
|
+
} else {
|
|
69
|
+
this._busyCounter--;
|
|
70
|
+
}
|
|
71
|
+
if (this._busyCounter <= 0) {
|
|
72
|
+
this._busyCounter = 0;
|
|
73
|
+
if (this._busyTimeout) {
|
|
74
|
+
clearTimeout(this._busyTimeout);
|
|
75
|
+
this._busyTimeout = null;
|
|
76
|
+
}
|
|
77
|
+
if (this._busyIndicator) {
|
|
78
|
+
this._busyIndicator.classList.remove("show");
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
if (this._busyIndicator && this._busyCounter === 0) {
|
|
81
|
+
this._busyIndicator.remove();
|
|
82
|
+
this._busyIndicator = null;
|
|
83
|
+
}
|
|
84
|
+
}, 150);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
constructor(options = {}) {
|
|
89
|
+
const modalId = options.id || `modal-${Date.now()}`;
|
|
90
|
+
super({
|
|
91
|
+
...options,
|
|
92
|
+
id: modalId,
|
|
93
|
+
// Pass the ID to parent constructor
|
|
94
|
+
tagName: "div",
|
|
95
|
+
className: `modal ${options.fade !== false ? "fade" : ""} ${options.className || ""}`,
|
|
96
|
+
attributes: {
|
|
97
|
+
tabindex: "-1",
|
|
98
|
+
"aria-hidden": "true",
|
|
99
|
+
"aria-labelledby": options.labelledBy || `${modalId}-label`,
|
|
100
|
+
"aria-describedby": options.describedBy || null,
|
|
101
|
+
...options.attributes
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
this.modalId = modalId;
|
|
105
|
+
this.title = options.title || "";
|
|
106
|
+
this.titleId = `${this.modalId}-label`;
|
|
107
|
+
this.size = options.size || "";
|
|
108
|
+
this.centered = options.centered !== void 0 ? options.centered : false;
|
|
109
|
+
this.scrollable = options.scrollable !== void 0 ? options.scrollable : false;
|
|
110
|
+
this.autoSize = options.autoSize || options.size === "auto";
|
|
111
|
+
this.backdrop = options.backdrop !== void 0 ? options.backdrop : true;
|
|
112
|
+
this.keyboard = options.keyboard !== void 0 ? options.keyboard : true;
|
|
113
|
+
this.focus = options.focus !== void 0 ? options.focus : true;
|
|
114
|
+
this.header = options.header !== void 0 ? options.header : true;
|
|
115
|
+
this.headerContent = options.headerContent || null;
|
|
116
|
+
this.closeButton = options.closeButton !== void 0 ? options.closeButton : true;
|
|
117
|
+
this.contextMenu = options.contextMenu || null;
|
|
118
|
+
this.body = options.body || options.content || "";
|
|
119
|
+
this.bodyView = null;
|
|
120
|
+
this.bodyClass = options.bodyClass || "";
|
|
121
|
+
this.noBodyPadding = options.noBodyPadding || false;
|
|
122
|
+
this.minWidth = options.minWidth || 300;
|
|
123
|
+
this.minHeight = options.minHeight || 200;
|
|
124
|
+
this.maxWidthPercent = options.maxWidthPercent || 0.9;
|
|
125
|
+
this.maxHeightPercent = options.maxHeightPercent || 0.8;
|
|
126
|
+
this._processBodyContent(this.body);
|
|
127
|
+
this.footer = options.footer || null;
|
|
128
|
+
this.footerView = null;
|
|
129
|
+
this.footerClass = options.footerClass || "";
|
|
130
|
+
this._processFooterContent(this.footer);
|
|
131
|
+
this.buttons = options.buttons || null;
|
|
132
|
+
this.onShow = options.onShow || null;
|
|
133
|
+
this.onShown = options.onShown || null;
|
|
134
|
+
this.onHide = options.onHide || null;
|
|
135
|
+
this.onHidden = options.onHidden || null;
|
|
136
|
+
this.onHidePrevented = options.onHidePrevented || null;
|
|
137
|
+
this.autoShow = options.autoShow !== void 0 ? options.autoShow : false;
|
|
138
|
+
this.modal = null;
|
|
139
|
+
this.relatedTarget = options.relatedTarget || null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Process body content to detect and handle View instances
|
|
143
|
+
*/
|
|
144
|
+
_processBodyContent(body) {
|
|
145
|
+
if (body && body.render) {
|
|
146
|
+
this.bodyView = body;
|
|
147
|
+
this.body = "";
|
|
148
|
+
this.addChild(this.bodyView);
|
|
149
|
+
} else if (typeof body === "function") {
|
|
150
|
+
try {
|
|
151
|
+
const result = body();
|
|
152
|
+
if (result instanceof View) {
|
|
153
|
+
this.bodyView = result;
|
|
154
|
+
this.body = "";
|
|
155
|
+
this.addChild(this.bodyView);
|
|
156
|
+
} else if (result instanceof Promise) {
|
|
157
|
+
this.bodyPromise = result;
|
|
158
|
+
this.body = '<div class="text-center"><div class="spinner-border spinner-border-sm"></div></div>';
|
|
159
|
+
} else {
|
|
160
|
+
this.body = result;
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("Error processing body function:", error);
|
|
164
|
+
this.body = body;
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
this.body = body;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Process footer content to detect and handle View instances
|
|
172
|
+
*/
|
|
173
|
+
_processFooterContent(footer) {
|
|
174
|
+
if (footer instanceof View) {
|
|
175
|
+
this.footerView = footer;
|
|
176
|
+
this.footer = null;
|
|
177
|
+
this.addChild(this.footerView);
|
|
178
|
+
} else if (typeof footer === "function") {
|
|
179
|
+
try {
|
|
180
|
+
const result = footer();
|
|
181
|
+
if (result instanceof View) {
|
|
182
|
+
this.footerView = result;
|
|
183
|
+
this.footer = null;
|
|
184
|
+
this.addChild(this.footerView);
|
|
185
|
+
} else if (result instanceof Promise) {
|
|
186
|
+
this.footerPromise = result;
|
|
187
|
+
this.footer = '<div class="text-center"><div class="spinner-border spinner-border-sm"></div></div>';
|
|
188
|
+
} else {
|
|
189
|
+
this.footer = result;
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error("Error processing footer function:", error);
|
|
193
|
+
this.footer = footer;
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
this.footer = footer;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get dialog template with all Bootstrap 5 features
|
|
201
|
+
*/
|
|
202
|
+
async getTemplate() {
|
|
203
|
+
const dialogClasses = ["modal-dialog"];
|
|
204
|
+
if (this.size && this.size !== "auto") {
|
|
205
|
+
if (this.size.startsWith("fullscreen")) {
|
|
206
|
+
dialogClasses.push(`modal-${this.size}`);
|
|
207
|
+
} else if (["sm", "lg", "xl"].includes(this.size)) {
|
|
208
|
+
dialogClasses.push(`modal-${this.size}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (this.centered) {
|
|
212
|
+
dialogClasses.push("modal-dialog-centered");
|
|
213
|
+
}
|
|
214
|
+
if (this.scrollable) {
|
|
215
|
+
dialogClasses.push("modal-dialog-scrollable");
|
|
216
|
+
}
|
|
217
|
+
return `
|
|
218
|
+
<div class="${dialogClasses.join(" ")}">
|
|
219
|
+
<div class="modal-content">
|
|
220
|
+
${await this.buildHeader()}
|
|
221
|
+
${await this.buildBody()}
|
|
222
|
+
${await this.buildFooter()}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Build modal header
|
|
229
|
+
*/
|
|
230
|
+
async buildHeader() {
|
|
231
|
+
if (!this.header) {
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
if (this.headerContent) {
|
|
235
|
+
return `<div class="modal-header">${this.headerContent}</div>`;
|
|
236
|
+
}
|
|
237
|
+
let headerActions = "";
|
|
238
|
+
if (this.contextMenu && this.contextMenu.items && this.contextMenu.items.length > 0) {
|
|
239
|
+
headerActions = await this.buildContextMenu();
|
|
240
|
+
} else if (this.closeButton) {
|
|
241
|
+
headerActions = '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>';
|
|
242
|
+
}
|
|
243
|
+
return `
|
|
244
|
+
<div class="modal-header">
|
|
245
|
+
${this.title ? `<h5 class="modal-title" id="${this.titleId}">${this.title}</h5>` : ""}
|
|
246
|
+
${headerActions}
|
|
247
|
+
</div>
|
|
248
|
+
`;
|
|
249
|
+
}
|
|
250
|
+
async buildContextMenu() {
|
|
251
|
+
const menuItems = await this.filterContextMenuItems();
|
|
252
|
+
if (menuItems.length === 0) {
|
|
253
|
+
return this.closeButton ? '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>' : "";
|
|
254
|
+
}
|
|
255
|
+
const triggerIcon = this.contextMenu.icon || "bi-three-dots-vertical";
|
|
256
|
+
const buttonClass = this.contextMenu.buttonClass || "btn btn-link p-1 mojo-modal-context-menu-btn";
|
|
257
|
+
const menuItemsHtml = menuItems.map((item) => {
|
|
258
|
+
if (item.type === "divider") {
|
|
259
|
+
return '<li><hr class="dropdown-divider"></li>';
|
|
260
|
+
}
|
|
261
|
+
const icon = item.icon ? `<i class="${item.icon} me-2"></i>` : "";
|
|
262
|
+
const label = item.label || "";
|
|
263
|
+
if (item.href) {
|
|
264
|
+
return `<li><a class="dropdown-item" href="${item.href}"${item.target ? ` target="${item.target}"` : ""}>${icon}${label}</a></li>`;
|
|
265
|
+
} else if (item.action) {
|
|
266
|
+
const dataAttrs = Object.keys(item).filter((key) => key.startsWith("data-")).map((key) => `${key}="${item[key]}"`).join(" ");
|
|
267
|
+
return `<li><a class="dropdown-item" data-action="${item.action}" ${dataAttrs}>${icon}${label}</a></li>`;
|
|
268
|
+
}
|
|
269
|
+
return "";
|
|
270
|
+
}).join("");
|
|
271
|
+
return `
|
|
272
|
+
<div class="dropdown">
|
|
273
|
+
<button class="${buttonClass}" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
274
|
+
<i class="${triggerIcon}"></i>
|
|
275
|
+
</button>
|
|
276
|
+
<ul class="dropdown-menu dropdown-menu-end">
|
|
277
|
+
${menuItemsHtml}
|
|
278
|
+
</ul>
|
|
279
|
+
</div>
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
async filterContextMenuItems() {
|
|
283
|
+
if (!this.contextMenu || !this.contextMenu.items) {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
const filteredItems = [];
|
|
287
|
+
for (const item of this.contextMenu.items) {
|
|
288
|
+
if (item.type === "divider") {
|
|
289
|
+
filteredItems.push(item);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (item.permissions) {
|
|
293
|
+
try {
|
|
294
|
+
const app = this.getApp?.();
|
|
295
|
+
let user = null;
|
|
296
|
+
if (app) {
|
|
297
|
+
user = app.activeUser || app.getState?.("activeUser");
|
|
298
|
+
}
|
|
299
|
+
if (!user && typeof window !== "undefined" && window.getApp) {
|
|
300
|
+
try {
|
|
301
|
+
const globalApp = window.getApp();
|
|
302
|
+
user = globalApp?.activeUser;
|
|
303
|
+
} catch (e) {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (user && user.hasPermission) {
|
|
307
|
+
if (!user.hasPermission(item.permissions)) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.warn("Error checking permissions for context menu item:", error);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
filteredItems.push(item);
|
|
319
|
+
}
|
|
320
|
+
return filteredItems;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Build modal body
|
|
324
|
+
*/
|
|
325
|
+
async buildBody() {
|
|
326
|
+
if (this.bodyView) {
|
|
327
|
+
this.bodyView.replaceById = true;
|
|
328
|
+
const bodyClass2 = this.noBodyPadding ? `modal-body p-0 ${this.bodyClass}` : `modal-body ${this.bodyClass}`;
|
|
329
|
+
return `<div class="${bodyClass2}" data-view-container="body">
|
|
330
|
+
<!-- View will be mounted here -->
|
|
331
|
+
<div id="${this.bodyView.id}"></div>
|
|
332
|
+
</div>`;
|
|
333
|
+
}
|
|
334
|
+
if (!this.body && this.body !== "") {
|
|
335
|
+
return "";
|
|
336
|
+
}
|
|
337
|
+
const bodyClass = this.noBodyPadding ? `modal-body p-0 ${this.bodyClass}` : `modal-body ${this.bodyClass}`;
|
|
338
|
+
return `
|
|
339
|
+
<div class="${bodyClass}">
|
|
340
|
+
${this.body}
|
|
341
|
+
</div>
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Build modal footer
|
|
346
|
+
*/
|
|
347
|
+
async buildFooter() {
|
|
348
|
+
if (this.footerView) {
|
|
349
|
+
return `<div class="modal-footer ${this.footerClass}" data-view-container="footer"></div>`;
|
|
350
|
+
}
|
|
351
|
+
if (this.footer !== null && typeof this.footer === "string") {
|
|
352
|
+
return `<div class="modal-footer ${this.footerClass}">${this.footer}</div>`;
|
|
353
|
+
}
|
|
354
|
+
if (this.buttons && this.buttons.length > 0) {
|
|
355
|
+
const buttonsHtml = this.buttons.map((btn) => {
|
|
356
|
+
const dismissAttr = btn.dismiss ? 'data-bs-dismiss="modal"' : "";
|
|
357
|
+
const actionAttr = btn.action ? `data-action="${btn.action}"` : "";
|
|
358
|
+
const idAttr = btn.id ? `id="${btn.id}"` : "";
|
|
359
|
+
const disabledAttr = btn.disabled ? "disabled" : "";
|
|
360
|
+
return `
|
|
361
|
+
<button type="${btn.type || "button"}"
|
|
362
|
+
class="btn ${btn.class || "btn-secondary"}"
|
|
363
|
+
${idAttr}
|
|
364
|
+
${dismissAttr}
|
|
365
|
+
${actionAttr}
|
|
366
|
+
${disabledAttr}>
|
|
367
|
+
${btn.icon ? `<i class="bi ${btn.icon} me-1"></i>` : ""}
|
|
368
|
+
${btn.text || "Button"}
|
|
369
|
+
</button>
|
|
370
|
+
`;
|
|
371
|
+
}).join("");
|
|
372
|
+
return `<div class="modal-footer ${this.footerClass}">${buttonsHtml}</div>`;
|
|
373
|
+
}
|
|
374
|
+
return "";
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Override mount to not require a container for dialogs
|
|
378
|
+
* Dialogs are appended to body directly
|
|
379
|
+
*/
|
|
380
|
+
async mount(_container = null) {
|
|
381
|
+
if (this.mounted || this.destroyed) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (!this.element) {
|
|
385
|
+
throw new Error("Cannot mount dialog without element");
|
|
386
|
+
}
|
|
387
|
+
await this.onBeforeMount();
|
|
388
|
+
document.body.appendChild(this.element);
|
|
389
|
+
this.bindEvents();
|
|
390
|
+
this.mounted = true;
|
|
391
|
+
await this.onAfterMount();
|
|
392
|
+
this.emit("mounted", { view: this });
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* After render - prepare for View instances and apply syntax highlighting
|
|
397
|
+
*/
|
|
398
|
+
async onAfterRender() {
|
|
399
|
+
await super.onAfterRender();
|
|
400
|
+
if (window.Prism && this.element) {
|
|
401
|
+
const codeBlocks = this.element.querySelectorAll("pre code");
|
|
402
|
+
if (codeBlocks.length > 0) {
|
|
403
|
+
window.Prism.highlightAllUnder(this.element);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (this.autoSize) {
|
|
407
|
+
this.setupAutoSizing();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* After mount - initialize Bootstrap modal and mount child views
|
|
412
|
+
*/
|
|
413
|
+
async onAfterMount() {
|
|
414
|
+
await super.onAfterMount();
|
|
415
|
+
if (typeof window !== "undefined" && window.bootstrap && window.bootstrap.Modal) {
|
|
416
|
+
if (this.backdrop === "static") {
|
|
417
|
+
this.element.setAttribute("data-bs-backdrop", "static");
|
|
418
|
+
}
|
|
419
|
+
if (!this.keyboard) {
|
|
420
|
+
this.element.setAttribute("data-bs-keyboard", "false");
|
|
421
|
+
}
|
|
422
|
+
this.modal = new window.bootstrap.Modal(this.element, {
|
|
423
|
+
backdrop: this.backdrop,
|
|
424
|
+
keyboard: this.keyboard,
|
|
425
|
+
focus: this.focus
|
|
426
|
+
});
|
|
427
|
+
this.bindBootstrapEvents();
|
|
428
|
+
if (this.autoShow) {
|
|
429
|
+
this.show(this.relatedTarget);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Setup auto-sizing - wait for modal animation to complete
|
|
435
|
+
*/
|
|
436
|
+
setupAutoSizing() {
|
|
437
|
+
if (!this.element) return;
|
|
438
|
+
this.element.addEventListener("shown.bs.modal", () => {
|
|
439
|
+
this.applyAutoSizing();
|
|
440
|
+
}, { once: true });
|
|
441
|
+
setTimeout(() => {
|
|
442
|
+
if (this.isShown()) {
|
|
443
|
+
this.applyAutoSizing();
|
|
444
|
+
}
|
|
445
|
+
}, 100);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Apply auto-sizing based on content dimensions
|
|
449
|
+
*/
|
|
450
|
+
applyAutoSizing() {
|
|
451
|
+
if (!this.element) return;
|
|
452
|
+
try {
|
|
453
|
+
const modalDialog = this.element.querySelector(".modal-dialog");
|
|
454
|
+
const modalContent = this.element.querySelector(".modal-content");
|
|
455
|
+
const modalBody = this.element.querySelector(".modal-body");
|
|
456
|
+
if (!modalDialog || !modalContent || !modalBody) {
|
|
457
|
+
console.warn("Dialog auto-sizing: Required elements not found");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (this.bodyView && !this.bodyView.element) {
|
|
461
|
+
setTimeout(() => this.applyAutoSizing(), 50);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const originalStyles = {
|
|
465
|
+
dialogMaxWidth: modalDialog.style.maxWidth,
|
|
466
|
+
dialogWidth: modalDialog.style.width,
|
|
467
|
+
contentWidth: modalContent.style.width,
|
|
468
|
+
contentMaxHeight: modalContent.style.maxHeight,
|
|
469
|
+
bodyOverflow: modalBody.style.overflowY
|
|
470
|
+
};
|
|
471
|
+
modalDialog.style.maxWidth = "none";
|
|
472
|
+
modalDialog.style.width = "auto";
|
|
473
|
+
modalContent.style.width = "auto";
|
|
474
|
+
modalContent.style.maxHeight = "none";
|
|
475
|
+
modalContent.offsetHeight;
|
|
476
|
+
const contentRect = modalContent.getBoundingClientRect();
|
|
477
|
+
const viewportMargin = 40;
|
|
478
|
+
const maxWidth = Math.min(
|
|
479
|
+
window.innerWidth * this.maxWidthPercent,
|
|
480
|
+
window.innerWidth - viewportMargin
|
|
481
|
+
);
|
|
482
|
+
const maxHeight = Math.min(
|
|
483
|
+
window.innerHeight * this.maxHeightPercent,
|
|
484
|
+
window.innerHeight - viewportMargin
|
|
485
|
+
);
|
|
486
|
+
let optimalWidth = Math.max(this.minWidth, Math.ceil(contentRect.width + 20));
|
|
487
|
+
let optimalHeight = Math.max(this.minHeight, Math.ceil(contentRect.height));
|
|
488
|
+
optimalWidth = Math.min(optimalWidth, maxWidth);
|
|
489
|
+
const heightExceedsMax = contentRect.height > maxHeight;
|
|
490
|
+
modalDialog.style.maxWidth = `${optimalWidth}px`;
|
|
491
|
+
modalDialog.style.width = `${optimalWidth}px`;
|
|
492
|
+
if (heightExceedsMax) {
|
|
493
|
+
modalContent.style.maxHeight = `${maxHeight}px`;
|
|
494
|
+
modalBody.style.overflowY = "auto";
|
|
495
|
+
optimalHeight = maxHeight;
|
|
496
|
+
}
|
|
497
|
+
this.autoSizedWidth = optimalWidth;
|
|
498
|
+
this.autoSizedHeight = optimalHeight;
|
|
499
|
+
this._originalStyles = originalStyles;
|
|
500
|
+
} catch (error) {
|
|
501
|
+
console.error("Error in dialog auto-sizing:", error);
|
|
502
|
+
this.element.querySelector(".modal-dialog").style.maxWidth = "";
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Reset auto-sizing and restore original modal dimensions
|
|
507
|
+
*/
|
|
508
|
+
resetAutoSizing() {
|
|
509
|
+
if (!this.autoSize || !this._originalStyles || !this.element) return;
|
|
510
|
+
try {
|
|
511
|
+
const modalDialog = this.element.querySelector(".modal-dialog");
|
|
512
|
+
const modalContent = this.element.querySelector(".modal-content");
|
|
513
|
+
const modalBody = this.element.querySelector(".modal-body");
|
|
514
|
+
if (modalDialog && modalContent && modalBody) {
|
|
515
|
+
modalDialog.style.maxWidth = this._originalStyles.dialogMaxWidth || "";
|
|
516
|
+
modalDialog.style.width = this._originalStyles.dialogWidth || "";
|
|
517
|
+
modalContent.style.width = this._originalStyles.contentWidth || "";
|
|
518
|
+
modalContent.style.maxHeight = this._originalStyles.contentMaxHeight || "";
|
|
519
|
+
modalBody.style.overflowY = this._originalStyles.bodyOverflow || "";
|
|
520
|
+
delete this.autoSizedWidth;
|
|
521
|
+
delete this.autoSizedHeight;
|
|
522
|
+
delete this._originalStyles;
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.error("Error resetting dialog auto-sizing:", error);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Bind Bootstrap modal events
|
|
530
|
+
*/
|
|
531
|
+
bindBootstrapEvents() {
|
|
532
|
+
this.element.addEventListener("show.bs.modal", (e) => {
|
|
533
|
+
const stackIndex = Dialog._openDialogs.length;
|
|
534
|
+
const newZIndex = Dialog._baseZIndex.modal + stackIndex * 20;
|
|
535
|
+
this.element.style.zIndex = newZIndex;
|
|
536
|
+
Dialog._openDialogs.push(this);
|
|
537
|
+
setTimeout(() => {
|
|
538
|
+
const backdrops = document.querySelectorAll(".modal-backdrop");
|
|
539
|
+
const backdrop = backdrops[backdrops.length - 1];
|
|
540
|
+
if (backdrop) {
|
|
541
|
+
backdrop.style.zIndex = newZIndex - 5;
|
|
542
|
+
}
|
|
543
|
+
}, 0);
|
|
544
|
+
if (this.onShow) this.onShow(e);
|
|
545
|
+
this.emit("show", {
|
|
546
|
+
dialog: this,
|
|
547
|
+
relatedTarget: e.relatedTarget
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
this.element.addEventListener("shown.bs.modal", (e) => {
|
|
551
|
+
if (this.onShown) this.onShown(e);
|
|
552
|
+
this.emit("shown", {
|
|
553
|
+
dialog: this,
|
|
554
|
+
relatedTarget: e.relatedTarget
|
|
555
|
+
});
|
|
556
|
+
if (this.focus) {
|
|
557
|
+
const firstInput = this.element.querySelector('input:not([type="hidden"]), textarea, select');
|
|
558
|
+
if (firstInput) {
|
|
559
|
+
firstInput.focus();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
this.element.addEventListener("hide.bs.modal", (e) => {
|
|
564
|
+
const focusedElement = this.element.querySelector(":focus");
|
|
565
|
+
if (focusedElement) {
|
|
566
|
+
focusedElement.blur();
|
|
567
|
+
}
|
|
568
|
+
if (this.onHide) {
|
|
569
|
+
const result = this.onHide(e);
|
|
570
|
+
if (result === false) {
|
|
571
|
+
e.preventDefault();
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
this.emit("hide", { dialog: this });
|
|
576
|
+
});
|
|
577
|
+
this.element.addEventListener("hidden.bs.modal", (e) => {
|
|
578
|
+
const index = Dialog._openDialogs.indexOf(this);
|
|
579
|
+
if (index > -1) {
|
|
580
|
+
Dialog._openDialogs.splice(index, 1);
|
|
581
|
+
}
|
|
582
|
+
if (Dialog._openDialogs.length > 0) {
|
|
583
|
+
document.body.classList.add("modal-open");
|
|
584
|
+
const topDialog = Dialog._openDialogs[Dialog._openDialogs.length - 1];
|
|
585
|
+
const topZIndex = parseInt(topDialog.element.style.zIndex, 10);
|
|
586
|
+
setTimeout(() => {
|
|
587
|
+
const backdrops = document.querySelectorAll(".modal-backdrop");
|
|
588
|
+
const backdrop = backdrops[backdrops.length - 1];
|
|
589
|
+
if (backdrop) {
|
|
590
|
+
backdrop.style.zIndex = topZIndex - 5;
|
|
591
|
+
}
|
|
592
|
+
}, 0);
|
|
593
|
+
}
|
|
594
|
+
if (this.previousFocus && document.body.contains(this.previousFocus)) {
|
|
595
|
+
this.previousFocus.focus();
|
|
596
|
+
}
|
|
597
|
+
if (this.onHidden) this.onHidden(e);
|
|
598
|
+
this.emit("hidden", { dialog: this });
|
|
599
|
+
});
|
|
600
|
+
this.element.addEventListener("hidePrevented.bs.modal", (e) => {
|
|
601
|
+
if (this.onHidePrevented) this.onHidePrevented(e);
|
|
602
|
+
this.emit("hidePrevented", { dialog: this });
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Show the dialog
|
|
607
|
+
* @param {HTMLElement} relatedTarget - Optional element that triggered the modal
|
|
608
|
+
*/
|
|
609
|
+
show(relatedTarget = null) {
|
|
610
|
+
this.previousFocus = document.activeElement;
|
|
611
|
+
window.lastDialog = this;
|
|
612
|
+
if (this.modal) {
|
|
613
|
+
this.modal.show(relatedTarget);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Hide the dialog
|
|
618
|
+
*/
|
|
619
|
+
hide() {
|
|
620
|
+
const focusedElement = this.element?.querySelector(":focus");
|
|
621
|
+
if (focusedElement) {
|
|
622
|
+
focusedElement.blur();
|
|
623
|
+
}
|
|
624
|
+
if (this.modal) {
|
|
625
|
+
this.modal.hide();
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Toggle the dialog
|
|
630
|
+
* @param {HTMLElement} relatedTarget - Optional element that triggered the modal
|
|
631
|
+
*/
|
|
632
|
+
toggle(relatedTarget = null) {
|
|
633
|
+
if (this.modal) {
|
|
634
|
+
this.modal.toggle(relatedTarget);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Destroy the dialog and clean up resources
|
|
639
|
+
*/
|
|
640
|
+
async destroy() {
|
|
641
|
+
if (this.modal) {
|
|
642
|
+
const focusedElement = this.element?.querySelector(":focus");
|
|
643
|
+
if (focusedElement) {
|
|
644
|
+
focusedElement.blur();
|
|
645
|
+
}
|
|
646
|
+
this.modal.dispose();
|
|
647
|
+
this.modal = null;
|
|
648
|
+
}
|
|
649
|
+
if (this.previousFocus && document.body.contains(this.previousFocus)) {
|
|
650
|
+
this.previousFocus.focus();
|
|
651
|
+
this.previousFocus = null;
|
|
652
|
+
}
|
|
653
|
+
if (this.autoSize) {
|
|
654
|
+
this.resetAutoSizing();
|
|
655
|
+
}
|
|
656
|
+
await super.destroy();
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Handle dynamic height changes
|
|
660
|
+
*/
|
|
661
|
+
handleUpdate() {
|
|
662
|
+
if (this.modal) {
|
|
663
|
+
this.modal.handleUpdate();
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Update dialog content
|
|
668
|
+
* @param {string|View} content - New content (string or View instance)
|
|
669
|
+
*/
|
|
670
|
+
async setContent(content) {
|
|
671
|
+
if (content instanceof View) {
|
|
672
|
+
if (this.bodyView) {
|
|
673
|
+
await this.bodyView.destroy();
|
|
674
|
+
this.removeChild(this.bodyView);
|
|
675
|
+
}
|
|
676
|
+
this.bodyView = content;
|
|
677
|
+
this.body = "";
|
|
678
|
+
this.addChild(this.bodyView);
|
|
679
|
+
const bodyEl = this.element?.querySelector(".modal-body");
|
|
680
|
+
if (bodyEl) {
|
|
681
|
+
bodyEl.innerHTML = "";
|
|
682
|
+
await this.bodyView.render(bodyEl);
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
this.body = content;
|
|
686
|
+
const bodyEl = this.element?.querySelector(".modal-body");
|
|
687
|
+
if (bodyEl) {
|
|
688
|
+
bodyEl.innerHTML = content;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
this.handleUpdate();
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Update dialog title
|
|
695
|
+
*/
|
|
696
|
+
setTitle(title) {
|
|
697
|
+
this.title = title;
|
|
698
|
+
const titleEl = this.element?.querySelector(".modal-title");
|
|
699
|
+
if (titleEl) {
|
|
700
|
+
titleEl.textContent = title;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Set loading state
|
|
705
|
+
*/
|
|
706
|
+
setLoading(loading = true, message = "Loading...") {
|
|
707
|
+
const bodyEl = this.element?.querySelector(".modal-body");
|
|
708
|
+
if (bodyEl) {
|
|
709
|
+
if (loading) {
|
|
710
|
+
bodyEl.innerHTML = `
|
|
711
|
+
<div class="text-center py-4">
|
|
712
|
+
<div class="spinner-border text-primary mb-3" role="status">
|
|
713
|
+
<span class="visually-hidden">Loading...</span>
|
|
714
|
+
</div>
|
|
715
|
+
<p>${message}</p>
|
|
716
|
+
</div>
|
|
717
|
+
`;
|
|
718
|
+
} else if (this.bodyView) {
|
|
719
|
+
bodyEl.replaceChildren(this.bodyView.element);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Clean up
|
|
725
|
+
*/
|
|
726
|
+
async onBeforeDestroy() {
|
|
727
|
+
if (this.bodyView) {
|
|
728
|
+
await this.bodyView.destroy();
|
|
729
|
+
}
|
|
730
|
+
if (this.footerView) {
|
|
731
|
+
await this.footerView.destroy();
|
|
732
|
+
}
|
|
733
|
+
await super.onBeforeDestroy();
|
|
734
|
+
if (this.modal) {
|
|
735
|
+
this.modal.dispose();
|
|
736
|
+
this.modal = null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Static method to show code in a dialog
|
|
741
|
+
*/
|
|
742
|
+
static async showCode(options = {}) {
|
|
743
|
+
const dialog = new Dialog({
|
|
744
|
+
title: options.title || "Source Code",
|
|
745
|
+
size: options.size || "lg",
|
|
746
|
+
scrollable: true,
|
|
747
|
+
body: Dialog.formatCode(options.code, options.language),
|
|
748
|
+
buttons: [
|
|
749
|
+
{
|
|
750
|
+
text: "Copy to Clipboard",
|
|
751
|
+
class: "btn-primary",
|
|
752
|
+
icon: "bi-clipboard",
|
|
753
|
+
action: "copy"
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
text: "Close",
|
|
757
|
+
class: "btn-secondary",
|
|
758
|
+
dismiss: true
|
|
759
|
+
}
|
|
760
|
+
]
|
|
761
|
+
});
|
|
762
|
+
dialog.on("action:copy", async () => {
|
|
763
|
+
if (navigator.clipboard) {
|
|
764
|
+
try {
|
|
765
|
+
await navigator.clipboard.writeText(options.code);
|
|
766
|
+
dialog.showCopySuccess();
|
|
767
|
+
} catch (err) {
|
|
768
|
+
console.error("Failed to copy:", err);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
await dialog.render(true, document.body);
|
|
773
|
+
if (window.Prism && dialog.element) {
|
|
774
|
+
window.Prism.highlightAllUnder(dialog.element);
|
|
775
|
+
}
|
|
776
|
+
dialog.show();
|
|
777
|
+
dialog.on("hidden", () => {
|
|
778
|
+
dialog.destroy();
|
|
779
|
+
dialog.element.remove();
|
|
780
|
+
});
|
|
781
|
+
return dialog;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Format code for display with syntax highlighting support
|
|
785
|
+
*/
|
|
786
|
+
static formatCode(code, language = "javascript") {
|
|
787
|
+
let highlightedCode;
|
|
788
|
+
if (window.Prism && window.Prism.languages[language]) {
|
|
789
|
+
highlightedCode = window.Prism.highlight(code, window.Prism.languages[language], language);
|
|
790
|
+
} else {
|
|
791
|
+
highlightedCode = code.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
792
|
+
}
|
|
793
|
+
const prismClass = window.Prism ? `language-${language}` : "";
|
|
794
|
+
const codeStyles = `
|
|
795
|
+
max-height: 60vh;
|
|
796
|
+
overflow-y: auto;
|
|
797
|
+
background: #1e1e1e;
|
|
798
|
+
color: #d4d4d4;
|
|
799
|
+
padding: 1.25rem;
|
|
800
|
+
border-radius: 0.5rem;
|
|
801
|
+
margin: 0;
|
|
802
|
+
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', 'Monaco', monospace;
|
|
803
|
+
font-size: 0.9rem;
|
|
804
|
+
line-height: 1.6;
|
|
805
|
+
border: 1px solid #2d2d30;
|
|
806
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
|
807
|
+
`.replace(/\s+/g, " ").trim();
|
|
808
|
+
return `
|
|
809
|
+
<style>
|
|
810
|
+
/* Custom Prism theme overrides for Dialog */
|
|
811
|
+
.dialog-code-block .token.comment { color: #6a9955; }
|
|
812
|
+
.dialog-code-block .token.string { color: #ce9178; }
|
|
813
|
+
.dialog-code-block .token.keyword { color: #569cd6; }
|
|
814
|
+
.dialog-code-block .token.function { color: #dcdcaa; }
|
|
815
|
+
.dialog-code-block .token.number { color: #b5cea8; }
|
|
816
|
+
.dialog-code-block .token.operator { color: #d4d4d4; }
|
|
817
|
+
.dialog-code-block .token.class-name { color: #4ec9b0; }
|
|
818
|
+
.dialog-code-block .token.punctuation { color: #d4d4d4; }
|
|
819
|
+
.dialog-code-block .token.boolean { color: #569cd6; }
|
|
820
|
+
.dialog-code-block .token.property { color: #9cdcfe; }
|
|
821
|
+
.dialog-code-block .token.tag { color: #569cd6; }
|
|
822
|
+
.dialog-code-block .token.attr-name { color: #9cdcfe; }
|
|
823
|
+
.dialog-code-block .token.attr-value { color: #ce9178; }
|
|
824
|
+
.dialog-code-block ::selection { background: #264f78; }
|
|
825
|
+
</style>
|
|
826
|
+
<pre class="${prismClass} dialog-code-block" style="${codeStyles}">
|
|
827
|
+
<code class="${prismClass}" style="color: inherit; background: transparent; text-shadow: none;">${highlightedCode}</code>
|
|
828
|
+
</pre>
|
|
829
|
+
`;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Trigger Prism highlighting on already rendered code blocks
|
|
833
|
+
* Call this after inserting code into the DOM if not using formatCode
|
|
834
|
+
*/
|
|
835
|
+
static highlightCodeBlocks(container = document) {
|
|
836
|
+
if (window.Prism && window.Prism.highlightAllUnder) {
|
|
837
|
+
window.Prism.highlightAllUnder(container);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Show copy success feedback
|
|
842
|
+
*/
|
|
843
|
+
showCopySuccess() {
|
|
844
|
+
const btn = this.element.querySelector('[data-action="copy"]');
|
|
845
|
+
if (btn) {
|
|
846
|
+
const originalHtml = btn.innerHTML;
|
|
847
|
+
btn.innerHTML = '<i class="bi bi-check me-1"></i>Copied!';
|
|
848
|
+
btn.classList.remove("btn-primary");
|
|
849
|
+
btn.classList.add("btn-success");
|
|
850
|
+
btn.disabled = true;
|
|
851
|
+
setTimeout(() => {
|
|
852
|
+
btn.innerHTML = originalHtml;
|
|
853
|
+
btn.classList.remove("btn-success");
|
|
854
|
+
btn.classList.add("btn-primary");
|
|
855
|
+
btn.disabled = false;
|
|
856
|
+
}, 2e3);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Show a dialog with promise-based button handling
|
|
861
|
+
* - If a button has a handler, it will be called. Return semantics:
|
|
862
|
+
* - true or undefined: resolve and close (with button.value || button.action || index)
|
|
863
|
+
* - null or false: keep dialog open (do not resolve)
|
|
864
|
+
* - any other value: resolve with that value and close
|
|
865
|
+
* - If no handler, resolve with action/value/index and close
|
|
866
|
+
* @param {Object} options - Dialog options
|
|
867
|
+
* @returns {Promise} Resolves with value/action/index or null on dismiss
|
|
868
|
+
*/
|
|
869
|
+
static async showDialog(options = {}) {
|
|
870
|
+
if (typeof options === "string") {
|
|
871
|
+
const message = arguments[0];
|
|
872
|
+
const title2 = arguments[1] || "Alert";
|
|
873
|
+
const opts = arguments[2] || {};
|
|
874
|
+
options = {
|
|
875
|
+
...opts,
|
|
876
|
+
body: message,
|
|
877
|
+
title: title2
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
const {
|
|
881
|
+
title = "Dialog",
|
|
882
|
+
content,
|
|
883
|
+
body = content || "",
|
|
884
|
+
size = "md",
|
|
885
|
+
centered = true,
|
|
886
|
+
buttons = [
|
|
887
|
+
{ text: "OK", class: "btn-primary", value: true }
|
|
888
|
+
],
|
|
889
|
+
rejectOnDismiss = false,
|
|
890
|
+
// Default to return null on dismissal
|
|
891
|
+
...dialogOptions
|
|
892
|
+
} = options;
|
|
893
|
+
const dialog = new Dialog({
|
|
894
|
+
title,
|
|
895
|
+
body,
|
|
896
|
+
size,
|
|
897
|
+
centered,
|
|
898
|
+
buttons,
|
|
899
|
+
...dialogOptions
|
|
900
|
+
});
|
|
901
|
+
await dialog.render(true, document.body);
|
|
902
|
+
return new Promise((resolve, reject) => {
|
|
903
|
+
let resolved = false;
|
|
904
|
+
const buttonElements = dialog.element.querySelectorAll(".modal-footer button");
|
|
905
|
+
buttonElements.forEach((btnElement, index) => {
|
|
906
|
+
const buttonConfig = buttons[index];
|
|
907
|
+
if (!buttonConfig) return;
|
|
908
|
+
btnElement.addEventListener("click", async (e) => {
|
|
909
|
+
if (resolved) return;
|
|
910
|
+
const defaultResolveValue = buttonConfig.value !== void 0 ? buttonConfig.value : buttonConfig.action ?? index;
|
|
911
|
+
if (typeof buttonConfig.handler === "function") {
|
|
912
|
+
try {
|
|
913
|
+
const result = await buttonConfig.handler({
|
|
914
|
+
dialog,
|
|
915
|
+
button: buttonConfig,
|
|
916
|
+
index,
|
|
917
|
+
event: e
|
|
918
|
+
});
|
|
919
|
+
if (result === null || result === false) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const valueToResolve = result === true || result === void 0 ? defaultResolveValue : result;
|
|
923
|
+
resolved = true;
|
|
924
|
+
if (!buttonConfig.dismiss) {
|
|
925
|
+
dialog.hide();
|
|
926
|
+
}
|
|
927
|
+
resolve(valueToResolve);
|
|
928
|
+
} catch (err) {
|
|
929
|
+
console.error("Dialog button handler error:", err);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
resolved = true;
|
|
934
|
+
if (!buttonConfig.dismiss) {
|
|
935
|
+
dialog.hide();
|
|
936
|
+
}
|
|
937
|
+
resolve(defaultResolveValue);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
dialog.on("hidden", () => {
|
|
942
|
+
if (!resolved) {
|
|
943
|
+
resolved = true;
|
|
944
|
+
if (rejectOnDismiss) {
|
|
945
|
+
reject(new Error("Dialog dismissed"));
|
|
946
|
+
} else {
|
|
947
|
+
resolve(null);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
setTimeout(() => {
|
|
951
|
+
dialog.destroy();
|
|
952
|
+
dialog.element.remove();
|
|
953
|
+
}, 100);
|
|
954
|
+
});
|
|
955
|
+
dialog.show();
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Static alert dialog helper
|
|
960
|
+
* @param {Object|string} options - Alert options or message string
|
|
961
|
+
* @returns {Promise} Resolves when OK is clicked
|
|
962
|
+
*/
|
|
963
|
+
static async alert(options = {}) {
|
|
964
|
+
if (typeof options === "string") {
|
|
965
|
+
options = {
|
|
966
|
+
message: options,
|
|
967
|
+
title: "Alert"
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
const {
|
|
971
|
+
message = "",
|
|
972
|
+
title = "Alert",
|
|
973
|
+
type = "info",
|
|
974
|
+
// info, success, warning, danger
|
|
975
|
+
...dialogOptions
|
|
976
|
+
} = options;
|
|
977
|
+
let icon = "";
|
|
978
|
+
let titleClass = "";
|
|
979
|
+
switch (type) {
|
|
980
|
+
case "success":
|
|
981
|
+
icon = '<i class="bi bi-check-circle-fill text-success me-2"></i>';
|
|
982
|
+
titleClass = "text-success";
|
|
983
|
+
break;
|
|
984
|
+
case "warning":
|
|
985
|
+
icon = '<i class="bi bi-exclamation-triangle-fill text-warning me-2"></i>';
|
|
986
|
+
titleClass = "text-warning";
|
|
987
|
+
break;
|
|
988
|
+
case "danger":
|
|
989
|
+
case "error":
|
|
990
|
+
icon = '<i class="bi bi-x-circle-fill text-danger me-2"></i>';
|
|
991
|
+
titleClass = "text-danger";
|
|
992
|
+
break;
|
|
993
|
+
default:
|
|
994
|
+
icon = '<i class="bi bi-info-circle-fill text-info me-2"></i>';
|
|
995
|
+
titleClass = "text-info";
|
|
996
|
+
}
|
|
997
|
+
return Dialog.showDialog({
|
|
998
|
+
title: `<span class="${titleClass}">${icon}${title}</span>`,
|
|
999
|
+
body: `<p>${message}</p>`,
|
|
1000
|
+
size: "sm",
|
|
1001
|
+
centered: true,
|
|
1002
|
+
buttons: [
|
|
1003
|
+
{ text: "OK", class: "btn-primary", value: true }
|
|
1004
|
+
],
|
|
1005
|
+
...dialogOptions
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Static confirm dialog
|
|
1010
|
+
*/
|
|
1011
|
+
static async confirm(message, title = "Confirm", options = {}) {
|
|
1012
|
+
if (typeof message === "object") {
|
|
1013
|
+
options = message;
|
|
1014
|
+
message = options.message;
|
|
1015
|
+
title = options.title || title;
|
|
1016
|
+
}
|
|
1017
|
+
const dialog = new Dialog({
|
|
1018
|
+
title,
|
|
1019
|
+
body: `<p>${message}</p>`,
|
|
1020
|
+
size: options.size || "sm",
|
|
1021
|
+
centered: true,
|
|
1022
|
+
backdrop: "static",
|
|
1023
|
+
buttons: [
|
|
1024
|
+
{ text: options.cancelText || "Cancel", class: "btn-secondary", dismiss: true, action: "cancel" },
|
|
1025
|
+
{ text: options.confirmText || "Confirm", class: options.confirmClass || "btn-primary", action: "confirm" }
|
|
1026
|
+
],
|
|
1027
|
+
...options
|
|
1028
|
+
});
|
|
1029
|
+
await dialog.render(true, document.body);
|
|
1030
|
+
dialog.show();
|
|
1031
|
+
return new Promise((resolve) => {
|
|
1032
|
+
let result = false;
|
|
1033
|
+
dialog.on("action:confirm", () => {
|
|
1034
|
+
result = true;
|
|
1035
|
+
dialog.hide();
|
|
1036
|
+
});
|
|
1037
|
+
dialog.on("hidden", () => {
|
|
1038
|
+
dialog.destroy();
|
|
1039
|
+
dialog.element.remove();
|
|
1040
|
+
resolve(result);
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Static prompt dialog
|
|
1046
|
+
*/
|
|
1047
|
+
static async prompt(message, title = "Input", options = {}) {
|
|
1048
|
+
const inputId = `prompt-input-${Date.now()}`;
|
|
1049
|
+
const defaultValue = options.defaultValue || "";
|
|
1050
|
+
const inputType = options.inputType || "text";
|
|
1051
|
+
const placeholder = options.placeholder || "";
|
|
1052
|
+
const dialog = new Dialog({
|
|
1053
|
+
title,
|
|
1054
|
+
body: `
|
|
1055
|
+
<p>${message}</p>
|
|
1056
|
+
<input type="${inputType}"
|
|
1057
|
+
class="form-control"
|
|
1058
|
+
id="${inputId}"
|
|
1059
|
+
value="${defaultValue}"
|
|
1060
|
+
placeholder="${placeholder}">
|
|
1061
|
+
`,
|
|
1062
|
+
size: options.size || "sm",
|
|
1063
|
+
centered: true,
|
|
1064
|
+
backdrop: "static",
|
|
1065
|
+
buttons: [
|
|
1066
|
+
{ text: "Cancel", class: "btn-secondary", dismiss: true },
|
|
1067
|
+
{ text: "OK", class: "btn-primary", action: "ok" }
|
|
1068
|
+
],
|
|
1069
|
+
...options
|
|
1070
|
+
});
|
|
1071
|
+
await dialog.render(true, document.body);
|
|
1072
|
+
dialog.show();
|
|
1073
|
+
dialog.on("shown", () => {
|
|
1074
|
+
const input = dialog.element.querySelector(`#${inputId}`);
|
|
1075
|
+
if (input) {
|
|
1076
|
+
input.focus();
|
|
1077
|
+
input.select();
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
return new Promise((resolve) => {
|
|
1081
|
+
let result = null;
|
|
1082
|
+
dialog.on("action:ok", () => {
|
|
1083
|
+
const input = dialog.element.querySelector(`#${inputId}`);
|
|
1084
|
+
result = input ? input.value : null;
|
|
1085
|
+
dialog.hide();
|
|
1086
|
+
});
|
|
1087
|
+
dialog.on("hidden", () => {
|
|
1088
|
+
dialog.destroy();
|
|
1089
|
+
dialog.element.remove();
|
|
1090
|
+
resolve(result);
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Get Bootstrap modal instance
|
|
1096
|
+
*/
|
|
1097
|
+
getModal() {
|
|
1098
|
+
return this.modal;
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Check if modal is shown
|
|
1102
|
+
*/
|
|
1103
|
+
isShown() {
|
|
1104
|
+
return this.element?.classList.contains("show") || false;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Show form in a dialog for simple data collection (no model saving)
|
|
1108
|
+
* @param {object} options - Configuration options
|
|
1109
|
+
* @returns {Promise} Promise that resolves with form data or null if cancelled
|
|
1110
|
+
*/
|
|
1111
|
+
static async showForm(options = {}) {
|
|
1112
|
+
const {
|
|
1113
|
+
title = "Form",
|
|
1114
|
+
formConfig = {},
|
|
1115
|
+
size = "md",
|
|
1116
|
+
centered = true,
|
|
1117
|
+
submitText = "Submit",
|
|
1118
|
+
cancelText = "Cancel",
|
|
1119
|
+
...dialogOptions
|
|
1120
|
+
} = options;
|
|
1121
|
+
const FormView = (await import("./FormView-DqUBMPJ9.js").then((n) => n.b)).default;
|
|
1122
|
+
const formView = new FormView({
|
|
1123
|
+
fileHandling: options.fileHandling || "base64",
|
|
1124
|
+
data: options.data,
|
|
1125
|
+
defaults: options.defaults,
|
|
1126
|
+
model: options.model,
|
|
1127
|
+
formConfig: {
|
|
1128
|
+
fields: formConfig.fields || options.fields,
|
|
1129
|
+
...formConfig,
|
|
1130
|
+
submitButton: false,
|
|
1131
|
+
resetButton: false
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
const dialog = new Dialog({
|
|
1135
|
+
title,
|
|
1136
|
+
body: formView,
|
|
1137
|
+
size,
|
|
1138
|
+
centered,
|
|
1139
|
+
buttons: [
|
|
1140
|
+
{
|
|
1141
|
+
text: cancelText,
|
|
1142
|
+
class: "btn-secondary",
|
|
1143
|
+
action: "cancel"
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
text: submitText,
|
|
1147
|
+
class: "btn-primary",
|
|
1148
|
+
action: "submit"
|
|
1149
|
+
}
|
|
1150
|
+
],
|
|
1151
|
+
...dialogOptions
|
|
1152
|
+
});
|
|
1153
|
+
await dialog.render(true, document.body);
|
|
1154
|
+
dialog.show();
|
|
1155
|
+
return new Promise((resolve) => {
|
|
1156
|
+
let resolved = false;
|
|
1157
|
+
dialog.on("action:submit", async () => {
|
|
1158
|
+
if (resolved) return;
|
|
1159
|
+
if (!formView.validate()) {
|
|
1160
|
+
formView.focusFirstError();
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (options.autoSave && options.model) {
|
|
1164
|
+
dialog.setLoading(true);
|
|
1165
|
+
const result = await formView.saveModel();
|
|
1166
|
+
if (!result.success) {
|
|
1167
|
+
dialog.setLoading(false);
|
|
1168
|
+
dialog.render();
|
|
1169
|
+
dialog.getApp().toast.error(result.message);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
resolved = true;
|
|
1173
|
+
dialog.hide();
|
|
1174
|
+
resolve(result);
|
|
1175
|
+
}
|
|
1176
|
+
try {
|
|
1177
|
+
const formData = await formView.getFormData();
|
|
1178
|
+
resolved = true;
|
|
1179
|
+
dialog.hide();
|
|
1180
|
+
resolve(formData);
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
console.error("Error collecting form data:", error);
|
|
1183
|
+
formView.showError("Error collecting form data");
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
dialog.on("action:cancel", () => {
|
|
1187
|
+
if (resolved) return;
|
|
1188
|
+
resolved = true;
|
|
1189
|
+
dialog.hide();
|
|
1190
|
+
resolve(null);
|
|
1191
|
+
});
|
|
1192
|
+
dialog.on("hidden", () => {
|
|
1193
|
+
if (!resolved) {
|
|
1194
|
+
resolved = true;
|
|
1195
|
+
resolve(null);
|
|
1196
|
+
}
|
|
1197
|
+
setTimeout(() => {
|
|
1198
|
+
formView.destroy();
|
|
1199
|
+
dialog.destroy();
|
|
1200
|
+
}, 100);
|
|
1201
|
+
});
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Show form in a dialog with automatic model saving
|
|
1206
|
+
* @param {object} options - Configuration options (requires model)
|
|
1207
|
+
* @returns {Promise} Promise that resolves with save result or null if cancelled
|
|
1208
|
+
*/
|
|
1209
|
+
static async showModelForm(options = {}) {
|
|
1210
|
+
const {
|
|
1211
|
+
title = "Edit",
|
|
1212
|
+
formConfig = {},
|
|
1213
|
+
size = "md",
|
|
1214
|
+
centered = true,
|
|
1215
|
+
submitText = "Save",
|
|
1216
|
+
cancelText = "Cancel",
|
|
1217
|
+
model,
|
|
1218
|
+
...dialogOptions
|
|
1219
|
+
} = options;
|
|
1220
|
+
if (!model) {
|
|
1221
|
+
throw new Error("showModelForm requires a model");
|
|
1222
|
+
}
|
|
1223
|
+
const FormView = (await import("./FormView-DqUBMPJ9.js").then((n) => n.b)).default;
|
|
1224
|
+
const formView = new FormView({
|
|
1225
|
+
fileHandling: options.fileHandling || "base64",
|
|
1226
|
+
model,
|
|
1227
|
+
data: options.data,
|
|
1228
|
+
defaults: options.defaults,
|
|
1229
|
+
formConfig: {
|
|
1230
|
+
fields: formConfig.fields || options.fields,
|
|
1231
|
+
...formConfig,
|
|
1232
|
+
submitButton: false,
|
|
1233
|
+
resetButton: false
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
const dialog = new Dialog({
|
|
1237
|
+
title,
|
|
1238
|
+
body: formView,
|
|
1239
|
+
size,
|
|
1240
|
+
centered,
|
|
1241
|
+
buttons: [
|
|
1242
|
+
{
|
|
1243
|
+
text: cancelText,
|
|
1244
|
+
class: "btn-secondary",
|
|
1245
|
+
action: "cancel"
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
text: submitText,
|
|
1249
|
+
class: "btn-primary",
|
|
1250
|
+
action: "submit"
|
|
1251
|
+
}
|
|
1252
|
+
],
|
|
1253
|
+
...dialogOptions
|
|
1254
|
+
});
|
|
1255
|
+
await dialog.render(true, document.body);
|
|
1256
|
+
dialog.show();
|
|
1257
|
+
return new Promise((resolve) => {
|
|
1258
|
+
let resolved = false;
|
|
1259
|
+
dialog.on("action:submit", async () => {
|
|
1260
|
+
if (resolved) return;
|
|
1261
|
+
dialog.setLoading(true, "Saving...");
|
|
1262
|
+
try {
|
|
1263
|
+
const result = await formView.handleSubmit();
|
|
1264
|
+
if (result.success) {
|
|
1265
|
+
resolved = true;
|
|
1266
|
+
dialog.hide();
|
|
1267
|
+
resolve(result);
|
|
1268
|
+
} else {
|
|
1269
|
+
dialog.setLoading(false);
|
|
1270
|
+
dialog.getApp().toast.error(result.error);
|
|
1271
|
+
}
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
console.error("Error saving form:", error);
|
|
1274
|
+
await dialog.setContent(formView);
|
|
1275
|
+
formView.showError(error.message || "An error occurred while saving");
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
dialog.on("action:cancel", () => {
|
|
1279
|
+
if (resolved) return;
|
|
1280
|
+
resolved = true;
|
|
1281
|
+
dialog.hide();
|
|
1282
|
+
resolve(null);
|
|
1283
|
+
});
|
|
1284
|
+
dialog.on("hidden", () => {
|
|
1285
|
+
if (!resolved) {
|
|
1286
|
+
resolved = true;
|
|
1287
|
+
resolve(null);
|
|
1288
|
+
}
|
|
1289
|
+
setTimeout(() => {
|
|
1290
|
+
formView.destroy();
|
|
1291
|
+
dialog.destroy();
|
|
1292
|
+
}, 100);
|
|
1293
|
+
});
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Show data in a dialog using DataView component
|
|
1298
|
+
* @param {object} options - Configuration options
|
|
1299
|
+
* @returns {Promise} Promise that resolves when dialog is closed
|
|
1300
|
+
*/
|
|
1301
|
+
static async showData(options = {}) {
|
|
1302
|
+
const {
|
|
1303
|
+
title = "Data View",
|
|
1304
|
+
data = {},
|
|
1305
|
+
model = null,
|
|
1306
|
+
fields = [],
|
|
1307
|
+
columns = 2,
|
|
1308
|
+
responsive = true,
|
|
1309
|
+
showEmptyValues = false,
|
|
1310
|
+
emptyValueText = "—",
|
|
1311
|
+
size = "lg",
|
|
1312
|
+
centered = true,
|
|
1313
|
+
closeText = "Close",
|
|
1314
|
+
...dialogOptions
|
|
1315
|
+
} = options;
|
|
1316
|
+
const DataView = (await import("./DataView-DjZQrpba.js")).default;
|
|
1317
|
+
const dataView = new DataView({
|
|
1318
|
+
data,
|
|
1319
|
+
model,
|
|
1320
|
+
fields,
|
|
1321
|
+
columns,
|
|
1322
|
+
responsive,
|
|
1323
|
+
showEmptyValues,
|
|
1324
|
+
emptyValueText
|
|
1325
|
+
});
|
|
1326
|
+
const dialog = new Dialog({
|
|
1327
|
+
title,
|
|
1328
|
+
body: dataView,
|
|
1329
|
+
size,
|
|
1330
|
+
centered,
|
|
1331
|
+
buttons: [
|
|
1332
|
+
{
|
|
1333
|
+
text: closeText,
|
|
1334
|
+
class: "btn-secondary",
|
|
1335
|
+
value: "close"
|
|
1336
|
+
}
|
|
1337
|
+
],
|
|
1338
|
+
...dialogOptions
|
|
1339
|
+
});
|
|
1340
|
+
await dialog.render(true, document.body);
|
|
1341
|
+
dialog.show();
|
|
1342
|
+
return new Promise((resolve) => {
|
|
1343
|
+
let resolved = false;
|
|
1344
|
+
const closeBtn = dialog.element.querySelector(".modal-footer button");
|
|
1345
|
+
const handleClose = () => {
|
|
1346
|
+
if (resolved) return;
|
|
1347
|
+
resolved = true;
|
|
1348
|
+
dialog.hide();
|
|
1349
|
+
resolve(true);
|
|
1350
|
+
};
|
|
1351
|
+
closeBtn?.addEventListener("click", handleClose);
|
|
1352
|
+
dialog.on("hidden", () => {
|
|
1353
|
+
if (!resolved) {
|
|
1354
|
+
resolved = true;
|
|
1355
|
+
resolve(true);
|
|
1356
|
+
}
|
|
1357
|
+
setTimeout(() => {
|
|
1358
|
+
dataView.destroy();
|
|
1359
|
+
dialog.destroy();
|
|
1360
|
+
dialog.element.remove();
|
|
1361
|
+
}, 100);
|
|
1362
|
+
});
|
|
1363
|
+
dataView.on("field:click", (data2) => {
|
|
1364
|
+
dialog.emit("dataview:field:click", data2);
|
|
1365
|
+
});
|
|
1366
|
+
dataView.on("error", (data2) => {
|
|
1367
|
+
dialog.emit("dataview:error", data2);
|
|
1368
|
+
});
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
Dialog.showConfirm = Dialog.confirm;
|
|
1373
|
+
Dialog.showError = Dialog.alert;
|
|
1374
|
+
export {
|
|
1375
|
+
Dialog as default
|
|
1376
|
+
};
|
|
1377
|
+
//# sourceMappingURL=Dialog-DSlctbon.js.map
|