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.
Files changed (91) hide show
  1. package/LICENSE +198 -0
  2. package/README.md +510 -0
  3. package/dist/admin.cjs.js +2 -0
  4. package/dist/admin.cjs.js.map +1 -0
  5. package/dist/admin.css +621 -0
  6. package/dist/admin.es.js +7973 -0
  7. package/dist/admin.es.js.map +1 -0
  8. package/dist/auth.cjs.js +2 -0
  9. package/dist/auth.cjs.js.map +1 -0
  10. package/dist/auth.css +804 -0
  11. package/dist/auth.es.js +2168 -0
  12. package/dist/auth.es.js.map +1 -0
  13. package/dist/charts.cjs.js +2 -0
  14. package/dist/charts.cjs.js.map +1 -0
  15. package/dist/charts.css +1002 -0
  16. package/dist/charts.es.js +16 -0
  17. package/dist/charts.es.js.map +1 -0
  18. package/dist/chunks/ContextMenu-BrHqj0fn.js +80 -0
  19. package/dist/chunks/ContextMenu-BrHqj0fn.js.map +1 -0
  20. package/dist/chunks/ContextMenu-gEcpSz56.js +2 -0
  21. package/dist/chunks/ContextMenu-gEcpSz56.js.map +1 -0
  22. package/dist/chunks/DataView-DPryYpEW.js +2 -0
  23. package/dist/chunks/DataView-DPryYpEW.js.map +1 -0
  24. package/dist/chunks/DataView-DjZQrpba.js +843 -0
  25. package/dist/chunks/DataView-DjZQrpba.js.map +1 -0
  26. package/dist/chunks/Dialog-BsRx4eg3.js +2 -0
  27. package/dist/chunks/Dialog-BsRx4eg3.js.map +1 -0
  28. package/dist/chunks/Dialog-DSlctbon.js +1377 -0
  29. package/dist/chunks/Dialog-DSlctbon.js.map +1 -0
  30. package/dist/chunks/FilePreviewView-BmFHzK5K.js +5868 -0
  31. package/dist/chunks/FilePreviewView-BmFHzK5K.js.map +1 -0
  32. package/dist/chunks/FilePreviewView-DcdRl_ta.js +2 -0
  33. package/dist/chunks/FilePreviewView-DcdRl_ta.js.map +1 -0
  34. package/dist/chunks/FormView-CmBuwKGD.js +2 -0
  35. package/dist/chunks/FormView-CmBuwKGD.js.map +1 -0
  36. package/dist/chunks/FormView-DqUBMPJ9.js +5054 -0
  37. package/dist/chunks/FormView-DqUBMPJ9.js.map +1 -0
  38. package/dist/chunks/MetricsChart-CM4CI6eA.js +2095 -0
  39. package/dist/chunks/MetricsChart-CM4CI6eA.js.map +1 -0
  40. package/dist/chunks/MetricsChart-CPidSMaN.js +2 -0
  41. package/dist/chunks/MetricsChart-CPidSMaN.js.map +1 -0
  42. package/dist/chunks/PDFViewer-BNQlnS83.js +2 -0
  43. package/dist/chunks/PDFViewer-BNQlnS83.js.map +1 -0
  44. package/dist/chunks/PDFViewer-Dyo-Oeyd.js +946 -0
  45. package/dist/chunks/PDFViewer-Dyo-Oeyd.js.map +1 -0
  46. package/dist/chunks/Page-B524zSQs.js +351 -0
  47. package/dist/chunks/Page-B524zSQs.js.map +1 -0
  48. package/dist/chunks/Page-BFgj0pAA.js +2 -0
  49. package/dist/chunks/Page-BFgj0pAA.js.map +1 -0
  50. package/dist/chunks/TokenManager-BXNva8Jk.js +287 -0
  51. package/dist/chunks/TokenManager-BXNva8Jk.js.map +1 -0
  52. package/dist/chunks/TokenManager-Bzn4guFm.js +2 -0
  53. package/dist/chunks/TokenManager-Bzn4guFm.js.map +1 -0
  54. package/dist/chunks/TopNav-D3I3_25f.js +371 -0
  55. package/dist/chunks/TopNav-D3I3_25f.js.map +1 -0
  56. package/dist/chunks/TopNav-MDjL4kV0.js +2 -0
  57. package/dist/chunks/TopNav-MDjL4kV0.js.map +1 -0
  58. package/dist/chunks/User-BalfYTEF.js +3 -0
  59. package/dist/chunks/User-BalfYTEF.js.map +1 -0
  60. package/dist/chunks/User-DwIT-CTQ.js +1937 -0
  61. package/dist/chunks/User-DwIT-CTQ.js.map +1 -0
  62. package/dist/chunks/WebApp-B6mgbNn2.js +4767 -0
  63. package/dist/chunks/WebApp-B6mgbNn2.js.map +1 -0
  64. package/dist/chunks/WebApp-DqDowtkl.js +2 -0
  65. package/dist/chunks/WebApp-DqDowtkl.js.map +1 -0
  66. package/dist/chunks/WebSocketClient-D6i85jl2.js +2 -0
  67. package/dist/chunks/WebSocketClient-D6i85jl2.js.map +1 -0
  68. package/dist/chunks/WebSocketClient-Dvl3AYx1.js +297 -0
  69. package/dist/chunks/WebSocketClient-Dvl3AYx1.js.map +1 -0
  70. package/dist/core.css +1181 -0
  71. package/dist/css/web-mojo.css +17 -0
  72. package/dist/css-manifest.json +6 -0
  73. package/dist/docit.cjs.js +2 -0
  74. package/dist/docit.cjs.js.map +1 -0
  75. package/dist/docit.es.js +959 -0
  76. package/dist/docit.es.js.map +1 -0
  77. package/dist/index.cjs.js +2 -0
  78. package/dist/index.cjs.js.map +1 -0
  79. package/dist/index.es.js +2681 -0
  80. package/dist/index.es.js.map +1 -0
  81. package/dist/lightbox.cjs.js +2 -0
  82. package/dist/lightbox.cjs.js.map +1 -0
  83. package/dist/lightbox.css +606 -0
  84. package/dist/lightbox.es.js +3737 -0
  85. package/dist/lightbox.es.js.map +1 -0
  86. package/dist/loader.es.js +115 -0
  87. package/dist/loader.umd.js +85 -0
  88. package/dist/portal.css +2446 -0
  89. package/dist/table.css +639 -0
  90. package/dist/toast.css +181 -0
  91. 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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
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